Skip to content

Commit

Permalink
archive: implement configurable tar filters
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
peff authored and gitster committed Jun 22, 2011
1 parent 08716b3 commit 767cf45
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 1 deletion.
16 changes: 16 additions & 0 deletions Documentation/git-archive.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ tar.umask::
details. If `--remote` is used then only the configuration of
the remote repository takes effect.

tar.<format>.command::
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.

ATTRIBUTES
----------

Expand Down Expand Up @@ -149,6 +159,12 @@ git archive -o latest.zip 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`.


SEE ALSO
--------
Expand Down
107 changes: 106 additions & 1 deletion archive-tar.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "cache.h"
#include "tar.h"
#include "archive.h"
#include "run-command.h"

#define RECORDSIZE (512)
#define BLOCKSIZE (RECORDSIZE * 20)
Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS;
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);
free(ar->data);
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")) {
Expand All @@ -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,
Expand All @@ -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;
filter.in = -1;

if (start_command(&filter) < 0)
die_errno("unable to start '%s' filter", argv[0]);
close(1);
if (dup2(filter.in, 1) < 0)
die_errno("unable to redirect descriptor");
close(filter.in);

r = write_tar_archive(ar, args);

close(1);
if (finish_command(&filter) != 0)
die("'%s' filter reported error", argv[0]);

strbuf_release(&cmd);
return r;
}

static struct archiver tar_archiver = {
"tar",
write_tar_archive,
Expand All @@ -256,6 +354,13 @@ static struct archiver tar_archiver = {

void init_tar_archiver(void)
{
int i;
register_archiver(&tar_archiver);

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)
register_archiver(tar_filters[i]);
}
}
43 changes: 43 additions & 0 deletions t/t5000-tar-tree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 tar.tar.foo.command "tr ab ba" &&
git config tar.bar.command "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 --format=tar.foo HEAD >config.tar.foo &&
tr ab ba <config.tar.foo >config.tar &&
test_cmp b.tar config.tar &&
git archive --format=bar HEAD >config.bar &&
tr ab ba <config.bar >config.tar &&
test_cmp b.tar config.tar
'

test_expect_success 'invoke tar filter by extension' '
git archive -o config-implicit.tar.foo HEAD &&
test_cmp config.tar.foo config-implicit.tar.foo &&
git archive -o config-implicit.bar HEAD &&
test_cmp config.tar.foo config-implicit.bar
'

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 config-implicittar.foo HEAD &&
test_cmp b.tar config-implicittar.foo
'

test_done

0 comments on commit 767cf45

Please sign in to comment.