Skip to content

Commit

Permalink
Merge branch 'ps/reftable-exclude' into seen
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Sep 9, 2024
2 parents abd4999 + adf7a0c commit 8a0b811
Show file tree
Hide file tree
Showing 16 changed files with 570 additions and 201 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,7 @@ UNIT_TEST_PROGRAMS += t-reftable-basics
UNIT_TEST_PROGRAMS += t-reftable-block
UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGRAMS += t-reftable-pq
UNIT_TEST_PROGRAMS += t-reftable-reader
UNIT_TEST_PROGRAMS += t-reftable-readwrite
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-reftable-stack
Expand All @@ -1371,9 +1372,9 @@ UNIT_TEST_PROGRAMS += t-strcmp-offset
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGRAMS += t-urlmatch-normalization
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o

# xdiff and reftable libs may in turn depend on what is in libgit.a
GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
Expand Down Expand Up @@ -2755,6 +2756,7 @@ OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
OBJECTS += $(UNIT_TESTS_OBJS)
OBJECTS += $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))

ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
Expand Down Expand Up @@ -3894,9 +3896,7 @@ $(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
-Wl,--allow-multiple-definition \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)

$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(UNIT_TEST_DIR)/test-lib.o \
$(UNIT_TEST_DIR)/lib-oid.o \
$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \
$(GITLIBS) GIT-LDFLAGS
$(call mkdir_p_parent_template)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
Expand Down
18 changes: 16 additions & 2 deletions builtin/receive-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,26 @@ static void show_one_alternate_ref(const struct object_id *oid,
static void write_head_info(void)
{
static struct oidset seen = OIDSET_INIT;
struct strvec excludes_vector = STRVEC_INIT;
const char **exclude_patterns;

/*
* We need access to the reference names both with and without their
* namespace and thus cannot use `refs_for_each_namespaced_ref()`. We
* thus have to adapt exclude patterns to carry the namespace prefix
* ourselves.
*/
exclude_patterns = get_namespaced_exclude_patterns(
hidden_refs_to_excludes(&hidden_refs),
get_git_namespace(), &excludes_vector);

refs_for_each_fullref_in(get_main_ref_store(the_repository), "",
hidden_refs_to_excludes(&hidden_refs),
show_ref_cb, &seen);
exclude_patterns, show_ref_cb, &seen);
for_each_alternate_ref(show_one_alternate_ref, &seen);

oidset_clear(&seen);
strvec_clear(&excludes_vector);

if (!sent_capabilities)
show_ref("capabilities^{}", null_oid());

Expand Down
35 changes: 31 additions & 4 deletions refs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,19 @@ const char **hidden_refs_to_excludes(const struct strvec *hide_refs)
return hide_refs->v;
}

const char **get_namespaced_exclude_patterns(const char **exclude_patterns,
const char *namespace,
struct strvec *out)
{
if (!namespace || !*namespace || !exclude_patterns || !*exclude_patterns)
return exclude_patterns;

for (size_t i = 0; exclude_patterns[i]; i++)
strvec_pushf(out, "%s%s", namespace, exclude_patterns[i]);

return out->v;
}

const char *find_descendant_ref(const char *dirname,
const struct string_list *extras,
const struct string_list *skip)
Expand Down Expand Up @@ -1635,11 +1648,19 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
const char **exclude_patterns,
each_ref_fn fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct strbuf prefix = STRBUF_INIT;
int ret;
strbuf_addf(&buf, "%srefs/", get_git_namespace());
ret = do_for_each_ref(refs, buf.buf, exclude_patterns, fn, 0, 0, cb_data);
strbuf_release(&buf);

exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
get_git_namespace(),
&namespaced_exclude_patterns);

strbuf_addf(&prefix, "%srefs/", get_git_namespace());
ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);

strvec_clear(&namespaced_exclude_patterns);
strbuf_release(&prefix);
return ret;
}

Expand Down Expand Up @@ -1720,6 +1741,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
const char **exclude_patterns,
each_ref_fn fn, void *cb_data)
{
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
struct string_list prefixes = STRING_LIST_INIT_DUP;
struct string_list_item *prefix;
struct strbuf buf = STRBUF_INIT;
Expand All @@ -1731,6 +1753,10 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
strbuf_addstr(&buf, namespace);
namespace_len = buf.len;

exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
namespace,
&namespaced_exclude_patterns);

for_each_string_list_item(prefix, &prefixes) {
strbuf_addstr(&buf, prefix->string);
ret = refs_for_each_fullref_in(ref_store, buf.buf,
Expand All @@ -1740,6 +1766,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
strbuf_setlen(&buf, namespace_len);
}

strvec_clear(&namespaced_exclude_patterns);
string_list_clear(&prefixes, 0);
strbuf_release(&buf);
return ret;
Expand Down
9 changes: 9 additions & 0 deletions refs.h
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,15 @@ int ref_is_hidden(const char *, const char *, const struct strvec *);
*/
const char **hidden_refs_to_excludes(const struct strvec *hide_refs);

/*
* Prefix all exclude patterns with the namespace, if any. This is required
* because exclude patterns apply to the stripped reference name, not the full
* reference name with the namespace.
*/
const char **get_namespaced_exclude_patterns(const char **exclude_patterns,
const char *namespace,
struct strvec *out);

/* Is this a per-worktree ref living in the refs/ namespace? */
int is_per_worktree_ref(const char *refname);

Expand Down
125 changes: 122 additions & 3 deletions refs/reftable-backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "../repo-settings.h"
#include "../setup.h"
#include "../strmap.h"
#include "../trace2.h"
#include "parse.h"
#include "refs-internal.h"

Expand Down Expand Up @@ -451,10 +452,81 @@ struct reftable_ref_iterator {

const char *prefix;
size_t prefix_len;
char **exclude_patterns;
size_t exclude_patterns_index;
size_t exclude_patterns_strlen;
unsigned int flags;
int err;
};

/*
* Handle exclude patterns. Returns either `1`, which tells the caller that the
* current reference shall not be shown. Or `0`, which indicates that it should
* be shown.
*/
static int should_exclude_current_ref(struct reftable_ref_iterator *iter)
{
while (iter->exclude_patterns[iter->exclude_patterns_index]) {
const char *pattern = iter->exclude_patterns[iter->exclude_patterns_index];
char *ref_after_pattern;
int cmp;

/*
* Lazily cache the pattern length so that we don't have to
* recompute it every time this function is called.
*/
if (!iter->exclude_patterns_strlen)
iter->exclude_patterns_strlen = strlen(pattern);

/*
* When the reference name is lexicographically bigger than the
* current exclude pattern we know that it won't ever match any
* of the following references, either. We thus advance to the
* next pattern and re-check whether it matches.
*
* Otherwise, if it's smaller, then we do not have a match and
* thus want to show the current reference.
*/
cmp = strncmp(iter->ref.refname, pattern,
iter->exclude_patterns_strlen);
if (cmp > 0) {
iter->exclude_patterns_index++;
iter->exclude_patterns_strlen = 0;
continue;
}
if (cmp < 0)
return 0;

/*
* The reference shares a prefix with the exclude pattern and
* shall thus be omitted. We skip all references that match the
* pattern by seeking to the first reference after the block of
* matches.
*
* This is done by appending the highest possible character to
* the pattern. Consequently, all references that have the
* pattern as prefix and whose suffix starts with anything in
* the range [0x00, 0xfe] are skipped. And given that 0xff is a
* non-printable character that shouldn't ever be in a ref name,
* we'd not yield any such record, either.
*
* Note that the seeked-to reference may also be excluded. This
* is not handled here though, but the caller is expected to
* loop and re-verify the next reference for us.
*/
ref_after_pattern = xstrfmt("%s%c", pattern, 0xff);
iter->err = reftable_iterator_seek_ref(&iter->iter, ref_after_pattern);
iter->exclude_patterns_index++;
iter->exclude_patterns_strlen = 0;
trace2_counter_add(TRACE2_COUNTER_ID_REFTABLE_RESEEKS, 1);

free(ref_after_pattern);
return 1;
}

return 0;
}

static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
{
struct reftable_ref_iterator *iter =
Expand Down Expand Up @@ -485,6 +557,9 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
break;
}

if (iter->exclude_patterns && should_exclude_current_ref(iter))
continue;

if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) !=
REF_WORKTREE_CURRENT)
Expand Down Expand Up @@ -574,6 +649,11 @@ static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator)
(struct reftable_ref_iterator *)ref_iterator;
reftable_ref_record_release(&iter->ref);
reftable_iterator_destroy(&iter->iter);
if (iter->exclude_patterns) {
for (size_t i = 0; iter->exclude_patterns[i]; i++)
free(iter->exclude_patterns[i]);
free(iter->exclude_patterns);
}
free(iter);
return ITER_DONE;
}
Expand All @@ -584,9 +664,45 @@ static struct ref_iterator_vtable reftable_ref_iterator_vtable = {
.abort = reftable_ref_iterator_abort
};

static char **filter_exclude_patterns(const char **exclude_patterns)
{
size_t filtered_size = 0, filtered_alloc = 0;
char **filtered = NULL;

if (!exclude_patterns)
return NULL;

for (size_t i = 0; ; i++) {
const char *exclude_pattern = exclude_patterns[i];
int has_glob = 0;

if (!exclude_pattern)
break;

for (const char *p = exclude_pattern; *p; p++) {
has_glob = is_glob_special(*p);
if (has_glob)
break;
}
if (has_glob)
continue;

ALLOC_GROW(filtered, filtered_size + 1, filtered_alloc);
filtered[filtered_size++] = xstrdup(exclude_pattern);
}

if (filtered_size) {
ALLOC_GROW(filtered, filtered_size + 1, filtered_alloc);
filtered[filtered_size++] = NULL;
}

return filtered;
}

static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_store *refs,
struct reftable_stack *stack,
const char *prefix,
const char **exclude_patterns,
int flags)
{
struct reftable_ref_iterator *iter;
Expand All @@ -599,6 +715,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
iter->base.oid = &iter->oid;
iter->flags = flags;
iter->refs = refs;
iter->exclude_patterns = filter_exclude_patterns(exclude_patterns);

ret = refs->err;
if (ret)
Expand All @@ -620,7 +737,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_

static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_store,
const char *prefix,
const char **exclude_patterns UNUSED,
const char **exclude_patterns,
unsigned int flags)
{
struct reftable_ref_iterator *main_iter, *worktree_iter;
Expand All @@ -631,7 +748,8 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
required_flags |= REF_STORE_ODB;
refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin");

main_iter = ref_iterator_for_stack(refs, refs->main_stack, prefix, flags);
main_iter = ref_iterator_for_stack(refs, refs->main_stack, prefix,
exclude_patterns, flags);

/*
* The worktree stack is only set when we're in an actual worktree
Expand All @@ -645,7 +763,8 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto
* Otherwise we merge both the common and the per-worktree refs into a
* single iterator.
*/
worktree_iter = ref_iterator_for_stack(refs, refs->worktree_stack, prefix, flags);
worktree_iter = ref_iterator_for_stack(refs, refs->worktree_stack, prefix,
exclude_patterns, flags);
return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base,
ref_iterator_select, NULL);
}
Expand Down
1 change: 1 addition & 0 deletions reftable/reader.c
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ static int table_iter_seek_to(struct table_iter *ti, uint64_t off, uint8_t typ)
ti->typ = block_reader_type(&ti->br);
ti->block_off = off;
block_iter_seek_start(&ti->bi, &ti->br);
ti->is_finished = 0;
return 0;
}

Expand Down
Loading

0 comments on commit 8a0b811

Please sign in to comment.