Skip to content

Commit dd5da48

Browse files
committed
Merge branch 'ds/for-each-ref-is-base' into next
'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
2 parents 610d16d + 4b707a6 commit dd5da48

File tree

11 files changed

+448
-2
lines changed

11 files changed

+448
-2
lines changed

Documentation/git-for-each-ref.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,48 @@ ahead-behind:<committish>::
264264
commits ahead and behind, respectively, when comparing the output
265265
ref to the `<committish>` specified in the format.
266266

267+
is-base:<committish>::
268+
In at most one row, `(<committish>)` will appear to indicate the ref
269+
that is most likely the ref used as a starting point for the branch
270+
that produced `<committish>`. This choice is made using a heuristic:
271+
choose the ref that minimizes the number of commits in the
272+
first-parent history of `<committish>` and not in the first-parent
273+
history of the ref.
274+
+
275+
For example, consider the following figure of first-parent histories of
276+
several refs:
277+
+
278+
----
279+
*--*--*--*--*--* refs/heads/A
280+
\
281+
\
282+
*--*--*--* refs/heads/B
283+
\ \
284+
\ \
285+
* * refs/heads/C
286+
\
287+
\
288+
*--* refs/heads/D
289+
----
290+
+
291+
Here, if `A`, `B`, and `C` are the filtered references, and the format
292+
string is `%(refname):%(is-base:D)`, then the output would be
293+
+
294+
----
295+
refs/heads/A:
296+
refs/heads/B:(D)
297+
refs/heads/C:
298+
----
299+
+
300+
This is because the first-parent history of `D` has its earliest
301+
intersection with the first-parent histories of the filtered refs at a
302+
common first-parent ancestor of `B` and `C` and ties are broken by the
303+
earliest ref in the sorted order.
304+
+
305+
Note that this token will not appear if the first-parent history of
306+
`<committish>` does not intersect the first-parent histories of the
307+
filtered refs.
308+
267309
describe[:options]::
268310
A human-readable name, like linkgit:git-describe[1];
269311
empty string for undescribable commits. The `describe` string may

commit-reach.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,3 +1229,129 @@ void tips_reachable_from_bases(struct repository *r,
12291229
repo_clear_commit_marks(r, SEEN);
12301230
free_commit_list(stack);
12311231
}
1232+
1233+
/*
1234+
* This slab initializes integers to zero, so use "-1" for "tip is best" and
1235+
* "i + 1" for "bases[i] is best".
1236+
*/
1237+
define_commit_slab(best_branch_base, int);
1238+
static struct best_branch_base best_branch_base;
1239+
#define get_best(c) (*best_branch_base_at(&best_branch_base, (c)))
1240+
#define set_best(c,v) (*best_branch_base_at(&best_branch_base, (c)) = (v))
1241+
1242+
int get_branch_base_for_tip(struct repository *r,
1243+
struct commit *tip,
1244+
struct commit **bases,
1245+
size_t bases_nr)
1246+
{
1247+
int best_index = -1;
1248+
struct commit *branch_point = NULL;
1249+
struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
1250+
int found_missing_gen = 0;
1251+
1252+
if (!bases_nr)
1253+
return -1;
1254+
1255+
repo_parse_commit(r, tip);
1256+
if (commit_graph_generation(tip) == GENERATION_NUMBER_INFINITY)
1257+
found_missing_gen = 1;
1258+
1259+
/* Check for missing generation numbers. */
1260+
for (size_t i = 0; i < bases_nr; i++) {
1261+
struct commit *c = bases[i];
1262+
repo_parse_commit(r, c);
1263+
if (commit_graph_generation(c) == GENERATION_NUMBER_INFINITY)
1264+
found_missing_gen = 1;
1265+
}
1266+
1267+
if (found_missing_gen) {
1268+
struct commit **commits;
1269+
size_t commits_nr = bases_nr + 1;
1270+
1271+
CALLOC_ARRAY(commits, commits_nr);
1272+
COPY_ARRAY(commits, bases, bases_nr);
1273+
commits[bases_nr] = tip;
1274+
ensure_generations_valid(r, commits, commits_nr);
1275+
free(commits);
1276+
}
1277+
1278+
/* Initialize queue and slab now that generations are guaranteed. */
1279+
init_best_branch_base(&best_branch_base);
1280+
set_best(tip, -1);
1281+
prio_queue_put(&queue, tip);
1282+
1283+
for (size_t i = 0; i < bases_nr; i++) {
1284+
struct commit *c = bases[i];
1285+
int best = get_best(c);
1286+
1287+
/* Has this already been marked as best by another commit? */
1288+
if (best) {
1289+
if (best == -1) {
1290+
/* We agree at this position. Stop now. */
1291+
best_index = i + 1;
1292+
goto cleanup;
1293+
}
1294+
continue;
1295+
}
1296+
1297+
set_best(c, i + 1);
1298+
prio_queue_put(&queue, c);
1299+
}
1300+
1301+
while (queue.nr) {
1302+
struct commit *c = prio_queue_get(&queue);
1303+
int best_for_c = get_best(c);
1304+
int best_for_p, positive;
1305+
struct commit *parent;
1306+
1307+
/* Have we reached a known branch point? It's optimal. */
1308+
if (c == branch_point)
1309+
break;
1310+
1311+
repo_parse_commit(r, c);
1312+
if (!c->parents)
1313+
continue;
1314+
1315+
parent = c->parents->item;
1316+
repo_parse_commit(r, parent);
1317+
best_for_p = get_best(parent);
1318+
1319+
if (!best_for_p) {
1320+
/* 'parent' is new, so pass along best_for_c. */
1321+
set_best(parent, best_for_c);
1322+
prio_queue_put(&queue, parent);
1323+
continue;
1324+
}
1325+
1326+
if (best_for_p > 0 && best_for_c > 0) {
1327+
/* Collision among bases. Minimize. */
1328+
if (best_for_c < best_for_p)
1329+
set_best(parent, best_for_c);
1330+
continue;
1331+
}
1332+
1333+
/*
1334+
* At this point, we have reached a commit that is reachable
1335+
* from the tip, either from 'c' or from an earlier commit to
1336+
* have 'parent' as its first parent.
1337+
*
1338+
* Update 'best_index' to match the minimum of all base indices
1339+
* to reach 'parent'.
1340+
*/
1341+
1342+
/* Exactly one is positive due to initial conditions. */
1343+
positive = (best_for_c < 0) ? best_for_p : best_for_c;
1344+
1345+
if (best_index < 0 || positive < best_index)
1346+
best_index = positive;
1347+
1348+
/* No matter what, track that the parent is reachable from tip. */
1349+
set_best(parent, -1);
1350+
branch_point = parent;
1351+
}
1352+
1353+
cleanup:
1354+
clear_best_branch_base(&best_branch_base);
1355+
clear_prio_queue(&queue);
1356+
return best_index > 0 ? best_index - 1 : -1;
1357+
}

commit-reach.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,21 @@ void tips_reachable_from_bases(struct repository *r,
139139
struct commit **tips, size_t tips_nr,
140140
int mark);
141141

142+
/*
143+
* Given a 'tip' commit and a list potential 'bases', return the index 'i' that
144+
* minimizes the number of commits in the first-parent history of 'tip' and not
145+
* in the first-parent history of 'bases[i]'.
146+
*
147+
* Among a list of long-lived branches that are updated only by merges (with the
148+
* first parent being the previous position of the branch), this would inform
149+
* which branch was used to create the tip reference.
150+
*
151+
* Returns -1 if no common point is found in first-parent histories, which is
152+
* rare, but possible with multiple root commits.
153+
*/
154+
int get_branch_base_for_tip(struct repository *r,
155+
struct commit *tip,
156+
struct commit **bases,
157+
size_t bases_nr);
158+
142159
#endif

commit.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,19 @@ struct commit *lookup_commit(struct repository *r, const struct object_id *oid)
8484
}
8585

8686
struct commit *lookup_commit_reference_by_name(const char *name)
87+
{
88+
return lookup_commit_reference_by_name_gently(name, 0);
89+
}
90+
91+
struct commit *lookup_commit_reference_by_name_gently(const char *name,
92+
int quiet)
8793
{
8894
struct object_id oid;
8995
struct commit *commit;
9096

9197
if (repo_get_oid_committish(the_repository, name, &oid))
9298
return NULL;
93-
commit = lookup_commit_reference(the_repository, &oid);
99+
commit = lookup_commit_reference_gently(the_repository, &oid, quiet);
94100
if (repo_parse_commit(the_repository, commit))
95101
return NULL;
96102
return commit;

commit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ struct commit *lookup_commit_reference_gently(struct repository *r,
8181
const struct object_id *oid,
8282
int quiet);
8383
struct commit *lookup_commit_reference_by_name(const char *name);
84+
struct commit *lookup_commit_reference_by_name_gently(const char *name,
85+
int quiet);
8486

8587
/*
8688
* Look up object named by "oid", dereference tag as necessary,

ref-filter.c

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ enum atom_type {
169169
ATOM_ELSE,
170170
ATOM_REST,
171171
ATOM_AHEADBEHIND,
172+
ATOM_ISBASE,
172173
};
173174

174175
/*
@@ -890,6 +891,23 @@ static int ahead_behind_atom_parser(struct ref_format *format,
890891
return 0;
891892
}
892893

894+
static int is_base_atom_parser(struct ref_format *format,
895+
struct used_atom *atom UNUSED,
896+
const char *arg, struct strbuf *err)
897+
{
898+
struct string_list_item *item;
899+
900+
if (!arg)
901+
return strbuf_addf_ret(err, -1, _("expected format: %%(is-base:<committish>)"));
902+
903+
item = string_list_append(&format->is_base_tips, arg);
904+
item->util = lookup_commit_reference_by_name(arg);
905+
if (!item->util)
906+
die("failed to find '%s'", arg);
907+
908+
return 0;
909+
}
910+
893911
static int head_atom_parser(struct ref_format *format UNUSED,
894912
struct used_atom *atom,
895913
const char *arg, struct strbuf *err)
@@ -955,6 +973,7 @@ static struct {
955973
[ATOM_ELSE] = { "else", SOURCE_NONE },
956974
[ATOM_REST] = { "rest", SOURCE_NONE, FIELD_STR, rest_atom_parser },
957975
[ATOM_AHEADBEHIND] = { "ahead-behind", SOURCE_OTHER, FIELD_STR, ahead_behind_atom_parser },
976+
[ATOM_ISBASE] = { "is-base", SOURCE_OTHER, FIELD_STR, is_base_atom_parser },
958977
/*
959978
* Please update $__git_ref_fieldlist in git-completion.bash
960979
* when you add new atoms
@@ -2340,6 +2359,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
23402359
int i;
23412360
struct object_info empty = OBJECT_INFO_INIT;
23422361
int ahead_behind_atoms = 0;
2362+
int is_base_atoms = 0;
23432363

23442364
CALLOC_ARRAY(ref->value, used_atom_cnt);
23452365

@@ -2489,6 +2509,15 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
24892509
v->s = xstrdup("");
24902510
}
24912511
continue;
2512+
} else if (atom_type == ATOM_ISBASE) {
2513+
if (ref->is_base && ref->is_base[is_base_atoms]) {
2514+
v->s = xstrfmt("(%s)", ref->is_base[is_base_atoms]);
2515+
free(ref->is_base[is_base_atoms]);
2516+
} else {
2517+
v->s = xstrdup("");
2518+
}
2519+
is_base_atoms++;
2520+
continue;
24922521
} else
24932522
continue;
24942523

@@ -2895,6 +2924,7 @@ static void free_array_item(struct ref_array_item *item)
28952924
free(item->value);
28962925
}
28972926
free(item->counts);
2927+
free(item->is_base);
28982928
free(item);
28992929
}
29002930

@@ -3059,6 +3089,49 @@ void filter_ahead_behind(struct repository *r,
30593089
free(commits);
30603090
}
30613091

3092+
void filter_is_base(struct repository *r,
3093+
struct ref_format *format,
3094+
struct ref_array *array)
3095+
{
3096+
struct commit **bases;
3097+
size_t bases_nr = 0;
3098+
struct ref_array_item **back_index;
3099+
3100+
if (!format->is_base_tips.nr || !array->nr)
3101+
return;
3102+
3103+
CALLOC_ARRAY(back_index, array->nr);
3104+
CALLOC_ARRAY(bases, array->nr);
3105+
3106+
for (size_t i = 0; i < array->nr; i++) {
3107+
const char *name = array->items[i]->refname;
3108+
struct commit *c = lookup_commit_reference_by_name_gently(name, 1);
3109+
3110+
CALLOC_ARRAY(array->items[i]->is_base, format->is_base_tips.nr);
3111+
3112+
if (!c)
3113+
continue;
3114+
3115+
back_index[bases_nr] = array->items[i];
3116+
bases[bases_nr] = c;
3117+
bases_nr++;
3118+
}
3119+
3120+
for (size_t i = 0; i < format->is_base_tips.nr; i++) {
3121+
struct commit *tip = format->is_base_tips.items[i].util;
3122+
int base_index = get_branch_base_for_tip(r, tip, bases, bases_nr);
3123+
3124+
if (base_index < 0)
3125+
continue;
3126+
3127+
/* Store the string for use in output later. */
3128+
back_index[base_index]->is_base[i] = xstrdup(format->is_base_tips.items[i].string);
3129+
}
3130+
3131+
free(back_index);
3132+
free(bases);
3133+
}
3134+
30623135
static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
30633136
{
30643137
int ret = 0;
@@ -3152,7 +3225,8 @@ static inline int can_do_iterative_format(struct ref_filter *filter,
31523225
return !(filter->reachable_from ||
31533226
filter->unreachable_from ||
31543227
sorting ||
3155-
format->bases.nr);
3228+
format->bases.nr ||
3229+
format->is_base_tips.nr);
31563230
}
31573231

31583232
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,
31763250
struct ref_array array = { 0 };
31773251
filter_refs(&array, filter, type);
31783252
filter_ahead_behind(the_repository, format, &array);
3253+
filter_is_base(the_repository, format, &array);
31793254
ref_array_sort(sorting, &array);
31803255
print_formatted_ref_array(&array, format);
31813256
ref_array_clear(&array);

0 commit comments

Comments
 (0)