archive: optionally add "virtual" files
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>
Johannes Schindelin committed
May 28, 2022 at 16:11 UTC
237a1d138c4322a7e934f129dee02e2ea6a214cd
3 files changed
+82
-20
Documentation/git-archive.txt
+12
-1
@@ -51,7 +51,7 @@ OPTIONS
51
--prefix=<prefix>/::
52
Prepend <prefix>/ to paths in the archive. Can be repeated; its
53
rightmost value is used for all tracked files. See below which
54
- value gets used by `--add-file`.
54
+ value gets used by `--add-file` and `--add-virtual-file`.
55
56
-o <file>::
57
--output=<file>::
@@ -63,6 +63,17 @@ OPTIONS
63
concatenating the value of the last `--prefix` option (if any)
64
before this `--add-file` and the basename of <file>.
65
66
+--add-virtual-file=<path>:<content>::
67
+ Add the specified contents to the archive. Can be repeated to add
68
+ multiple files. The path of the file in the archive is built
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.
76
+
77
--worktree-attributes::
78
Look for attributes in .gitattributes files in the working tree
79
as well (see <<ATTRIBUTES>>).
archive.c
+58
-19
@@ -263,6 +263,7 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
263
struct extra_file_info {
264
char *base;
265
struct stat stat;
266
+ void *content;
267
};
268
269
int write_archive_entries(struct archiver_args *args,
@@ -331,19 +332,27 @@ int write_archive_entries(struct archiver_args *args,
332
333
put_be64(fake_oid.hash, i + 1);
334
334
- strbuf_reset(&path_in_archive);
335
- if (info->base)
336
- strbuf_addstr(&path_in_archive, info->base);
337
- strbuf_addstr(&path_in_archive, basename(path));
338
-
339
- strbuf_reset(&content);
340
- if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
341
- err = error_errno(_("cannot read '%s'"), path);
342
- else
343
- err = write_entry(args, &fake_oid, path_in_archive.buf,
344
- path_in_archive.len,
335
+ if (!info->content) {
336
+ strbuf_reset(&path_in_archive);
337
+ if (info->base)
338
+ strbuf_addstr(&path_in_archive, info->base);
339
+ strbuf_addstr(&path_in_archive, basename(path));
340
+
341
+ strbuf_reset(&content);
342
+ if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
343
+ err = error_errno(_("cannot read '%s'"), path);
344
+ else
345
+ err = write_entry(args, &fake_oid, path_in_archive.buf,
346
+ path_in_archive.len,
347
+ canon_mode(info->stat.st_mode),
348
+ content.buf, content.len);
349
+ } else {
350
+ err = write_entry(args, &fake_oid,
351
+ path, strlen(path),
352
canon_mode(info->stat.st_mode),
346
- content.buf, content.len);
353
+ info->content, info->stat.st_size);
354
+ }
355
+
356
if (err)
357
break;
358
}
@@ -493,6 +502,7 @@ static void extra_file_info_clear(void *util, const char *str)
502
{
503
struct extra_file_info *info = util;
504
free(info->base);
505
+ free(info->content);
506
free(info);
507
}
508
@@ -514,14 +524,40 @@ static int add_file_cb(const struct option *opt, const char *arg, int unset)
524
if (!arg)
525
return -1;
526
517
- path = prefix_filename(args->prefix, arg);
518
- item = string_list_append_nodup(&args->extra_files, path);
519
- item->util = info = xmalloc(sizeof(*info));
527
+ info = xmalloc(sizeof(*info));
528
info->base = xstrdup_or_null(base);
521
- if (stat(path, &info->stat))
522
- die(_("File not found: %s"), path);
523
- if (!S_ISREG(info->stat.st_mode))
524
- die(_("Not a regular file: %s"), path);
529
+
530
+ if (!strcmp(opt->long_name, "add-file")) {
531
+ path = prefix_filename(args->prefix, arg);
532
+ if (stat(path, &info->stat))
533
+ die(_("File not found: %s"), path);
534
+ if (!S_ISREG(info->stat.st_mode))
535
+ die(_("Not a regular file: %s"), path);
536
+ info->content = NULL; /* read the file later */
537
+ } else if (!strcmp(opt->long_name, "add-virtual-file")) {
538
+ const char *colon = strchr(arg, ':');
539
+ char *p;
540
+
541
+ if (!colon)
542
+ die(_("missing colon: '%s'"), arg);
543
+
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
+ }
551
+ memset(&info->stat, 0, sizeof(info->stat));
552
+ info->stat.st_mode = S_IFREG | 0644;
553
+ info->content = xstrdup(colon + 1);
554
+ info->stat.st_size = strlen(info->content);
555
+ } else {
556
+ BUG("add_file_cb() called for %s", opt->long_name);
557
+ }
558
+ item = string_list_append_nodup(&args->extra_files, path);
559
+ item->util = info;
560
+
561
return 0;
562
}
563
@@ -554,6 +590,9 @@ static int parse_archive_args(int argc, const char **argv,
590
{ OPTION_CALLBACK, 0, "add-file", args, N_("file"),
591
N_("add untracked file to archive"), 0, add_file_cb,
592
(intptr_t)&base },
593
+ { OPTION_CALLBACK, 0, "add-virtual-file", args,
594
+ N_("path:content"), N_("add untracked file to archive"), 0,
595
+ add_file_cb, (intptr_t)&base },
596
OPT_STRING('o', "output", &output, N_("file"),
597
N_("write the archive to this file")),
598
OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
t/t5003-archive-zip.sh
+12
@@ -206,6 +206,18 @@ test_expect_success 'git archive --format=zip --add-file' '
206
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
+ git archive --format=zip >with_file_with_content.zip \
211
+ --add-virtual-file=hello:world $EMPTY_TREE &&
212
+ test_when_finished "rm -rf tmp-unpack" &&
213
+ mkdir tmp-unpack && (
214
+ cd tmp-unpack &&
215
+ "$GIT_UNZIP" ../with_file_with_content.zip &&
216
+ test_path_is_file hello &&
217
+ test world = $(cat hello)
218
+ )
219
+'
220
+
221
test_expect_success 'git archive --format=zip --add-file twice' '
222
echo untracked >untracked &&
223
git archive --format=zip --prefix=one/ --add-file=untracked \