Skip to content

Commit 52f57e9

Browse files
committed
Merge branch 'ps/reftable-exclude'
The reftable backend learned to more efficiently handle exclude patterns while enumerating the refs. * ps/reftable-exclude: refs/reftable: wire up support for exclude patterns reftable/reader: make table iterator reseekable t/unit-tests: introduce reftable library Makefile: stop listing test library objects twice builtin/receive-pack: fix exclude patterns when announcing refs refs: properly apply exclude patterns to namespaced refs
2 parents c639478 + 1869525 commit 52f57e9

16 files changed

+594
-201
lines changed

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,7 @@ UNIT_TEST_PROGRAMS += t-reftable-basics
13561356
UNIT_TEST_PROGRAMS += t-reftable-block
13571357
UNIT_TEST_PROGRAMS += t-reftable-merged
13581358
UNIT_TEST_PROGRAMS += t-reftable-pq
1359+
UNIT_TEST_PROGRAMS += t-reftable-reader
13591360
UNIT_TEST_PROGRAMS += t-reftable-readwrite
13601361
UNIT_TEST_PROGRAMS += t-reftable-record
13611362
UNIT_TEST_PROGRAMS += t-reftable-stack
@@ -1365,9 +1366,9 @@ UNIT_TEST_PROGRAMS += t-strcmp-offset
13651366
UNIT_TEST_PROGRAMS += t-trailer
13661367
UNIT_TEST_PROGRAMS += t-urlmatch-normalization
13671368
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
1368-
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
13691369
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
13701370
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
1371+
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
13711372

13721373
# xdiff and reftable libs may in turn depend on what is in libgit.a
13731374
GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
@@ -2725,6 +2726,7 @@ OBJECTS += $(FUZZ_OBJS)
27252726
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
27262727
OBJECTS += $(UNIT_TEST_OBJS)
27272728
OBJECTS += $(CLAR_TEST_OBJS)
2729+
OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
27282730

27292731
ifndef NO_CURL
27302732
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3864,9 +3866,7 @@ $(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
38643866
-Wl,--allow-multiple-definition \
38653867
$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
38663868

3867-
$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
3868-
$(UNIT_TEST_DIR)/test-lib.o \
3869-
$(UNIT_TEST_DIR)/lib-oid.o \
3869+
$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \
38703870
$(GITLIBS) GIT-LDFLAGS
38713871
$(call mkdir_p_parent_template)
38723872
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \

builtin/receive-pack.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,26 @@ static void show_one_alternate_ref(const struct object_id *oid,
340340
static void write_head_info(void)
341341
{
342342
static struct oidset seen = OIDSET_INIT;
343+
struct strvec excludes_vector = STRVEC_INIT;
344+
const char **exclude_patterns;
345+
346+
/*
347+
* We need access to the reference names both with and without their
348+
* namespace and thus cannot use `refs_for_each_namespaced_ref()`. We
349+
* thus have to adapt exclude patterns to carry the namespace prefix
350+
* ourselves.
351+
*/
352+
exclude_patterns = get_namespaced_exclude_patterns(
353+
hidden_refs_to_excludes(&hidden_refs),
354+
get_git_namespace(), &excludes_vector);
343355

344356
refs_for_each_fullref_in(get_main_ref_store(the_repository), "",
345-
hidden_refs_to_excludes(&hidden_refs),
346-
show_ref_cb, &seen);
357+
exclude_patterns, show_ref_cb, &seen);
347358
for_each_alternate_ref(show_one_alternate_ref, &seen);
359+
348360
oidset_clear(&seen);
361+
strvec_clear(&excludes_vector);
362+
349363
if (!sent_capabilities)
350364
show_ref("capabilities^{}", null_oid());
351365

refs.c

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,19 @@ const char **hidden_refs_to_excludes(const struct strvec *hide_refs)
15181518
return hide_refs->v;
15191519
}
15201520

1521+
const char **get_namespaced_exclude_patterns(const char **exclude_patterns,
1522+
const char *namespace,
1523+
struct strvec *out)
1524+
{
1525+
if (!namespace || !*namespace || !exclude_patterns || !*exclude_patterns)
1526+
return exclude_patterns;
1527+
1528+
for (size_t i = 0; exclude_patterns[i]; i++)
1529+
strvec_pushf(out, "%s%s", namespace, exclude_patterns[i]);
1530+
1531+
return out->v;
1532+
}
1533+
15211534
const char *find_descendant_ref(const char *dirname,
15221535
const struct string_list *extras,
15231536
const struct string_list *skip)
@@ -1635,11 +1648,19 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
16351648
const char **exclude_patterns,
16361649
each_ref_fn fn, void *cb_data)
16371650
{
1638-
struct strbuf buf = STRBUF_INIT;
1651+
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
1652+
struct strbuf prefix = STRBUF_INIT;
16391653
int ret;
1640-
strbuf_addf(&buf, "%srefs/", get_git_namespace());
1641-
ret = do_for_each_ref(refs, buf.buf, exclude_patterns, fn, 0, 0, cb_data);
1642-
strbuf_release(&buf);
1654+
1655+
exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
1656+
get_git_namespace(),
1657+
&namespaced_exclude_patterns);
1658+
1659+
strbuf_addf(&prefix, "%srefs/", get_git_namespace());
1660+
ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
1661+
1662+
strvec_clear(&namespaced_exclude_patterns);
1663+
strbuf_release(&prefix);
16431664
return ret;
16441665
}
16451666

@@ -1720,6 +1741,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
17201741
const char **exclude_patterns,
17211742
each_ref_fn fn, void *cb_data)
17221743
{
1744+
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
17231745
struct string_list prefixes = STRING_LIST_INIT_DUP;
17241746
struct string_list_item *prefix;
17251747
struct strbuf buf = STRBUF_INIT;
@@ -1731,6 +1753,10 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
17311753
strbuf_addstr(&buf, namespace);
17321754
namespace_len = buf.len;
17331755

1756+
exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
1757+
namespace,
1758+
&namespaced_exclude_patterns);
1759+
17341760
for_each_string_list_item(prefix, &prefixes) {
17351761
strbuf_addstr(&buf, prefix->string);
17361762
ret = refs_for_each_fullref_in(ref_store, buf.buf,
@@ -1740,6 +1766,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
17401766
strbuf_setlen(&buf, namespace_len);
17411767
}
17421768

1769+
strvec_clear(&namespaced_exclude_patterns);
17431770
string_list_clear(&prefixes, 0);
17441771
strbuf_release(&buf);
17451772
return ret;

refs.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,15 @@ int ref_is_hidden(const char *, const char *, const struct strvec *);
861861
*/
862862
const char **hidden_refs_to_excludes(const struct strvec *hide_refs);
863863

864+
/*
865+
* Prefix all exclude patterns with the namespace, if any. This is required
866+
* because exclude patterns apply to the stripped reference name, not the full
867+
* reference name with the namespace.
868+
*/
869+
const char **get_namespaced_exclude_patterns(const char **exclude_patterns,
870+
const char *namespace,
871+
struct strvec *out);
872+
864873
/* Is this a per-worktree ref living in the refs/ namespace? */
865874
int is_per_worktree_ref(const char *refname);
866875

refs/reftable-backend.c

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "../repo-settings.h"
2323
#include "../setup.h"
2424
#include "../strmap.h"
25+
#include "../trace2.h"
2526
#include "parse.h"
2627
#include "refs-internal.h"
2728

@@ -451,10 +452,81 @@ struct reftable_ref_iterator {
451452

452453
const char *prefix;
453454
size_t prefix_len;
455+
char **exclude_patterns;
456+
size_t exclude_patterns_index;
457+
size_t exclude_patterns_strlen;
454458
unsigned int flags;
455459
int err;
456460
};
457461

462+
/*
463+
* Handle exclude patterns. Returns either `1`, which tells the caller that the
464+
* current reference shall not be shown. Or `0`, which indicates that it should
465+
* be shown.
466+
*/
467+
static int should_exclude_current_ref(struct reftable_ref_iterator *iter)
468+
{
469+
while (iter->exclude_patterns[iter->exclude_patterns_index]) {
470+
const char *pattern = iter->exclude_patterns[iter->exclude_patterns_index];
471+
char *ref_after_pattern;
472+
int cmp;
473+
474+
/*
475+
* Lazily cache the pattern length so that we don't have to
476+
* recompute it every time this function is called.
477+
*/
478+
if (!iter->exclude_patterns_strlen)
479+
iter->exclude_patterns_strlen = strlen(pattern);
480+
481+
/*
482+
* When the reference name is lexicographically bigger than the
483+
* current exclude pattern we know that it won't ever match any
484+
* of the following references, either. We thus advance to the
485+
* next pattern and re-check whether it matches.
486+
*
487+
* Otherwise, if it's smaller, then we do not have a match and
488+
* thus want to show the current reference.
489+
*/
490+
cmp = strncmp(iter->ref.refname, pattern,
491+
iter->exclude_patterns_strlen);
492+
if (cmp > 0) {
493+
iter->exclude_patterns_index++;
494+
iter->exclude_patterns_strlen = 0;
495+
continue;
496+
}
497+
if (cmp < 0)
498+
return 0;
499+
500+
/*
501+
* The reference shares a prefix with the exclude pattern and
502+
* shall thus be omitted. We skip all references that match the
503+
* pattern by seeking to the first reference after the block of
504+
* matches.
505+
*
506+
* This is done by appending the highest possible character to
507+
* the pattern. Consequently, all references that have the
508+
* pattern as prefix and whose suffix starts with anything in
509+
* the range [0x00, 0xfe] are skipped. And given that 0xff is a
510+
* non-printable character that shouldn't ever be in a ref name,
511+
* we'd not yield any such record, either.
512+
*
513+
* Note that the seeked-to reference may also be excluded. This
514+
* is not handled here though, but the caller is expected to
515+
* loop and re-verify the next reference for us.
516+
*/
517+
ref_after_pattern = xstrfmt("%s%c", pattern, 0xff);
518+
iter->err = reftable_iterator_seek_ref(&iter->iter, ref_after_pattern);
519+
iter->exclude_patterns_index++;
520+
iter->exclude_patterns_strlen = 0;
521+
trace2_counter_add(TRACE2_COUNTER_ID_REFTABLE_RESEEKS, 1);
522+
523+
free(ref_after_pattern);
524+
return 1;
525+
}
526+
527+
return 0;
528+
}
529+
458530
static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
459531
{
460532
struct reftable_ref_iterator *iter =
@@ -485,6 +557,9 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
485557
break;
486558
}
487559

560+
if (iter->exclude_patterns && should_exclude_current_ref(iter))
561+
continue;
562+
488563
if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
489564
parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) !=
490565
REF_WORKTREE_CURRENT)
@@ -574,6 +649,11 @@ static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator)
574649
(struct reftable_ref_iterator *)ref_iterator;
575650
reftable_ref_record_release(&iter->ref);
576651
reftable_iterator_destroy(&iter->iter);
652+
if (iter->exclude_patterns) {
653+
for (size_t i = 0; iter->exclude_patterns[i]; i++)
654+
free(iter->exclude_patterns[i]);
655+
free(iter->exclude_patterns);
656+
}
577657
free(iter);
578658
return ITER_DONE;
579659
}
@@ -584,9 +664,53 @@ static struct ref_iterator_vtable reftable_ref_iterator_vtable = {
584664
.abort = reftable_ref_iterator_abort
585665
};
586666

667+
static int qsort_strcmp(const void *va, const void *vb)
668+
{
669+
const char *a = *(const char **)va;
670+
const char *b = *(const char **)vb;
671+
return strcmp(a, b);
672+
}
673+
674+
static char **filter_exclude_patterns(const char **exclude_patterns)
675+
{
676+
size_t filtered_size = 0, filtered_alloc = 0;
677+
char **filtered = NULL;
678+
679+
if (!exclude_patterns)
680+
return NULL;
681+
682+
for (size_t i = 0; ; i++) {
683+
const char *exclude_pattern = exclude_patterns[i];
684+
int has_glob = 0;
685+
686+
if (!exclude_pattern)
687+
break;
688+
689+
for (const char *p = exclude_pattern; *p; p++) {
690+
has_glob = is_glob_special(*p);
691+
if (has_glob)
692+
break;
693+
}
694+
if (has_glob)
695+
continue;
696+
697+
ALLOC_GROW(filtered, filtered_size + 1, filtered_alloc);
698+
filtered[filtered_size++] = xstrdup(exclude_pattern);
699+
}
700+
701+
if (filtered_size) {
702+
QSORT(filtered, filtered_size, qsort_strcmp);
703+
ALLOC_GROW(filtered, filtered_size + 1, filtered_alloc);
704+
filtered[filtered_size++] = NULL;
705+
}
706+
707+
return filtered;
708+
}
709+
587710
static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_store *refs,
588711
struct reftable_stack *stack,
589712
const char *prefix,
713+
const char **exclude_patterns,
590714
int flags)
591715
{
592716
struct reftable_ref_iterator *iter;
@@ -599,6 +723,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
599723
iter->base.oid = &iter->oid;
600724
iter->flags = flags;
601725
iter->refs = refs;
726+
iter->exclude_patterns = filter_exclude_patterns(exclude_patterns);
602727

603728
ret = refs->err;
604729
if (ret)
@@ -620,7 +745,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
620745

621746
static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_store,
622747
const char *prefix,
623-
const char **exclude_patterns UNUSED,
748+
const char **exclude_patterns,
624749
unsigned int flags)
625750
{
626751
struct reftable_ref_iterator *main_iter, *worktree_iter;
@@ -631,7 +756,8 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
631756
required_flags |= REF_STORE_ODB;
632757
refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin");
633758

634-
main_iter = ref_iterator_for_stack(refs, refs->main_stack, prefix, flags);
759+
main_iter = ref_iterator_for_stack(refs, refs->main_stack, prefix,
760+
exclude_patterns, flags);
635761

636762
/*
637763
* The worktree stack is only set when we're in an actual worktree
@@ -645,7 +771,8 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
645771
* Otherwise we merge both the common and the per-worktree refs into a
646772
* single iterator.
647773
*/
648-
worktree_iter = ref_iterator_for_stack(refs, refs->worktree_stack, prefix, flags);
774+
worktree_iter = ref_iterator_for_stack(refs, refs->worktree_stack, prefix,
775+
exclude_patterns, flags);
649776
return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base,
650777
ref_iterator_select, NULL);
651778
}

reftable/reader.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ static int table_iter_seek_to(struct table_iter *ti, uint64_t off, uint8_t typ)
328328
ti->typ = block_reader_type(&ti->br);
329329
ti->block_off = off;
330330
block_iter_seek_start(&ti->bi, &ti->br);
331+
ti->is_finished = 0;
331332
return 0;
332333
}
333334

0 commit comments

Comments
 (0)