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-recursivemaint
commit
45a177069f
11
commit.c
11
commit.c
|
@ -574,6 +574,17 @@ struct commit_list *copy_commit_list(struct commit_list *list)
|
|||
return head;
|
||||
}
|
||||
|
||||
struct commit_list *reverse_commit_list(struct commit_list *list)
|
||||
{
|
||||
struct commit_list *next = NULL, *current, *backup;
|
||||
for (current = list; current; current = backup) {
|
||||
backup = current->next;
|
||||
current->next = next;
|
||||
next = current;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
void free_commit_list(struct commit_list *list)
|
||||
{
|
||||
while (list)
|
||||
|
|
3
commit.h
3
commit.h
|
@ -179,6 +179,9 @@ void commit_list_sort_by_date(struct commit_list **list);
|
|||
/* Shallow copy of the input list */
|
||||
struct commit_list *copy_commit_list(struct commit_list *list);
|
||||
|
||||
/* Modify list in-place to reverse it, returning new head; list will be tail */
|
||||
struct commit_list *reverse_commit_list(struct commit_list *list);
|
||||
|
||||
void free_commit_list(struct commit_list *list);
|
||||
|
||||
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
|
||||
|
|
121
merge-ort.c
121
merge-ort.c
|
@ -17,8 +17,10 @@
|
|||
#include "cache.h"
|
||||
#include "merge-ort.h"
|
||||
|
||||
#include "alloc.h"
|
||||
#include "blob.h"
|
||||
#include "cache-tree.h"
|
||||
#include "commit.h"
|
||||
#include "commit-reach.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
|
@ -251,10 +253,11 @@ static void free_strmap_strings(struct strmap *map)
|
|||
}
|
||||
}
|
||||
|
||||
static void clear_internal_opts(struct merge_options_internal *opti,
|
||||
int reinitialize)
|
||||
static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
|
||||
int reinitialize)
|
||||
{
|
||||
assert(!reinitialize);
|
||||
void (*strmap_func)(struct strmap *, int) =
|
||||
reinitialize ? strmap_partial_clear : strmap_clear;
|
||||
|
||||
/*
|
||||
* 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,
|
|||
* to deallocate them.
|
||||
*/
|
||||
free_strmap_strings(&opti->paths);
|
||||
strmap_clear(&opti->paths, 1);
|
||||
strmap_func(&opti->paths, 1);
|
||||
|
||||
/*
|
||||
* All keys and values in opti->conflicted are a subset of those in
|
||||
* opti->paths. We don't want to deallocate anything twice, so we
|
||||
* don't free the keys and we pass 0 for free_values.
|
||||
*/
|
||||
strmap_clear(&opti->conflicted, 0);
|
||||
strmap_func(&opti->conflicted, 0);
|
||||
|
||||
/*
|
||||
* opti->paths_to_free is similar to opti->paths; we created it with
|
||||
|
@ -1342,12 +1345,29 @@ void merge_finalize(struct merge_options *opt,
|
|||
|
||||
assert(opt->priv == NULL);
|
||||
|
||||
clear_internal_opts(opti, 0);
|
||||
clear_or_reinit_internal_opts(opti, 0);
|
||||
FREE_AND_NULL(opti);
|
||||
}
|
||||
|
||||
/*** Function Grouping: helper functions for merge_incore_*() ***/
|
||||
|
||||
static inline void set_commit_tree(struct commit *c, struct tree *t)
|
||||
{
|
||||
c->maybe_tree = t;
|
||||
}
|
||||
|
||||
static struct commit *make_virtual_commit(struct repository *repo,
|
||||
struct tree *tree,
|
||||
const char *comment)
|
||||
{
|
||||
struct commit *commit = alloc_commit_node(repo);
|
||||
|
||||
set_merge_remote_desc(commit, comment, (struct object *)commit);
|
||||
set_commit_tree(commit, tree);
|
||||
commit->object.parsed = 1;
|
||||
return commit;
|
||||
}
|
||||
|
||||
static void merge_start(struct merge_options *opt, struct merge_result *result)
|
||||
{
|
||||
/* Sanity checks on opt */
|
||||
|
@ -1445,6 +1465,89 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Originally from merge_recursive_internal(); somewhat adapted, though.
|
||||
*/
|
||||
static void merge_ort_internal(struct merge_options *opt,
|
||||
struct commit_list *merge_bases,
|
||||
struct commit *h1,
|
||||
struct commit *h2,
|
||||
struct merge_result *result)
|
||||
{
|
||||
struct commit_list *iter;
|
||||
struct commit *merged_merge_bases;
|
||||
const char *ancestor_name;
|
||||
struct strbuf merge_base_abbrev = STRBUF_INIT;
|
||||
|
||||
if (!merge_bases) {
|
||||
merge_bases = get_merge_bases(h1, h2);
|
||||
/* See merge-ort.h:merge_incore_recursive() declaration NOTE */
|
||||
merge_bases = reverse_commit_list(merge_bases);
|
||||
}
|
||||
|
||||
merged_merge_bases = pop_commit(&merge_bases);
|
||||
if (merged_merge_bases == NULL) {
|
||||
/* if there is no common ancestor, use an empty tree */
|
||||
struct tree *tree;
|
||||
|
||||
tree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);
|
||||
merged_merge_bases = make_virtual_commit(opt->repo, tree,
|
||||
"ancestor");
|
||||
ancestor_name = "empty tree";
|
||||
} else if (merge_bases) {
|
||||
ancestor_name = "merged common ancestors";
|
||||
} else {
|
||||
strbuf_add_unique_abbrev(&merge_base_abbrev,
|
||||
&merged_merge_bases->object.oid,
|
||||
DEFAULT_ABBREV);
|
||||
ancestor_name = merge_base_abbrev.buf;
|
||||
}
|
||||
|
||||
for (iter = merge_bases; iter; iter = iter->next) {
|
||||
const char *saved_b1, *saved_b2;
|
||||
struct commit *prev = merged_merge_bases;
|
||||
|
||||
opt->priv->call_depth++;
|
||||
/*
|
||||
* When the merge fails, the result contains files
|
||||
* with conflict markers. The cleanness flag is
|
||||
* ignored (unless indicating an error), it was never
|
||||
* actually used, as result of merge_trees has always
|
||||
* overwritten it: the committed "conflicts" were
|
||||
* already resolved.
|
||||
*/
|
||||
saved_b1 = opt->branch1;
|
||||
saved_b2 = opt->branch2;
|
||||
opt->branch1 = "Temporary merge branch 1";
|
||||
opt->branch2 = "Temporary merge branch 2";
|
||||
merge_ort_internal(opt, NULL, prev, iter->item, result);
|
||||
if (result->clean < 0)
|
||||
return;
|
||||
opt->branch1 = saved_b1;
|
||||
opt->branch2 = saved_b2;
|
||||
opt->priv->call_depth--;
|
||||
|
||||
merged_merge_bases = make_virtual_commit(opt->repo,
|
||||
result->tree,
|
||||
"merged tree");
|
||||
commit_list_insert(prev, &merged_merge_bases->parents);
|
||||
commit_list_insert(iter->item,
|
||||
&merged_merge_bases->parents->next);
|
||||
|
||||
clear_or_reinit_internal_opts(opt->priv, 1);
|
||||
}
|
||||
|
||||
opt->ancestor = ancestor_name;
|
||||
merge_ort_nonrecursive_internal(opt,
|
||||
repo_get_commit_tree(opt->repo,
|
||||
merged_merge_bases),
|
||||
repo_get_commit_tree(opt->repo, h1),
|
||||
repo_get_commit_tree(opt->repo, h2),
|
||||
result);
|
||||
strbuf_release(&merge_base_abbrev);
|
||||
opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */
|
||||
}
|
||||
|
||||
void merge_incore_nonrecursive(struct merge_options *opt,
|
||||
struct tree *merge_base,
|
||||
struct tree *side1,
|
||||
|
@ -1462,5 +1565,9 @@ void merge_incore_recursive(struct merge_options *opt,
|
|||
struct commit *side2,
|
||||
struct merge_result *result)
|
||||
{
|
||||
die("Not yet implemented");
|
||||
/* We set the ancestor label based on the merge_bases */
|
||||
assert(opt->ancestor == NULL);
|
||||
|
||||
merge_start(opt, result);
|
||||
merge_ort_internal(opt, merge_bases, side1, side2, result);
|
||||
}
|
||||
|
|
10
merge-ort.h
10
merge-ort.h
|
@ -34,6 +34,16 @@ struct merge_result {
|
|||
/*
|
||||
* rename-detecting three-way merge with recursive ancestor consolidation.
|
||||
* working tree and index are untouched.
|
||||
*
|
||||
* merge_bases will be consumed (emptied) so make a copy if you need it.
|
||||
*
|
||||
* NOTE: empirically, the recursive algorithm will perform better if you
|
||||
* pass the merge_bases in the order of oldest commit to the
|
||||
* newest[1][2].
|
||||
*
|
||||
* [1] https://lore.kernel.org/git/nycvar.QRO.7.76.6.1907252055500.21907@tvgsbejvaqbjf.bet/
|
||||
* [2] commit 8918b0c9c2 ("merge-recur: try to merge older merge bases
|
||||
* first", 2006-08-09)
|
||||
*/
|
||||
void merge_incore_recursive(struct merge_options *opt,
|
||||
struct commit_list *merge_bases,
|
||||
|
|
|
@ -3517,17 +3517,6 @@ static int merge_trees_internal(struct merge_options *opt,
|
|||
return clean;
|
||||
}
|
||||
|
||||
static struct commit_list *reverse_commit_list(struct commit_list *list)
|
||||
{
|
||||
struct commit_list *next = NULL, *current, *backup;
|
||||
for (current = list; current; current = backup) {
|
||||
backup = current->next;
|
||||
current->next = next;
|
||||
next = current;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Merge the commits h1 and h2, returning a flag (int) indicating the
|
||||
* cleanness of the merge. Also, if opt->priv->call_depth, create a
|
||||
|
|
Loading…
Reference in New Issue