Skip to content

Commit 765c7e4

Browse files
committed
Merge branch 'jk/archive-tar-filter'
* jk/archive-tar-filter: upload-archive: allow user to turn off filters archive: provide builtin .tar.gz filter archive: implement configurable tar filters archive: refactor file extension format-guessing archive: move file extension format-guessing lower archive: pass archiver struct to write_archive callback archive: refactor list of archive formats archive-tar: don't reload default config options archive: reorder option parsing and config reading
2 parents 17a403c + 7b97730 commit 765c7e4

File tree

8 files changed

+375
-73
lines changed

8 files changed

+375
-73
lines changed

Documentation/git-archive.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ tar.umask::
101101
details. If `--remote` is used then only the configuration of
102102
the remote repository takes effect.
103103

104+
tar.<format>.command::
105+
This variable specifies a shell command through which the tar
106+
output generated by `git archive` should be piped. The command
107+
is executed using the shell with the generated tar file on its
108+
standard input, and should produce the final output on its
109+
standard output. Any compression-level options will be passed
110+
to the command (e.g., "-9"). An output file with the same
111+
extension as `<format>` will be use this format if no other
112+
format is given.
113+
+
114+
The "tar.gz" and "tgz" formats are defined automatically and default to
115+
`gzip -cn`. You may override them with custom commands.
116+
117+
tar.<format>.remote::
118+
If true, enable `<format>` for use by remote clients via
119+
linkgit:git-upload-archive[1]. Defaults to false for
120+
user-defined formats, but true for the "tar.gz" and "tgz"
121+
formats.
122+
104123
ATTRIBUTES
105124
----------
106125

@@ -133,6 +152,14 @@ git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
133152

134153
Create a compressed tarball for v1.4.0 release.
135154

155+
git archive --format=tar.gz --prefix=git-1.4.0/ v1.4.0 >git-1.4.0.tar.gz::
156+
157+
Same as above, but using the builtin tar.gz handling.
158+
159+
git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0::
160+
161+
Same as above, but the format is inferred from the output file.
162+
136163
git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
137164

138165
Create a compressed tarball for v1.4.0 release, but without a
@@ -149,6 +176,12 @@ git archive -o latest.zip HEAD::
149176
commit on the current branch. Note that the output format is
150177
inferred by the extension of the output file.
151178

179+
git config tar.tar.xz.command "xz -c"::
180+
181+
Configure a "tar.xz" format for making LZMA-compressed tarfiles.
182+
You can use it specifying `--format=tar.xz`, or by creating an
183+
output file like `-o foo.tar.xz`.
184+
152185

153186
SEE ALSO
154187
--------

archive-tar.c

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "cache.h"
55
#include "tar.h"
66
#include "archive.h"
7+
#include "run-command.h"
78

89
#define RECORDSIZE (512)
910
#define BLOCKSIZE (RECORDSIZE * 20)
@@ -13,6 +14,9 @@ static unsigned long offset;
1314

1415
static int tar_umask = 002;
1516

17+
static int write_tar_filter_archive(const struct archiver *ar,
18+
struct archiver_args *args);
19+
1620
/* writes out the whole block, but only if it is full */
1721
static void write_if_needed(void)
1822
{
@@ -220,6 +224,67 @@ static int write_global_extended_header(struct archiver_args *args)
220224
return err;
221225
}
222226

227+
static struct archiver **tar_filters;
228+
static int nr_tar_filters;
229+
static int alloc_tar_filters;
230+
231+
static struct archiver *find_tar_filter(const char *name, int len)
232+
{
233+
int i;
234+
for (i = 0; i < nr_tar_filters; i++) {
235+
struct archiver *ar = tar_filters[i];
236+
if (!strncmp(ar->name, name, len) && !ar->name[len])
237+
return ar;
238+
}
239+
return NULL;
240+
}
241+
242+
static int tar_filter_config(const char *var, const char *value, void *data)
243+
{
244+
struct archiver *ar;
245+
const char *dot;
246+
const char *name;
247+
const char *type;
248+
int namelen;
249+
250+
if (prefixcmp(var, "tar."))
251+
return 0;
252+
dot = strrchr(var, '.');
253+
if (dot == var + 9)
254+
return 0;
255+
256+
name = var + 4;
257+
namelen = dot - name;
258+
type = dot + 1;
259+
260+
ar = find_tar_filter(name, namelen);
261+
if (!ar) {
262+
ar = xcalloc(1, sizeof(*ar));
263+
ar->name = xmemdupz(name, namelen);
264+
ar->write_archive = write_tar_filter_archive;
265+
ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS;
266+
ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters);
267+
tar_filters[nr_tar_filters++] = ar;
268+
}
269+
270+
if (!strcmp(type, "command")) {
271+
if (!value)
272+
return config_error_nonbool(var);
273+
free(ar->data);
274+
ar->data = xstrdup(value);
275+
return 0;
276+
}
277+
if (!strcmp(type, "remote")) {
278+
if (git_config_bool(var, value))
279+
ar->flags |= ARCHIVER_REMOTE;
280+
else
281+
ar->flags &= ~ARCHIVER_REMOTE;
282+
return 0;
283+
}
284+
285+
return 0;
286+
}
287+
223288
static int git_tar_config(const char *var, const char *value, void *cb)
224289
{
225290
if (!strcmp(var, "tar.umask")) {
@@ -231,15 +296,15 @@ static int git_tar_config(const char *var, const char *value, void *cb)
231296
}
232297
return 0;
233298
}
234-
return git_default_config(var, value, cb);
299+
300+
return tar_filter_config(var, value, cb);
235301
}
236302

237-
int write_tar_archive(struct archiver_args *args)
303+
static int write_tar_archive(const struct archiver *ar,
304+
struct archiver_args *args)
238305
{
239306
int err = 0;
240307

241-
git_config(git_tar_config, NULL);
242-
243308
if (args->commit_sha1)
244309
err = write_global_extended_header(args);
245310
if (!err)
@@ -248,3 +313,65 @@ int write_tar_archive(struct archiver_args *args)
248313
write_trailer();
249314
return err;
250315
}
316+
317+
static int write_tar_filter_archive(const struct archiver *ar,
318+
struct archiver_args *args)
319+
{
320+
struct strbuf cmd = STRBUF_INIT;
321+
struct child_process filter;
322+
const char *argv[2];
323+
int r;
324+
325+
if (!ar->data)
326+
die("BUG: tar-filter archiver called with no filter defined");
327+
328+
strbuf_addstr(&cmd, ar->data);
329+
if (args->compression_level >= 0)
330+
strbuf_addf(&cmd, " -%d", args->compression_level);
331+
332+
memset(&filter, 0, sizeof(filter));
333+
argv[0] = cmd.buf;
334+
argv[1] = NULL;
335+
filter.argv = argv;
336+
filter.use_shell = 1;
337+
filter.in = -1;
338+
339+
if (start_command(&filter) < 0)
340+
die_errno("unable to start '%s' filter", argv[0]);
341+
close(1);
342+
if (dup2(filter.in, 1) < 0)
343+
die_errno("unable to redirect descriptor");
344+
close(filter.in);
345+
346+
r = write_tar_archive(ar, args);
347+
348+
close(1);
349+
if (finish_command(&filter) != 0)
350+
die("'%s' filter reported error", argv[0]);
351+
352+
strbuf_release(&cmd);
353+
return r;
354+
}
355+
356+
static struct archiver tar_archiver = {
357+
"tar",
358+
write_tar_archive,
359+
ARCHIVER_REMOTE
360+
};
361+
362+
void init_tar_archiver(void)
363+
{
364+
int i;
365+
register_archiver(&tar_archiver);
366+
367+
tar_filter_config("tar.tgz.command", "gzip -cn", NULL);
368+
tar_filter_config("tar.tgz.remote", "true", NULL);
369+
tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL);
370+
tar_filter_config("tar.tar.gz.remote", "true", NULL);
371+
git_config(git_tar_config, NULL);
372+
for (i = 0; i < nr_tar_filters; i++) {
373+
/* omit any filters that never had a command configured */
374+
if (tar_filters[i]->data)
375+
register_archiver(tar_filters[i]);
376+
}
377+
}

archive-zip.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time)
261261
*dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
262262
}
263263

264-
int write_zip_archive(struct archiver_args *args)
264+
static int write_zip_archive(const struct archiver *ar,
265+
struct archiver_args *args)
265266
{
266267
int err;
267268

@@ -278,3 +279,14 @@ int write_zip_archive(struct archiver_args *args)
278279

279280
return err;
280281
}
282+
283+
static struct archiver zip_archiver = {
284+
"zip",
285+
write_zip_archive,
286+
ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE
287+
};
288+
289+
void init_zip_archiver(void)
290+
{
291+
register_archiver(&zip_archiver);
292+
}

archive.c

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ static char const * const archive_usage[] = {
1414
NULL
1515
};
1616

17-
#define USES_ZLIB_COMPRESSION 1
18-
19-
static const struct archiver {
20-
const char *name;
21-
write_archive_fn_t write_archive;
22-
unsigned int flags;
23-
} archivers[] = {
24-
{ "tar", write_tar_archive },
25-
{ "zip", write_zip_archive, USES_ZLIB_COMPRESSION },
26-
};
17+
static const struct archiver **archivers;
18+
static int nr_archivers;
19+
static int alloc_archivers;
20+
21+
void register_archiver(struct archiver *ar)
22+
{
23+
ALLOC_GROW(archivers, nr_archivers + 1, alloc_archivers);
24+
archivers[nr_archivers++] = ar;
25+
}
2726

2827
static void format_subst(const struct commit *commit,
2928
const char *src, size_t len,
@@ -208,9 +207,9 @@ static const struct archiver *lookup_archiver(const char *name)
208207
if (!name)
209208
return NULL;
210209

211-
for (i = 0; i < ARRAY_SIZE(archivers); i++) {
212-
if (!strcmp(name, archivers[i].name))
213-
return &archivers[i];
210+
for (i = 0; i < nr_archivers; i++) {
211+
if (!strcmp(name, archivers[i]->name))
212+
return archivers[i];
214213
}
215214
return NULL;
216215
}
@@ -299,9 +298,10 @@ static void parse_treeish_arg(const char **argv,
299298
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, NULL, (p) }
300299

301300
static int parse_archive_args(int argc, const char **argv,
302-
const struct archiver **ar, struct archiver_args *args)
301+
const struct archiver **ar, struct archiver_args *args,
302+
const char *name_hint, int is_remote)
303303
{
304-
const char *format = "tar";
304+
const char *format = NULL;
305305
const char *base = NULL;
306306
const char *remote = NULL;
307307
const char *exec = NULL;
@@ -355,21 +355,27 @@ static int parse_archive_args(int argc, const char **argv,
355355
base = "";
356356

357357
if (list) {
358-
for (i = 0; i < ARRAY_SIZE(archivers); i++)
359-
printf("%s\n", archivers[i].name);
358+
for (i = 0; i < nr_archivers; i++)
359+
if (!is_remote || archivers[i]->flags & ARCHIVER_REMOTE)
360+
printf("%s\n", archivers[i]->name);
360361
exit(0);
361362
}
362363

364+
if (!format && name_hint)
365+
format = archive_format_from_filename(name_hint);
366+
if (!format)
367+
format = "tar";
368+
363369
/* We need at least one parameter -- tree-ish */
364370
if (argc < 1)
365371
usage_with_options(archive_usage, opts);
366372
*ar = lookup_archiver(format);
367-
if (!*ar)
373+
if (!*ar || (is_remote && !((*ar)->flags & ARCHIVER_REMOTE)))
368374
die("Unknown archive format '%s'", format);
369375

370376
args->compression_level = Z_DEFAULT_COMPRESSION;
371377
if (compression_level != -1) {
372-
if ((*ar)->flags & USES_ZLIB_COMPRESSION)
378+
if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS)
373379
args->compression_level = compression_level;
374380
else {
375381
die("Argument not supported for format '%s': -%d",
@@ -385,19 +391,55 @@ static int parse_archive_args(int argc, const char **argv,
385391
}
386392

387393
int write_archive(int argc, const char **argv, const char *prefix,
388-
int setup_prefix)
394+
int setup_prefix, const char *name_hint, int remote)
389395
{
396+
int nongit = 0;
390397
const struct archiver *ar = NULL;
391398
struct archiver_args args;
392399

393-
argc = parse_archive_args(argc, argv, &ar, &args);
394400
if (setup_prefix && prefix == NULL)
395-
prefix = setup_git_directory();
401+
prefix = setup_git_directory_gently(&nongit);
402+
403+
git_config(git_default_config, NULL);
404+
init_tar_archiver();
405+
init_zip_archiver();
406+
407+
argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
408+
if (nongit) {
409+
/*
410+
* We know this will die() with an error, so we could just
411+
* die ourselves; but its error message will be more specific
412+
* than what we could write here.
413+
*/
414+
setup_git_directory();
415+
}
396416

397417
parse_treeish_arg(argv, &args, prefix);
398418
parse_pathspec_arg(argv + 1, &args);
399419

400-
git_config(git_default_config, NULL);
420+
return ar->write_archive(ar, &args);
421+
}
401422

402-
return ar->write_archive(&args);
423+
static int match_extension(const char *filename, const char *ext)
424+
{
425+
int prefixlen = strlen(filename) - strlen(ext);
426+
427+
/*
428+
* We need 1 character for the '.', and 1 character to ensure that the
429+
* prefix is non-empty (k.e., we don't match .tar.gz with no actual
430+
* filename).
431+
*/
432+
if (prefixlen < 2 || filename[prefixlen-1] != '.')
433+
return 0;
434+
return !strcmp(filename + prefixlen, ext);
435+
}
436+
437+
const char *archive_format_from_filename(const char *filename)
438+
{
439+
int i;
440+
441+
for (i = 0; i < nr_archivers; i++)
442+
if (match_extension(filename, archivers[i]->name))
443+
return archivers[i]->name;
444+
return NULL;
403445
}

0 commit comments

Comments
 (0)