Merge branch 'vd/for-each-ref-unsorted-optimization'

"git for-each-ref --no-sort" still sorted the refs alphabetically
which paid non-trivial cost.  It has been redefined to show output
in an unspecified order, to allow certain optimizations to take
advantage of.

* vd/for-each-ref-unsorted-optimization:
  t/perf: add perf tests for for-each-ref
  ref-filter.c: use peeled tag for '*' format fields
  for-each-ref: clean up documentation of --format
  ref-filter.c: filter & format refs in the same callback
  ref-filter.c: refactor to create common helper functions
  ref-filter.c: rename 'ref_filter_handler()' to 'filter_one()'
  ref-filter.h: add functions for filter/format & format-only
  ref-filter.h: move contains caches into filter
  ref-filter.h: add max_count and omit_empty to ref_format
  ref-filter.c: really don't sort when using --no-sort
This commit is contained in:
Junio C Hamano
2023-12-09 16:37:50 -08:00
12 changed files with 523 additions and 180 deletions

View File

@ -2508,17 +2508,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
return 0;
/*
* If it is a tag object, see if we use a value that derefs
* the object, and if we do grab the object it refers to.
* If it is a tag object, see if we use the peeled value. If we do,
* grab the peeled OID.
*/
oi_deref.oid = *get_tagged_oid((struct tag *)obj);
if (need_tagged && peel_iterated_oid(&obj->oid, &oi_deref.oid))
die("bad tag");
/*
* NEEDSWORK: This derefs tag only once, which
* is good to deal with chains of trust, but
* is not consistent with what deref_tag() does
* which peels the onion to the core.
*/
return get_object(ref, 1, &obj, &oi_deref, err);
}
@ -2716,15 +2711,18 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
return ref;
}
static void ref_array_append(struct ref_array *array, struct ref_array_item *ref)
{
ALLOC_GROW(array->items, array->nr + 1, array->alloc);
array->items[array->nr++] = ref;
}
struct ref_array_item *ref_array_push(struct ref_array *array,
const char *refname,
const struct object_id *oid)
{
struct ref_array_item *ref = new_ref_array_item(refname, oid);
ALLOC_GROW(array->items, array->nr + 1, array->alloc);
array->items[array->nr++] = ref;
ref_array_append(array, ref);
return ref;
}
@ -2761,48 +2759,36 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)
return ref_kind_from_refname(refname);
}
struct ref_filter_cbdata {
struct ref_array *array;
struct ref_filter *filter;
struct contains_cache contains_cache;
struct contains_cache no_contains_cache;
};
/*
* A call-back given to for_each_ref(). Filter refs and keep them for
* later object processing.
*/
static int ref_filter_handler(const char *refname, const struct object_id *oid, int flag, void *cb_data)
static struct ref_array_item *apply_ref_filter(const char *refname, const struct object_id *oid,
int flag, struct ref_filter *filter)
{
struct ref_filter_cbdata *ref_cbdata = cb_data;
struct ref_filter *filter = ref_cbdata->filter;
struct ref_array_item *ref;
struct commit *commit = NULL;
unsigned int kind;
if (flag & REF_BAD_NAME) {
warning(_("ignoring ref with broken name %s"), refname);
return 0;
return NULL;
}
if (flag & REF_ISBROKEN) {
warning(_("ignoring broken ref %s"), refname);
return 0;
return NULL;
}
/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
kind = filter_ref_kind(filter, refname);
if (!(kind & filter->kind))
return 0;
return NULL;
if (!filter_pattern_match(filter, refname))
return 0;
return NULL;
if (filter_exclude_match(filter, refname))
return 0;
return NULL;
if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))
return 0;
return NULL;
/*
* A merge filter is applied on refs pointing to commits. Hence
@ -2813,15 +2799,15 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
filter->with_commit || filter->no_commit || filter->verbose) {
commit = lookup_commit_reference_gently(the_repository, oid, 1);
if (!commit)
return 0;
return NULL;
/* We perform the filtering for the '--contains' option... */
if (filter->with_commit &&
!commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache))
return 0;
!commit_contains(filter, commit, filter->with_commit, &filter->internal.contains_cache))
return NULL;
/* ...or for the `--no-contains' option */
if (filter->no_commit &&
commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache))
return 0;
commit_contains(filter, commit, filter->no_commit, &filter->internal.no_contains_cache))
return NULL;
}
/*
@ -2829,11 +2815,32 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
* to do its job and the resulting list may yet to be pruned
* by maxcount logic.
*/
ref = ref_array_push(ref_cbdata->array, refname, oid);
ref = new_ref_array_item(refname, oid);
ref->commit = commit;
ref->flag = flag;
ref->kind = kind;
return ref;
}
struct ref_filter_cbdata {
struct ref_array *array;
struct ref_filter *filter;
};
/*
* A call-back given to for_each_ref(). Filter refs and keep them for
* later object processing.
*/
static int filter_one(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
struct ref_filter_cbdata *ref_cbdata = cb_data;
struct ref_array_item *ref;
ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter);
if (ref)
ref_array_append(ref_cbdata->array, ref);
return 0;
}
@ -2851,6 +2858,49 @@ static void free_array_item(struct ref_array_item *item)
free(item);
}
struct ref_filter_and_format_cbdata {
struct ref_filter *filter;
struct ref_format *format;
struct ref_filter_and_format_internal {
int count;
} internal;
};
static int filter_and_format_one(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
struct ref_filter_and_format_cbdata *ref_cbdata = cb_data;
struct ref_array_item *ref;
struct strbuf output = STRBUF_INIT, err = STRBUF_INIT;
ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter);
if (!ref)
return 0;
if (format_ref_array_item(ref, ref_cbdata->format, &output, &err))
die("%s", err.buf);
if (output.len || !ref_cbdata->format->array_opts.omit_empty) {
fwrite(output.buf, 1, output.len, stdout);
putchar('\n');
}
strbuf_release(&output);
strbuf_release(&err);
free_array_item(ref);
/*
* Increment the running count of refs that match the filter. If
* max_count is set and we've reached the max, stop the ref
* iteration by returning a nonzero value.
*/
if (ref_cbdata->format->array_opts.max_count &&
++ref_cbdata->internal.count >= ref_cbdata->format->array_opts.max_count)
return 1;
return 0;
}
/* Free all memory allocated for ref_array */
void ref_array_clear(struct ref_array *array)
{
@ -2969,6 +3019,43 @@ void filter_ahead_behind(struct repository *r,
free(commits);
}
static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
{
int ret = 0;
filter->kind = type & FILTER_REFS_KIND_MASK;
init_contains_cache(&filter->internal.contains_cache);
init_contains_cache(&filter->internal.no_contains_cache);
/* Simple per-ref filtering */
if (!filter->kind)
die("filter_refs: invalid type");
else {
/*
* For common cases where we need only branches or remotes or tags,
* we only iterate through those refs. If a mix of refs is needed,
* we iterate over all refs and filter out required refs with the help
* of filter_ref_kind().
*/
if (filter->kind == FILTER_REFS_BRANCHES)
ret = for_each_fullref_in("refs/heads/", fn, cb_data);
else if (filter->kind == FILTER_REFS_REMOTES)
ret = for_each_fullref_in("refs/remotes/", fn, cb_data);
else if (filter->kind == FILTER_REFS_TAGS)
ret = for_each_fullref_in("refs/tags/", fn, cb_data);
else if (filter->kind & FILTER_REFS_ALL)
ret = for_each_fullref_in_pattern(filter, fn, cb_data);
if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
head_ref(fn, cb_data);
}
clear_contains_cache(&filter->internal.contains_cache);
clear_contains_cache(&filter->internal.no_contains_cache);
return ret;
}
/*
* API for filtering a set of refs. Based on the type of refs the user
* has requested, we iterate through those refs and apply filters
@ -2984,38 +3071,10 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
ref_cbdata.array = array;
ref_cbdata.filter = filter;
filter->kind = type & FILTER_REFS_KIND_MASK;
save_commit_buffer_orig = save_commit_buffer;
save_commit_buffer = 0;
init_contains_cache(&ref_cbdata.contains_cache);
init_contains_cache(&ref_cbdata.no_contains_cache);
/* Simple per-ref filtering */
if (!filter->kind)
die("filter_refs: invalid type");
else {
/*
* For common cases where we need only branches or remotes or tags,
* we only iterate through those refs. If a mix of refs is needed,
* we iterate over all refs and filter out required refs with the help
* of filter_ref_kind().
*/
if (filter->kind == FILTER_REFS_BRANCHES)
ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata);
else if (filter->kind == FILTER_REFS_REMOTES)
ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata);
else if (filter->kind == FILTER_REFS_TAGS)
ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata);
else if (filter->kind & FILTER_REFS_ALL)
ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata);
if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
head_ref(ref_filter_handler, &ref_cbdata);
}
clear_contains_cache(&ref_cbdata.contains_cache);
clear_contains_cache(&ref_cbdata.no_contains_cache);
ret = do_filter_refs(filter, type, filter_one, &ref_cbdata);
/* Filters that need revision walking */
reach_filter(array, &filter->reachable_from, INCLUDE_REACHED);
@ -3025,6 +3084,51 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
return ret;
}
static inline int can_do_iterative_format(struct ref_filter *filter,
struct ref_sorting *sorting,
struct ref_format *format)
{
/*
* Filtering & formatting results within a single ref iteration
* callback is not compatible with options that require
* post-processing a filtered ref_array. These include:
* - filtering on reachability
* - sorting the filtered results
* - including ahead-behind information in the formatted output
*/
return !(filter->reachable_from ||
filter->unreachable_from ||
sorting ||
format->bases.nr);
}
void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
struct ref_sorting *sorting,
struct ref_format *format)
{
if (can_do_iterative_format(filter, sorting, format)) {
int save_commit_buffer_orig;
struct ref_filter_and_format_cbdata ref_cbdata = {
.filter = filter,
.format = format,
};
save_commit_buffer_orig = save_commit_buffer;
save_commit_buffer = 0;
do_filter_refs(filter, type, filter_and_format_one, &ref_cbdata);
save_commit_buffer = save_commit_buffer_orig;
} else {
struct ref_array array = { 0 };
filter_refs(&array, filter, type);
filter_ahead_behind(the_repository, format, &array);
ref_array_sort(sorting, &array);
print_formatted_ref_array(&array, format);
ref_array_clear(&array);
}
}
static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b)
{
if (!(a->kind ^ b->kind))
@ -3142,7 +3246,8 @@ void ref_sorting_set_sort_flags_all(struct ref_sorting *sorting,
void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
{
QSORT_S(array->items, array->nr, compare_refs, sorting);
if (sorting)
QSORT_S(array->items, array->nr, compare_refs, sorting);
}
static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
@ -3213,6 +3318,29 @@ int format_ref_array_item(struct ref_array_item *info,
return 0;
}
void print_formatted_ref_array(struct ref_array *array, struct ref_format *format)
{
int total;
struct strbuf output = STRBUF_INIT, err = STRBUF_INIT;
total = format->array_opts.max_count;
if (!total || array->nr < total)
total = array->nr;
for (int i = 0; i < total; i++) {
strbuf_reset(&err);
strbuf_reset(&output);
if (format_ref_array_item(array->items[i], format, &output, &err))
die("%s", err.buf);
if (output.len || !format->array_opts.omit_empty) {
fwrite(output.buf, 1, output.len, stdout);
putchar('\n');
}
}
strbuf_release(&err);
strbuf_release(&output);
}
void pretty_print_ref(const char *name, const struct object_id *oid,
struct ref_format *format)
{
@ -3248,18 +3376,6 @@ static int parse_sorting_atom(const char *atom)
return res;
}
/* If no sorting option is given, use refname to sort as default */
static struct ref_sorting *ref_default_sorting(void)
{
static const char cstr_name[] = "refname";
struct ref_sorting *sorting = xcalloc(1, sizeof(*sorting));
sorting->next = NULL;
sorting->atom = parse_sorting_atom(cstr_name);
return sorting;
}
static void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg)
{
struct ref_sorting *s;
@ -3283,9 +3399,7 @@ struct ref_sorting *ref_sorting_options(struct string_list *options)
struct string_list_item *item;
struct ref_sorting *sorting = NULL, **tail = &sorting;
if (!options->nr) {
sorting = ref_default_sorting();
} else {
if (options->nr) {
for_each_string_list_item(item, options)
parse_ref_sorting(tail, item->string);
}