Skip to content

Commit de73a20

Browse files
committed
Merge branch 'rs/archive-from-subdirectory-fixes'
"git archive" run from a subdirectory mishandled attributes and paths outside the current directory. * rs/archive-from-subdirectory-fixes: archive: improve support for running in subdirectory
2 parents 09a7b61 + 92b1dd1 commit de73a20

File tree

3 files changed

+83
-21
lines changed

3 files changed

+83
-21
lines changed

archive.c

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,29 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
173173
args->convert = check_attr_export_subst(check);
174174
}
175175

176+
if (args->prefix) {
177+
static struct strbuf new_path = STRBUF_INIT;
178+
static struct strbuf buf = STRBUF_INIT;
179+
const char *rel;
180+
181+
rel = relative_path(path_without_prefix, args->prefix, &buf);
182+
183+
/*
184+
* We don't add an entry for the current working
185+
* directory when we are at the root; skip it also when
186+
* we're in a subdirectory or submodule. Skip entries
187+
* higher up as well.
188+
*/
189+
if (!strcmp(rel, "./") || starts_with(rel, "../"))
190+
return S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0;
191+
192+
/* rel can refer to path, so don't edit it in place */
193+
strbuf_reset(&new_path);
194+
strbuf_add(&new_path, args->base, args->baselen);
195+
strbuf_addstr(&new_path, rel);
196+
strbuf_swap(&path, &new_path);
197+
}
198+
176199
if (args->verbose)
177200
fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
178201

@@ -408,15 +431,41 @@ static int reject_entry(const struct object_id *oid UNUSED,
408431
return ret;
409432
}
410433

434+
static int reject_outside(const struct object_id *oid UNUSED,
435+
struct strbuf *base, const char *filename,
436+
unsigned mode, void *context)
437+
{
438+
struct archiver_args *args = context;
439+
struct strbuf buf = STRBUF_INIT;
440+
struct strbuf path = STRBUF_INIT;
441+
int ret = 0;
442+
443+
if (S_ISDIR(mode))
444+
return READ_TREE_RECURSIVE;
445+
446+
strbuf_addbuf(&path, base);
447+
strbuf_addstr(&path, filename);
448+
if (starts_with(relative_path(path.buf, args->prefix, &buf), "../"))
449+
ret = -1;
450+
strbuf_release(&buf);
451+
strbuf_release(&path);
452+
return ret;
453+
}
454+
411455
static int path_exists(struct archiver_args *args, const char *path)
412456
{
413457
const char *paths[] = { path, NULL };
414458
struct path_exists_context ctx;
415459
int ret;
416460

417461
ctx.args = args;
418-
parse_pathspec(&ctx.pathspec, 0, 0, "", paths);
462+
parse_pathspec(&ctx.pathspec, 0, PATHSPEC_PREFER_CWD,
463+
args->prefix, paths);
419464
ctx.pathspec.recursive = 1;
465+
if (args->prefix && read_tree(args->repo, args->tree, &ctx.pathspec,
466+
reject_outside, args))
467+
die(_("pathspec '%s' matches files outside the "
468+
"current directory"), path);
420469
ret = read_tree(args->repo, args->tree,
421470
&ctx.pathspec,
422471
reject_entry, &ctx);
@@ -432,9 +481,8 @@ static void parse_pathspec_arg(const char **pathspec,
432481
* Also if pathspec patterns are dependent, we're in big
433482
* trouble as we test each one separately
434483
*/
435-
parse_pathspec(&ar_args->pathspec, 0,
436-
PATHSPEC_PREFER_FULL,
437-
"", pathspec);
484+
parse_pathspec(&ar_args->pathspec, 0, PATHSPEC_PREFER_CWD,
485+
ar_args->prefix, pathspec);
438486
ar_args->pathspec.recursive = 1;
439487
if (pathspec) {
440488
while (*pathspec) {
@@ -446,8 +494,7 @@ static void parse_pathspec_arg(const char **pathspec,
446494
}
447495

448496
static void parse_treeish_arg(const char **argv,
449-
struct archiver_args *ar_args, const char *prefix,
450-
int remote)
497+
struct archiver_args *ar_args, int remote)
451498
{
452499
const char *name = argv[0];
453500
const struct object_id *commit_oid;
@@ -487,20 +534,6 @@ static void parse_treeish_arg(const char **argv,
487534
if (!tree)
488535
die(_("not a tree object: %s"), oid_to_hex(&oid));
489536

490-
if (prefix) {
491-
struct object_id tree_oid;
492-
unsigned short mode;
493-
int err;
494-
495-
err = get_tree_entry(ar_args->repo,
496-
&tree->object.oid,
497-
prefix, &tree_oid,
498-
&mode);
499-
if (err || !S_ISDIR(mode))
500-
die(_("current working directory is untracked"));
501-
502-
tree = parse_tree_indirect(&tree_oid);
503-
}
504537
ar_args->refname = ref;
505538
ar_args->tree = tree;
506539
ar_args->commit_oid = commit_oid;
@@ -718,7 +751,7 @@ int write_archive(int argc, const char **argv, const char *prefix,
718751
setup_git_directory();
719752
}
720753

721-
parse_treeish_arg(argv, &args, prefix, remote);
754+
parse_treeish_arg(argv, &args, remote);
722755
parse_pathspec_arg(argv + 1, &args);
723756

724757
rc = ar->write_archive(ar, &args);

t/t5000-tar-tree.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,19 @@ test_expect_success 'catch non-matching pathspec' '
426426
test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
427427
'
428428

429+
test_expect_success 'reject paths outside the current directory' '
430+
test_must_fail git -C a/bin archive HEAD .. >/dev/null 2>err &&
431+
grep "outside the current directory" err
432+
'
433+
434+
test_expect_success 'allow pathspecs that resolve to the current directory' '
435+
git -C a/bin archive -v HEAD ../bin >/dev/null 2>actual &&
436+
cat >expect <<-\EOF &&
437+
sh
438+
EOF
439+
test_cmp expect actual
440+
'
441+
429442
# Pull the size and date of each entry in a tarfile using the system tar.
430443
#
431444
# We'll pull out only the year from the date; that avoids any question of

t/t5001-archive-attr.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ test_expect_success 'setup' '
3333
echo ignored-by-tree.d export-ignore >>.gitattributes &&
3434
git add ignored-by-tree ignored-by-tree.d .gitattributes &&
3535
36+
mkdir subdir &&
37+
>subdir/included &&
38+
>subdir/ignored-by-subtree &&
39+
>subdir/ignored-by-tree &&
40+
echo ignored-by-subtree export-ignore >subdir/.gitattributes &&
41+
git add subdir &&
42+
3643
echo ignored by worktree >ignored-by-worktree &&
3744
echo ignored-by-worktree export-ignore >.gitattributes &&
3845
git add ignored-by-worktree &&
@@ -93,6 +100,15 @@ test_expect_exists archive-pathspec-wildcard/ignored-by-worktree
93100
test_expect_missing archive-pathspec-wildcard/excluded-by-pathspec.d
94101
test_expect_missing archive-pathspec-wildcard/excluded-by-pathspec.d/file
95102

103+
test_expect_success 'git -C subdir archive' '
104+
git -C subdir archive HEAD >archive-subdir.tar &&
105+
extract_tar_to_dir archive-subdir
106+
'
107+
108+
test_expect_exists archive-subdir/included
109+
test_expect_missing archive-subdir/ignored-by-subtree
110+
test_expect_missing archive-subdir/ignored-by-tree
111+
96112
test_expect_success 'git archive with worktree attributes' '
97113
git archive --worktree-attributes HEAD >worktree.tar &&
98114
(mkdir worktree && cd worktree && "$TAR" xf -) <worktree.tar

0 commit comments

Comments
 (0)