diff options
-rw-r--r-- | ref-filter.c | 89 | ||||
-rwxr-xr-x | t/t6300-for-each-ref.sh | 26 |
2 files changed, 89 insertions, 26 deletions
diff --git a/ref-filter.c b/ref-filter.c index 791f0648a6..56528caafd 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -22,6 +22,7 @@ #include "commit-reach.h" #include "worktree.h" #include "hashmap.h" +#include "argv-array.h" static struct ref_msg { const char *gone; @@ -1863,21 +1864,62 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname) return match_pattern(filter, refname); } -/* - * Find the longest prefix of pattern we can pass to - * `for_each_fullref_in()`, namely the part of pattern preceding the - * first glob character. (Note that `for_each_fullref_in()` is - * perfectly happy working with a prefix that doesn't end at a - * pathname component boundary.) - */ -static void find_longest_prefix(struct strbuf *out, const char *pattern) +static int qsort_strcmp(const void *va, const void *vb) +{ + const char *a = *(const char **)va; + const char *b = *(const char **)vb; + + return strcmp(a, b); +} + +static void find_longest_prefixes_1(struct string_list *out, + struct strbuf *prefix, + const char **patterns, size_t nr) { - const char *p; + size_t i; + + for (i = 0; i < nr; i++) { + char c = patterns[i][prefix->len]; + if (!c || is_glob_special(c)) { + string_list_append(out, prefix->buf); + return; + } + } + + i = 0; + while (i < nr) { + size_t end; + + /* + * Set "end" to the index of the element _after_ the last one + * in our group. + */ + for (end = i + 1; end < nr; end++) { + if (patterns[i][prefix->len] != patterns[end][prefix->len]) + break; + } - for (p = pattern; *p && !is_glob_special(*p); p++) - ; + strbuf_addch(prefix, patterns[i][prefix->len]); + find_longest_prefixes_1(out, prefix, patterns + i, end - i); + strbuf_setlen(prefix, prefix->len - 1); - strbuf_add(out, pattern, p - pattern); + i = end; + } +} + +static void find_longest_prefixes(struct string_list *out, + const char **patterns) +{ + struct argv_array sorted = ARGV_ARRAY_INIT; + struct strbuf prefix = STRBUF_INIT; + + argv_array_pushv(&sorted, patterns); + QSORT(sorted.argv, sorted.argc, qsort_strcmp); + + find_longest_prefixes_1(out, &prefix, sorted.argv, sorted.argc); + + argv_array_clear(&sorted); + strbuf_release(&prefix); } /* @@ -1890,7 +1932,8 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, void *cb_data, int broken) { - struct strbuf prefix = STRBUF_INIT; + struct string_list prefixes = STRING_LIST_INIT_DUP; + struct string_list_item *prefix; int ret; if (!filter->match_as_path) { @@ -1916,21 +1959,15 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, return for_each_fullref_in("", cb, cb_data, broken); } - if (filter->name_patterns[1]) { - /* - * multiple patterns; in theory this could still work as long - * as the patterns are disjoint. We'd just make multiple calls - * to for_each_ref(). But if they're not disjoint, we'd end up - * reporting the same ref multiple times. So let's punt on that - * for now. - */ - return for_each_fullref_in("", cb, cb_data, broken); - } + find_longest_prefixes(&prefixes, filter->name_patterns); - find_longest_prefix(&prefix, filter->name_patterns[0]); + for_each_string_list_item(prefix, &prefixes) { + ret = for_each_fullref_in(prefix->string, cb, cb_data, broken); + if (ret) + break; + } - ret = for_each_fullref_in(prefix.buf, cb, cb_data, broken); - strbuf_release(&prefix); + string_list_clear(&prefixes, 0); return ret; } diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index d9235217fc..ab69aa176d 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -346,6 +346,32 @@ test_expect_success 'Verify descending sort' ' ' cat >expected <<\EOF +refs/tags/testtag +refs/tags/testtag-2 +EOF + +test_expect_success 'exercise patterns with prefixes' ' + git tag testtag-2 && + test_when_finished "git tag -d testtag-2" && + git for-each-ref --format="%(refname)" \ + refs/tags/testtag refs/tags/testtag-2 >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +refs/tags/testtag +refs/tags/testtag-2 +EOF + +test_expect_success 'exercise glob patterns with prefixes' ' + git tag testtag-2 && + test_when_finished "git tag -d testtag-2" && + git for-each-ref --format="%(refname)" \ + refs/tags/testtag "refs/tags/testtag-*" >actual && + test_cmp expected actual +' + +cat >expected <<\EOF 'refs/heads/master' 'refs/remotes/origin/master' 'refs/tags/testtag' |