path: root/archive.c
diff options
authorJeff King <>2011-06-22 01:24:48 (GMT)
committerJunio C Hamano <>2011-06-22 18:12:35 (GMT)
commit56baa61d011e9ed5199766fea89ea8c2c183f0ba (patch)
tree5eb3e46eb126a1d53d90397eb014b78f50feeb14 /archive.c
parent4d7c98986379b0ab93cbf9092b60dfb5ab1cee7c (diff)
archive: move file extension format-guessing lower
The process for guessing an archive output format based on the filename is something like this: a. parse --output in cmd_archive; check the filename against a static set of mapping heuristics (right now it just matches ".zip" for zip files). b. if found, stick a fake "--format=zip" at the beginning of the arguments list (if the user did specify a --format manually, the later option will override our fake one) c. if it's a remote call, ship the arguments to the remote (including the fake), which will call write_archive on their end d. if it's local, ship the arguments to write_archive locally There are two problems: 1. The set of mappings is static and at too high a level. The write_archive level is going to check config for user-defined formats, some of which will specify extensions. We need to delay lookup until those are parsed, so we can match against them. 2. For a remote archive call, our set of mappings (or formats) may not match the remote side's. This is OK in practice right now, because all versions of git understand "zip" and "tar". But as new formats are added, there is going to be a mismatch between what the client can do and what the remote server can do. To fix (1), this patch refactors the location guessing to happen at the write_archive level, instead of the cmd_archive level. So instead of sticking a fake --format field in the argv list, we actually pass a "name hint" down the callchain; this hint is used at the appropriate time to guess the format (if one hasn't been given already). This patch leaves (2) unfixed. The name_hint is converted to a "--format" option as before, and passed to the remote. This means the local side's idea of how extensions map to formats will take precedence. Another option would be to pass the name hint to the remote side and let the remote choose. This isn't a good idea for two reasons: 1. There's no room in the protocol for passing that information. We can pass a new argument, but older versions of git on the server will choke on it. 2. Letting the remote side decide creates a silent inconsistency in user experience. Consider the case that the locally installed git knows about the "tar.gz" format, but a remote server doesn't. Running "git archive -o foo.tar.gz" will use the tar.gz format. If we use --remote, and the local side chooses the format, then we send "--format=tar.gz" to the remote, which will complain about the unknown format. But if we let the remote side choose the format, then it will realize that it doesn't know about "tar.gz" and output uncompressed tar without even issuing a warning. Signed-off-by: Jeff King <> Signed-off-by: Junio C Hamano <>
Diffstat (limited to 'archive.c')
1 files changed, 21 insertions, 4 deletions
diff --git a/archive.c b/archive.c
index a0a5beb..7d0ca32 100644
--- a/archive.c
+++ b/archive.c
@@ -298,9 +298,10 @@ static void parse_treeish_arg(const char **argv,
static int parse_archive_args(int argc, const char **argv,
- const struct archiver **ar, struct archiver_args *args)
+ const struct archiver **ar, struct archiver_args *args,
+ const char *name_hint)
- const char *format = "tar";
+ const char *format = NULL;
const char *base = NULL;
const char *remote = NULL;
const char *exec = NULL;
@@ -359,6 +360,11 @@ static int parse_archive_args(int argc, const char **argv,
+ if (!format && name_hint)
+ format = archive_format_from_filename(name_hint);
+ if (!format)
+ format = "tar";
/* We need at least one parameter -- tree-ish */
if (argc < 1)
usage_with_options(archive_usage, opts);
@@ -384,7 +390,7 @@ static int parse_archive_args(int argc, const char **argv,
int write_archive(int argc, const char **argv, const char *prefix,
- int setup_prefix)
+ int setup_prefix, const char *name_hint)
int nongit = 0;
const struct archiver *ar = NULL;
@@ -397,7 +403,7 @@ int write_archive(int argc, const char **argv, const char *prefix,
- argc = parse_archive_args(argc, argv, &ar, &args);
+ argc = parse_archive_args(argc, argv, &ar, &args, name_hint);
if (nongit) {
* We know this will die() with an error, so we could just
@@ -412,3 +418,14 @@ int write_archive(int argc, const char **argv, const char *prefix,
return ar->write_archive(ar, &args);
+const char *archive_format_from_filename(const char *filename)
+ const char *ext = strrchr(filename, '.');
+ if (!ext)
+ return NULL;
+ ext++;
+ if (!strcasecmp(ext, "zip"))
+ return "zip";
+ return NULL;