mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
Merge branch 'ds/for-each-ref-is-base'
'git for-each-ref' learned a new "--format" atom to find the branch that the history leading to a given commit "%(is-base:<commit>)" is likely based on. * ds/for-each-ref-is-base: p1500: add is-base performance tests for-each-ref: add 'is-base' token commit: add gentle reference lookup method commit-reach: add get_branch_base_for_tip
This commit is contained in:
@@ -264,6 +264,48 @@ ahead-behind:<committish>::
|
|||||||
commits ahead and behind, respectively, when comparing the output
|
commits ahead and behind, respectively, when comparing the output
|
||||||
ref to the `<committish>` specified in the format.
|
ref to the `<committish>` specified in the format.
|
||||||
|
|
||||||
|
is-base:<committish>::
|
||||||
|
In at most one row, `(<committish>)` will appear to indicate the ref
|
||||||
|
that is most likely the ref used as a starting point for the branch
|
||||||
|
that produced `<committish>`. This choice is made using a heuristic:
|
||||||
|
choose the ref that minimizes the number of commits in the
|
||||||
|
first-parent history of `<committish>` and not in the first-parent
|
||||||
|
history of the ref.
|
||||||
|
+
|
||||||
|
For example, consider the following figure of first-parent histories of
|
||||||
|
several refs:
|
||||||
|
+
|
||||||
|
----
|
||||||
|
*--*--*--*--*--* refs/heads/A
|
||||||
|
\
|
||||||
|
\
|
||||||
|
*--*--*--* refs/heads/B
|
||||||
|
\ \
|
||||||
|
\ \
|
||||||
|
* * refs/heads/C
|
||||||
|
\
|
||||||
|
\
|
||||||
|
*--* refs/heads/D
|
||||||
|
----
|
||||||
|
+
|
||||||
|
Here, if `A`, `B`, and `C` are the filtered references, and the format
|
||||||
|
string is `%(refname):%(is-base:D)`, then the output would be
|
||||||
|
+
|
||||||
|
----
|
||||||
|
refs/heads/A:
|
||||||
|
refs/heads/B:(D)
|
||||||
|
refs/heads/C:
|
||||||
|
----
|
||||||
|
+
|
||||||
|
This is because the first-parent history of `D` has its earliest
|
||||||
|
intersection with the first-parent histories of the filtered refs at a
|
||||||
|
common first-parent ancestor of `B` and `C` and ties are broken by the
|
||||||
|
earliest ref in the sorted order.
|
||||||
|
+
|
||||||
|
Note that this token will not appear if the first-parent history of
|
||||||
|
`<committish>` does not intersect the first-parent histories of the
|
||||||
|
filtered refs.
|
||||||
|
|
||||||
describe[:options]::
|
describe[:options]::
|
||||||
A human-readable name, like linkgit:git-describe[1];
|
A human-readable name, like linkgit:git-describe[1];
|
||||||
empty string for undescribable commits. The `describe` string may
|
empty string for undescribable commits. The `describe` string may
|
||||||
|
|||||||
126
commit-reach.c
126
commit-reach.c
@@ -1229,3 +1229,129 @@ done:
|
|||||||
repo_clear_commit_marks(r, SEEN);
|
repo_clear_commit_marks(r, SEEN);
|
||||||
free_commit_list(stack);
|
free_commit_list(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This slab initializes integers to zero, so use "-1" for "tip is best" and
|
||||||
|
* "i + 1" for "bases[i] is best".
|
||||||
|
*/
|
||||||
|
define_commit_slab(best_branch_base, int);
|
||||||
|
static struct best_branch_base best_branch_base;
|
||||||
|
#define get_best(c) (*best_branch_base_at(&best_branch_base, (c)))
|
||||||
|
#define set_best(c,v) (*best_branch_base_at(&best_branch_base, (c)) = (v))
|
||||||
|
|
||||||
|
int get_branch_base_for_tip(struct repository *r,
|
||||||
|
struct commit *tip,
|
||||||
|
struct commit **bases,
|
||||||
|
size_t bases_nr)
|
||||||
|
{
|
||||||
|
int best_index = -1;
|
||||||
|
struct commit *branch_point = NULL;
|
||||||
|
struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
|
||||||
|
int found_missing_gen = 0;
|
||||||
|
|
||||||
|
if (!bases_nr)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
repo_parse_commit(r, tip);
|
||||||
|
if (commit_graph_generation(tip) == GENERATION_NUMBER_INFINITY)
|
||||||
|
found_missing_gen = 1;
|
||||||
|
|
||||||
|
/* Check for missing generation numbers. */
|
||||||
|
for (size_t i = 0; i < bases_nr; i++) {
|
||||||
|
struct commit *c = bases[i];
|
||||||
|
repo_parse_commit(r, c);
|
||||||
|
if (commit_graph_generation(c) == GENERATION_NUMBER_INFINITY)
|
||||||
|
found_missing_gen = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_missing_gen) {
|
||||||
|
struct commit **commits;
|
||||||
|
size_t commits_nr = bases_nr + 1;
|
||||||
|
|
||||||
|
CALLOC_ARRAY(commits, commits_nr);
|
||||||
|
COPY_ARRAY(commits, bases, bases_nr);
|
||||||
|
commits[bases_nr] = tip;
|
||||||
|
ensure_generations_valid(r, commits, commits_nr);
|
||||||
|
free(commits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize queue and slab now that generations are guaranteed. */
|
||||||
|
init_best_branch_base(&best_branch_base);
|
||||||
|
set_best(tip, -1);
|
||||||
|
prio_queue_put(&queue, tip);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < bases_nr; i++) {
|
||||||
|
struct commit *c = bases[i];
|
||||||
|
int best = get_best(c);
|
||||||
|
|
||||||
|
/* Has this already been marked as best by another commit? */
|
||||||
|
if (best) {
|
||||||
|
if (best == -1) {
|
||||||
|
/* We agree at this position. Stop now. */
|
||||||
|
best_index = i + 1;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_best(c, i + 1);
|
||||||
|
prio_queue_put(&queue, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (queue.nr) {
|
||||||
|
struct commit *c = prio_queue_get(&queue);
|
||||||
|
int best_for_c = get_best(c);
|
||||||
|
int best_for_p, positive;
|
||||||
|
struct commit *parent;
|
||||||
|
|
||||||
|
/* Have we reached a known branch point? It's optimal. */
|
||||||
|
if (c == branch_point)
|
||||||
|
break;
|
||||||
|
|
||||||
|
repo_parse_commit(r, c);
|
||||||
|
if (!c->parents)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
parent = c->parents->item;
|
||||||
|
repo_parse_commit(r, parent);
|
||||||
|
best_for_p = get_best(parent);
|
||||||
|
|
||||||
|
if (!best_for_p) {
|
||||||
|
/* 'parent' is new, so pass along best_for_c. */
|
||||||
|
set_best(parent, best_for_c);
|
||||||
|
prio_queue_put(&queue, parent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_for_p > 0 && best_for_c > 0) {
|
||||||
|
/* Collision among bases. Minimize. */
|
||||||
|
if (best_for_c < best_for_p)
|
||||||
|
set_best(parent, best_for_c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, we have reached a commit that is reachable
|
||||||
|
* from the tip, either from 'c' or from an earlier commit to
|
||||||
|
* have 'parent' as its first parent.
|
||||||
|
*
|
||||||
|
* Update 'best_index' to match the minimum of all base indices
|
||||||
|
* to reach 'parent'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Exactly one is positive due to initial conditions. */
|
||||||
|
positive = (best_for_c < 0) ? best_for_p : best_for_c;
|
||||||
|
|
||||||
|
if (best_index < 0 || positive < best_index)
|
||||||
|
best_index = positive;
|
||||||
|
|
||||||
|
/* No matter what, track that the parent is reachable from tip. */
|
||||||
|
set_best(parent, -1);
|
||||||
|
branch_point = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
clear_best_branch_base(&best_branch_base);
|
||||||
|
clear_prio_queue(&queue);
|
||||||
|
return best_index > 0 ? best_index - 1 : -1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,4 +139,21 @@ void tips_reachable_from_bases(struct repository *r,
|
|||||||
struct commit **tips, size_t tips_nr,
|
struct commit **tips, size_t tips_nr,
|
||||||
int mark);
|
int mark);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a 'tip' commit and a list potential 'bases', return the index 'i' that
|
||||||
|
* minimizes the number of commits in the first-parent history of 'tip' and not
|
||||||
|
* in the first-parent history of 'bases[i]'.
|
||||||
|
*
|
||||||
|
* Among a list of long-lived branches that are updated only by merges (with the
|
||||||
|
* first parent being the previous position of the branch), this would inform
|
||||||
|
* which branch was used to create the tip reference.
|
||||||
|
*
|
||||||
|
* Returns -1 if no common point is found in first-parent histories, which is
|
||||||
|
* rare, but possible with multiple root commits.
|
||||||
|
*/
|
||||||
|
int get_branch_base_for_tip(struct repository *r,
|
||||||
|
struct commit *tip,
|
||||||
|
struct commit **bases,
|
||||||
|
size_t bases_nr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
8
commit.c
8
commit.c
@@ -84,13 +84,19 @@ struct commit *lookup_commit(struct repository *r, const struct object_id *oid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct commit *lookup_commit_reference_by_name(const char *name)
|
struct commit *lookup_commit_reference_by_name(const char *name)
|
||||||
|
{
|
||||||
|
return lookup_commit_reference_by_name_gently(name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct commit *lookup_commit_reference_by_name_gently(const char *name,
|
||||||
|
int quiet)
|
||||||
{
|
{
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
|
|
||||||
if (repo_get_oid_committish(the_repository, name, &oid))
|
if (repo_get_oid_committish(the_repository, name, &oid))
|
||||||
return NULL;
|
return NULL;
|
||||||
commit = lookup_commit_reference(the_repository, &oid);
|
commit = lookup_commit_reference_gently(the_repository, &oid, quiet);
|
||||||
if (repo_parse_commit(the_repository, commit))
|
if (repo_parse_commit(the_repository, commit))
|
||||||
return NULL;
|
return NULL;
|
||||||
return commit;
|
return commit;
|
||||||
|
|||||||
2
commit.h
2
commit.h
@@ -81,6 +81,8 @@ struct commit *lookup_commit_reference_gently(struct repository *r,
|
|||||||
const struct object_id *oid,
|
const struct object_id *oid,
|
||||||
int quiet);
|
int quiet);
|
||||||
struct commit *lookup_commit_reference_by_name(const char *name);
|
struct commit *lookup_commit_reference_by_name(const char *name);
|
||||||
|
struct commit *lookup_commit_reference_by_name_gently(const char *name,
|
||||||
|
int quiet);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look up object named by "oid", dereference tag as necessary,
|
* Look up object named by "oid", dereference tag as necessary,
|
||||||
|
|||||||
77
ref-filter.c
77
ref-filter.c
@@ -169,6 +169,7 @@ enum atom_type {
|
|||||||
ATOM_ELSE,
|
ATOM_ELSE,
|
||||||
ATOM_REST,
|
ATOM_REST,
|
||||||
ATOM_AHEADBEHIND,
|
ATOM_AHEADBEHIND,
|
||||||
|
ATOM_ISBASE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -890,6 +891,23 @@ static int ahead_behind_atom_parser(struct ref_format *format,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int is_base_atom_parser(struct ref_format *format,
|
||||||
|
struct used_atom *atom UNUSED,
|
||||||
|
const char *arg, struct strbuf *err)
|
||||||
|
{
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
|
if (!arg)
|
||||||
|
return strbuf_addf_ret(err, -1, _("expected format: %%(is-base:<committish>)"));
|
||||||
|
|
||||||
|
item = string_list_append(&format->is_base_tips, arg);
|
||||||
|
item->util = lookup_commit_reference_by_name(arg);
|
||||||
|
if (!item->util)
|
||||||
|
die("failed to find '%s'", arg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int head_atom_parser(struct ref_format *format UNUSED,
|
static int head_atom_parser(struct ref_format *format UNUSED,
|
||||||
struct used_atom *atom,
|
struct used_atom *atom,
|
||||||
const char *arg, struct strbuf *err)
|
const char *arg, struct strbuf *err)
|
||||||
@@ -955,6 +973,7 @@ static struct {
|
|||||||
[ATOM_ELSE] = { "else", SOURCE_NONE },
|
[ATOM_ELSE] = { "else", SOURCE_NONE },
|
||||||
[ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser },
|
[ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser },
|
||||||
[ATOM_AHEADBEHIND] = { "ahead-behind", SOURCE_OTHER, FIELD_STR, ahead_behind_atom_parser },
|
[ATOM_AHEADBEHIND] = { "ahead-behind", SOURCE_OTHER, FIELD_STR, ahead_behind_atom_parser },
|
||||||
|
[ATOM_ISBASE] = { "is-base", SOURCE_OTHER, FIELD_STR, is_base_atom_parser },
|
||||||
/*
|
/*
|
||||||
* Please update $__git_ref_fieldlist in git-completion.bash
|
* Please update $__git_ref_fieldlist in git-completion.bash
|
||||||
* when you add new atoms
|
* when you add new atoms
|
||||||
@@ -2340,6 +2359,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
|
|||||||
int i;
|
int i;
|
||||||
struct object_info empty = OBJECT_INFO_INIT;
|
struct object_info empty = OBJECT_INFO_INIT;
|
||||||
int ahead_behind_atoms = 0;
|
int ahead_behind_atoms = 0;
|
||||||
|
int is_base_atoms = 0;
|
||||||
|
|
||||||
CALLOC_ARRAY(ref->value, used_atom_cnt);
|
CALLOC_ARRAY(ref->value, used_atom_cnt);
|
||||||
|
|
||||||
@@ -2489,6 +2509,15 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
|
|||||||
v->s = xstrdup("");
|
v->s = xstrdup("");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
} else if (atom_type == ATOM_ISBASE) {
|
||||||
|
if (ref->is_base && ref->is_base[is_base_atoms]) {
|
||||||
|
v->s = xstrfmt("(%s)", ref->is_base[is_base_atoms]);
|
||||||
|
free(ref->is_base[is_base_atoms]);
|
||||||
|
} else {
|
||||||
|
v->s = xstrdup("");
|
||||||
|
}
|
||||||
|
is_base_atoms++;
|
||||||
|
continue;
|
||||||
} else
|
} else
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -2895,6 +2924,7 @@ static void free_array_item(struct ref_array_item *item)
|
|||||||
free(item->value);
|
free(item->value);
|
||||||
}
|
}
|
||||||
free(item->counts);
|
free(item->counts);
|
||||||
|
free(item->is_base);
|
||||||
free(item);
|
free(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3059,6 +3089,49 @@ void filter_ahead_behind(struct repository *r,
|
|||||||
free(commits);
|
free(commits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void filter_is_base(struct repository *r,
|
||||||
|
struct ref_format *format,
|
||||||
|
struct ref_array *array)
|
||||||
|
{
|
||||||
|
struct commit **bases;
|
||||||
|
size_t bases_nr = 0;
|
||||||
|
struct ref_array_item **back_index;
|
||||||
|
|
||||||
|
if (!format->is_base_tips.nr || !array->nr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CALLOC_ARRAY(back_index, array->nr);
|
||||||
|
CALLOC_ARRAY(bases, array->nr);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < array->nr; i++) {
|
||||||
|
const char *name = array->items[i]->refname;
|
||||||
|
struct commit *c = lookup_commit_reference_by_name_gently(name, 1);
|
||||||
|
|
||||||
|
CALLOC_ARRAY(array->items[i]->is_base, format->is_base_tips.nr);
|
||||||
|
|
||||||
|
if (!c)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
back_index[bases_nr] = array->items[i];
|
||||||
|
bases[bases_nr] = c;
|
||||||
|
bases_nr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < format->is_base_tips.nr; i++) {
|
||||||
|
struct commit *tip = format->is_base_tips.items[i].util;
|
||||||
|
int base_index = get_branch_base_for_tip(r, tip, bases, bases_nr);
|
||||||
|
|
||||||
|
if (base_index < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Store the string for use in output later. */
|
||||||
|
back_index[base_index]->is_base[i] = xstrdup(format->is_base_tips.items[i].string);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(back_index);
|
||||||
|
free(bases);
|
||||||
|
}
|
||||||
|
|
||||||
static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
|
static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
@@ -3152,7 +3225,8 @@ static inline int can_do_iterative_format(struct ref_filter *filter,
|
|||||||
return !(filter->reachable_from ||
|
return !(filter->reachable_from ||
|
||||||
filter->unreachable_from ||
|
filter->unreachable_from ||
|
||||||
sorting ||
|
sorting ||
|
||||||
format->bases.nr);
|
format->bases.nr ||
|
||||||
|
format->is_base_tips.nr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
|
void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
|
||||||
@@ -3176,6 +3250,7 @@ void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
|
|||||||
struct ref_array array = { 0 };
|
struct ref_array array = { 0 };
|
||||||
filter_refs(&array, filter, type);
|
filter_refs(&array, filter, type);
|
||||||
filter_ahead_behind(the_repository, format, &array);
|
filter_ahead_behind(the_repository, format, &array);
|
||||||
|
filter_is_base(the_repository, format, &array);
|
||||||
ref_array_sort(sorting, &array);
|
ref_array_sort(sorting, &array);
|
||||||
print_formatted_ref_array(&array, format);
|
print_formatted_ref_array(&array, format);
|
||||||
ref_array_clear(&array);
|
ref_array_clear(&array);
|
||||||
|
|||||||
15
ref-filter.h
15
ref-filter.h
@@ -48,6 +48,7 @@ struct ref_array_item {
|
|||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
struct atom_value *value;
|
struct atom_value *value;
|
||||||
struct ahead_behind_count **counts;
|
struct ahead_behind_count **counts;
|
||||||
|
char **is_base;
|
||||||
|
|
||||||
char refname[FLEX_ARRAY];
|
char refname[FLEX_ARRAY];
|
||||||
};
|
};
|
||||||
@@ -101,6 +102,9 @@ 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;
|
||||||
|
|
||||||
|
/* List of bases for is-base indicators. */
|
||||||
|
struct string_list is_base_tips;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int max_count;
|
int max_count;
|
||||||
int omit_empty;
|
int omit_empty;
|
||||||
@@ -114,6 +118,7 @@ struct ref_format {
|
|||||||
#define REF_FORMAT_INIT { \
|
#define REF_FORMAT_INIT { \
|
||||||
.use_color = -1, \
|
.use_color = -1, \
|
||||||
.bases = STRING_LIST_INIT_DUP, \
|
.bases = STRING_LIST_INIT_DUP, \
|
||||||
|
.is_base_tips = STRING_LIST_INIT_DUP, \
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Macros for checking --merged and --no-merged options */
|
/* Macros for checking --merged and --no-merged options */
|
||||||
@@ -203,6 +208,16 @@ void filter_ahead_behind(struct repository *r,
|
|||||||
struct ref_format *format,
|
struct ref_format *format,
|
||||||
struct ref_array *array);
|
struct ref_array *array);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the provided format includes is-base atoms, then compute the base checks
|
||||||
|
* for those tips against all refs.
|
||||||
|
*
|
||||||
|
* If this is not called, then any is-base atoms will be blank.
|
||||||
|
*/
|
||||||
|
void filter_is_base(struct repository *r,
|
||||||
|
struct ref_format *format,
|
||||||
|
struct ref_array *array);
|
||||||
|
|
||||||
void ref_filter_init(struct ref_filter *filter);
|
void ref_filter_init(struct ref_filter *filter);
|
||||||
void ref_filter_clear(struct ref_filter *filter);
|
void ref_filter_clear(struct ref_filter *filter);
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ int cmd__reach(int ac, const char **av)
|
|||||||
repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));
|
repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0));
|
||||||
else if (!strcmp(av[1], "is_descendant_of"))
|
else if (!strcmp(av[1], "is_descendant_of"))
|
||||||
printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X));
|
printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X));
|
||||||
|
else if (!strcmp(av[1], "get_branch_base_for_tip"))
|
||||||
|
printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_array, X_nr));
|
||||||
else if (!strcmp(av[1], "get_merge_bases_many")) {
|
else if (!strcmp(av[1], "get_merge_bases_many")) {
|
||||||
struct commit_list *list = NULL;
|
struct commit_list *list = NULL;
|
||||||
if (repo_get_merge_bases_many(the_repository,
|
if (repo_get_merge_bases_many(the_repository,
|
||||||
|
|||||||
@@ -20,6 +20,21 @@ test_expect_success 'setup' '
|
|||||||
echo tag-$ref ||
|
echo tag-$ref ||
|
||||||
return 1
|
return 1
|
||||||
done >tags &&
|
done >tags &&
|
||||||
|
|
||||||
|
echo "A:HEAD" >test-tool-refs &&
|
||||||
|
for line in $(cat refs)
|
||||||
|
do
|
||||||
|
echo "X:$line" >>test-tool-refs || return 1
|
||||||
|
done &&
|
||||||
|
echo "A:HEAD" >test-tool-tags &&
|
||||||
|
for line in $(cat tags)
|
||||||
|
do
|
||||||
|
echo "X:$line" >>test-tool-tags || return 1
|
||||||
|
done &&
|
||||||
|
|
||||||
|
commit=$(git commit-tree $(git rev-parse HEAD^{tree})) &&
|
||||||
|
git update-ref refs/heads/disjoint-base $commit &&
|
||||||
|
|
||||||
git commit-graph write --reachable
|
git commit-graph write --reachable
|
||||||
'
|
'
|
||||||
|
|
||||||
@@ -47,4 +62,20 @@ test_perf 'contains: git tag --merged' '
|
|||||||
xargs git tag --merged=HEAD <tags
|
xargs git tag --merged=HEAD <tags
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_perf 'is-base check: test-tool reach (refs)' '
|
||||||
|
test-tool reach get_branch_base_for_tip <test-tool-refs
|
||||||
|
'
|
||||||
|
|
||||||
|
test_perf 'is-base check: test-tool reach (tags)' '
|
||||||
|
test-tool reach get_branch_base_for_tip <test-tool-tags
|
||||||
|
'
|
||||||
|
|
||||||
|
test_perf 'is-base check: git for-each-ref' '
|
||||||
|
git for-each-ref --format="%(is-base:HEAD)" --stdin <refs
|
||||||
|
'
|
||||||
|
|
||||||
|
test_perf 'is-base check: git for-each-ref (disjoint-base)' '
|
||||||
|
git for-each-ref --format="%(is-base:refs/heads/disjoint-base)" --stdin <refs
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
@@ -1907,6 +1907,15 @@ test_expect_success 'git for-each-ref with nested tags' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'is-base atom with non-commits' '
|
||||||
|
git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
|
||||||
|
grep "(HEAD) refs/heads/main" out &&
|
||||||
|
|
||||||
|
test_line_count = 2 err &&
|
||||||
|
grep "error: object .* is a commit, not a blob" err &&
|
||||||
|
grep "error: bad tag pointer to" err
|
||||||
|
'
|
||||||
|
|
||||||
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)"
|
||||||
|
|
||||||
|
|||||||
@@ -612,4 +612,125 @@ test_expect_success 'for-each-ref merged:none' '
|
|||||||
--format="%(refname)" --stdin
|
--format="%(refname)" --stdin
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# For get_branch_base_for_tip, we only care about
|
||||||
|
# first-parent history. Here is the test graph with
|
||||||
|
# second parents removed:
|
||||||
|
#
|
||||||
|
# (10,10)
|
||||||
|
# /
|
||||||
|
# (10,9) (9,10)
|
||||||
|
# / /
|
||||||
|
# (10,8) (9,9) (8,10)
|
||||||
|
# / / /
|
||||||
|
# ( continued...)
|
||||||
|
# \ / / /
|
||||||
|
# (3,1) (2,2) (1,3)
|
||||||
|
# \ / /
|
||||||
|
# (2,1) (1,2)
|
||||||
|
# \ /
|
||||||
|
# (1,1)
|
||||||
|
#
|
||||||
|
# In short, for a commit (i,j), the first-parent history
|
||||||
|
# walks all commits (i, k) with k from j to 1, then the
|
||||||
|
# commits (l, 1) with l from i to 1.
|
||||||
|
|
||||||
|
test_expect_success 'get_branch_base_for_tip: none reach' '
|
||||||
|
# (2,3) branched from the first tip (i,4) in X with i > 2
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
A:commit-2-3
|
||||||
|
X:commit-1-2
|
||||||
|
X:commit-1-4
|
||||||
|
X:commit-4-4
|
||||||
|
X:commit-8-4
|
||||||
|
X:commit-10-4
|
||||||
|
EOF
|
||||||
|
echo "get_branch_base_for_tip(A,X):2" >expect &&
|
||||||
|
test_all_modes get_branch_base_for_tip
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'get_branch_base_for_tip: equal to tip' '
|
||||||
|
# (2,3) branched from the first tip (i,4) in X with i > 2
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
A:commit-8-4
|
||||||
|
X:commit-1-2
|
||||||
|
X:commit-1-4
|
||||||
|
X:commit-4-4
|
||||||
|
X:commit-8-4
|
||||||
|
X:commit-10-4
|
||||||
|
EOF
|
||||||
|
echo "get_branch_base_for_tip(A,X):3" >expect &&
|
||||||
|
test_all_modes get_branch_base_for_tip
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'get_branch_base_for_tip: all reach tip' '
|
||||||
|
# (2,3) branched from the first tip (i,4) in X with i > 2
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
A:commit-4-1
|
||||||
|
X:commit-4-2
|
||||||
|
X:commit-5-1
|
||||||
|
EOF
|
||||||
|
echo "get_branch_base_for_tip(A,X):0" >expect &&
|
||||||
|
test_all_modes get_branch_base_for_tip
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'for-each-ref is-base: none reach' '
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
refs/heads/commit-1-1
|
||||||
|
refs/heads/commit-4-2
|
||||||
|
refs/heads/commit-4-4
|
||||||
|
refs/heads/commit-8-4
|
||||||
|
EOF
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/heads/commit-1-1:
|
||||||
|
refs/heads/commit-4-2:(commit-2-3)
|
||||||
|
refs/heads/commit-4-4:
|
||||||
|
refs/heads/commit-8-4:
|
||||||
|
EOF
|
||||||
|
run_all_modes git for-each-ref \
|
||||||
|
--format="%(refname):%(is-base:commit-2-3)" --stdin
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'for-each-ref is-base: all reach' '
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
refs/heads/commit-4-2
|
||||||
|
refs/heads/commit-5-1
|
||||||
|
EOF
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/heads/commit-4-2:(commit-4-1)
|
||||||
|
refs/heads/commit-5-1:
|
||||||
|
EOF
|
||||||
|
run_all_modes git for-each-ref \
|
||||||
|
--format="%(refname):%(is-base:commit-4-1)" --stdin
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'for-each-ref is-base: equal to tip' '
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
refs/heads/commit-4-2
|
||||||
|
refs/heads/commit-5-1
|
||||||
|
EOF
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/heads/commit-4-2:(commit-4-2)
|
||||||
|
refs/heads/commit-5-1:
|
||||||
|
EOF
|
||||||
|
run_all_modes git for-each-ref \
|
||||||
|
--format="%(refname):%(is-base:commit-4-2)" --stdin
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'for-each-ref is-base:multiple' '
|
||||||
|
cat >input <<-\EOF &&
|
||||||
|
refs/heads/commit-1-1
|
||||||
|
refs/heads/commit-4-2
|
||||||
|
refs/heads/commit-4-4
|
||||||
|
refs/heads/commit-8-4
|
||||||
|
EOF
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/heads/commit-1-1[-]
|
||||||
|
refs/heads/commit-4-2[(commit-2-3)-]
|
||||||
|
refs/heads/commit-4-4[-]
|
||||||
|
refs/heads/commit-8-4[-(commit-6-5)]
|
||||||
|
EOF
|
||||||
|
run_all_modes git for-each-ref \
|
||||||
|
--format="%(refname)[%(is-base:commit-2-3)-%(is-base:commit-6-5)]" --stdin
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
Reference in New Issue
Block a user