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 <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff King committed
Jun 21, 2011 at 21:26 UTC
767cf4579f0e34a3cfc6704d5c313842321dfafa
3 files changed
+165
-1
Documentation/git-archive.txt
+16
@@ -101,6 +101,16 @@ tar.umask::
101
details. If `--remote` is used then only the configuration of
102
the remote repository takes effect.
103
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
ATTRIBUTES
115
----------
116
@@ -149,6 +159,12 @@ git archive -o latest.zip HEAD::
159
commit on the current branch. Note that the output format is
160
inferred by the extension of the output file.
161
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
+
168
169
SEE ALSO
170
--------
archive-tar.c
+106
-1
@@ -4,6 +4,7 @@
4
#include "cache.h"
5
#include "tar.h"
6
#include "archive.h"
7
+#include "run-command.h"
8
9
#define RECORDSIZE (512)
10
#define BLOCKSIZE (RECORDSIZE * 20)
@@ -13,6 +14,9 @@ static unsigned long offset;
14
15
static int tar_umask = 002;
16
17
+static int write_tar_filter_archive(const struct archiver *ar,
18
+ struct archiver_args *args);
19
+
20
/* writes out the whole block, but only if it is full */
21
static void write_if_needed(void)
22
{
@@ -220,6 +224,60 @@ static int write_global_extended_header(struct archiver_args *args)
224
return err;
225
}
226
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
+
281
static int git_tar_config(const char *var, const char *value, void *cb)
282
{
283
if (!strcmp(var, "tar.umask")) {
@@ -231,7 +289,8 @@ static int git_tar_config(const char *var, const char *value, void *cb)
289
}
290
return 0;
291
}
234
- return 0;
292
+
293
+ return tar_filter_config(var, value, cb);
294
}
295
296
static int write_tar_archive(const struct archiver *ar,
@@ -248,6 +307,45 @@ static int write_tar_archive(const struct archiver *ar,
307
return err;
308
}
309
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
+
349
static struct archiver tar_archiver = {
350
"tar",
351
write_tar_archive,
@@ -256,6 +354,13 @@ static struct archiver tar_archiver = {
354
355
void init_tar_archiver(void)
356
{
357
+ int i;
358
register_archiver(&tar_archiver);
359
+
360
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
+ }
366
}
t/t5000-tar-tree.sh
+43
@@ -252,4 +252,47 @@ test_expect_success 'git-archive --prefix=olde-' '
252
test -f h/olde-a/bin/sh
253
'
254
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
+
298
test_done