Skip to content

Commit ed22b41

Browse files
pcloudsgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d31f3ad commit ed22b41

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

archive.c

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "archive.h"
66
#include "parse-options.h"
77
#include "unpack-trees.h"
8+
#include "dir.h"
89

910
static char const * const archive_usage[] = {
1011
N_("git archive [options] <tree-ish> [<path>...]"),
@@ -97,9 +98,19 @@ static void setup_archive_check(struct git_attr_check *check)
9798
check[1].attr = attr_export_subst;
9899
}
99100

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+
100110
struct archiver_context {
101111
struct archiver_args *args;
102112
write_archive_entry_fn_t write_entry;
113+
struct directory *bottom;
103114
};
104115

105116
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,
145156
return write_entry(args, sha1, path.buf, path.len, mode);
146157
}
147158

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+
148218
int write_archive_entries(struct archiver_args *args,
149219
write_archive_entry_fn_t write_entry)
150220
{
@@ -166,6 +236,7 @@ int write_archive_entries(struct archiver_args *args,
166236
return err;
167237
}
168238

239+
memset(&context, 0, sizeof(context));
169240
context.args = args;
170241
context.write_entry = write_entry;
171242

@@ -186,9 +257,17 @@ int write_archive_entries(struct archiver_args *args,
186257
}
187258

188259
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);
190264
if (err == READ_TREE_RECURSIVE)
191265
err = 0;
266+
while (context.bottom) {
267+
struct directory *next = context.bottom->up;
268+
free(context.bottom);
269+
context.bottom = next;
270+
}
192271
return err;
193272
}
194273

@@ -210,7 +289,16 @@ static int reject_entry(const unsigned char *sha1, const char *base,
210289
int baselen, const char *filename, unsigned mode,
211290
int stage, void *context)
212291
{
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;
214302
}
215303

216304
static int path_exists(struct tree *tree, const char *path)
@@ -220,7 +308,9 @@ static int path_exists(struct tree *tree, const char *path)
220308
int ret;
221309

222310
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);
224314
free_pathspec(&pathspec);
225315
return ret != 0;
226316
}
@@ -236,6 +326,7 @@ static void parse_pathspec_arg(const char **pathspec,
236326
parse_pathspec(&ar_args->pathspec, 0,
237327
PATHSPEC_PREFER_FULL,
238328
"", pathspec);
329+
ar_args->pathspec.recursive = 1;
239330
if (pathspec) {
240331
while (*pathspec) {
241332
if (**pathspec && !path_exists(ar_args->tree, *pathspec))

t/t5000-tar-tree.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,18 @@ test_expect_success GZIP 'remote tar.gz can be disabled' '
300300
>remote.tar.gz
301301
'
302302

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+
303317
test_done

0 commit comments

Comments
 (0)