Skip to content

Commit b2c45f5

Browse files
committed
Merge branch 'nd/archive-pathspec'
"git archive" learned to filter what gets archived with pathspec. * nd/archive-pathspec: archive: support filtering paths with glob
2 parents fb06b52 + ed22b41 commit b2c45f5

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>...]"),
@@ -98,9 +99,19 @@ static void setup_archive_check(struct git_attr_check *check)
9899
check[1].attr = attr_export_subst;
99100
}
100101

102+
struct directory {
103+
struct directory *up;
104+
unsigned char sha1[20];
105+
int baselen, len;
106+
unsigned mode;
107+
int stage;
108+
char path[FLEX_ARRAY];
109+
};
110+
101111
struct archiver_context {
102112
struct archiver_args *args;
103113
write_archive_entry_fn_t write_entry;
114+
struct directory *bottom;
104115
};
105116

106117
static int write_archive_entry(const unsigned char *sha1, const char *base,
@@ -146,6 +157,65 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
146157
return write_entry(args, sha1, path.buf, path.len, mode);
147158
}
148159

160+
static void queue_directory(const unsigned char *sha1,
161+
const char *base, int baselen, const char *filename,
162+
unsigned mode, int stage, struct archiver_context *c)
163+
{
164+
struct directory *d;
165+
d = xmallocz(sizeof(*d) + baselen + 1 + strlen(filename));
166+
d->up = c->bottom;
167+
d->baselen = baselen;
168+
d->mode = mode;
169+
d->stage = stage;
170+
c->bottom = d;
171+
d->len = sprintf(d->path, "%.*s%s/", baselen, base, filename);
172+
hashcpy(d->sha1, sha1);
173+
}
174+
175+
static int write_directory(struct archiver_context *c)
176+
{
177+
struct directory *d = c->bottom;
178+
int ret;
179+
180+
if (!d)
181+
return 0;
182+
c->bottom = d->up;
183+
d->path[d->len - 1] = '\0'; /* no trailing slash */
184+
ret =
185+
write_directory(c) ||
186+
write_archive_entry(d->sha1, d->path, d->baselen,
187+
d->path + d->baselen, d->mode,
188+
d->stage, c) != READ_TREE_RECURSIVE;
189+
free(d);
190+
return ret ? -1 : 0;
191+
}
192+
193+
static int queue_or_write_archive_entry(const unsigned char *sha1,
194+
const char *base, int baselen, const char *filename,
195+
unsigned mode, int stage, void *context)
196+
{
197+
struct archiver_context *c = context;
198+
199+
while (c->bottom &&
200+
!(baselen >= c->bottom->len &&
201+
!strncmp(base, c->bottom->path, c->bottom->len))) {
202+
struct directory *next = c->bottom->up;
203+
free(c->bottom);
204+
c->bottom = next;
205+
}
206+
207+
if (S_ISDIR(mode)) {
208+
queue_directory(sha1, base, baselen, filename,
209+
mode, stage, c);
210+
return READ_TREE_RECURSIVE;
211+
}
212+
213+
if (write_directory(c))
214+
return -1;
215+
return write_archive_entry(sha1, base, baselen, filename, mode,
216+
stage, context);
217+
}
218+
149219
int write_archive_entries(struct archiver_args *args,
150220
write_archive_entry_fn_t write_entry)
151221
{
@@ -167,6 +237,7 @@ int write_archive_entries(struct archiver_args *args,
167237
return err;
168238
}
169239

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

@@ -187,9 +258,17 @@ int write_archive_entries(struct archiver_args *args,
187258
}
188259

189260
err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
190-
write_archive_entry, &context);
261+
args->pathspec.has_wildcard ?
262+
queue_or_write_archive_entry :
263+
write_archive_entry,
264+
&context);
191265
if (err == READ_TREE_RECURSIVE)
192266
err = 0;
267+
while (context.bottom) {
268+
struct directory *next = context.bottom->up;
269+
free(context.bottom);
270+
context.bottom = next;
271+
}
193272
return err;
194273
}
195274

@@ -211,7 +290,16 @@ static int reject_entry(const unsigned char *sha1, const char *base,
211290
int baselen, const char *filename, unsigned mode,
212291
int stage, void *context)
213292
{
214-
return -1;
293+
int ret = -1;
294+
if (S_ISDIR(mode)) {
295+
struct strbuf sb = STRBUF_INIT;
296+
strbuf_addstr(&sb, base);
297+
strbuf_addstr(&sb, filename);
298+
if (!match_pathspec(context, sb.buf, sb.len, 0, NULL, 1))
299+
ret = READ_TREE_RECURSIVE;
300+
strbuf_release(&sb);
301+
}
302+
return ret;
215303
}
216304

217305
static int path_exists(struct tree *tree, const char *path)
@@ -221,7 +309,9 @@ static int path_exists(struct tree *tree, const char *path)
221309
int ret;
222310

223311
parse_pathspec(&pathspec, 0, 0, "", paths);
224-
ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
312+
pathspec.recursive = 1;
313+
ret = read_tree_recursive(tree, "", 0, 0, &pathspec,
314+
reject_entry, &pathspec);
225315
free_pathspec(&pathspec);
226316
return ret != 0;
227317
}
@@ -237,6 +327,7 @@ static void parse_pathspec_arg(const char **pathspec,
237327
parse_pathspec(&ar_args->pathspec, 0,
238328
PATHSPEC_PREFER_FULL,
239329
"", pathspec);
330+
ar_args->pathspec.recursive = 1;
240331
if (pathspec) {
241332
while (*pathspec) {
242333
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
@@ -305,4 +305,18 @@ test_expect_success GZIP 'remote tar.gz can be disabled' '
305305
>remote.tar.gz
306306
'
307307

308+
test_expect_success 'archive and :(glob)' '
309+
git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
310+
cat >expect <<EOF &&
311+
a/
312+
a/bin/
313+
a/bin/sh
314+
EOF
315+
test_cmp expect actual
316+
'
317+
318+
test_expect_success 'catch non-matching pathspec' '
319+
test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
320+
'
321+
308322
test_done

0 commit comments

Comments
 (0)