]> git.ipfire.org Git - thirdparty/git.git/commitdiff
archive: optionally add "virtual" files
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Sat, 28 May 2022 23:11:12 +0000 (16:11 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 31 May 2022 06:07:22 +0000 (23:07 -0700)
With the `--add-virtual-file=<path>:<content>` option, `git archive` now
supports use cases where relatively trivial files need to be added that
do not exist on disk.

This will allow us to generate `.zip` files with generated content,
without having to add said content to the object database and without
having to write it out to disk.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
[jc: tweaked <path> handling]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-archive.txt
archive.c
t/t5003-archive-zip.sh

index 94519aae231ba4bf8cc44159e60b86cb8a8c7bd4..b41cc5bc2e076d7ec9eaa78c64a72b8b04a69023 100644 (file)
@@ -51,7 +51,7 @@ OPTIONS
 --prefix=<prefix>/::
        Prepend <prefix>/ to paths in the archive.  Can be repeated; its
        rightmost value is used for all tracked files.  See below which
-       value gets used by `--add-file`.
+       value gets used by `--add-file` and `--add-virtual-file`.
 
 -o <file>::
 --output=<file>::
@@ -63,6 +63,17 @@ OPTIONS
        concatenating the value of the last `--prefix` option (if any)
        before this `--add-file` and the basename of <file>.
 
+--add-virtual-file=<path>:<content>::
+       Add the specified contents to the archive.  Can be repeated to add
+       multiple files.  The path of the file in the archive is built
+       by concatenating the value of the last `--prefix` option (if any)
+       before this `--add-virtual-file` and `<path>`.
++
+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.
+
 --worktree-attributes::
        Look for attributes in .gitattributes files in the working tree
        as well (see <<ATTRIBUTES>>).
index e2121ebefb0451deeaacd65687eba46149ffe844..29a90c703294cd152ebf9013e4fcda1413c694a0 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -263,6 +263,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
 struct extra_file_info {
        char *base;
        struct stat stat;
+       void *content;
 };
 
 int write_archive_entries(struct archiver_args *args,
@@ -331,19 +332,27 @@ int write_archive_entries(struct archiver_args *args,
 
                put_be64(fake_oid.hash, i + 1);
 
-               strbuf_reset(&path_in_archive);
-               if (info->base)
-                       strbuf_addstr(&path_in_archive, info->base);
-               strbuf_addstr(&path_in_archive, basename(path));
-
-               strbuf_reset(&content);
-               if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
-                       err = error_errno(_("cannot read '%s'"), path);
-               else
-                       err = write_entry(args, &fake_oid, path_in_archive.buf,
-                                         path_in_archive.len,
+               if (!info->content) {
+                       strbuf_reset(&path_in_archive);
+                       if (info->base)
+                               strbuf_addstr(&path_in_archive, info->base);
+                       strbuf_addstr(&path_in_archive, basename(path));
+
+                       strbuf_reset(&content);
+                       if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
+                               err = error_errno(_("cannot read '%s'"), path);
+                       else
+                               err = write_entry(args, &fake_oid, path_in_archive.buf,
+                                                 path_in_archive.len,
+                                                 canon_mode(info->stat.st_mode),
+                                                 content.buf, content.len);
+               } else {
+                       err = write_entry(args, &fake_oid,
+                                         path, strlen(path),
                                          canon_mode(info->stat.st_mode),
-                                         content.buf, content.len);
+                                         info->content, info->stat.st_size);
+               }
+
                if (err)
                        break;
        }
@@ -493,6 +502,7 @@ static void extra_file_info_clear(void *util, const char *str)
 {
        struct extra_file_info *info = util;
        free(info->base);
+       free(info->content);
        free(info);
 }
 
@@ -514,14 +524,40 @@ static int add_file_cb(const struct option *opt, const char *arg, int unset)
        if (!arg)
                return -1;
 
-       path = prefix_filename(args->prefix, arg);
-       item = string_list_append_nodup(&args->extra_files, path);
-       item->util = info = xmalloc(sizeof(*info));
+       info = xmalloc(sizeof(*info));
        info->base = xstrdup_or_null(base);
-       if (stat(path, &info->stat))
-               die(_("File not found: %s"), path);
-       if (!S_ISREG(info->stat.st_mode))
-               die(_("Not a regular file: %s"), path);
+
+       if (!strcmp(opt->long_name, "add-file")) {
+               path = prefix_filename(args->prefix, arg);
+               if (stat(path, &info->stat))
+                       die(_("File not found: %s"), path);
+               if (!S_ISREG(info->stat.st_mode))
+                       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;
+
+               if (!colon)
+                       die(_("missing colon: '%s'"), arg);
+
+               p = xstrndup(arg, colon - arg);
+               if (!args->prefix)
+                       path = p;
+               else {
+                       path = prefix_filename(args->prefix, p);
+                       free(p);
+               }
+               memset(&info->stat, 0, sizeof(info->stat));
+               info->stat.st_mode = S_IFREG | 0644;
+               info->content = xstrdup(colon + 1);
+               info->stat.st_size = strlen(info->content);
+       } else {
+               BUG("add_file_cb() called for %s", opt->long_name);
+       }
+       item = string_list_append_nodup(&args->extra_files, path);
+       item->util = info;
+
        return 0;
 }
 
@@ -554,6 +590,9 @@ static int parse_archive_args(int argc, const char **argv,
                { OPTION_CALLBACK, 0, "add-file", args, N_("file"),
                  N_("add untracked file to archive"), 0, add_file_cb,
                  (intptr_t)&base },
+               { OPTION_CALLBACK, 0, "add-virtual-file", args,
+                 N_("path:content"), N_("add untracked file to archive"), 0,
+                 add_file_cb, (intptr_t)&base },
                OPT_STRING('o', "output", &output, N_("file"),
                        N_("write the archive to this file")),
                OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
index d726964307ca89373eeaf206bbb2dc2d4c1c3008..d6027189e2565dd5c790631d5febf254cb3214c8 100755 (executable)
@@ -206,6 +206,18 @@ test_expect_success 'git archive --format=zip --add-file' '
 check_zip with_untracked
 check_added with_untracked untracked untracked
 
+test_expect_success UNZIP 'git archive --format=zip --add-virtual-file' '
+       git archive --format=zip >with_file_with_content.zip \
+               --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 world = $(cat hello)
+       )
+'
+
 test_expect_success 'git archive --format=zip --add-file twice' '
        echo untracked >untracked &&
        git archive --format=zip --prefix=one/ --add-file=untracked \