Skip to content

Commit 92b1dd1

Browse files
rscharfegitster
authored andcommitted
archive: improve support for running in subdirectory
When git archive is started in a subdirectory, it archives its corresponding tree and its child objects, only. That is intended. It does that by effectively cd'ing into that tree and setting "prefix" to the empty string. This has unfortunate consequences, though: Attributes are anchored at the root of the repository and git archive still applies them to subtrees, causing mismatches. And when checking pathspecs it cannot tell the difference between one that doesn't match anthing or one that matches some actual blob outside of the subdirectory, leading to a confusing error message. Fix that by keeping the "prefix" value and passing it to pathspec and attribute functions, and shortening it using relative_path() for paths written to the archive and (if --verbose is given) to stdout. Still reject attempts to archive files outside the current directory, but print a more specific error in that case. Recognizing it requires a full traversal of the subtree for each pathspec, however. Allowing them would be easier, but archive entry paths starting with "../" can be problematic to extract -- e.g. bsdtar skips them by default. Reported-by: Cristian Le <[email protected]> Reported-by: Matthias Görgens <[email protected]> Signed-off-by: René Scharfe <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 73876f4 commit 92b1dd1

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
@@ -166,6 +166,29 @@ static int write_archive_entry(const struct object_id *oid, const char *base,
166166
args->convert = check_attr_export_subst(check);
167167
}
168168

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

@@ -401,15 +424,41 @@ static int reject_entry(const struct object_id *oid UNUSED,
401424
return ret;
402425
}
403426

427+
static int reject_outside(const struct object_id *oid UNUSED,
428+
struct strbuf *base, const char *filename,
429+
unsigned mode, void *context)
430+
{
431+
struct archiver_args *args = context;
432+
struct strbuf buf = STRBUF_INIT;
433+
struct strbuf path = STRBUF_INIT;
434+
int ret = 0;
435+
436+
if (S_ISDIR(mode))
437+
return READ_TREE_RECURSIVE;
438+
439+
strbuf_addbuf(&path, base);
440+
strbuf_addstr(&path, filename);
441+
if (starts_with(relative_path(path.buf, args->prefix, &buf), "../"))
442+
ret = -1;
443+
strbuf_release(&buf);
444+
strbuf_release(&path);
445+
return ret;
446+
}
447+
404448
static int path_exists(struct archiver_args *args, const char *path)
405449
{
406450
const char *paths[] = { path, NULL };
407451
struct path_exists_context ctx;
408452
int ret;
409453

410454
ctx.args = args;
411-
parse_pathspec(&ctx.pathspec, 0, 0, "", paths);
455+
parse_pathspec(&ctx.pathspec, 0, PATHSPEC_PREFER_CWD,
456+
args->prefix, paths);
412457
ctx.pathspec.recursive = 1;
458+
if (args->prefix && read_tree(args->repo, args->tree, &ctx.pathspec,
459+
reject_outside, args))
460+
die(_("pathspec '%s' matches files outside the "
461+
"current directory"), path);
413462
ret = read_tree(args->repo, args->tree,
414463
&ctx.pathspec,
415464
reject_entry, &ctx);
@@ -425,9 +474,8 @@ static void parse_pathspec_arg(const char **pathspec,
425474
* Also if pathspec patterns are dependent, we're in big
426475
* trouble as we test each one separately
427476
*/
428-
parse_pathspec(&ar_args->pathspec, 0,
429-
PATHSPEC_PREFER_FULL,
430-
"", pathspec);
477+
parse_pathspec(&ar_args->pathspec, 0, PATHSPEC_PREFER_CWD,
478+
ar_args->prefix, pathspec);
431479
ar_args->pathspec.recursive = 1;
432480
if (pathspec) {
433481
while (*pathspec) {
@@ -439,8 +487,7 @@ static void parse_pathspec_arg(const char **pathspec,
439487
}
440488

441489
static void parse_treeish_arg(const char **argv,
442-
struct archiver_args *ar_args, const char *prefix,
443-
int remote)
490+
struct archiver_args *ar_args, int remote)
444491
{
445492
const char *name = argv[0];
446493
const struct object_id *commit_oid;
@@ -479,20 +526,6 @@ static void parse_treeish_arg(const char **argv,
479526
if (!tree)
480527
die(_("not a tree object: %s"), oid_to_hex(&oid));
481528

482-
if (prefix) {
483-
struct object_id tree_oid;
484-
unsigned short mode;
485-
int err;
486-
487-
err = get_tree_entry(ar_args->repo,
488-
&tree->object.oid,
489-
prefix, &tree_oid,
490-
&mode);
491-
if (err || !S_ISDIR(mode))
492-
die(_("current working directory is untracked"));
493-
494-
tree = parse_tree_indirect(&tree_oid);
495-
}
496529
ar_args->refname = ref;
497530
ar_args->tree = tree;
498531
ar_args->commit_oid = commit_oid;
@@ -710,7 +743,7 @@ int write_archive(int argc, const char **argv, const char *prefix,
710743
setup_git_directory();
711744
}
712745

713-
parse_treeish_arg(argv, &args, prefix, remote);
746+
parse_treeish_arg(argv, &args, remote);
714747
parse_pathspec_arg(argv + 1, &args);
715748

716749
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
@@ -433,6 +433,19 @@ test_expect_success 'catch non-matching pathspec' '
433433
test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
434434
'
435435

436+
test_expect_success 'reject paths outside the current directory' '
437+
test_must_fail git -C a/bin archive HEAD .. >/dev/null 2>err &&
438+
grep "outside the current directory" err
439+
'
440+
441+
test_expect_success 'allow pathspecs that resolve to the current directory' '
442+
git -C a/bin archive -v HEAD ../bin >/dev/null 2>actual &&
443+
cat >expect <<-\EOF &&
444+
sh
445+
EOF
446+
test_cmp expect actual
447+
'
448+
436449
# Pull the size and date of each entry in a tarfile using the system tar.
437450
#
438451
# 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)