Merge branch 'tb/ref-filter-multiple-patterns'
"git for-each-ref" with multiple patterns have been optimized. * tb/ref-filter-multiple-patterns: ref-filter.c: find disjoint pattern prefixes
This commit is contained in:
commit
b4b8c35729
89
ref-filter.c
89
ref-filter.c
@ -22,6 +22,7 @@
|
|||||||
#include "commit-reach.h"
|
#include "commit-reach.h"
|
||||||
#include "worktree.h"
|
#include "worktree.h"
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
|
#include "argv-array.h"
|
||||||
|
|
||||||
static struct ref_msg {
|
static struct ref_msg {
|
||||||
const char *gone;
|
const char *gone;
|
||||||
@ -1863,21 +1864,62 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
|
|||||||
return match_pattern(filter, refname);
|
return match_pattern(filter, refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int qsort_strcmp(const void *va, const void *vb)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
const char *p;
|
const char *a = *(const char **)va;
|
||||||
|
const char *b = *(const char **)vb;
|
||||||
|
|
||||||
for (p = pattern; *p && !is_glob_special(*p); p++)
|
return strcmp(a, b);
|
||||||
;
|
}
|
||||||
|
|
||||||
strbuf_add(out, pattern, p - pattern);
|
static void find_longest_prefixes_1(struct string_list *out,
|
||||||
|
struct strbuf *prefix,
|
||||||
|
const char **patterns, size_t nr)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_addch(prefix, patterns[i][prefix->len]);
|
||||||
|
find_longest_prefixes_1(out, prefix, patterns + i, end - i);
|
||||||
|
strbuf_setlen(prefix, prefix->len - 1);
|
||||||
|
|
||||||
|
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,
|
void *cb_data,
|
||||||
int broken)
|
int broken)
|
||||||
{
|
{
|
||||||
struct strbuf prefix = STRBUF_INIT;
|
struct string_list prefixes = STRING_LIST_INIT_DUP;
|
||||||
|
struct string_list_item *prefix;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!filter->match_as_path) {
|
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);
|
return for_each_fullref_in("", cb, cb_data, broken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter->name_patterns[1]) {
|
find_longest_prefixes(&prefixes, filter->name_patterns);
|
||||||
/*
|
|
||||||
* multiple patterns; in theory this could still work as long
|
for_each_string_list_item(prefix, &prefixes) {
|
||||||
* as the patterns are disjoint. We'd just make multiple calls
|
ret = for_each_fullref_in(prefix->string, cb, cb_data, broken);
|
||||||
* to for_each_ref(). But if they're not disjoint, we'd end up
|
if (ret)
|
||||||
* reporting the same ref multiple times. So let's punt on that
|
break;
|
||||||
* for now.
|
|
||||||
*/
|
|
||||||
return for_each_fullref_in("", cb, cb_data, broken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
find_longest_prefix(&prefix, filter->name_patterns[0]);
|
string_list_clear(&prefixes, 0);
|
||||||
|
|
||||||
ret = for_each_fullref_in(prefix.buf, cb, cb_data, broken);
|
|
||||||
strbuf_release(&prefix);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,6 +345,32 @@ test_expect_success 'Verify descending sort' '
|
|||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
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
|
cat >expected <<\EOF
|
||||||
'refs/heads/master'
|
'refs/heads/master'
|
||||||
'refs/remotes/origin/master'
|
'refs/remotes/origin/master'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user