Skip to content

Commit 2947a79

Browse files
rscharfegitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 200589a commit 2947a79

File tree

5 files changed

+150
-1
lines changed

5 files changed

+150
-1
lines changed

Documentation/git-archive.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ OPTIONS
5555
--output=<file>::
5656
Write the archive to <file> instead of stdout.
5757

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+
5864
--worktree-attributes::
5965
Look for attributes in .gitattributes files in the working tree
6066
as well (see <<ATTRIBUTES>>).

archive.c

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,22 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
266266
stage, context);
267267
}
268268

269+
struct extra_file_info {
270+
char *base;
271+
struct stat stat;
272+
};
273+
269274
int write_archive_entries(struct archiver_args *args,
270275
write_archive_entry_fn_t write_entry)
271276
{
272277
struct archiver_context context;
273278
struct unpack_trees_options opts;
274279
struct tree_desc t;
275280
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;
276285

277286
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
278287
size_t len = args->baselen;
@@ -318,6 +327,33 @@ int write_archive_entries(struct archiver_args *args,
318327
free(context.bottom);
319328
context.bottom = next;
320329
}
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+
321357
return err;
322358
}
323359

@@ -457,6 +493,42 @@ static void parse_treeish_arg(const char **argv,
457493
ar_args->time = archive_time;
458494
}
459495

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+
460532
#define OPT__COMPR(s, v, h, p) \
461533
OPT_SET_INT_F(s, NULL, v, h, p, PARSE_OPT_NONEG)
462534
#define OPT__COMPR_HIDDEN(s, v, p) \
@@ -481,6 +553,9 @@ static int parse_archive_args(int argc, const char **argv,
481553
OPT_STRING(0, "format", &format, N_("fmt"), N_("archive format")),
482554
OPT_STRING(0, "prefix", &base, N_("prefix"),
483555
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 },
484559
OPT_STRING('o', "output", &output, N_("file"),
485560
N_("write the archive to this file")),
486561
OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
@@ -515,6 +590,8 @@ static int parse_archive_args(int argc, const char **argv,
515590
die(_("Option --exec can only be used together with --remote"));
516591
if (output)
517592
die(_("Unexpected option --output"));
593+
if (is_remote && args->extra_files.nr)
594+
die(_("Options --add-file and --remote cannot be used together"));
518595

519596
if (!base)
520597
base = "";
@@ -561,11 +638,14 @@ int write_archive(int argc, const char **argv, const char *prefix,
561638
{
562639
const struct archiver *ar = NULL;
563640
struct archiver_args args;
641+
int rc;
564642

565643
git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable);
566644
git_config(git_default_config, NULL);
567645

568646
args.repo = repo;
647+
args.prefix = prefix;
648+
string_list_init(&args.extra_files, 1);
569649
argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
570650
if (!startup_info->have_repository) {
571651
/*
@@ -579,7 +659,11 @@ int write_archive(int argc, const char **argv, const char *prefix,
579659
parse_treeish_arg(argv, &args, prefix, remote);
580660
parse_pathspec_arg(argv + 1, &args);
581661

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;
583667
}
584668

585669
static int match_extension(const char *filename, const char *ext)

archive.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct repository;
99
struct archiver_args {
1010
struct repository *repo;
1111
const char *refname;
12+
const char *prefix;
1213
const char *base;
1314
size_t baselen;
1415
struct tree *tree;
@@ -20,6 +21,7 @@ struct archiver_args {
2021
unsigned int worktree_attributes : 1;
2122
unsigned int convert : 1;
2223
int compression_level;
24+
struct string_list extra_files;
2325
};
2426

2527
/* main api */

t/t5000-tar-tree.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ check_tar() {
9494
'
9595
}
9696

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+
97107
test_expect_success 'setup' '
98108
test_oid_cache <<-EOF
99109
obj sha1:19f9c8273ec45a8938e6999cb59b3ff66739902a
@@ -164,6 +174,25 @@ test_expect_success 'git-archive --prefix=olde-' '
164174

165175
check_tar with_olde-prefix olde-
166176

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+
167196
test_expect_success 'git archive on large files' '
168197
test_config core.bigfilethreshold 1 &&
169198
git archive HEAD >b3.tar &&

t/t5003-archive-zip.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ check_zip() {
7272
"
7373
}
7474

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+
7585
test_expect_success \
7686
'populate workdir' \
7787
'mkdir a &&
@@ -188,4 +198,22 @@ test_expect_success 'git archive --format=zip on large files' '
188198

189199
check_zip large-compressed
190200

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+
191219
test_done

0 commit comments

Comments
 (0)