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
This commit is contained in:
@ -34,10 +34,12 @@ OPTIONS
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
--format=<fmt>::
|
--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
|
is not given, and the output file is specified, the format is
|
||||||
inferred from the filename if possible (e.g. writing to "foo.zip"
|
inferred from the filename if possible (e.g. writing to `foo.zip`
|
||||||
makes the output to be in the zip format). Otherwise the output
|
makes the output to be in the `zip` format). Otherwise the output
|
||||||
format is `tar`.
|
format is `tar`.
|
||||||
|
|
||||||
-l::
|
-l::
|
||||||
@ -143,17 +145,16 @@ tar.<format>.command::
|
|||||||
is executed using the shell with the generated tar file on its
|
is executed using the shell with the generated tar file on its
|
||||||
standard input, and should produce the final output on its
|
standard input, and should produce the final output on its
|
||||||
standard output. Any compression-level options will be passed
|
standard output. Any compression-level options will be passed
|
||||||
to the command (e.g., "-9"). An output file with the same
|
to the command (e.g., `-9`).
|
||||||
extension as `<format>` will be use this format if no other
|
|
||||||
format is given.
|
|
||||||
+
|
+
|
||||||
The "tar.gz" and "tgz" formats are defined automatically and default to
|
The `tar.gz` and `tgz` formats are defined automatically and use the
|
||||||
`gzip -cn`. You may override them with custom commands.
|
magic command `git archive gzip` by default, which invokes an internal
|
||||||
|
implementation of gzip.
|
||||||
|
|
||||||
tar.<format>.remote::
|
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
|
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.
|
formats.
|
||||||
|
|
||||||
[[ATTRIBUTES]]
|
[[ATTRIBUTES]]
|
||||||
|
|||||||
@ -38,11 +38,18 @@ static int write_tar_filter_archive(const struct archiver *ar,
|
|||||||
#define USTAR_MAX_MTIME 077777777777ULL
|
#define USTAR_MAX_MTIME 077777777777ULL
|
||||||
#endif
|
#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 */
|
/* writes out the whole block, but only if it is full */
|
||||||
static void write_if_needed(void)
|
static void write_if_needed(void)
|
||||||
{
|
{
|
||||||
if (offset == BLOCKSIZE) {
|
if (offset == BLOCKSIZE) {
|
||||||
write_or_die(1, block, BLOCKSIZE);
|
write_block(block);
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +73,7 @@ static void do_write_blocked(const void *data, unsigned long size)
|
|||||||
write_if_needed();
|
write_if_needed();
|
||||||
}
|
}
|
||||||
while (size >= BLOCKSIZE) {
|
while (size >= BLOCKSIZE) {
|
||||||
write_or_die(1, buf, BLOCKSIZE);
|
write_block(buf);
|
||||||
size -= BLOCKSIZE;
|
size -= BLOCKSIZE;
|
||||||
buf += BLOCKSIZE;
|
buf += BLOCKSIZE;
|
||||||
}
|
}
|
||||||
@ -101,10 +108,10 @@ static void write_trailer(void)
|
|||||||
{
|
{
|
||||||
int tail = BLOCKSIZE - offset;
|
int tail = BLOCKSIZE - offset;
|
||||||
memset(block + offset, 0, tail);
|
memset(block + offset, 0, tail);
|
||||||
write_or_die(1, block, BLOCKSIZE);
|
write_block(block);
|
||||||
if (tail < 2 * RECORDSIZE) {
|
if (tail < 2 * RECORDSIZE) {
|
||||||
memset(block, 0, offset);
|
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 (!strcmp(type, "command")) {
|
||||||
if (!value)
|
if (!value)
|
||||||
return config_error_nonbool(var);
|
return config_error_nonbool(var);
|
||||||
free(ar->data);
|
free(ar->filter_command);
|
||||||
ar->data = xstrdup(value);
|
ar->filter_command = xstrdup(value);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!strcmp(type, "remote")) {
|
if (!strcmp(type, "remote")) {
|
||||||
@ -425,17 +432,65 @@ static int write_tar_archive(const struct archiver *ar,
|
|||||||
return err;
|
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,
|
static int write_tar_filter_archive(const struct archiver *ar,
|
||||||
struct archiver_args *args)
|
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 strbuf cmd = STRBUF_INIT;
|
||||||
struct child_process filter = CHILD_PROCESS_INIT;
|
struct child_process filter = CHILD_PROCESS_INIT;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
if (!ar->data)
|
if (!ar->filter_command)
|
||||||
BUG("tar-filter archiver called with no filter defined");
|
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)
|
if (args->compression_level >= 0)
|
||||||
strbuf_addf(&cmd, " -%d", args->compression_level);
|
strbuf_addf(&cmd, " -%d", args->compression_level);
|
||||||
|
|
||||||
@ -471,14 +526,14 @@ void init_tar_archiver(void)
|
|||||||
int i;
|
int i;
|
||||||
register_archiver(&tar_archiver);
|
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.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);
|
tar_filter_config("tar.tar.gz.remote", "true", NULL);
|
||||||
git_config(git_tar_config, NULL);
|
git_config(git_tar_config, NULL);
|
||||||
for (i = 0; i < nr_tar_filters; i++) {
|
for (i = 0; i < nr_tar_filters; i++) {
|
||||||
/* omit any filters that never had a command configured */
|
/* 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]);
|
register_archiver(tar_filters[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ struct archiver {
|
|||||||
const char *name;
|
const char *name;
|
||||||
int (*write_archive)(const struct archiver *, struct archiver_args *);
|
int (*write_archive)(const struct archiver *, struct archiver_args *);
|
||||||
unsigned flags;
|
unsigned flags;
|
||||||
void *data;
|
char *filter_command;
|
||||||
};
|
};
|
||||||
void register_archiver(struct archiver *);
|
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_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
|
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 &&
|
git archive --format=tar.gz HEAD >j1.tar.gz &&
|
||||||
test_cmp_bin j.tgz 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 &&
|
git archive --output=j2.tgz HEAD &&
|
||||||
test_cmp_bin j.tgz j2.tgz
|
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 &&
|
git archive --output=j3.tar.gz HEAD &&
|
||||||
test_cmp_bin j.tgz j3.tar.gz
|
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_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 &&
|
git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
|
||||||
test_cmp_bin j.tgz 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 &&
|
git config tar.tar.gz.remote false &&
|
||||||
test_must_fail git archive --remote=. --format=tar.gz HEAD \
|
test_must_fail git archive --remote=. --format=tar.gz HEAD \
|
||||||
>remote.tar.gz
|
>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)' '
|
test_expect_success 'archive and :(glob)' '
|
||||||
git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
|
git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
|
||||||
cat >expect <<EOF &&
|
cat >expect <<EOF &&
|
||||||
|
|||||||
Reference in New Issue
Block a user