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-sortmaint
commit
98d0a1f93e
|
@ -51,17 +51,14 @@ OPTIONS
|
||||||
key.
|
key.
|
||||||
|
|
||||||
--format=<format>::
|
--format=<format>::
|
||||||
A string that interpolates `%(fieldname)` from a ref being shown
|
A string that interpolates `%(fieldname)` from a ref being shown and
|
||||||
and the object it points at. If `fieldname`
|
the object it points at. In addition, the string literal `%%`
|
||||||
is prefixed with an asterisk (`*`) and the ref points
|
renders as `%` and `%xx` - where `xx` are hex digits - renders as
|
||||||
at a tag object, use the value for the field in the object
|
the character with hex code `xx`. For example, `%00` interpolates to
|
||||||
which the tag object refers to (instead of the field in the tag object).
|
`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
|
||||||
When unspecified, `<format>` defaults to
|
+
|
||||||
`%(objectname) SPC %(objecttype) TAB %(refname)`.
|
When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
|
||||||
It also interpolates `%%` to `%`, and `%xx` where `xx`
|
TAB %(refname)`.
|
||||||
are hex digits interpolates to character with hex code
|
|
||||||
`xx`; for example `%00` interpolates to `\0` (NUL),
|
|
||||||
`%09` to `\t` (TAB) and `%0a` to `\n` (LF).
|
|
||||||
|
|
||||||
--color[=<when>]::
|
--color[=<when>]::
|
||||||
Respect any colors specified in the `--format` option. The
|
Respect any colors specified in the `--format` option. The
|
||||||
|
@ -298,6 +295,10 @@ fields will correspond to the appropriate date or name-email-date tuple
|
||||||
from the `committer` or `tagger` fields depending on the object type.
|
from the `committer` or `tagger` fields depending on the object type.
|
||||||
These are intended for working on a mix of annotated and lightweight tags.
|
These are intended for working on a mix of annotated and lightweight tags.
|
||||||
|
|
||||||
|
For tag objects, a `fieldname` prefixed with an asterisk (`*`) expands to
|
||||||
|
the `fieldname` value of the peeled object, rather than that of the tag
|
||||||
|
object itself.
|
||||||
|
|
||||||
Fields that have name-email-date tuple as its value (`author`,
|
Fields that have name-email-date tuple as its value (`author`,
|
||||||
`committer`, and `tagger`) can be suffixed with `name`, `email`,
|
`committer`, and `tagger`) can be suffixed with `name`, `email`,
|
||||||
and `date` to extract the named component. For email fields (`authoremail`,
|
and `date` to extract the named component. For email fields (`authoremail`,
|
||||||
|
|
|
@ -45,7 +45,6 @@ static const char *head;
|
||||||
static struct object_id head_oid;
|
static struct object_id head_oid;
|
||||||
static int recurse_submodules = 0;
|
static int recurse_submodules = 0;
|
||||||
static int submodule_propagate_branches = 0;
|
static int submodule_propagate_branches = 0;
|
||||||
static int omit_empty = 0;
|
|
||||||
|
|
||||||
static int branch_use_color = -1;
|
static int branch_use_color = -1;
|
||||||
static char branch_colors[][COLOR_MAXLEN] = {
|
static char branch_colors[][COLOR_MAXLEN] = {
|
||||||
|
@ -438,8 +437,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
struct ref_array array;
|
struct ref_array array;
|
||||||
struct strbuf out = STRBUF_INIT;
|
|
||||||
struct strbuf err = STRBUF_INIT;
|
|
||||||
int maxwidth = 0;
|
int maxwidth = 0;
|
||||||
const char *remote_prefix = "";
|
const char *remote_prefix = "";
|
||||||
char *to_free = NULL;
|
char *to_free = NULL;
|
||||||
|
@ -469,24 +466,27 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
|
||||||
filter_ahead_behind(the_repository, format, &array);
|
filter_ahead_behind(the_repository, format, &array);
|
||||||
ref_array_sort(sorting, &array);
|
ref_array_sort(sorting, &array);
|
||||||
|
|
||||||
for (i = 0; i < array.nr; i++) {
|
if (column_active(colopts)) {
|
||||||
strbuf_reset(&err);
|
struct strbuf out = STRBUF_INIT, err = STRBUF_INIT;
|
||||||
strbuf_reset(&out);
|
|
||||||
if (format_ref_array_item(array.items[i], format, &out, &err))
|
assert(!filter->verbose && "--column and --verbose are incompatible");
|
||||||
die("%s", err.buf);
|
|
||||||
if (column_active(colopts)) {
|
for (i = 0; i < array.nr; i++) {
|
||||||
assert(!filter->verbose && "--column and --verbose are incompatible");
|
strbuf_reset(&err);
|
||||||
/* format to a string_list to let print_columns() do its job */
|
strbuf_reset(&out);
|
||||||
|
if (format_ref_array_item(array.items[i], format, &out, &err))
|
||||||
|
die("%s", err.buf);
|
||||||
|
|
||||||
|
/* format to a string_list to let print_columns() do its job */
|
||||||
string_list_append(output, out.buf);
|
string_list_append(output, out.buf);
|
||||||
} else {
|
|
||||||
fwrite(out.buf, 1, out.len, stdout);
|
|
||||||
if (out.len || !omit_empty)
|
|
||||||
putchar('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strbuf_release(&err);
|
||||||
|
strbuf_release(&out);
|
||||||
|
} else {
|
||||||
|
print_formatted_ref_array(&array, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
strbuf_release(&err);
|
|
||||||
strbuf_release(&out);
|
|
||||||
ref_array_clear(&array);
|
ref_array_clear(&array);
|
||||||
free(to_free);
|
free(to_free);
|
||||||
}
|
}
|
||||||
|
@ -737,7 +737,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
||||||
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
|
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
|
||||||
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
|
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
|
||||||
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
|
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
|
||||||
OPT_BOOL(0, "omit-empty", &omit_empty,
|
OPT_BOOL(0, "omit-empty", &format.array_opts.omit_empty,
|
||||||
N_("do not output a newline after empty formatted refs")),
|
N_("do not output a newline after empty formatted refs")),
|
||||||
OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1),
|
OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1),
|
||||||
OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2),
|
OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2),
|
||||||
|
@ -767,7 +767,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
||||||
if (argc == 2 && !strcmp(argv[1], "-h"))
|
if (argc == 2 && !strcmp(argv[1], "-h"))
|
||||||
usage_with_options(builtin_branch_usage, options);
|
usage_with_options(builtin_branch_usage, options);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to set sort keys from config. If config does not set any,
|
||||||
|
* fall back on default (refname) sorting.
|
||||||
|
*/
|
||||||
git_config(git_branch_config, &sorting_options);
|
git_config(git_branch_config, &sorting_options);
|
||||||
|
if (!sorting_options.nr)
|
||||||
|
string_list_append(&sorting_options, "refname");
|
||||||
|
|
||||||
track = git_branch_track;
|
track = git_branch_track;
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,11 @@ static char const * const for_each_ref_usage[] = {
|
||||||
|
|
||||||
int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
struct ref_sorting *sorting;
|
struct ref_sorting *sorting;
|
||||||
struct string_list sorting_options = STRING_LIST_INIT_DUP;
|
struct string_list sorting_options = STRING_LIST_INIT_DUP;
|
||||||
int maxcount = 0, icase = 0, omit_empty = 0;
|
int icase = 0;
|
||||||
struct ref_array array;
|
|
||||||
struct ref_filter filter = REF_FILTER_INIT;
|
struct ref_filter filter = REF_FILTER_INIT;
|
||||||
struct ref_format format = REF_FORMAT_INIT;
|
struct ref_format format = REF_FORMAT_INIT;
|
||||||
struct strbuf output = STRBUF_INIT;
|
|
||||||
struct strbuf err = STRBUF_INIT;
|
|
||||||
int from_stdin = 0;
|
int from_stdin = 0;
|
||||||
struct strvec vec = STRVEC_INIT;
|
struct strvec vec = STRVEC_INIT;
|
||||||
|
|
||||||
|
@ -40,11 +36,11 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
||||||
N_("quote placeholders suitably for python"), QUOTE_PYTHON),
|
N_("quote placeholders suitably for python"), QUOTE_PYTHON),
|
||||||
OPT_BIT(0 , "tcl", &format.quote_style,
|
OPT_BIT(0 , "tcl", &format.quote_style,
|
||||||
N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
|
N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
|
||||||
OPT_BOOL(0, "omit-empty", &omit_empty,
|
OPT_BOOL(0, "omit-empty", &format.array_opts.omit_empty,
|
||||||
N_("do not output a newline after empty formatted refs")),
|
N_("do not output a newline after empty formatted refs")),
|
||||||
|
|
||||||
OPT_GROUP(""),
|
OPT_GROUP(""),
|
||||||
OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
|
OPT_INTEGER( 0 , "count", &format.array_opts.max_count, N_("show only <n> matched refs")),
|
||||||
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
|
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
|
||||||
OPT__COLOR(&format.use_color, N_("respect format colors")),
|
OPT__COLOR(&format.use_color, N_("respect format colors")),
|
||||||
OPT_REF_FILTER_EXCLUDE(&filter),
|
OPT_REF_FILTER_EXCLUDE(&filter),
|
||||||
|
@ -61,15 +57,16 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
memset(&array, 0, sizeof(array));
|
|
||||||
|
|
||||||
format.format = "%(objectname) %(objecttype)\t%(refname)";
|
format.format = "%(objectname) %(objecttype)\t%(refname)";
|
||||||
|
|
||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
|
|
||||||
|
/* Set default (refname) sorting */
|
||||||
|
string_list_append(&sorting_options, "refname");
|
||||||
|
|
||||||
parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
|
parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
|
||||||
if (maxcount < 0) {
|
if (format.array_opts.max_count < 0) {
|
||||||
error("invalid --count argument: `%d'", maxcount);
|
error("invalid --count argument: `%d'", format.array_opts.max_count);
|
||||||
usage_with_options(for_each_ref_usage, opts);
|
usage_with_options(for_each_ref_usage, opts);
|
||||||
}
|
}
|
||||||
if (HAS_MULTI_BITS(format.quote_style)) {
|
if (HAS_MULTI_BITS(format.quote_style)) {
|
||||||
|
@ -101,26 +98,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.match_as_path = 1;
|
filter.match_as_path = 1;
|
||||||
filter_refs(&array, &filter, FILTER_REFS_ALL);
|
filter_and_format_refs(&filter, FILTER_REFS_ALL, sorting, &format);
|
||||||
filter_ahead_behind(the_repository, &format, &array);
|
|
||||||
|
|
||||||
ref_array_sort(sorting, &array);
|
|
||||||
|
|
||||||
if (!maxcount || array.nr < maxcount)
|
|
||||||
maxcount = array.nr;
|
|
||||||
for (i = 0; i < maxcount; i++) {
|
|
||||||
strbuf_reset(&err);
|
|
||||||
strbuf_reset(&output);
|
|
||||||
if (format_ref_array_item(array.items[i], &format, &output, &err))
|
|
||||||
die("%s", err.buf);
|
|
||||||
fwrite(output.buf, 1, output.len, stdout);
|
|
||||||
if (output.len || !omit_empty)
|
|
||||||
putchar('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
strbuf_release(&err);
|
|
||||||
strbuf_release(&output);
|
|
||||||
ref_array_clear(&array);
|
|
||||||
ref_filter_clear(&filter);
|
ref_filter_clear(&filter);
|
||||||
ref_sorting_release(sorting);
|
ref_sorting_release(sorting);
|
||||||
strvec_clear(&vec);
|
strvec_clear(&vec);
|
||||||
|
|
|
@ -58,6 +58,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
|
||||||
struct transport *transport;
|
struct transport *transport;
|
||||||
const struct ref *ref;
|
const struct ref *ref;
|
||||||
struct ref_array ref_array;
|
struct ref_array ref_array;
|
||||||
|
struct ref_sorting *sorting;
|
||||||
struct string_list sorting_options = STRING_LIST_INIT_DUP;
|
struct string_list sorting_options = STRING_LIST_INIT_DUP;
|
||||||
|
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
|
@ -141,13 +142,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
|
||||||
item->symref = xstrdup_or_null(ref->symref);
|
item->symref = xstrdup_or_null(ref->symref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sorting_options.nr) {
|
sorting = ref_sorting_options(&sorting_options);
|
||||||
struct ref_sorting *sorting;
|
ref_array_sort(sorting, &ref_array);
|
||||||
|
|
||||||
sorting = ref_sorting_options(&sorting_options);
|
|
||||||
ref_array_sort(sorting, &ref_array);
|
|
||||||
ref_sorting_release(sorting);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < ref_array.nr; i++) {
|
for (i = 0; i < ref_array.nr; i++) {
|
||||||
const struct ref_array_item *ref = ref_array.items[i];
|
const struct ref_array_item *ref = ref_array.items[i];
|
||||||
|
@ -157,6 +153,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
|
||||||
status = 0; /* we found something */
|
status = 0; /* we found something */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref_sorting_release(sorting);
|
||||||
ref_array_clear(&ref_array);
|
ref_array_clear(&ref_array);
|
||||||
if (transport_disconnect(transport))
|
if (transport_disconnect(transport))
|
||||||
status = 1;
|
status = 1;
|
||||||
|
|
|
@ -44,18 +44,11 @@ static const char * const git_tag_usage[] = {
|
||||||
static unsigned int colopts;
|
static unsigned int colopts;
|
||||||
static int force_sign_annotate;
|
static int force_sign_annotate;
|
||||||
static int config_sign_tag = -1; /* unspecified */
|
static int config_sign_tag = -1; /* unspecified */
|
||||||
static int omit_empty = 0;
|
|
||||||
|
|
||||||
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
|
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
|
||||||
struct ref_format *format)
|
struct ref_format *format)
|
||||||
{
|
{
|
||||||
struct ref_array array;
|
|
||||||
struct strbuf output = STRBUF_INIT;
|
|
||||||
struct strbuf err = STRBUF_INIT;
|
|
||||||
char *to_free = NULL;
|
char *to_free = NULL;
|
||||||
int i;
|
|
||||||
|
|
||||||
memset(&array, 0, sizeof(array));
|
|
||||||
|
|
||||||
if (filter->lines == -1)
|
if (filter->lines == -1)
|
||||||
filter->lines = 0;
|
filter->lines = 0;
|
||||||
|
@ -73,23 +66,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
|
||||||
if (verify_ref_format(format))
|
if (verify_ref_format(format))
|
||||||
die(_("unable to parse format string"));
|
die(_("unable to parse format string"));
|
||||||
filter->with_commit_tag_algo = 1;
|
filter->with_commit_tag_algo = 1;
|
||||||
filter_refs(&array, filter, FILTER_REFS_TAGS);
|
filter_and_format_refs(filter, FILTER_REFS_TAGS, sorting, format);
|
||||||
filter_ahead_behind(the_repository, format, &array);
|
|
||||||
ref_array_sort(sorting, &array);
|
|
||||||
|
|
||||||
for (i = 0; i < array.nr; i++) {
|
|
||||||
strbuf_reset(&output);
|
|
||||||
strbuf_reset(&err);
|
|
||||||
if (format_ref_array_item(array.items[i], format, &output, &err))
|
|
||||||
die("%s", err.buf);
|
|
||||||
fwrite(output.buf, 1, output.len, stdout);
|
|
||||||
if (output.len || !omit_empty)
|
|
||||||
putchar('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
strbuf_release(&err);
|
|
||||||
strbuf_release(&output);
|
|
||||||
ref_array_clear(&array);
|
|
||||||
free(to_free);
|
free(to_free);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -481,7 +459,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
||||||
OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
|
OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
|
||||||
OPT_MERGED(&filter, N_("print only tags that are merged")),
|
OPT_MERGED(&filter, N_("print only tags that are merged")),
|
||||||
OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
|
OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
|
||||||
OPT_BOOL(0, "omit-empty", &omit_empty,
|
OPT_BOOL(0, "omit-empty", &format.array_opts.omit_empty,
|
||||||
N_("do not output a newline after empty formatted refs")),
|
N_("do not output a newline after empty formatted refs")),
|
||||||
OPT_REF_SORT(&sorting_options),
|
OPT_REF_SORT(&sorting_options),
|
||||||
{
|
{
|
||||||
|
@ -501,7 +479,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
||||||
|
|
||||||
setup_ref_filter_porcelain_msg();
|
setup_ref_filter_porcelain_msg();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to set sort keys from config. If config does not set any,
|
||||||
|
* fall back on default (refname) sorting.
|
||||||
|
*/
|
||||||
git_config(git_tag_config, &sorting_options);
|
git_config(git_tag_config, &sorting_options);
|
||||||
|
if (!sorting_options.nr)
|
||||||
|
string_list_append(&sorting_options, "refname");
|
||||||
|
|
||||||
memset(&opt, 0, sizeof(opt));
|
memset(&opt, 0, sizeof(opt));
|
||||||
filter.lines = -1;
|
filter.lines = -1;
|
||||||
|
|
282
ref-filter.c
282
ref-filter.c
|
@ -2508,17 +2508,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If it is a tag object, see if we use a value that derefs
|
* If it is a tag object, see if we use the peeled value. If we do,
|
||||||
* the object, and if we do grab the object it refers to.
|
* 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);
|
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;
|
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,
|
struct ref_array_item *ref_array_push(struct ref_array *array,
|
||||||
const char *refname,
|
const char *refname,
|
||||||
const struct object_id *oid)
|
const struct object_id *oid)
|
||||||
{
|
{
|
||||||
struct ref_array_item *ref = new_ref_array_item(refname, oid);
|
struct ref_array_item *ref = new_ref_array_item(refname, oid);
|
||||||
|
ref_array_append(array, ref);
|
||||||
ALLOC_GROW(array->items, array->nr + 1, array->alloc);
|
|
||||||
array->items[array->nr++] = ref;
|
|
||||||
|
|
||||||
return 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);
|
return ref_kind_from_refname(refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ref_filter_cbdata {
|
static struct ref_array_item *apply_ref_filter(const char *refname, const struct object_id *oid,
|
||||||
struct ref_array *array;
|
int flag, struct ref_filter *filter)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
struct ref_filter_cbdata *ref_cbdata = cb_data;
|
|
||||||
struct ref_filter *filter = ref_cbdata->filter;
|
|
||||||
struct ref_array_item *ref;
|
struct ref_array_item *ref;
|
||||||
struct commit *commit = NULL;
|
struct commit *commit = NULL;
|
||||||
unsigned int kind;
|
unsigned int kind;
|
||||||
|
|
||||||
if (flag & REF_BAD_NAME) {
|
if (flag & REF_BAD_NAME) {
|
||||||
warning(_("ignoring ref with broken name %s"), refname);
|
warning(_("ignoring ref with broken name %s"), refname);
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag & REF_ISBROKEN) {
|
if (flag & REF_ISBROKEN) {
|
||||||
warning(_("ignoring broken ref %s"), refname);
|
warning(_("ignoring broken ref %s"), refname);
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
|
/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
|
||||||
kind = filter_ref_kind(filter, refname);
|
kind = filter_ref_kind(filter, refname);
|
||||||
if (!(kind & filter->kind))
|
if (!(kind & filter->kind))
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
if (!filter_pattern_match(filter, refname))
|
if (!filter_pattern_match(filter, refname))
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
if (filter_exclude_match(filter, refname))
|
if (filter_exclude_match(filter, refname))
|
||||||
return 0;
|
return NULL;
|
||||||
|
|
||||||
if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))
|
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
|
* 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) {
|
filter->with_commit || filter->no_commit || filter->verbose) {
|
||||||
commit = lookup_commit_reference_gently(the_repository, oid, 1);
|
commit = lookup_commit_reference_gently(the_repository, oid, 1);
|
||||||
if (!commit)
|
if (!commit)
|
||||||
return 0;
|
return NULL;
|
||||||
/* We perform the filtering for the '--contains' option... */
|
/* We perform the filtering for the '--contains' option... */
|
||||||
if (filter->with_commit &&
|
if (filter->with_commit &&
|
||||||
!commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache))
|
!commit_contains(filter, commit, filter->with_commit, &filter->internal.contains_cache))
|
||||||
return 0;
|
return NULL;
|
||||||
/* ...or for the `--no-contains' option */
|
/* ...or for the `--no-contains' option */
|
||||||
if (filter->no_commit &&
|
if (filter->no_commit &&
|
||||||
commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache))
|
commit_contains(filter, commit, filter->no_commit, &filter->internal.no_contains_cache))
|
||||||
return 0;
|
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
|
* to do its job and the resulting list may yet to be pruned
|
||||||
* by maxcount logic.
|
* by maxcount logic.
|
||||||
*/
|
*/
|
||||||
ref = ref_array_push(ref_cbdata->array, refname, oid);
|
ref = new_ref_array_item(refname, oid);
|
||||||
ref->commit = commit;
|
ref->commit = commit;
|
||||||
ref->flag = flag;
|
ref->flag = flag;
|
||||||
ref->kind = kind;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2851,6 +2858,49 @@ static void free_array_item(struct ref_array_item *item)
|
||||||
free(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 */
|
/* Free all memory allocated for ref_array */
|
||||||
void ref_array_clear(struct ref_array *array)
|
void ref_array_clear(struct ref_array *array)
|
||||||
{
|
{
|
||||||
|
@ -2969,6 +3019,43 @@ void filter_ahead_behind(struct repository *r,
|
||||||
free(commits);
|
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
|
* 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
|
* 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.array = array;
|
||||||
ref_cbdata.filter = filter;
|
ref_cbdata.filter = filter;
|
||||||
|
|
||||||
filter->kind = type & FILTER_REFS_KIND_MASK;
|
|
||||||
|
|
||||||
save_commit_buffer_orig = save_commit_buffer;
|
save_commit_buffer_orig = save_commit_buffer;
|
||||||
save_commit_buffer = 0;
|
save_commit_buffer = 0;
|
||||||
|
|
||||||
init_contains_cache(&ref_cbdata.contains_cache);
|
ret = do_filter_refs(filter, type, filter_one, &ref_cbdata);
|
||||||
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);
|
|
||||||
|
|
||||||
/* Filters that need revision walking */
|
/* Filters that need revision walking */
|
||||||
reach_filter(array, &filter->reachable_from, INCLUDE_REACHED);
|
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;
|
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)
|
static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b)
|
||||||
{
|
{
|
||||||
if (!(a->kind ^ b->kind))
|
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)
|
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)
|
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;
|
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,
|
void pretty_print_ref(const char *name, const struct object_id *oid,
|
||||||
struct ref_format *format)
|
struct ref_format *format)
|
||||||
{
|
{
|
||||||
|
@ -3248,18 +3376,6 @@ static int parse_sorting_atom(const char *atom)
|
||||||
return res;
|
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)
|
static void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg)
|
||||||
{
|
{
|
||||||
struct ref_sorting *s;
|
struct ref_sorting *s;
|
||||||
|
@ -3283,9 +3399,7 @@ struct ref_sorting *ref_sorting_options(struct string_list *options)
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
struct ref_sorting *sorting = NULL, **tail = &sorting;
|
struct ref_sorting *sorting = NULL, **tail = &sorting;
|
||||||
|
|
||||||
if (!options->nr) {
|
if (options->nr) {
|
||||||
sorting = ref_default_sorting();
|
|
||||||
} else {
|
|
||||||
for_each_string_list_item(item, options)
|
for_each_string_list_item(item, options)
|
||||||
parse_ref_sorting(tail, item->string);
|
parse_ref_sorting(tail, item->string);
|
||||||
}
|
}
|
||||||
|
|
25
ref-filter.h
25
ref-filter.h
|
@ -7,6 +7,7 @@
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
#include "strvec.h"
|
#include "strvec.h"
|
||||||
|
#include "commit-reach.h"
|
||||||
|
|
||||||
/* Quoting styles */
|
/* Quoting styles */
|
||||||
#define QUOTE_NONE 0
|
#define QUOTE_NONE 0
|
||||||
|
@ -75,6 +76,11 @@ struct ref_filter {
|
||||||
lines;
|
lines;
|
||||||
int abbrev,
|
int abbrev,
|
||||||
verbose;
|
verbose;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct contains_cache contains_cache;
|
||||||
|
struct contains_cache no_contains_cache;
|
||||||
|
} internal;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ref_format {
|
struct ref_format {
|
||||||
|
@ -92,6 +98,11 @@ struct ref_format {
|
||||||
|
|
||||||
/* List of bases for ahead-behind counts. */
|
/* List of bases for ahead-behind counts. */
|
||||||
struct string_list bases;
|
struct string_list bases;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int max_count;
|
||||||
|
int omit_empty;
|
||||||
|
} array_opts;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define REF_FILTER_INIT { \
|
#define REF_FILTER_INIT { \
|
||||||
|
@ -126,6 +137,14 @@ struct ref_format {
|
||||||
* filtered refs in the ref_array structure.
|
* filtered refs in the ref_array structure.
|
||||||
*/
|
*/
|
||||||
int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type);
|
int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type);
|
||||||
|
/*
|
||||||
|
* Filter refs using the given ref_filter and type, sort the contents
|
||||||
|
* according to the given ref_sorting, format the filtered refs with the
|
||||||
|
* given ref_format, and print them to stdout.
|
||||||
|
*/
|
||||||
|
void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
|
||||||
|
struct ref_sorting *sorting,
|
||||||
|
struct ref_format *format);
|
||||||
/* Clear all memory allocated to ref_array */
|
/* Clear all memory allocated to ref_array */
|
||||||
void ref_array_clear(struct ref_array *array);
|
void ref_array_clear(struct ref_array *array);
|
||||||
/* Used to verify if the given format is correct and to parse out the used atoms */
|
/* Used to verify if the given format is correct and to parse out the used atoms */
|
||||||
|
@ -150,6 +169,12 @@ char *get_head_description(void);
|
||||||
/* Set up translated strings in the output. */
|
/* Set up translated strings in the output. */
|
||||||
void setup_ref_filter_porcelain_msg(void);
|
void setup_ref_filter_porcelain_msg(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print up to maxcount ref_array elements to stdout using the given
|
||||||
|
* ref_format.
|
||||||
|
*/
|
||||||
|
void print_formatted_ref_array(struct ref_array *array, struct ref_format *format);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Print a single ref, outside of any ref-filter. Note that the
|
* Print a single ref, outside of any ref-filter. Note that the
|
||||||
* name must be a fully qualified refname.
|
* name must be a fully qualified refname.
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='performance of for-each-ref'
|
||||||
|
. ./perf-lib.sh
|
||||||
|
|
||||||
|
test_perf_fresh_repo
|
||||||
|
|
||||||
|
ref_count_per_type=10000
|
||||||
|
test_iteration_count=10
|
||||||
|
|
||||||
|
test_expect_success "setup" '
|
||||||
|
test_commit_bulk $(( 1 + $ref_count_per_type )) &&
|
||||||
|
|
||||||
|
# Create refs
|
||||||
|
test_seq $ref_count_per_type |
|
||||||
|
sed "s,.*,update refs/heads/branch_& HEAD~&\nupdate refs/custom/special_& HEAD~&," |
|
||||||
|
git update-ref --stdin &&
|
||||||
|
|
||||||
|
# Create annotated tags
|
||||||
|
for i in $(test_seq $ref_count_per_type)
|
||||||
|
do
|
||||||
|
# Base tags
|
||||||
|
echo "tag tag_$i" &&
|
||||||
|
echo "mark :$i" &&
|
||||||
|
echo "from HEAD~$i" &&
|
||||||
|
printf "tagger %s <%s> %s\n" \
|
||||||
|
"$GIT_COMMITTER_NAME" \
|
||||||
|
"$GIT_COMMITTER_EMAIL" \
|
||||||
|
"$GIT_COMMITTER_DATE" &&
|
||||||
|
echo "data <<EOF" &&
|
||||||
|
echo "tag $i" &&
|
||||||
|
echo "EOF" &&
|
||||||
|
|
||||||
|
# Nested tags
|
||||||
|
echo "tag nested_$i" &&
|
||||||
|
echo "from :$i" &&
|
||||||
|
printf "tagger %s <%s> %s\n" \
|
||||||
|
"$GIT_COMMITTER_NAME" \
|
||||||
|
"$GIT_COMMITTER_EMAIL" \
|
||||||
|
"$GIT_COMMITTER_DATE" &&
|
||||||
|
echo "data <<EOF" &&
|
||||||
|
echo "nested tag $i" &&
|
||||||
|
echo "EOF" || return 1
|
||||||
|
done | git fast-import
|
||||||
|
'
|
||||||
|
|
||||||
|
test_for_each_ref () {
|
||||||
|
title="for-each-ref"
|
||||||
|
if test $# -gt 0; then
|
||||||
|
title="$title ($1)"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
args="$@"
|
||||||
|
|
||||||
|
test_perf "$title" "
|
||||||
|
for i in \$(test_seq $test_iteration_count); do
|
||||||
|
git for-each-ref $args >/dev/null
|
||||||
|
done
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_tests () {
|
||||||
|
test_for_each_ref "$1"
|
||||||
|
test_for_each_ref "$1, no sort" --no-sort
|
||||||
|
test_for_each_ref "$1, --count=1" --count=1
|
||||||
|
test_for_each_ref "$1, --count=1, no sort" --no-sort --count=1
|
||||||
|
test_for_each_ref "$1, tags" refs/tags/
|
||||||
|
test_for_each_ref "$1, tags, no sort" --no-sort refs/tags/
|
||||||
|
test_for_each_ref "$1, tags, dereferenced" '--format="%(refname) %(objectname) %(*objectname)"' refs/tags/
|
||||||
|
test_for_each_ref "$1, tags, dereferenced, no sort" --no-sort '--format="%(refname) %(objectname) %(*objectname)"' refs/tags/
|
||||||
|
|
||||||
|
test_perf "for-each-ref ($1, tags) + cat-file --batch-check (dereferenced)" "
|
||||||
|
for i in \$(test_seq $test_iteration_count); do
|
||||||
|
git for-each-ref --format='%(objectname)^{} %(refname) %(objectname)' refs/tags/ | \
|
||||||
|
git cat-file --batch-check='%(objectname) %(rest)' >/dev/null
|
||||||
|
done
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_tests "loose"
|
||||||
|
|
||||||
|
test_expect_success 'pack refs' '
|
||||||
|
git pack-refs --all
|
||||||
|
'
|
||||||
|
run_tests "packed"
|
||||||
|
|
||||||
|
test_done
|
|
@ -1576,9 +1576,10 @@ test_expect_success 'tracking with unexpected .fetch refspec' '
|
||||||
|
|
||||||
test_expect_success 'configured committerdate sort' '
|
test_expect_success 'configured committerdate sort' '
|
||||||
git init -b main sort &&
|
git init -b main sort &&
|
||||||
|
test_config -C sort branch.sort "committerdate" &&
|
||||||
|
|
||||||
(
|
(
|
||||||
cd sort &&
|
cd sort &&
|
||||||
git config branch.sort committerdate &&
|
|
||||||
test_commit initial &&
|
test_commit initial &&
|
||||||
git checkout -b a &&
|
git checkout -b a &&
|
||||||
test_commit a &&
|
test_commit a &&
|
||||||
|
@ -1598,9 +1599,10 @@ test_expect_success 'configured committerdate sort' '
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'option override configured sort' '
|
test_expect_success 'option override configured sort' '
|
||||||
|
test_config -C sort branch.sort "committerdate" &&
|
||||||
|
|
||||||
(
|
(
|
||||||
cd sort &&
|
cd sort &&
|
||||||
git config branch.sort committerdate &&
|
|
||||||
git branch --sort=refname >actual &&
|
git branch --sort=refname >actual &&
|
||||||
cat >expect <<-\EOF &&
|
cat >expect <<-\EOF &&
|
||||||
a
|
a
|
||||||
|
@ -1612,10 +1614,70 @@ test_expect_success 'option override configured sort' '
|
||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'invalid sort parameter in configuration' '
|
test_expect_success '--no-sort cancels config sort keys' '
|
||||||
|
test_config -C sort branch.sort "-refname" &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd sort &&
|
||||||
|
|
||||||
|
# objecttype is identical for all of them, so sort falls back on
|
||||||
|
# default (ascending refname)
|
||||||
|
git branch \
|
||||||
|
--no-sort \
|
||||||
|
--sort="objecttype" >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
a
|
||||||
|
* b
|
||||||
|
c
|
||||||
|
main
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--no-sort cancels command line sort keys' '
|
||||||
|
(
|
||||||
|
cd sort &&
|
||||||
|
|
||||||
|
# objecttype is identical for all of them, so sort falls back on
|
||||||
|
# default (ascending refname)
|
||||||
|
git branch \
|
||||||
|
--sort="-refname" \
|
||||||
|
--no-sort \
|
||||||
|
--sort="objecttype" >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
a
|
||||||
|
* b
|
||||||
|
c
|
||||||
|
main
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--no-sort without subsequent --sort prints expected branches' '
|
||||||
|
(
|
||||||
|
cd sort &&
|
||||||
|
|
||||||
|
# Sort the results with `sort` for a consistent comparison
|
||||||
|
# against expected
|
||||||
|
git branch --no-sort | sort >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
a
|
||||||
|
c
|
||||||
|
main
|
||||||
|
* b
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'invalid sort parameter in configuration' '
|
||||||
|
test_config -C sort branch.sort "v:notvalid" &&
|
||||||
|
|
||||||
(
|
(
|
||||||
cd sort &&
|
cd sort &&
|
||||||
git config branch.sort "v:notvalid" &&
|
|
||||||
|
|
||||||
# this works in the "listing" mode, so bad sort key
|
# this works in the "listing" mode, so bad sort key
|
||||||
# is a dying offence.
|
# is a dying offence.
|
||||||
|
|
|
@ -1335,6 +1335,27 @@ test_expect_success '--no-sort cancels the previous sort keys' '
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--no-sort without subsequent --sort prints expected refs' '
|
||||||
|
cat >expected <<-\EOF &&
|
||||||
|
refs/tags/multi-ref1-100000-user1
|
||||||
|
refs/tags/multi-ref1-100000-user2
|
||||||
|
refs/tags/multi-ref1-200000-user1
|
||||||
|
refs/tags/multi-ref1-200000-user2
|
||||||
|
refs/tags/multi-ref2-100000-user1
|
||||||
|
refs/tags/multi-ref2-100000-user2
|
||||||
|
refs/tags/multi-ref2-200000-user1
|
||||||
|
refs/tags/multi-ref2-200000-user2
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Sort the results with `sort` for a consistent comparison against
|
||||||
|
# expected
|
||||||
|
git for-each-ref \
|
||||||
|
--format="%(refname)" \
|
||||||
|
--no-sort \
|
||||||
|
"refs/tags/multi-*" | sort >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
|
test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
|
||||||
test_when_finished "git checkout main" &&
|
test_when_finished "git checkout main" &&
|
||||||
git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
|
git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
|
||||||
|
@ -1818,6 +1839,28 @@ test_expect_success 'git for-each-ref with non-existing refs' '
|
||||||
test_must_be_empty actual
|
test_must_be_empty actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git for-each-ref with nested tags' '
|
||||||
|
git tag -am "Normal tag" nested/base HEAD &&
|
||||||
|
git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
|
||||||
|
git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
|
||||||
|
|
||||||
|
head_oid="$(git rev-parse HEAD)" &&
|
||||||
|
base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
|
||||||
|
nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
|
||||||
|
nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/tags/nested/base $base_tag_oid tag $head_oid commit
|
||||||
|
refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
|
||||||
|
refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git for-each-ref \
|
||||||
|
--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
|
||||||
|
refs/tags/nested/ >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
|
GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
|
||||||
TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
|
TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,8 @@ test_expect_success 'check signed tags with --points-at' '
|
||||||
sed -e "s/Z$//" >expect <<-\EOF &&
|
sed -e "s/Z$//" >expect <<-\EOF &&
|
||||||
refs/heads/side Z
|
refs/heads/side Z
|
||||||
refs/tags/annotated-tag four
|
refs/tags/annotated-tag four
|
||||||
refs/tags/doubly-annotated-tag An annotated tag
|
refs/tags/doubly-annotated-tag four
|
||||||
refs/tags/doubly-signed-tag A signed tag
|
refs/tags/doubly-signed-tag four
|
||||||
refs/tags/four Z
|
refs/tags/four Z
|
||||||
refs/tags/signed-tag four
|
refs/tags/signed-tag four
|
||||||
EOF
|
EOF
|
||||||
|
|
|
@ -1862,6 +1862,51 @@ test_expect_success 'option override configured sort' '
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--no-sort cancels config sort keys' '
|
||||||
|
test_config tag.sort "-refname" &&
|
||||||
|
|
||||||
|
# objecttype is identical for all of them, so sort falls back on
|
||||||
|
# default (ascending refname)
|
||||||
|
git tag -l \
|
||||||
|
--no-sort \
|
||||||
|
--sort="objecttype" \
|
||||||
|
"foo*" >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
foo1.10
|
||||||
|
foo1.3
|
||||||
|
foo1.6
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--no-sort cancels command line sort keys' '
|
||||||
|
# objecttype is identical for all of them, so sort falls back on
|
||||||
|
# default (ascending refname)
|
||||||
|
git tag -l \
|
||||||
|
--sort="-refname" \
|
||||||
|
--no-sort \
|
||||||
|
--sort="objecttype" \
|
||||||
|
"foo*" >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
foo1.10
|
||||||
|
foo1.3
|
||||||
|
foo1.6
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--no-sort without subsequent --sort prints expected tags' '
|
||||||
|
# Sort the results with `sort` for a consistent comparison against
|
||||||
|
# expected
|
||||||
|
git tag -l --no-sort "foo*" | sort >actual &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
foo1.10
|
||||||
|
foo1.3
|
||||||
|
foo1.6
|
||||||
|
EOF
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'invalid sort parameter on command line' '
|
test_expect_success 'invalid sort parameter on command line' '
|
||||||
test_must_fail git tag -l --sort=notvalid "foo*" >actual
|
test_must_fail git tag -l --sort=notvalid "foo*" >actual
|
||||||
'
|
'
|
||||||
|
|
Loading…
Reference in New Issue