diff options
author | Junio C Hamano <gitster@pobox.com> | 2014-10-08 13:05:25 -0700 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2014-10-08 13:05:26 -0700 |
commit | b2c45f5b961b3b5d894fb723d2031e8ff41cadf7 (patch) | |
tree | ae94ddbc40beb6c552d4c6907eff133831be930f | |
parent | fb06b5280ea05d75515fa780cf08d4ec9d6fe101 (diff) | |
parent | ed22b4173bd8d6dbce6236480bd30a63dd54834e (diff) | |
download | git-b2c45f5b961b3b5d894fb723d2031e8ff41cadf7.tar.gz |
Merge branch 'nd/archive-pathspec'
"git archive" learned to filter what gets archived with pathspec.
* nd/archive-pathspec:
archive: support filtering paths with glob
-rw-r--r-- | archive.c | 97 | ||||
-rwxr-xr-x | t/t5000-tar-tree.sh | 14 |
2 files changed, 108 insertions, 3 deletions
@@ -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>...]"), @@ -98,9 +99,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, @@ -146,6 +157,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) { @@ -167,6 +237,7 @@ int write_archive_entries(struct archiver_args *args, return err; } + memset(&context, 0, sizeof(context)); context.args = args; context.write_entry = write_entry; @@ -187,9 +258,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; } @@ -211,7 +290,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) @@ -221,7 +309,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; } @@ -237,6 +327,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)) diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 7b8babd89b..d01bbdc968 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -305,4 +305,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 |