archive: add --add-file
Allow users to append non-tracked files. This simplifies the generation of source packages with a few extra files, e.g. containing version information. They get the same access times and user information as tracked files. Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
René Scharfe committed
Sep 19, 2020 at 23:23 UTC
2947a7930d2864cfbc3f9815959cd6539e2ea9ad
5 files changed
+150
-1
Documentation/git-archive.txt
+6
@@ -55,6 +55,12 @@ OPTIONS
55
--output=<file>::
56
Write the archive to <file> instead of stdout.
57
58
+--add-file=<file>::
59
+ Add a non-tracked file to the archive. Can be repeated to add
60
+ multiple files. The path of the file in the archive is built
61
+ by concatenating the value for `--prefix` (if any) and the
62
+ basename of <file>.
63
+
64
--worktree-attributes::
65
Look for attributes in .gitattributes files in the working tree
66
as well (see <<ATTRIBUTES>>).
archive.c
+85
-1
@@ -266,6 +266,11 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
266
stage, context);
267
}
268
269
+struct extra_file_info {
270
+ char *base;
271
+ struct stat stat;
272
+};
273
+
274
int write_archive_entries(struct archiver_args *args,
275
write_archive_entry_fn_t write_entry)
276
{
@@ -273,6 +278,10 @@ int write_archive_entries(struct archiver_args *args,
278
struct unpack_trees_options opts;
279
struct tree_desc t;
280
int err;
281
+ struct strbuf path_in_archive = STRBUF_INIT;
282
+ struct strbuf content = STRBUF_INIT;
283
+ struct object_id fake_oid = null_oid;
284
+ int i;
285
286
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
287
size_t len = args->baselen;
@@ -318,6 +327,33 @@ int write_archive_entries(struct archiver_args *args,
327
free(context.bottom);
328
context.bottom = next;
329
}
330
+
331
+ for (i = 0; i < args->extra_files.nr; i++) {
332
+ struct string_list_item *item = args->extra_files.items + i;
333
+ char *path = item->string;
334
+ struct extra_file_info *info = item->util;
335
+
336
+ put_be64(fake_oid.hash, i + 1);
337
+
338
+ strbuf_reset(&path_in_archive);
339
+ if (info->base)
340
+ strbuf_addstr(&path_in_archive, info->base);
341
+ strbuf_addstr(&path_in_archive, basename(path));
342
+
343
+ strbuf_reset(&content);
344
+ if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
345
+ err = error_errno(_("could not read '%s'"), path);
346
+ else
347
+ err = write_entry(args, &fake_oid, path_in_archive.buf,
348
+ path_in_archive.len,
349
+ info->stat.st_mode,
350
+ content.buf, content.len);
351
+ if (err)
352
+ break;
353
+ }
354
+ strbuf_release(&path_in_archive);
355
+ strbuf_release(&content);
356
+
357
return err;
358
}
359
@@ -457,6 +493,42 @@ static void parse_treeish_arg(const char **argv,
493
ar_args->time = archive_time;
494
}
495
496
+static void extra_file_info_clear(void *util, const char *str)
497
+{
498
+ struct extra_file_info *info = util;
499
+ free(info->base);
500
+ free(info);
501
+}
502
+
503
+static int add_file_cb(const struct option *opt, const char *arg, int unset)
504
+{
505
+ struct archiver_args *args = opt->value;
506
+ const char **basep = (const char **)opt->defval;
507
+ const char *base = *basep;
508
+ char *path;
509
+ struct string_list_item *item;
510
+ struct extra_file_info *info;
511
+
512
+ if (unset) {
513
+ string_list_clear_func(&args->extra_files,
514
+ extra_file_info_clear);
515
+ return 0;
516
+ }
517
+
518
+ if (!arg)
519
+ return -1;
520
+
521
+ path = prefix_filename(args->prefix, arg);
522
+ item = string_list_append_nodup(&args->extra_files, path);
523
+ item->util = info = xmalloc(sizeof(*info));
524
+ info->base = xstrdup_or_null(base);
525
+ if (stat(path, &info->stat))
526
+ die(_("File not found: %s"), path);
527
+ if (!S_ISREG(info->stat.st_mode))
528
+ die(_("Not a regular file: %s"), path);
529
+ return 0;
530
+}
531
+
532
#define OPT__COMPR(s, v, h, p) \
533
OPT_SET_INT_F(s, NULL, v, h, p, PARSE_OPT_NONEG)
534
#define OPT__COMPR_HIDDEN(s, v, p) \
@@ -481,6 +553,9 @@ static int parse_archive_args(int argc, const char **argv,
553
OPT_STRING(0, "format", &format, N_("fmt"), N_("archive format")),
554
OPT_STRING(0, "prefix", &base, N_("prefix"),
555
N_("prepend prefix to each pathname in the archive")),
556
+ { OPTION_CALLBACK, 0, "add-file", args, N_("file"),
557
+ N_("add untracked file to archive"), 0, add_file_cb,
558
+ (intptr_t)&base },
559
OPT_STRING('o', "output", &output, N_("file"),
560
N_("write the archive to this file")),
561
OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
@@ -515,6 +590,8 @@ static int parse_archive_args(int argc, const char **argv,
590
die(_("Option --exec can only be used together with --remote"));
591
if (output)
592
die(_("Unexpected option --output"));
593
+ if (is_remote && args->extra_files.nr)
594
+ die(_("Options --add-file and --remote cannot be used together"));
595
596
if (!base)
597
base = "";
@@ -561,11 +638,14 @@ int write_archive(int argc, const char **argv, const char *prefix,
638
{
639
const struct archiver *ar = NULL;
640
struct archiver_args args;
641
+ int rc;
642
643
git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable);
644
git_config(git_default_config, NULL);
645
646
args.repo = repo;
647
+ args.prefix = prefix;
648
+ string_list_init(&args.extra_files, 1);
649
argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
650
if (!startup_info->have_repository) {
651
/*
@@ -579,7 +659,11 @@ int write_archive(int argc, const char **argv, const char *prefix,
659
parse_treeish_arg(argv, &args, prefix, remote);
660
parse_pathspec_arg(argv + 1, &args);
661
582
- return ar->write_archive(ar, &args);
662
+ rc = ar->write_archive(ar, &args);
663
+
664
+ string_list_clear_func(&args.extra_files, extra_file_info_clear);
665
+
666
+ return rc;
667
}
668
669
static int match_extension(const char *filename, const char *ext)
archive.h
+2
@@ -9,6 +9,7 @@ struct repository;
9
struct archiver_args {
10
struct repository *repo;
11
const char *refname;
12
+ const char *prefix;
13
const char *base;
14
size_t baselen;
15
struct tree *tree;
@@ -20,6 +21,7 @@ struct archiver_args {
21
unsigned int worktree_attributes : 1;
22
unsigned int convert : 1;
23
int compression_level;
24
+ struct string_list extra_files;
25
};
26
27
/* main api */
t/t5000-tar-tree.sh
+29
@@ -94,6 +94,16 @@ check_tar() {
94
'
95
}
96
97
+check_added() {
98
+ dir=$1
99
+ path_in_fs=$2
100
+ path_in_archive=$3
101
+
102
+ test_expect_success " validate extra file $path_in_archive" '
103
+ diff -r $path_in_fs $dir/$path_in_archive
104
+ '
105
+}
106
+
107
test_expect_success 'setup' '
108
test_oid_cache <<-EOF
109
obj sha1:19f9c8273ec45a8938e6999cb59b3ff66739902a
@@ -164,6 +174,25 @@ test_expect_success 'git-archive --prefix=olde-' '
174
175
check_tar with_olde-prefix olde-
176
177
+test_expect_success 'git archive --add-file' '
178
+ echo untracked >untracked &&
179
+ git archive --add-file=untracked HEAD >with_untracked.tar
180
+'
181
+
182
+check_tar with_untracked
183
+check_added with_untracked untracked untracked
184
+
185
+test_expect_success 'git archive --add-file twice' '
186
+ echo untracked >untracked &&
187
+ git archive --prefix=one/ --add-file=untracked \
188
+ --prefix=two/ --add-file=untracked \
189
+ --prefix= HEAD >with_untracked2.tar
190
+'
191
+
192
+check_tar with_untracked2
193
+check_added with_untracked2 untracked one/untracked
194
+check_added with_untracked2 untracked two/untracked
195
+
196
test_expect_success 'git archive on large files' '
197
test_config core.bigfilethreshold 1 &&
198
git archive HEAD >b3.tar &&
t/t5003-archive-zip.sh
+28
@@ -72,6 +72,16 @@ check_zip() {
72
"
73
}
74
75
+check_added() {
76
+ dir=$1
77
+ path_in_fs=$2
78
+ path_in_archive=$3
79
+
80
+ test_expect_success UNZIP " validate extra file $path_in_archive" '
81
+ diff -r $path_in_fs $dir/$path_in_archive
82
+ '
83
+}
84
+
85
test_expect_success \
86
'populate workdir' \
87
'mkdir a &&
@@ -188,4 +198,22 @@ test_expect_success 'git archive --format=zip on large files' '
198
199
check_zip large-compressed
200
201
+test_expect_success 'git archive --format=zip --add-file' '
202
+ echo untracked >untracked &&
203
+ git archive --format=zip --add-file=untracked HEAD >with_untracked.zip
204
+'
205
+
206
+check_zip with_untracked
207
+check_added with_untracked untracked untracked
208
+
209
+test_expect_success 'git archive --format=zip --add-file twice' '
210
+ echo untracked >untracked &&
211
+ git archive --format=zip --prefix=one/ --add-file=untracked \
212
+ --prefix=two/ --add-file=untracked \
213
+ --prefix= HEAD >with_untracked2.zip
214
+'
215
+check_zip with_untracked2
216
+check_added with_untracked2 untracked one/untracked
217
+check_added with_untracked2 untracked two/untracked
218
+
219
test_done