Skip to content

Commit 45a1770

Browse files
committed
Merge branch 'en/merge-ort-recursive'
The ORT merge strategy learned to synthesize virtual ancestor tree by recursively merging multiple merge bases together, just like the recursive backend has done for years. * en/merge-ort-recursive: merge-ort: implement merge_incore_recursive() merge-ort: make clear_internal_opts() aware of partial clearing merge-ort: copy a few small helper functions from merge-recursive.c commit: move reverse_commit_list() from merge-recursive
2 parents d3fa84d + 8119214 commit 45a1770

File tree

5 files changed

+138
-18
lines changed

5 files changed

+138
-18
lines changed

commit.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,17 @@ struct commit_list *copy_commit_list(struct commit_list *list)
574574
return head;
575575
}
576576

577+
struct commit_list *reverse_commit_list(struct commit_list *list)
578+
{
579+
struct commit_list *next = NULL, *current, *backup;
580+
for (current = list; current; current = backup) {
581+
backup = current->next;
582+
current->next = next;
583+
next = current;
584+
}
585+
return next;
586+
}
587+
577588
void free_commit_list(struct commit_list *list)
578589
{
579590
while (list)

commit.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ void commit_list_sort_by_date(struct commit_list **list);
179179
/* Shallow copy of the input list */
180180
struct commit_list *copy_commit_list(struct commit_list *list);
181181

182+
/* Modify list in-place to reverse it, returning new head; list will be tail */
183+
struct commit_list *reverse_commit_list(struct commit_list *list);
184+
182185
void free_commit_list(struct commit_list *list);
183186

184187
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */

merge-ort.c

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
#include "cache.h"
1818
#include "merge-ort.h"
1919

20+
#include "alloc.h"
2021
#include "blob.h"
2122
#include "cache-tree.h"
23+
#include "commit.h"
2224
#include "commit-reach.h"
2325
#include "diff.h"
2426
#include "diffcore.h"
@@ -251,10 +253,11 @@ static void free_strmap_strings(struct strmap *map)
251253
}
252254
}
253255

254-
static void clear_internal_opts(struct merge_options_internal *opti,
255-
int reinitialize)
256+
static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
257+
int reinitialize)
256258
{
257-
assert(!reinitialize);
259+
void (*strmap_func)(struct strmap *, int) =
260+
reinitialize ? strmap_partial_clear : strmap_clear;
258261

259262
/*
260263
* We marked opti->paths with strdup_strings = 0, so that we
@@ -264,14 +267,14 @@ static void clear_internal_opts(struct merge_options_internal *opti,
264267
* to deallocate them.
265268
*/
266269
free_strmap_strings(&opti->paths);
267-
strmap_clear(&opti->paths, 1);
270+
strmap_func(&opti->paths, 1);
268271

269272
/*
270273
* All keys and values in opti->conflicted are a subset of those in
271274
* opti->paths. We don't want to deallocate anything twice, so we
272275
* don't free the keys and we pass 0 for free_values.
273276
*/
274-
strmap_clear(&opti->conflicted, 0);
277+
strmap_func(&opti->conflicted, 0);
275278

276279
/*
277280
* opti->paths_to_free is similar to opti->paths; we created it with
@@ -1342,12 +1345,29 @@ void merge_finalize(struct merge_options *opt,
13421345

13431346
assert(opt->priv == NULL);
13441347

1345-
clear_internal_opts(opti, 0);
1348+
clear_or_reinit_internal_opts(opti, 0);
13461349
FREE_AND_NULL(opti);
13471350
}
13481351

13491352
/*** Function Grouping: helper functions for merge_incore_*() ***/
13501353

1354+
static inline void set_commit_tree(struct commit *c, struct tree *t)
1355+
{
1356+
c->maybe_tree = t;
1357+
}
1358+
1359+
static struct commit *make_virtual_commit(struct repository *repo,
1360+
struct tree *tree,
1361+
const char *comment)
1362+
{
1363+
struct commit *commit = alloc_commit_node(repo);
1364+
1365+
set_merge_remote_desc(commit, comment, (struct object *)commit);
1366+
set_commit_tree(commit, tree);
1367+
commit->object.parsed = 1;
1368+
return commit;
1369+
}
1370+
13511371
static void merge_start(struct merge_options *opt, struct merge_result *result)
13521372
{
13531373
/* Sanity checks on opt */
@@ -1445,6 +1465,89 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
14451465
}
14461466
}
14471467

1468+
/*
1469+
* Originally from merge_recursive_internal(); somewhat adapted, though.
1470+
*/
1471+
static void merge_ort_internal(struct merge_options *opt,
1472+
struct commit_list *merge_bases,
1473+
struct commit *h1,
1474+
struct commit *h2,
1475+
struct merge_result *result)
1476+
{
1477+
struct commit_list *iter;
1478+
struct commit *merged_merge_bases;
1479+
const char *ancestor_name;
1480+
struct strbuf merge_base_abbrev = STRBUF_INIT;
1481+
1482+
if (!merge_bases) {
1483+
merge_bases = get_merge_bases(h1, h2);
1484+
/* See merge-ort.h:merge_incore_recursive() declaration NOTE */
1485+
merge_bases = reverse_commit_list(merge_bases);
1486+
}
1487+
1488+
merged_merge_bases = pop_commit(&merge_bases);
1489+
if (merged_merge_bases == NULL) {
1490+
/* if there is no common ancestor, use an empty tree */
1491+
struct tree *tree;
1492+
1493+
tree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);
1494+
merged_merge_bases = make_virtual_commit(opt->repo, tree,
1495+
"ancestor");
1496+
ancestor_name = "empty tree";
1497+
} else if (merge_bases) {
1498+
ancestor_name = "merged common ancestors";
1499+
} else {
1500+
strbuf_add_unique_abbrev(&merge_base_abbrev,
1501+
&merged_merge_bases->object.oid,
1502+
DEFAULT_ABBREV);
1503+
ancestor_name = merge_base_abbrev.buf;
1504+
}
1505+
1506+
for (iter = merge_bases; iter; iter = iter->next) {
1507+
const char *saved_b1, *saved_b2;
1508+
struct commit *prev = merged_merge_bases;
1509+
1510+
opt->priv->call_depth++;
1511+
/*
1512+
* When the merge fails, the result contains files
1513+
* with conflict markers. The cleanness flag is
1514+
* ignored (unless indicating an error), it was never
1515+
* actually used, as result of merge_trees has always
1516+
* overwritten it: the committed "conflicts" were
1517+
* already resolved.
1518+
*/
1519+
saved_b1 = opt->branch1;
1520+
saved_b2 = opt->branch2;
1521+
opt->branch1 = "Temporary merge branch 1";
1522+
opt->branch2 = "Temporary merge branch 2";
1523+
merge_ort_internal(opt, NULL, prev, iter->item, result);
1524+
if (result->clean < 0)
1525+
return;
1526+
opt->branch1 = saved_b1;
1527+
opt->branch2 = saved_b2;
1528+
opt->priv->call_depth--;
1529+
1530+
merged_merge_bases = make_virtual_commit(opt->repo,
1531+
result->tree,
1532+
"merged tree");
1533+
commit_list_insert(prev, &merged_merge_bases->parents);
1534+
commit_list_insert(iter->item,
1535+
&merged_merge_bases->parents->next);
1536+
1537+
clear_or_reinit_internal_opts(opt->priv, 1);
1538+
}
1539+
1540+
opt->ancestor = ancestor_name;
1541+
merge_ort_nonrecursive_internal(opt,
1542+
repo_get_commit_tree(opt->repo,
1543+
merged_merge_bases),
1544+
repo_get_commit_tree(opt->repo, h1),
1545+
repo_get_commit_tree(opt->repo, h2),
1546+
result);
1547+
strbuf_release(&merge_base_abbrev);
1548+
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
1549+
}
1550+
14481551
void merge_incore_nonrecursive(struct merge_options *opt,
14491552
struct tree *merge_base,
14501553
struct tree *side1,
@@ -1462,5 +1565,9 @@ void merge_incore_recursive(struct merge_options *opt,
14621565
struct commit *side2,
14631566
struct merge_result *result)
14641567
{
1465-
die("Not yet implemented");
1568+
/* We set the ancestor label based on the merge_bases */
1569+
assert(opt->ancestor == NULL);
1570+
1571+
merge_start(opt, result);
1572+
merge_ort_internal(opt, merge_bases, side1, side2, result);
14661573
}

merge-ort.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ struct merge_result {
3434
/*
3535
* rename-detecting three-way merge with recursive ancestor consolidation.
3636
* working tree and index are untouched.
37+
*
38+
* merge_bases will be consumed (emptied) so make a copy if you need it.
39+
*
40+
* NOTE: empirically, the recursive algorithm will perform better if you
41+
* pass the merge_bases in the order of oldest commit to the
42+
* newest[1][2].
43+
*
44+
* [1] https://lore.kernel.org/git/[email protected]/
45+
* [2] commit 8918b0c9c2 ("merge-recur: try to merge older merge bases
46+
* first", 2006-08-09)
3747
*/
3848
void merge_incore_recursive(struct merge_options *opt,
3949
struct commit_list *merge_bases,

merge-recursive.c

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3517,17 +3517,6 @@ static int merge_trees_internal(struct merge_options *opt,
35173517
return clean;
35183518
}
35193519

3520-
static struct commit_list *reverse_commit_list(struct commit_list *list)
3521-
{
3522-
struct commit_list *next = NULL, *current, *backup;
3523-
for (current = list; current; current = backup) {
3524-
backup = current->next;
3525-
current->next = next;
3526-
next = current;
3527-
}
3528-
return next;
3529-
}
3530-
35313520
/*
35323521
* Merge the commits h1 and h2, returning a flag (int) indicating the
35333522
* cleanness of the merge. Also, if opt->priv->call_depth, create a

0 commit comments

Comments
 (0)