Merge branch 'rs/archive-with-internal-gzip'

Teach "git archive" to (optionally and then by default) avoid
spawning an external "gzip" process when creating ".tar.gz" (and
".tgz") archives.

* rs/archive-with-internal-gzip:
  archive-tar: use internal gzip by default
  archive-tar: use OS_CODE 3 (Unix) for internal gzip
  archive-tar: add internal gzip implementation
  archive-tar: factor out write_block()
  archive: rename archiver data field to filter_command
  archive: update format documentation
maint
Junio C Hamano 2022-07-11 15:38:51 -07:00
commit b5a2d6cc49
4 changed files with 100 additions and 28 deletions

View File

@ -34,10 +34,12 @@ OPTIONS
-------

--format=<fmt>::
Format of the resulting archive: 'tar' or 'zip'. If this option
Format of the resulting archive. Possible values are `tar`,
`zip`, `tar.gz`, `tgz`, and any format defined using the
configuration option `tar.<format>.command`. If `--format`
is not given, and the output file is specified, the format is
inferred from the filename if possible (e.g. writing to "foo.zip"
makes the output to be in the zip format). Otherwise the output
inferred from the filename if possible (e.g. writing to `foo.zip`
makes the output to be in the `zip` format). Otherwise the output
format is `tar`.

-l::
@ -143,17 +145,16 @@ tar.<format>.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.
to the command (e.g., `-9`).
+
The "tar.gz" and "tgz" formats are defined automatically and default to
`gzip -cn`. You may override them with custom commands.
The `tar.gz` and `tgz` formats are defined automatically and use the
magic command `git archive gzip` by default, which invokes an internal
implementation of gzip.

tar.<format>.remote::
If true, enable `<format>` for use by remote clients via
If true, enable the format for use by remote clients via
linkgit:git-upload-archive[1]. Defaults to false for
user-defined formats, but true for the "tar.gz" and "tgz"
user-defined formats, but true for the `tar.gz` and `tgz`
formats.

[[ATTRIBUTES]]

View File

@ -38,11 +38,18 @@ static int write_tar_filter_archive(const struct archiver *ar,
#define USTAR_MAX_MTIME 077777777777ULL
#endif

static void tar_write_block(const void *buf)
{
write_or_die(1, buf, BLOCKSIZE);
}

static void (*write_block)(const void *) = tar_write_block;

/* writes out the whole block, but only if it is full */
static void write_if_needed(void)
{
if (offset == BLOCKSIZE) {
write_or_die(1, block, BLOCKSIZE);
write_block(block);
offset = 0;
}
}
@ -66,7 +73,7 @@ static void do_write_blocked(const void *data, unsigned long size)
write_if_needed();
}
while (size >= BLOCKSIZE) {
write_or_die(1, buf, BLOCKSIZE);
write_block(buf);
size -= BLOCKSIZE;
buf += BLOCKSIZE;
}
@ -101,10 +108,10 @@ static void write_trailer(void)
{
int tail = BLOCKSIZE - offset;
memset(block + offset, 0, tail);
write_or_die(1, block, BLOCKSIZE);
write_block(block);
if (tail < 2 * RECORDSIZE) {
memset(block, 0, offset);
write_or_die(1, block, BLOCKSIZE);
write_block(block);
}
}

@ -383,8 +390,8 @@ static int tar_filter_config(const char *var, const char *value, void *data)
if (!strcmp(type, "command")) {
if (!value)
return config_error_nonbool(var);
free(ar->data);
ar->data = xstrdup(value);
free(ar->filter_command);
ar->filter_command = xstrdup(value);
return 0;
}
if (!strcmp(type, "remote")) {
@ -425,17 +432,65 @@ static int write_tar_archive(const struct archiver *ar,
return err;
}

static git_zstream gzstream;
static unsigned char outbuf[16384];

static void tgz_deflate(int flush)
{
while (gzstream.avail_in || flush == Z_FINISH) {
int status = git_deflate(&gzstream, flush);
if (!gzstream.avail_out || status == Z_STREAM_END) {
write_or_die(1, outbuf, gzstream.next_out - outbuf);
gzstream.next_out = outbuf;
gzstream.avail_out = sizeof(outbuf);
if (status == Z_STREAM_END)
break;
}
if (status != Z_OK && status != Z_BUF_ERROR)
die(_("deflate error (%d)"), status);
}
}

static void tgz_write_block(const void *data)
{
gzstream.next_in = (void *)data;
gzstream.avail_in = BLOCKSIZE;
tgz_deflate(Z_NO_FLUSH);
}

static const char internal_gzip_command[] = "git archive gzip";

static int write_tar_filter_archive(const struct archiver *ar,
struct archiver_args *args)
{
#if ZLIB_VERNUM >= 0x1221
struct gz_header_s gzhead = { .os = 3 }; /* Unix, for reproducibility */
#endif
struct strbuf cmd = STRBUF_INIT;
struct child_process filter = CHILD_PROCESS_INIT;
int r;

if (!ar->data)
if (!ar->filter_command)
BUG("tar-filter archiver called with no filter defined");

strbuf_addstr(&cmd, ar->data);
if (!strcmp(ar->filter_command, internal_gzip_command)) {
write_block = tgz_write_block;
git_deflate_init_gzip(&gzstream, args->compression_level);
#if ZLIB_VERNUM >= 0x1221
if (deflateSetHeader(&gzstream.z, &gzhead) != Z_OK)
BUG("deflateSetHeader() called too late");
#endif
gzstream.next_out = outbuf;
gzstream.avail_out = sizeof(outbuf);

r = write_tar_archive(ar, args);

tgz_deflate(Z_FINISH);
git_deflate_end(&gzstream);
return r;
}

strbuf_addstr(&cmd, ar->filter_command);
if (args->compression_level >= 0)
strbuf_addf(&cmd, " -%d", args->compression_level);

@ -471,14 +526,14 @@ void init_tar_archiver(void)
int i;
register_archiver(&tar_archiver);

tar_filter_config("tar.tgz.command", "gzip -cn", NULL);
tar_filter_config("tar.tgz.command", internal_gzip_command, NULL);
tar_filter_config("tar.tgz.remote", "true", NULL);
tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL);
tar_filter_config("tar.tar.gz.command", internal_gzip_command, NULL);
tar_filter_config("tar.tar.gz.remote", "true", NULL);
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)
if (tar_filters[i]->filter_command)
register_archiver(tar_filters[i]);
}
}

View File

@ -43,7 +43,7 @@ struct archiver {
const char *name;
int (*write_archive)(const struct archiver *, struct archiver_args *);
unsigned flags;
void *data;
char *filter_command;
};
void register_archiver(struct archiver *);


View File

@ -339,21 +339,21 @@ test_expect_success 'only enabled filters are available remotely' '
test_cmp_bin remote.bar config.bar
'

test_expect_success GZIP 'git archive --format=tgz' '
test_expect_success 'git archive --format=tgz' '
git archive --format=tgz HEAD >j.tgz
'

test_expect_success GZIP 'git archive --format=tar.gz' '
test_expect_success 'git archive --format=tar.gz' '
git archive --format=tar.gz HEAD >j1.tar.gz &&
test_cmp_bin j.tgz j1.tar.gz
'

test_expect_success GZIP 'infer tgz from .tgz filename' '
test_expect_success 'infer tgz from .tgz filename' '
git archive --output=j2.tgz HEAD &&
test_cmp_bin j.tgz j2.tgz
'

test_expect_success GZIP 'infer tgz from .tar.gz filename' '
test_expect_success 'infer tgz from .tar.gz filename' '
git archive --output=j3.tar.gz HEAD &&
test_cmp_bin j.tgz j3.tar.gz
'
@ -363,17 +363,33 @@ test_expect_success GZIP 'extract tgz file' '
test_cmp_bin b.tar j.tar
'

test_expect_success GZIP 'remote tar.gz is allowed by default' '
test_expect_success 'remote tar.gz is allowed by default' '
git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
test_cmp_bin j.tgz remote.tar.gz
'

test_expect_success GZIP 'remote tar.gz can be disabled' '
test_expect_success 'remote tar.gz can be disabled' '
git config tar.tar.gz.remote false &&
test_must_fail git archive --remote=. --format=tar.gz HEAD \
>remote.tar.gz
'

test_expect_success GZIP 'git archive --format=tgz (external gzip)' '
test_config tar.tgz.command "gzip -cn" &&
git archive --format=tgz HEAD >external_gzip.tgz
'

test_expect_success GZIP 'git archive --format=tar.gz (external gzip)' '
test_config tar.tar.gz.command "gzip -cn" &&
git archive --format=tar.gz HEAD >external_gzip.tar.gz &&
test_cmp_bin external_gzip.tgz external_gzip.tar.gz
'

test_expect_success GZIP 'extract tgz file (external gzip)' '
gzip -d -c <external_gzip.tgz >external_gzip.tar &&
test_cmp_bin b.tar external_gzip.tar
'

test_expect_success 'archive and :(glob)' '
git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
cat >expect <<EOF &&