archive: handle commits with an empty tree

git-archive relies on get_pathspec to convert its argv into a list of pathspecs. When get_pathspec is given an empty argv list, it returns a single pathspec, the empty string, to indicate that everything matches. When we feed this to our path_exists function, we typically see that the pathspec turns up at least one item in the tree, and we are happy. But when our tree is empty, we erroneously think it is because the pathspec is too limited, when in fact it is simply that there is nothing to be found in the tree. This is a weird corner case, but the correct behavior is almost certainly to produce an empty archive, not to exit with an error. This patch teaches git-archive to create empty archives when there is no pathspec given (we continue to complain if a pathspec is given, since it obviously is not matched). It also confirms that the tar and zip writers produce sane output in this instance. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Mar 10, 2013 at 21:32 UTC bd54cf17a4acfb9555b8a83b7e74fb0274c38bb4
3 files changed +103 -1
archive.c
+1 -1
@@ -234,7 +234,7 @@ static void parse_pathspec_arg(const char **pathspec,
234 ar_args->pathspec = pathspec = get_pathspec("", pathspec);
235 if (pathspec) {
236 while (*pathspec) {
237 - if (!path_exists(ar_args->tree, *pathspec))
237 + if (**pathspec && !path_exists(ar_args->tree, *pathspec))
238 die("path not found: %s", *pathspec);
239 pathspec++;
240 }
t/t5004-archive-corner-cases.sh new
+102
@@ -0,0 +1,102 @@
1 +#!/bin/sh
2 +
3 +test_description='test corner cases of git-archive'
4 +. ./test-lib.sh
5 +
6 +test_expect_success 'create commit with empty tree' '
7 + git commit --allow-empty -m foo
8 +'
9 +
10 +# Make a dir and clean it up afterwards
11 +make_dir() {
12 + mkdir "$1" &&
13 + test_when_finished "rm -rf '$1'"
14 +}
15 +
16 +# Check that the dir given in "$1" contains exactly the
17 +# set of paths given as arguments.
18 +check_dir() {
19 + dir=$1; shift
20 + {
21 + echo "$dir" &&
22 + for i in "$@"; do
23 + echo "$dir/$i"
24 + done
25 + } | sort >expect &&
26 + find "$dir" -print | sort >actual &&
27 + test_cmp expect actual
28 +}
29 +
30 +test_expect_success 'tar archive of empty tree is empty' '
31 + git archive --format=tar HEAD >empty.tar &&
32 + make_dir extract &&
33 + "$TAR" xf empty.tar -C extract &&
34 + check_dir extract
35 +'
36 +
37 +test_expect_success 'tar archive of empty tree with prefix' '
38 + git archive --format=tar --prefix=foo/ HEAD >prefix.tar &&
39 + make_dir extract &&
40 + "$TAR" xf prefix.tar -C extract &&
41 + check_dir extract foo
42 +'
43 +
44 +test_expect_success UNZIP 'zip archive of empty tree is empty' '
45 + # Detect the exit code produced when our particular flavor of unzip
46 + # sees an empty archive. Infozip will generate a warning and exit with
47 + # code 1. But in the name of sanity, we do not expect other unzip
48 + # implementations to do the same thing (it would be perfectly
49 + # reasonable to exit 0, for example).
50 + #
51 + # This makes our test less rigorous on some platforms (unzip may not
52 + # handle the empty repo at all, making our later check of its exit code
53 + # a no-op). But we cannot do anything reasonable except skip the test
54 + # on such platforms anyway, and this is the moral equivalent.
55 + "$GIT_UNZIP" "$TEST_DIRECTORY"/t5004/empty.zip
56 + expect_code=$?
57 +
58 + git archive --format=zip HEAD >empty.zip &&
59 + make_dir extract &&
60 + (
61 + cd extract &&
62 + test_expect_code $expect_code "$GIT_UNZIP" ../empty.zip
63 + ) &&
64 + check_dir extract
65 +'
66 +
67 +test_expect_success UNZIP 'zip archive of empty tree with prefix' '
68 + # We do not have to play exit-code tricks here, because our
69 + # result should not be empty; it has a directory in it.
70 + git archive --format=zip --prefix=foo/ HEAD >prefix.zip &&
71 + make_dir extract &&
72 + (
73 + cd extract &&
74 + "$GIT_UNZIP" ../prefix.zip
75 + ) &&
76 + check_dir extract foo
77 +'
78 +
79 +test_expect_success 'archive complains about pathspec on empty tree' '
80 + test_must_fail git archive --format=tar HEAD -- foo >/dev/null
81 +'
82 +
83 +test_expect_success 'create a commit with an empty subtree' '
84 + empty_tree=$(git hash-object -t tree /dev/null) &&
85 + root_tree=$(printf "040000 tree $empty_tree\tsub\n" | git mktree)
86 +'
87 +
88 +test_expect_success 'archive empty subtree with no pathspec' '
89 + git archive --format=tar $root_tree >subtree-all.tar &&
90 + make_dir extract &&
91 + "$TAR" xf subtree-all.tar -C extract &&
92 + check_dir extract sub
93 +'
94 +
95 +test_expect_success 'archive empty subtree by direct pathspec' '
96 + git archive --format=tar $root_tree -- sub >subtree-path.tar &&
97 + make_dir extract &&
98 + "$TAR" xf subtree-path.tar -C extract &&
99 + check_dir extract sub
100 +'
101 +
102 +test_done
t/t5004/empty.zip
Binary files /dev/null and b/t/t5004/empty.zip differ