Commit 767cf457 authored by Jeff King's avatar Jeff King Committed by Junio C Hamano

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: default avatarJeff King <[email protected]>
Signed-off-by: default avatarJunio C Hamano <[email protected]>
parent 08716b3c
......@@ -101,6 +101,16 @@ tar.umask::
details. If `--remote` is used then only the configuration of
the remote repository takes effect.
This variable specifies a shell command through which the tar
output generated by `git archive` should be piped. The command
is executed using the shell with the generated tar file on its
standard input, and should produce the final output on its
standard output. Any compression-level options will be passed
to the command (e.g., "-9"). An output file with the same
extension as `<format>` will be use this format if no other
format is given.
......@@ -149,6 +159,12 @@ git archive -o HEAD::
commit on the current branch. Note that the output format is
inferred by the extension of the output file.
git config tar.tar.xz.command "xz -c"::
Configure a "tar.xz" format for making LZMA-compressed tarfiles.
You can use it specifying `--format=tar.xz`, or by creating an
output file like `-o foo.tar.xz`.
......@@ -4,6 +4,7 @@
#include "cache.h"
#include "tar.h"
#include "archive.h"
#include "run-command.h"
#define RECORDSIZE (512)
......@@ -13,6 +14,9 @@ static unsigned long offset;
static int tar_umask = 002;
static int write_tar_filter_archive(const struct archiver *ar,
struct archiver_args *args);
/* writes out the whole block, but only if it is full */
static void write_if_needed(void)
......@@ -220,6 +224,60 @@ static int write_global_extended_header(struct archiver_args *args)
return err;
static struct archiver **tar_filters;
static int nr_tar_filters;
static int alloc_tar_filters;
static struct archiver *find_tar_filter(const char *name, int len)
int i;
for (i = 0; i < nr_tar_filters; i++) {
struct archiver *ar = tar_filters[i];
if (!strncmp(ar->name, name, len) && !ar->name[len])
return ar;
return NULL;
static int tar_filter_config(const char *var, const char *value, void *data)
struct archiver *ar;
const char *dot;
const char *name;
const char *type;
int namelen;
if (prefixcmp(var, "tar."))
return 0;
dot = strrchr(var, '.');
if (dot == var + 9)
return 0;
name = var + 4;
namelen = dot - name;
type = dot + 1;
ar = find_tar_filter(name, namelen);
if (!ar) {
ar = xcalloc(1, sizeof(*ar));
ar->name = xmemdupz(name, namelen);
ar->write_archive = write_tar_filter_archive;
ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters);
tar_filters[nr_tar_filters++] = ar;
if (!strcmp(type, "command")) {
if (!value)
return config_error_nonbool(var);
ar->data = xstrdup(value);
return 0;
return 0;
static int git_tar_config(const char *var, const char *value, void *cb)
if (!strcmp(var, "tar.umask")) {
......@@ -231,7 +289,8 @@ static int git_tar_config(const char *var, const char *value, void *cb)
return 0;
return 0;
return tar_filter_config(var, value, cb);
static int write_tar_archive(const struct archiver *ar,
......@@ -248,6 +307,45 @@ static int write_tar_archive(const struct archiver *ar,
return err;
static int write_tar_filter_archive(const struct archiver *ar,
struct archiver_args *args)
struct strbuf cmd = STRBUF_INIT;
struct child_process filter;
const char *argv[2];
int r;
if (!ar->data)
die("BUG: tar-filter archiver called with no filter defined");
strbuf_addstr(&cmd, ar->data);
if (args->compression_level >= 0)
strbuf_addf(&cmd, " -%d", args->compression_level);
memset(&filter, 0, sizeof(filter));
argv[0] = cmd.buf;
argv[1] = NULL;
filter.argv = argv;
filter.use_shell = 1; = -1;
if (start_command(&filter) < 0)
die_errno("unable to start '%s' filter", argv[0]);
if (dup2(, 1) < 0)
die_errno("unable to redirect descriptor");
r = write_tar_archive(ar, args);
if (finish_command(&filter) != 0)
die("'%s' filter reported error", argv[0]);
return r;
static struct archiver tar_archiver = {
......@@ -256,6 +354,13 @@ static struct archiver tar_archiver = {
void init_tar_archiver(void)
int i;
git_config(git_tar_config, NULL);
for (i = 0; i < nr_tar_filters; i++) {
/* omit any filters that never had a command configured */
if (tar_filters[i]->data)
......@@ -252,4 +252,47 @@ test_expect_success 'git-archive --prefix=olde-' '
test -f h/olde-a/bin/sh
test_expect_success 'setup tar filters' '
git config "tr ab ba" &&
git config "tr ab ba"
test_expect_success 'archive --list mentions user filter' '
git archive --list >output &&
grep "^tar\.foo\$" output &&
grep "^bar\$" output
test_expect_success 'archive --list shows remote user filters' '
git archive --list --remote=. >output &&
grep "^tar\.foo\$" output &&
grep "^bar\$" output
test_expect_success 'invoke tar filter by format' '
git archive HEAD > &&
tr ab ba < >config.tar &&
test_cmp b.tar config.tar &&
git archive --format=bar HEAD > &&
tr ab ba < >config.tar &&
test_cmp b.tar config.tar
test_expect_success 'invoke tar filter by extension' '
git archive -o HEAD &&
test_cmp &&
git archive -o HEAD &&
test_expect_success 'default output format remains tar' '
git archive -o config-implicit.baz HEAD &&
test_cmp b.tar config-implicit.baz
test_expect_success 'extension matching requires dot' '
git archive -o HEAD &&
test_cmp b.tar
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment