summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ref-filter.c89
-rwxr-xr-xt/t6300-for-each-ref.sh26
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'