@@ -67,10 +67,16 @@ OPTIONS
by concatenating the value for `--prefix` (if any) and the
basename of <file>.
+
-The `<path>` cannot contain any colon, the file mode is limited to
-a regular file, and the option may be subject to platform-dependent
-command-line limits. For non-trivial cases, write an untracked file
-and use `--add-file` instead.
+The `<path>` argument can start and end with a literal double-quote
+character; The contained file name is interpreted as a C-style string,
+i.e. the backslash is interpreted as escape character. The path must
+be quoted if it contains a colon, to avoid the colon from being
+misinterpreted as the separator between the path and the contents, or
+if the path begins or ends with a double-quote character.
++
+The file mode is limited to a regular file, and the option may be
+subject to platform-dependent command-line limits. For non-trivial
+cases, write an untracked file and use `--add-file` instead.
--worktree-attributes::
Look for attributes in .gitattributes files in the working tree
@@ -9,6 +9,7 @@
#include "parse-options.h"
#include "unpack-trees.h"
#include "dir.h"
+#include "quote.h"
static char const * const archive_usage[] = {
N_("git archive [<options>] <tree-ish> [<path>...]"),
@@ -533,22 +534,31 @@ static int add_file_cb(const struct option *opt, const char *arg, int unset)
die(_("Not a regular file: %s"), path);
info->content = NULL; /* read the file later */
} else if (!strcmp(opt->long_name, "add-virtual-file")) {
- const char *colon = strchr(arg, ':');
- char *p;
+ struct strbuf buf = STRBUF_INIT;
+ const char *p = arg;
+
+ if (*p != '"')
+ p = strchr(p, ':');
+ else if (unquote_c_style(&buf, p, &p) < 0)
+ die(_("unclosed quote: '%s'"), arg);
- if (!colon)
+ if (!p || *p != ':')
die(_("missing colon: '%s'"), arg);
- p = xstrndup(arg, colon - arg);
- if (!args->prefix)
- path = p;
- else {
- path = prefix_filename(args->prefix, p);
- free(p);
+ if (p == arg)
+ die(_("empty file name: '%s'"), arg);
+
+ path = buf.len ?
+ strbuf_detach(&buf, NULL) : xstrndup(arg, p - arg);
+
+ if (args->prefix) {
+ char *save = path;
+ path = prefix_filename(args->prefix, path);
+ free(save);
}
memset(&info->stat, 0, sizeof(info->stat));
info->stat.st_mode = S_IFREG | 0644;
- info->content = xstrdup(colon + 1);
+ info->content = xstrdup(p + 1);
info->stat.st_size = strlen(info->content);
} else {
BUG("add_file_cb() called for %s", opt->long_name);
@@ -207,13 +207,21 @@ check_zip with_untracked
check_added with_untracked untracked untracked
test_expect_success UNZIP 'git archive --format=zip --add-virtual-file' '
+ if test_have_prereq FUNNYNAMES
+ then
+ QUOTED=quoted:colon
+ else
+ QUOTED=quoted
+ fi &&
git archive --format=zip >with_file_with_content.zip \
+ --add-virtual-file=\"$QUOTED\": \
--add-virtual-file=hello:world $EMPTY_TREE &&
test_when_finished "rm -rf tmp-unpack" &&
mkdir tmp-unpack && (
cd tmp-unpack &&
"$GIT_UNZIP" ../with_file_with_content.zip &&
test_path_is_file hello &&
+ test_path_is_file $QUOTED &&
test world = $(cat hello)
)
'