archive --add-virtual-file: allow paths containing colons

By allowing the path to be enclosed in double-quotes, we can avoid the limitation that paths cannot contain colons. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed May 28, 2022 at 16:11 UTC de1f68a968e64b3e1e2979222238fec1f045bbf3
3 files changed +38 -14
Documentation/git-archive.txt
+10 -4
@@ -69,10 +69,16 @@ OPTIONS
69 by concatenating the value of the last `--prefix` option (if any)
70 before this `--add-virtual-file` and `<path>`.
71 +
72 -The `<path>` cannot contain any colon, the file mode is limited to
73 -a regular file, and the option may be subject to platform-dependent
74 -command-line limits. For non-trivial cases, write an untracked file
75 -and use `--add-file` instead.
72 +The `<path>` argument can start and end with a literal double-quote
73 +character; the contained file name is interpreted as a C-style string,
74 +i.e. the backslash is interpreted as escape character. The path must
75 +be quoted if it contains a colon, to avoid the colon from being
76 +misinterpreted as the separator between the path and the contents, or
77 +if the path begins or ends with a double-quote character.
78 ++
79 +The file mode is limited to a regular file, and the option may be
80 +subject to platform-dependent command-line limits. For non-trivial
81 +cases, write an untracked file and use `--add-file` instead.
82
83 --worktree-attributes::
84 Look for attributes in .gitattributes files in the working tree
archive.c
+20 -10
@@ -9,6 +9,7 @@
9 #include "parse-options.h"
10 #include "unpack-trees.h"
11 #include "dir.h"
12 +#include "quote.h"
13
14 static char const * const archive_usage[] = {
15 N_("git archive [<options>] <tree-ish> [<path>...]"),
@@ -535,22 +536,31 @@ static int add_file_cb(const struct option *opt, const char *arg, int unset)
536 die(_("Not a regular file: %s"), path);
537 info->content = NULL; /* read the file later */
538 } else if (!strcmp(opt->long_name, "add-virtual-file")) {
538 - const char *colon = strchr(arg, ':');
539 - char *p;
539 + struct strbuf buf = STRBUF_INIT;
540 + const char *p = arg;
541 +
542 + if (*p != '"')
543 + p = strchr(p, ':');
544 + else if (unquote_c_style(&buf, p, &p) < 0)
545 + die(_("unclosed quote: '%s'"), arg);
546
541 - if (!colon)
547 + if (!p || *p != ':')
548 die(_("missing colon: '%s'"), arg);
549
544 - p = xstrndup(arg, colon - arg);
545 - if (!args->prefix)
546 - path = p;
547 - else {
548 - path = prefix_filename(args->prefix, p);
549 - free(p);
550 + if (p == arg)
551 + die(_("empty file name: '%s'"), arg);
552 +
553 + path = buf.len ?
554 + strbuf_detach(&buf, NULL) : xstrndup(arg, p - arg);
555 +
556 + if (args->prefix) {
557 + char *save = path;
558 + path = prefix_filename(args->prefix, path);
559 + free(save);
560 }
561 memset(&info->stat, 0, sizeof(info->stat));
562 info->stat.st_mode = S_IFREG | 0644;
553 - info->content = xstrdup(colon + 1);
563 + info->content = xstrdup(p + 1);
564 info->stat.st_size = strlen(info->content);
565 } else {
566 BUG("add_file_cb() called for %s", opt->long_name);
t/t5003-archive-zip.sh
+8
@@ -207,13 +207,21 @@ check_zip with_untracked
207 check_added with_untracked untracked untracked
208
209 test_expect_success UNZIP 'git archive --format=zip --add-virtual-file' '
210 + if test_have_prereq FUNNYNAMES
211 + then
212 + PATHNAME="pathname with : colon"
213 + else
214 + PATHNAME="pathname without colon"
215 + fi &&
216 git archive --format=zip >with_file_with_content.zip \
217 + --add-virtual-file=\""$PATHNAME"\": \
218 --add-virtual-file=hello:world $EMPTY_TREE &&
219 test_when_finished "rm -rf tmp-unpack" &&
220 mkdir tmp-unpack && (
221 cd tmp-unpack &&
222 "$GIT_UNZIP" ../with_file_with_content.zip &&
223 test_path_is_file hello &&
224 + test_path_is_file "$PATHNAME" &&
225 test world = $(cat hello)
226 )
227 '