Skip to content

Commit

Permalink
archive: support filtering paths with glob
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Nguyễn Thái Ngọc Duy authored and Junio C Hamano committed Sep 22, 2014
1 parent d31f3ad commit ed22b41
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
97 changes: 94 additions & 3 deletions archive.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "archive.h"
#include "parse-options.h"
#include "unpack-trees.h"
#include "dir.h"

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

struct directory {
struct directory *up;
unsigned char sha1[20];
int baselen, len;
unsigned mode;
int stage;
char path[FLEX_ARRAY];
};

struct archiver_context {
struct archiver_args *args;
write_archive_entry_fn_t write_entry;
struct directory *bottom;
};

static int write_archive_entry(const unsigned char *sha1, const char *base,
Expand Down Expand Up @@ -145,6 +156,65 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
return write_entry(args, sha1, path.buf, path.len, mode);
}

static void queue_directory(const unsigned char *sha1,
const char *base, int baselen, const char *filename,
unsigned mode, int stage, struct archiver_context *c)
{
struct directory *d;
d = xmallocz(sizeof(*d) + baselen + 1 + strlen(filename));
d->up = c->bottom;
d->baselen = baselen;
d->mode = mode;
d->stage = stage;
c->bottom = d;
d->len = sprintf(d->path, "%.*s%s/", baselen, base, filename);
hashcpy(d->sha1, sha1);
}

static int write_directory(struct archiver_context *c)
{
struct directory *d = c->bottom;
int ret;

if (!d)
return 0;
c->bottom = d->up;
d->path[d->len - 1] = '\0'; /* no trailing slash */
ret =
write_directory(c) ||
write_archive_entry(d->sha1, d->path, d->baselen,
d->path + d->baselen, d->mode,
d->stage, c) != READ_TREE_RECURSIVE;
free(d);
return ret ? -1 : 0;
}

static int queue_or_write_archive_entry(const unsigned char *sha1,
const char *base, int baselen, const char *filename,
unsigned mode, int stage, void *context)
{
struct archiver_context *c = context;

while (c->bottom &&
!(baselen >= c->bottom->len &&
!strncmp(base, c->bottom->path, c->bottom->len))) {
struct directory *next = c->bottom->up;
free(c->bottom);
c->bottom = next;
}

if (S_ISDIR(mode)) {
queue_directory(sha1, base, baselen, filename,
mode, stage, c);
return READ_TREE_RECURSIVE;
}

if (write_directory(c))
return -1;
return write_archive_entry(sha1, base, baselen, filename, mode,
stage, context);
}

int write_archive_entries(struct archiver_args *args,
write_archive_entry_fn_t write_entry)
{
Expand All @@ -166,6 +236,7 @@ int write_archive_entries(struct archiver_args *args,
return err;
}

memset(&context, 0, sizeof(context));
context.args = args;
context.write_entry = write_entry;

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

err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
write_archive_entry, &context);
args->pathspec.has_wildcard ?
queue_or_write_archive_entry :
write_archive_entry,
&context);
if (err == READ_TREE_RECURSIVE)
err = 0;
while (context.bottom) {
struct directory *next = context.bottom->up;
free(context.bottom);
context.bottom = next;
}
return err;
}

Expand All @@ -210,7 +289,16 @@ static int reject_entry(const unsigned char *sha1, const char *base,
int baselen, const char *filename, unsigned mode,
int stage, void *context)
{
return -1;
int ret = -1;
if (S_ISDIR(mode)) {
struct strbuf sb = STRBUF_INIT;
strbuf_addstr(&sb, base);
strbuf_addstr(&sb, filename);
if (!match_pathspec(context, sb.buf, sb.len, 0, NULL, 1))
ret = READ_TREE_RECURSIVE;
strbuf_release(&sb);
}
return ret;
}

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

parse_pathspec(&pathspec, 0, 0, "", paths);
ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
pathspec.recursive = 1;
ret = read_tree_recursive(tree, "", 0, 0, &pathspec,
reject_entry, &pathspec);
free_pathspec(&pathspec);
return ret != 0;
}
Expand All @@ -236,6 +326,7 @@ static void parse_pathspec_arg(const char **pathspec,
parse_pathspec(&ar_args->pathspec, 0,
PATHSPEC_PREFER_FULL,
"", pathspec);
ar_args->pathspec.recursive = 1;
if (pathspec) {
while (*pathspec) {
if (**pathspec && !path_exists(ar_args->tree, *pathspec))
Expand Down
14 changes: 14 additions & 0 deletions t/t5000-tar-tree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,18 @@ test_expect_success GZIP 'remote tar.gz can be disabled' '
>remote.tar.gz
'

test_expect_success 'archive and :(glob)' '
git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
cat >expect <<EOF &&
a/
a/bin/
a/bin/sh
EOF
test_cmp expect actual
'

test_expect_success 'catch non-matching pathspec' '
test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
'

test_done

0 comments on commit ed22b41

Please sign in to comment.