archive: support filtering paths with glob

This patch fixes two problems with using :(glob) (or even "*.c" without ":(glob)"). The first one is we forgot to turn on the 'recursive' flag in struct pathspec. Without that, tree_entry_interesting() will not mark potential directories "interesting" so that it can confirm whether those directories have anything matching the pathspec. The marking directories interesting has a side effect that we need to walk inside a directory to realize that there's nothing interested in there. By that time, 'archive' code has already written the (empty) directory down. That means lots of empty directories in the result archive. This problem is fixed by lazily writing directories down when we know they are actually needed. There is a theoretical bug in this implementation: we can't write empty trees/directories that match that pathspec. path_exists() is also made stricter in order to detect non-matching pathspec because when this 'recursive' flag is on, we most likely match some directories. The easiest way is not consider any directories "matched". Noticed-by: Peter Wu <peter@lekensteyn.nl> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Sep 21, 2014 at 10:55 UTC ed22b4173bd8d6dbce6236480bd30a63dd54834e
2 files changed +108 -3
archive.c
+94 -3
@@ -5,6 +5,7 @@
5 #include "archive.h"
6 #include "parse-options.h"
7 #include "unpack-trees.h"
8 +#include "dir.h"
9
10 static char const * const archive_usage[] = {
11 N_("git archive [options] <tree-ish> [<path>...]"),
@@ -97,9 +98,19 @@ static void setup_archive_check(struct git_attr_check *check)
98 check[1].attr = attr_export_subst;
99 }
100
101 +struct directory {
102 + struct directory *up;
103 + unsigned char sha1[20];
104 + int baselen, len;
105 + unsigned mode;
106 + int stage;
107 + char path[FLEX_ARRAY];
108 +};
109 +
110 struct archiver_context {
111 struct archiver_args *args;
112 write_archive_entry_fn_t write_entry;
113 + struct directory *bottom;
114 };
115
116 static int write_archive_entry(const unsigned char *sha1, const char *base,
@@ -145,6 +156,65 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
156 return write_entry(args, sha1, path.buf, path.len, mode);
157 }
158
159 +static void queue_directory(const unsigned char *sha1,
160 + const char *base, int baselen, const char *filename,
161 + unsigned mode, int stage, struct archiver_context *c)
162 +{
163 + struct directory *d;
164 + d = xmallocz(sizeof(*d) + baselen + 1 + strlen(filename));
165 + d->up = c->bottom;
166 + d->baselen = baselen;
167 + d->mode = mode;
168 + d->stage = stage;
169 + c->bottom = d;
170 + d->len = sprintf(d->path, "%.*s%s/", baselen, base, filename);
171 + hashcpy(d->sha1, sha1);
172 +}
173 +
174 +static int write_directory(struct archiver_context *c)
175 +{
176 + struct directory *d = c->bottom;
177 + int ret;
178 +
179 + if (!d)
180 + return 0;
181 + c->bottom = d->up;
182 + d->path[d->len - 1] = '\0'; /* no trailing slash */
183 + ret =
184 + write_directory(c) ||
185 + write_archive_entry(d->sha1, d->path, d->baselen,
186 + d->path + d->baselen, d->mode,
187 + d->stage, c) != READ_TREE_RECURSIVE;
188 + free(d);
189 + return ret ? -1 : 0;
190 +}
191 +
192 +static int queue_or_write_archive_entry(const unsigned char *sha1,
193 + const char *base, int baselen, const char *filename,
194 + unsigned mode, int stage, void *context)
195 +{
196 + struct archiver_context *c = context;
197 +
198 + while (c->bottom &&
199 + !(baselen >= c->bottom->len &&
200 + !strncmp(base, c->bottom->path, c->bottom->len))) {
201 + struct directory *next = c->bottom->up;
202 + free(c->bottom);
203 + c->bottom = next;
204 + }
205 +
206 + if (S_ISDIR(mode)) {
207 + queue_directory(sha1, base, baselen, filename,
208 + mode, stage, c);
209 + return READ_TREE_RECURSIVE;
210 + }
211 +
212 + if (write_directory(c))
213 + return -1;
214 + return write_archive_entry(sha1, base, baselen, filename, mode,
215 + stage, context);
216 +}
217 +
218 int write_archive_entries(struct archiver_args *args,
219 write_archive_entry_fn_t write_entry)
220 {
@@ -166,6 +236,7 @@ int write_archive_entries(struct archiver_args *args,
236 return err;
237 }
238
239 + memset(&context, 0, sizeof(context));
240 context.args = args;
241 context.write_entry = write_entry;
242
@@ -186,9 +257,17 @@ int write_archive_entries(struct archiver_args *args,
257 }
258
259 err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
189 - write_archive_entry, &context);
260 + args->pathspec.has_wildcard ?
261 + queue_or_write_archive_entry :
262 + write_archive_entry,
263 + &context);
264 if (err == READ_TREE_RECURSIVE)
265 err = 0;
266 + while (context.bottom) {
267 + struct directory *next = context.bottom->up;
268 + free(context.bottom);
269 + context.bottom = next;
270 + }
271 return err;
272 }
273
@@ -210,7 +289,16 @@ static int reject_entry(const unsigned char *sha1, const char *base,
289 int baselen, const char *filename, unsigned mode,
290 int stage, void *context)
291 {
213 - return -1;
292 + int ret = -1;
293 + if (S_ISDIR(mode)) {
294 + struct strbuf sb = STRBUF_INIT;
295 + strbuf_addstr(&sb, base);
296 + strbuf_addstr(&sb, filename);
297 + if (!match_pathspec(context, sb.buf, sb.len, 0, NULL, 1))
298 + ret = READ_TREE_RECURSIVE;
299 + strbuf_release(&sb);
300 + }
301 + return ret;
302 }
303
304 static int path_exists(struct tree *tree, const char *path)
@@ -220,7 +308,9 @@ static int path_exists(struct tree *tree, const char *path)
308 int ret;
309
310 parse_pathspec(&pathspec, 0, 0, "", paths);
223 - ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
311 + pathspec.recursive = 1;
312 + ret = read_tree_recursive(tree, "", 0, 0, &pathspec,
313 + reject_entry, &pathspec);
314 free_pathspec(&pathspec);
315 return ret != 0;
316 }
@@ -236,6 +326,7 @@ static void parse_pathspec_arg(const char **pathspec,
326 parse_pathspec(&ar_args->pathspec, 0,
327 PATHSPEC_PREFER_FULL,
328 "", pathspec);
329 + ar_args->pathspec.recursive = 1;
330 if (pathspec) {
331 while (*pathspec) {
332 if (**pathspec && !path_exists(ar_args->tree, *pathspec))
t/t5000-tar-tree.sh
+14
@@ -300,4 +300,18 @@ test_expect_success GZIP 'remote tar.gz can be disabled' '
300 >remote.tar.gz
301 '
302
303 +test_expect_success 'archive and :(glob)' '
304 + git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
305 + cat >expect <<EOF &&
306 +a/
307 +a/bin/
308 +a/bin/sh
309 +EOF
310 + test_cmp expect actual
311 +'
312 +
313 +test_expect_success 'catch non-matching pathspec' '
314 + test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
315 +'
316 +
317 test_done