Skip to content

Commit 4df096a

Browse files
Franck Bui-HuuJunio C Hamano
Franck Bui-Huu
authored and
Junio C Hamano
committed
Add git-archive
git-archive is a command to make TAR and ZIP archives of a git tree. It helps prevent a proliferation of git-{format}-tree commands. Instead of directly calling git-{tar,zip}-tree command, it defines a very simple API, that archiver should implement and register in "git-archive.c". This API is made up by 2 functions whose prototype is defined in "archive.h" file. - The first one is used to parse 'extra' parameters which have signification only for the specific archiver. That would allow different archive backends to have different kind of options. - The second one is used to ask to an archive backend to build the archive given some already resolved parameters. The main reason for making this API is to avoid using git-{tar,zip}-tree commands, hence making them useless. Maybe it's time for them to die ? It also implements remote operations by defining a very simple protocol: it first sends the name of the specific uploader followed the repository name (git-upload-tar git://example.org/repo.git). Then it sends options. It's done by sending a sequence of one argument per packet, with prefix "argument ", followed by a flush. The remote protocol is implemented in "git-archive.c" for client side and is triggered by "--remote=<repo>" option. For example, to fetch a TAR archive in a remote repo, you can issue: $ git archive --format=tar --remote=git://xxx/yyy/zzz.git HEAD We choose to not make a new command "git-fetch-archive" for example, avoind one more GIT command which should be nice for users (less commands to remember, keeps existing --remote option). Signed-off-by: Franck Bui-Huu <[email protected]> Acked-by: Rene Scharfe <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2878836 commit 4df096a

File tree

8 files changed

+375
-1
lines changed

8 files changed

+375
-1
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ git-apply
88
git-applymbox
99
git-applypatch
1010
git-archimport
11+
git-archive
1112
git-bisect
1213
git-branch
1314
git-cat-file

Diff for: Documentation/git-archive.txt

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
git-archive(1)
2+
==============
3+
4+
NAME
5+
----
6+
git-archive - Creates a archive of the files in the named tree
7+
8+
9+
SYNOPSIS
10+
--------
11+
'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
12+
[--remote=<repo>] <tree-ish> [path...]
13+
14+
DESCRIPTION
15+
-----------
16+
Creates an archive of the specified format containing the tree
17+
structure for the named tree. If <prefix> is specified it is
18+
prepended to the filenames in the archive.
19+
20+
'git-archive' behaves differently when given a tree ID versus when
21+
given a commit ID or tag ID. In the first case the current time is
22+
used as modification time of each file in the archive. In the latter
23+
case the commit time as recorded in the referenced commit object is
24+
used instead. Additionally the commit ID is stored in a global
25+
extended pax header if the tar format is used; it can be extracted
26+
using 'git-get-tar-commit-id'. In ZIP files it is stored as a file
27+
comment.
28+
29+
OPTIONS
30+
-------
31+
32+
--format=<fmt>::
33+
Format of the resulting archive: 'tar', 'zip'...
34+
35+
--list::
36+
Show all available formats.
37+
38+
--prefix=<prefix>/::
39+
Prepend <prefix>/ to each filename in the archive.
40+
41+
<extra>::
42+
This can be any options that the archiver backend understand.
43+
44+
--remote=<repo>::
45+
Instead of making a tar archive from local repository,
46+
retrieve a tar archive from a remote repository.
47+
48+
<tree-ish>::
49+
The tree or commit to produce an archive for.
50+
51+
path::
52+
If one or more paths are specified, include only these in the
53+
archive, otherwise include all files and subdirectories.
54+
55+
CONFIGURATION
56+
-------------
57+
By default, file and directories modes are set to 0666 or 0777 in tar
58+
archives. It is possible to change this by setting the "umask" variable
59+
in the repository configuration as follows :
60+
61+
[tar]
62+
umask = 002 ;# group friendly
63+
64+
The special umask value "user" indicates that the user's current umask
65+
will be used instead. The default value remains 0, which means world
66+
readable/writable files and directories.
67+
68+
EXAMPLES
69+
--------
70+
git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
71+
72+
Create a tar archive that contains the contents of the
73+
latest commit on the current branch, and extracts it in
74+
`/var/tmp/junk` directory.
75+
76+
git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
77+
78+
Create a compressed tarball for v1.4.0 release.
79+
80+
git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
81+
82+
Create a compressed tarball for v1.4.0 release, but without a
83+
global extended pax header.
84+
85+
git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip::
86+
87+
Put everything in the current head's Documentation/ directory
88+
into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
89+
90+
Author
91+
------
92+
Written by Franck Bui-Huu and Rene Scharfe.
93+
94+
Documentation
95+
--------------
96+
Documentation by David Greaves, Junio C Hamano and the git-list <[email protected]>.
97+
98+
GIT
99+
---
100+
Part of the gitlink:git[7] suite

Diff for: Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ LIB_FILE=libgit.a
232232
XDIFF_LIB=xdiff/lib.a
233233

234234
LIB_H = \
235-
blob.h cache.h commit.h csum-file.h delta.h \
235+
archive.h blob.h cache.h commit.h csum-file.h delta.h \
236236
diff.h object.h pack.h pkt-line.h quote.h refs.h \
237237
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
238238
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h
@@ -256,6 +256,7 @@ LIB_OBJS = \
256256
BUILTIN_OBJS = \
257257
builtin-add.o \
258258
builtin-apply.o \
259+
builtin-archive.o \
259260
builtin-cat-file.o \
260261
builtin-checkout-index.o \
261262
builtin-check-ref-format.o \

Diff for: archive.h

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#ifndef ARCHIVE_H
2+
#define ARCHIVE_H
3+
4+
#define MAX_EXTRA_ARGS 32
5+
#define MAX_ARGS (MAX_EXTRA_ARGS + 32)
6+
7+
struct archiver_args {
8+
const char *base;
9+
struct tree *tree;
10+
const unsigned char *commit_sha1;
11+
time_t time;
12+
const char **pathspec;
13+
void *extra;
14+
};
15+
16+
typedef int (*write_archive_fn_t)(struct archiver_args *);
17+
18+
typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv);
19+
20+
struct archiver {
21+
const char *name;
22+
const char *remote;
23+
struct archiver_args args;
24+
write_archive_fn_t write_archive;
25+
parse_extra_args_fn_t parse_extra;
26+
};
27+
28+
extern struct archiver archivers[];
29+
30+
extern int parse_archive_args(int argc,
31+
const char **argv,
32+
struct archiver *ar);
33+
34+
extern void parse_treeish_arg(const char **treeish,
35+
struct archiver_args *ar_args,
36+
const char *prefix);
37+
38+
extern void parse_pathspec_arg(const char **pathspec,
39+
struct archiver_args *args);
40+
41+
#endif /* ARCHIVE_H */

Diff for: builtin-archive.c

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Copyright (c) 2006 Franck Bui-Huu
3+
* Copyright (c) 2006 Rene Scharfe
4+
*/
5+
#include <time.h>
6+
#include "cache.h"
7+
#include "builtin.h"
8+
#include "archive.h"
9+
#include "commit.h"
10+
#include "tree-walk.h"
11+
#include "exec_cmd.h"
12+
#include "pkt-line.h"
13+
14+
static const char archive_usage[] = \
15+
"git-archive --format=<fmt> [--prefix=<prefix>/] [<extra>] <tree-ish> [path...]";
16+
17+
struct archiver archivers[] = {
18+
{ "" /* dummy */ },
19+
};
20+
21+
static int run_remote_archiver(struct archiver *ar, int argc,
22+
const char **argv)
23+
{
24+
char *url, buf[1024];
25+
int fd[2], i, len, rv;
26+
pid_t pid;
27+
28+
sprintf(buf, "git-upload-archive");
29+
30+
url = xstrdup(ar->remote);
31+
pid = git_connect(fd, url, buf);
32+
if (pid < 0)
33+
return pid;
34+
35+
for (i = 1; i < argc; i++) {
36+
if (!strncmp(argv[i], "--remote=", 9))
37+
continue;
38+
packet_write(fd[1], "argument %s\n", argv[i]);
39+
}
40+
packet_flush(fd[1]);
41+
42+
len = packet_read_line(fd[0], buf, sizeof(buf));
43+
if (!len)
44+
die("git-archive: expected ACK/NAK, got EOF");
45+
if (buf[len-1] == '\n')
46+
buf[--len] = 0;
47+
if (strcmp(buf, "ACK")) {
48+
if (len > 5 && !strncmp(buf, "NACK ", 5))
49+
die("git-archive: NACK %s", buf + 5);
50+
die("git-archive: protocol error");
51+
}
52+
53+
len = packet_read_line(fd[0], buf, sizeof(buf));
54+
if (len)
55+
die("git-archive: expected a flush");
56+
57+
/* Now, start reading from fd[0] and spit it out to stdout */
58+
rv = copy_fd(fd[0], 1);
59+
60+
close(fd[0]);
61+
rv |= finish_connect(pid);
62+
63+
return !!rv;
64+
}
65+
66+
static int init_archiver(const char *name, struct archiver *ar)
67+
{
68+
int rv = -1, i;
69+
70+
for (i = 0; i < ARRAY_SIZE(archivers); i++) {
71+
if (!strcmp(name, archivers[i].name)) {
72+
memcpy(ar, &archivers[i], sizeof(struct archiver));
73+
rv = 0;
74+
break;
75+
}
76+
}
77+
return rv;
78+
}
79+
80+
void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args)
81+
{
82+
ar_args->pathspec = get_pathspec(ar_args->base, pathspec);
83+
}
84+
85+
void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
86+
const char *prefix)
87+
{
88+
const char *name = argv[0];
89+
const unsigned char *commit_sha1;
90+
time_t archive_time;
91+
struct tree *tree;
92+
struct commit *commit;
93+
unsigned char sha1[20];
94+
95+
if (get_sha1(name, sha1))
96+
die("Not a valid object name");
97+
98+
commit = lookup_commit_reference_gently(sha1, 1);
99+
if (commit) {
100+
commit_sha1 = commit->object.sha1;
101+
archive_time = commit->date;
102+
} else {
103+
commit_sha1 = NULL;
104+
archive_time = time(NULL);
105+
}
106+
107+
tree = parse_tree_indirect(sha1);
108+
if (tree == NULL)
109+
die("not a tree object");
110+
111+
if (prefix) {
112+
unsigned char tree_sha1[20];
113+
unsigned int mode;
114+
int err;
115+
116+
err = get_tree_entry(tree->object.sha1, prefix,
117+
tree_sha1, &mode);
118+
if (err || !S_ISDIR(mode))
119+
die("current working directory is untracked");
120+
121+
free(tree);
122+
tree = parse_tree_indirect(tree_sha1);
123+
}
124+
ar_args->tree = tree;
125+
ar_args->commit_sha1 = commit_sha1;
126+
ar_args->time = archive_time;
127+
}
128+
129+
static const char *default_parse_extra(struct archiver *ar,
130+
const char **argv)
131+
{
132+
static char msg[64];
133+
134+
snprintf(msg, sizeof(msg) - 4, "'%s' format does not handle %s",
135+
ar->name, *argv);
136+
137+
return strcat(msg, "...");
138+
}
139+
140+
int parse_archive_args(int argc, const char **argv, struct archiver *ar)
141+
{
142+
const char *extra_argv[MAX_EXTRA_ARGS];
143+
int extra_argc = 0;
144+
const char *format = NULL; /* might want to default to "tar" */
145+
const char *remote = NULL;
146+
const char *base = "";
147+
int list = 0;
148+
int i;
149+
150+
for (i = 1; i < argc; i++) {
151+
const char *arg = argv[i];
152+
153+
if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) {
154+
list = 1;
155+
continue;
156+
}
157+
if (!strncmp(arg, "--format=", 9)) {
158+
format = arg + 9;
159+
continue;
160+
}
161+
if (!strncmp(arg, "--prefix=", 9)) {
162+
base = arg + 9;
163+
continue;
164+
}
165+
if (!strncmp(arg, "--remote=", 9)) {
166+
remote = arg + 9;
167+
continue;
168+
}
169+
if (!strcmp(arg, "--")) {
170+
i++;
171+
break;
172+
}
173+
if (arg[0] == '-') {
174+
if (extra_argc > MAX_EXTRA_ARGS - 1)
175+
die("Too many extra options");
176+
extra_argv[extra_argc++] = arg;
177+
continue;
178+
}
179+
break;
180+
}
181+
182+
if (list) {
183+
if (!remote) {
184+
for (i = 0; i < ARRAY_SIZE(archivers); i++)
185+
printf("%s\n", archivers[i].name);
186+
exit(0);
187+
}
188+
die("--list and --remote are mutually exclusive");
189+
}
190+
191+
if (argc - i < 1)
192+
usage(archive_usage);
193+
if (!format)
194+
die("You must specify an archive format");
195+
if (init_archiver(format, ar) < 0)
196+
die("Unknown archive format '%s'", format);
197+
198+
if (extra_argc && !remote) {
199+
if (!ar->parse_extra) {
200+
die("%s", default_parse_extra(ar, extra_argv));
201+
}
202+
ar->args.extra = ar->parse_extra(extra_argc, extra_argv);
203+
}
204+
ar->remote = remote;
205+
ar->args.base = base;
206+
207+
return i;
208+
}
209+
210+
int cmd_archive(int argc, const char **argv, const char *prefix)
211+
{
212+
struct archiver ar;
213+
int tree_idx;
214+
215+
tree_idx = parse_archive_args(argc, argv, &ar);
216+
217+
if (ar.remote)
218+
return run_remote_archiver(&ar, argc, argv);
219+
220+
if (prefix == NULL)
221+
prefix = setup_git_directory();
222+
223+
argv += tree_idx;
224+
parse_treeish_arg(argv, &ar.args, prefix);
225+
parse_pathspec_arg(argv + 1, &ar.args);
226+
227+
return ar.write_archive(&ar.args);
228+
}

0 commit comments

Comments
 (0)