Skip to content

Commit 767cf45

Browse files
peffgitster
authored andcommitted
archive: implement configurable tar filters
It's common to pipe the tar output produce by "git archive" through gzip or some other compressor. Locally, this can easily be done by using a shell pipe. When requesting a remote archive, though, it cannot be done through the upload-archive interface. This patch allows configurable tar filters, so that one could define a "tar.gz" format that automatically pipes tar output through gzip. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 08716b3 commit 767cf45

File tree

3 files changed

+165
-1
lines changed

3 files changed

+165
-1
lines changed

Documentation/git-archive.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ 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+
104114
ATTRIBUTES
105115
----------
106116

@@ -149,6 +159,12 @@ git archive -o latest.zip HEAD::
149159
commit on the current branch. Note that the output format is
150160
inferred by the extension of the output file.
151161

162+
git config tar.tar.xz.command "xz -c"::
163+
164+
Configure a "tar.xz" format for making LZMA-compressed tarfiles.
165+
You can use it specifying `--format=tar.xz`, or by creating an
166+
output file like `-o foo.tar.xz`.
167+
152168

153169
SEE ALSO
154170
--------

archive-tar.c

Lines changed: 106 additions & 1 deletion
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,60 @@ 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+
278+
return 0;
279+
}
280+
223281
static int git_tar_config(const char *var, const char *value, void *cb)
224282
{
225283
if (!strcmp(var, "tar.umask")) {
@@ -231,7 +289,8 @@ static int git_tar_config(const char *var, const char *value, void *cb)
231289
}
232290
return 0;
233291
}
234-
return 0;
292+
293+
return tar_filter_config(var, value, cb);
235294
}
236295

237296
static int write_tar_archive(const struct archiver *ar,
@@ -248,6 +307,45 @@ static int write_tar_archive(const struct archiver *ar,
248307
return err;
249308
}
250309

310+
static int write_tar_filter_archive(const struct archiver *ar,
311+
struct archiver_args *args)
312+
{
313+
struct strbuf cmd = STRBUF_INIT;
314+
struct child_process filter;
315+
const char *argv[2];
316+
int r;
317+
318+
if (!ar->data)
319+
die("BUG: tar-filter archiver called with no filter defined");
320+
321+
strbuf_addstr(&cmd, ar->data);
322+
if (args->compression_level >= 0)
323+
strbuf_addf(&cmd, " -%d", args->compression_level);
324+
325+
memset(&filter, 0, sizeof(filter));
326+
argv[0] = cmd.buf;
327+
argv[1] = NULL;
328+
filter.argv = argv;
329+
filter.use_shell = 1;
330+
filter.in = -1;
331+
332+
if (start_command(&filter) < 0)
333+
die_errno("unable to start '%s' filter", argv[0]);
334+
close(1);
335+
if (dup2(filter.in, 1) < 0)
336+
die_errno("unable to redirect descriptor");
337+
close(filter.in);
338+
339+
r = write_tar_archive(ar, args);
340+
341+
close(1);
342+
if (finish_command(&filter) != 0)
343+
die("'%s' filter reported error", argv[0]);
344+
345+
strbuf_release(&cmd);
346+
return r;
347+
}
348+
251349
static struct archiver tar_archiver = {
252350
"tar",
253351
write_tar_archive,
@@ -256,6 +354,13 @@ static struct archiver tar_archiver = {
256354

257355
void init_tar_archiver(void)
258356
{
357+
int i;
259358
register_archiver(&tar_archiver);
359+
260360
git_config(git_tar_config, NULL);
361+
for (i = 0; i < nr_tar_filters; i++) {
362+
/* omit any filters that never had a command configured */
363+
if (tar_filters[i]->data)
364+
register_archiver(tar_filters[i]);
365+
}
261366
}

t/t5000-tar-tree.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,47 @@ test_expect_success 'git-archive --prefix=olde-' '
252252
test -f h/olde-a/bin/sh
253253
'
254254

255+
test_expect_success 'setup tar filters' '
256+
git config tar.tar.foo.command "tr ab ba" &&
257+
git config tar.bar.command "tr ab ba"
258+
'
259+
260+
test_expect_success 'archive --list mentions user filter' '
261+
git archive --list >output &&
262+
grep "^tar\.foo\$" output &&
263+
grep "^bar\$" output
264+
'
265+
266+
test_expect_success 'archive --list shows remote user filters' '
267+
git archive --list --remote=. >output &&
268+
grep "^tar\.foo\$" output &&
269+
grep "^bar\$" output
270+
'
271+
272+
test_expect_success 'invoke tar filter by format' '
273+
git archive --format=tar.foo HEAD >config.tar.foo &&
274+
tr ab ba <config.tar.foo >config.tar &&
275+
test_cmp b.tar config.tar &&
276+
git archive --format=bar HEAD >config.bar &&
277+
tr ab ba <config.bar >config.tar &&
278+
test_cmp b.tar config.tar
279+
'
280+
281+
test_expect_success 'invoke tar filter by extension' '
282+
git archive -o config-implicit.tar.foo HEAD &&
283+
test_cmp config.tar.foo config-implicit.tar.foo &&
284+
git archive -o config-implicit.bar HEAD &&
285+
test_cmp config.tar.foo config-implicit.bar
286+
'
287+
288+
test_expect_success 'default output format remains tar' '
289+
git archive -o config-implicit.baz HEAD &&
290+
test_cmp b.tar config-implicit.baz
291+
'
292+
293+
test_expect_success 'extension matching requires dot' '
294+
git archive -o config-implicittar.foo HEAD &&
295+
test_cmp b.tar config-implicittar.foo
296+
'
297+
255298
test_done

0 commit comments

Comments
 (0)