archive: don't add empty directories to archives

While git doesn't track empty directories, git archive can be tricked into putting some into archives. One way is to construct an empty tree object, as t5004 does. While that is supported by the object database, it can't be represented in the index and thus it's unlikely to occur in the wild. Another way is using the literal name of a directory in an exclude pathspec -- its contents are are excluded, but the directory stub is included. That's inconsistent: exclude pathspecs containing wildcards don't leave empty directories in the archive. Yet another way is have a few levels of nested subdirectories (e.g. d1/d2/d3/file1) and ignoring the entries at the leaves (e.g. file1). The directories with the ignored content are ignored as well (e.g. d3), but their empty parents are included (e.g. d2). As empty directories are not supported by git, they should also not be written into archives. If an empty directory is really needed then it can be tracked and archived by placing an empty .gitignore file in it. There already is a mechanism in place for suppressing empty directories. When read_tree_recursive() encounters a directory excluded by a pathspec then it enters it anyway because it might contain included entries. It calls the callback function before it is able to decide if the directory is actually needed. For that reason git archive adds directories to a queue and writes entries for them only when it encounters the first child item -- but currently only if pathspecs with wildcards are used. Queue *all* directories, no matter if there even are pathspecs present. This prevents git archive from writing entries for empty directories in all cases. Suggested-by: Jeff King <peff@peff.net> Signed-off-by: Rene Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Sep 13, 2017 at 00:43 UTC 43180940473cdafa95eb6b63b0efa9e36ac905da
4 files changed +6 -21
archive.c
+2 -17
@@ -121,11 +121,6 @@ static int check_attr_export_subst(const struct attr_check *check)
121 return check && ATTR_TRUE(check->items[1].value);
122 }
123
124 -static int should_queue_directories(const struct archiver_args *args)
125 -{
126 - return args->pathspec.has_wildcard;
127 -}
128 -
124 static int write_archive_entry(const unsigned char *sha1, const char *base,
125 int baselen, const char *filename, unsigned mode, int stage,
126 void *context)
@@ -147,7 +142,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
142 strbuf_addch(&path, '/');
143 path_without_prefix = path.buf + args->baselen;
144
150 - if (!S_ISDIR(mode) || !should_queue_directories(args)) {
145 + if (!S_ISDIR(mode)) {
146 const struct attr_check *check;
147 check = get_archive_attrs(path_without_prefix);
148 if (check_attr_export_ignore(check))
@@ -169,14 +164,6 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
164 return write_entry(args, sha1, path.buf, path.len, mode);
165 }
166
172 -static int write_archive_entry_buf(const unsigned char *sha1, struct strbuf *base,
173 - const char *filename, unsigned mode, int stage,
174 - void *context)
175 -{
176 - return write_archive_entry(sha1, base->buf, base->len,
177 - filename, mode, stage, context);
178 -}
179 -
167 static void queue_directory(const unsigned char *sha1,
168 struct strbuf *base, const char *filename,
169 unsigned mode, int stage, struct archiver_context *c)
@@ -290,9 +277,7 @@ int write_archive_entries(struct archiver_args *args,
277 }
278
279 err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
293 - should_queue_directories(args) ?
294 - queue_or_write_archive_entry :
295 - write_archive_entry_buf,
280 + queue_or_write_archive_entry,
281 &context);
282 if (err == READ_TREE_RECURSIVE)
283 err = 0;
t/t5001-archive-attr.sh
+1 -1
@@ -73,7 +73,7 @@ test_expect_missing archive-pathspec/ignored-by-tree
73 test_expect_missing archive-pathspec/ignored-by-tree.d
74 test_expect_missing archive-pathspec/ignored-by-tree.d/file
75 test_expect_exists archive-pathspec/ignored-by-worktree
76 -test_expect_missing archive-pathspec/excluded-by-pathspec.d failure
76 +test_expect_missing archive-pathspec/excluded-by-pathspec.d
77 test_expect_missing archive-pathspec/excluded-by-pathspec.d/file
78
79 test_expect_success 'git archive with wildcard pathspec' '
t/t5002-archive-attr-pattern.sh
+1 -1
@@ -76,7 +76,7 @@ test_expect_missing archive/deep/and/slashless/ &&
76 test_expect_missing archive/deep/and/slashless/foo &&
77 test_expect_missing archive/deep/with/wildcard/ &&
78 test_expect_missing archive/deep/with/wildcard/foo &&
79 -test_expect_exists archive/one-level-lower/
79 +test_expect_missing archive/one-level-lower/
80 test_expect_missing archive/one-level-lower/two-levels-lower/ignored-only-if-dir/
81 test_expect_missing archive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir
82
t/t5004-archive-corner-cases.sh
+2 -2
@@ -108,14 +108,14 @@ test_expect_success 'archive empty subtree with no pathspec' '
108 git archive --format=tar $root_tree >subtree-all.tar &&
109 make_dir extract &&
110 "$TAR" xf subtree-all.tar -C extract &&
111 - check_dir extract sub
111 + check_dir extract
112 '
113
114 test_expect_success 'archive empty subtree by direct pathspec' '
115 git archive --format=tar $root_tree -- sub >subtree-path.tar &&
116 make_dir extract &&
117 "$TAR" xf subtree-path.tar -C extract &&
118 - check_dir extract sub
118 + check_dir extract
119 '
120
121 ZIPINFO=zipinfo