Skip to content

Commit 27be76b

Browse files
ttaylorrgitster
authored andcommitted
refs.c: remove empty '--exclude' patterns
In 59c35fa (refs/packed-backend.c: implement jump lists to avoid excluded pattern(s), 2023-07-10), the packed-refs backend learned how to construct "jump lists" to avoid enumerating sections of the packed-refs file that we know the caller is going to throw out anyway. This process works by finding the start- and end-points (that is, where in the packed-refs file corresponds to the range we're going to ignore) for each exclude pattern, then constructing a jump list based on that. At enumeration time we'll consult the jump list to skip past everything in the range(s) found in the previous step, saving time when excluding a large portion of references. But when there is a --exclude pattern which is just the empty string, the behavior is a little funky. When we try and exclude the empty string, the matched range covers the entire packed-refs file, meaning that we won't output any packed references. But the empty pattern doesn't actually match any references to begin with! For example, on my copy of git.git I can do: $ git for-each-ref '' | wc -l 0 So "git for-each-ref --exclude=''" shouldn't actually remove anything from the output, and ought to be equivalent to "git for-each-ref". But it's not, and in fact: $ git for-each-ref | wc -l 2229 $ git for-each-ref --exclude='' | wc -l 480 But why does the '--exclude' version output only some of the references in the repository? Here's a hint: $ find .git/refs -type f | wc -l 480 Indeed, because the files backend doesn't implement[^1] the same jump list concept as the packed backend we get the correct result for the loose references, but none of the packed references. Since the empty string exclude pattern doesn't match anything, we can discard them before the packed-refs backend has a chance to even see it (and likewise for reftable, which also implements a similar concept since 1869525 (refs/reftable: wire up support for exclude patterns, 2024-09-16)). This approach (copying only some of the patterns into a strvec at the refs.c layer) may seem heavy-handed, but it's setting us up to fix another bug in the following commit where the fix will involve modifying the incoming patterns. [^1]: As noted in 59c35fa. We technically could avoid opening and enumerating the contents of, for e.g., "$GIT_DIR/refs/heads/foo/" if we knew that we were excluding anything under the 'refs/heads/foo' hierarchy. But the --exclude stuff is all best-effort anyway, since the caller is expected to cull out any results that they don't want. Noticed-by: Jeff King <[email protected]> Signed-off-by: Taylor Blau <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 5c21db3 commit 27be76b

File tree

2 files changed

+26
-0
lines changed

2 files changed

+26
-0
lines changed

refs.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,20 @@ struct ref_iterator *refs_ref_iterator_begin(
15601560
enum do_for_each_ref_flags flags)
15611561
{
15621562
struct ref_iterator *iter;
1563+
struct strvec normalized_exclude_patterns = STRVEC_INIT;
1564+
1565+
if (exclude_patterns) {
1566+
for (size_t i = 0; exclude_patterns[i]; i++) {
1567+
const char *pattern = exclude_patterns[i];
1568+
size_t len = strlen(pattern);
1569+
if (!len)
1570+
continue;
1571+
1572+
strvec_push(&normalized_exclude_patterns, pattern);
1573+
}
1574+
1575+
exclude_patterns = normalized_exclude_patterns.v;
1576+
}
15631577

15641578
if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
15651579
static int ref_paranoia = -1;
@@ -1580,6 +1594,8 @@ struct ref_iterator *refs_ref_iterator_begin(
15801594
if (trim)
15811595
iter = prefix_ref_iterator_begin(iter, "", trim);
15821596

1597+
strvec_clear(&normalized_exclude_patterns);
1598+
15831599
return iter;
15841600
}
15851601

t/t1419-exclude-refs.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,14 @@ test_expect_success 'meta-characters are discarded' '
125125
assert_no_jumps perf
126126
'
127127

128+
test_expect_success 'empty string exclude pattern is ignored' '
129+
git update-ref refs/heads/loose $(git rev-parse refs/heads/foo/1) &&
130+
131+
for_each_ref__exclude refs/heads "" >actual 2>perf &&
132+
for_each_ref >expect &&
133+
134+
test_cmp expect actual &&
135+
assert_no_jumps perf
136+
'
137+
128138
test_done

0 commit comments

Comments
 (0)