diff options
-rw-r--r-- | Documentation/git-ls-files.txt | 3 | ||||
-rw-r--r-- | builtin/ls-files.c | 27 | ||||
-rw-r--r-- | dir.c | 46 | ||||
-rw-r--r-- | dir.h | 4 | ||||
-rwxr-xr-x | t/t3007-ls-files-recurse-submodules.sh | 108 |
5 files changed, 175 insertions, 13 deletions
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index ea01d45e5c..446209e206 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -140,8 +140,7 @@ a space) at the start of each line: --recurse-submodules:: Recursively calls ls-files on each submodule in the repository. - Currently there is only support for the --cached mode without a - pathspec. + Currently there is only support for the --cached mode. --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object diff --git a/builtin/ls-files.c b/builtin/ls-files.c index b6144a56d3..0f25914348 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -195,6 +195,7 @@ static void show_gitlink(const struct cache_entry *ce) { struct child_process cp = CHILD_PROCESS_INIT; int status; + int i; argv_array_pushf(&cp.args, "--super-prefix=%s%s/", super_prefix ? super_prefix : "", @@ -205,6 +206,15 @@ static void show_gitlink(const struct cache_entry *ce) /* add supported options */ argv_array_pushv(&cp.args, submodules_options.argv); + /* + * Pass in the original pathspec args. The submodule will be + * responsible for prepending the 'submodule_prefix' prior to comparing + * against the pathspec for matches. + */ + argv_array_push(&cp.args, "--"); + for (i = 0; i < pathspec.nr; i++) + argv_array_push(&cp.args, pathspec.items[i].original); + cp.git_cmd = 1; cp.dir = ce->name; status = run_command(&cp); @@ -223,7 +233,8 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); - if (recurse_submodules && S_ISGITLINK(ce->ce_mode)) { + if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && + submodule_path_match(&pathspec, name.buf, ps_matched)) { show_gitlink(ce); } else if (match_pathspec(&pathspec, name.buf, name.len, len, ps_matched, @@ -602,16 +613,20 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) die("ls-files --recurse-submodules does not support " "--error-unmatch"); - if (recurse_submodules && argc) - die("ls-files --recurse-submodules does not support pathspec"); - parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD | PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, prefix, argv); - /* Find common prefix for all pathspec's */ - max_prefix = common_prefix(&pathspec); + /* + * Find common prefix for all pathspec's + * This is used as a performance optimization which unfortunately cannot + * be done when recursing into submodules + */ + if (recurse_submodules) + max_prefix = NULL; + else + max_prefix = common_prefix(&pathspec); max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ @@ -207,8 +207,9 @@ int within_depth(const char *name, int namelen, return 1; } -#define DO_MATCH_EXCLUDE 1 -#define DO_MATCH_DIRECTORY 2 +#define DO_MATCH_EXCLUDE (1<<0) +#define DO_MATCH_DIRECTORY (1<<1) +#define DO_MATCH_SUBMODULE (1<<2) /* * Does 'match' match the given name? @@ -283,6 +284,32 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, item->nowildcard_len - prefix)) return MATCHED_FNMATCH; + /* Perform checks to see if "name" is a super set of the pathspec */ + if (flags & DO_MATCH_SUBMODULE) { + /* name is a literal prefix of the pathspec */ + if ((namelen < matchlen) && + (match[namelen] == '/') && + !ps_strncmp(item, match, name, namelen)) + return MATCHED_RECURSIVELY; + + /* name" doesn't match up to the first wild character */ + if (item->nowildcard_len < item->len && + ps_strncmp(item, match, name, + item->nowildcard_len - prefix)) + return 0; + + /* + * Here is where we would perform a wildmatch to check if + * "name" can be matched as a directory (or a prefix) against + * the pathspec. Since wildmatch doesn't have this capability + * at the present we have to punt and say that it is a match, + * potentially returning a false positive + * The submodules themselves will be able to perform more + * accurate matching to determine if the pathspec matches. + */ + return MATCHED_RECURSIVELY; + } + return 0; } @@ -386,6 +413,21 @@ int match_pathspec(const struct pathspec *ps, return negative ? 0 : positive; } +/** + * Check if a submodule is a superset of the pathspec + */ +int submodule_path_match(const struct pathspec *ps, + const char *submodule_name, + char *seen) +{ + int matched = do_match_pathspec(ps, submodule_name, + strlen(submodule_name), + 0, seen, + DO_MATCH_DIRECTORY | + DO_MATCH_SUBMODULE); + return matched; +} + int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix) @@ -304,6 +304,10 @@ extern int git_fnmatch(const struct pathspec_item *item, const char *pattern, const char *string, int prefix); +extern int submodule_path_match(const struct pathspec *ps, + const char *submodule_name, + char *seen); + static inline int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec, char *seen) diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh index 33a2ea71cf..a5426171d3 100755 --- a/t/t3007-ls-files-recurse-submodules.sh +++ b/t/t3007-ls-files-recurse-submodules.sh @@ -81,9 +81,111 @@ test_expect_success 'ls-files recurses more than 1 level' ' test_cmp expect actual ' -test_expect_success '--recurse-submodules does not support using path arguments' ' - test_must_fail git ls-files --recurse-submodules b 2>actual && - test_i18ngrep "does not support pathspec" actual +test_expect_success '--recurse-submodules and pathspecs setup' ' + echo e >submodule/subsub/e.txt && + git -C submodule/subsub add e.txt && + git -C submodule/subsub commit -m "adding e.txt" && + echo f >submodule/f.TXT && + echo g >submodule/g.txt && + git -C submodule add f.TXT g.txt && + git -C submodule commit -m "add f and g" && + echo h >h.txt && + mkdir sib && + echo sib >sib/file && + git add h.txt sib/file && + git commit -m "add h and sib/file" && + git init sub && + echo sub >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + git submodule add ./sub && + git commit -m "added sub" && + + cat >expect <<-\EOF && + .gitmodules + a + b/b + h.txt + sib/file + sub/file + submodule/.gitmodules + submodule/c + submodule/f.TXT + submodule/g.txt + submodule/subsub/d + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual && + cat actual && + git ls-files --recurse-submodules "*" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/g.txt + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules "*.txt" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/f.TXT + submodule/g.txt + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules ":(icase)*.txt" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/f.TXT + submodule/g.txt + EOF + + git ls-files --recurse-submodules ":(icase)*.txt" ":(exclude)submodule/subsub/*" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + sub/file + EOF + + git ls-files --recurse-submodules "sub" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "sub/" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "sub/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "su*/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "su?/file" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + sib/file + sub/file + EOF + + git ls-files --recurse-submodules "s??/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "s???file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "s*file" >actual && + test_cmp expect actual ' test_expect_success '--recurse-submodules does not support --error-unmatch' ' |