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 documentationmaint
commit
b5a2d6cc49
|
@ -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]]
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 *);
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
Loading…
Reference in New Issue