Skip to content

Commit

Permalink
Merge branch 'en/merge-ort-recursive' into jch
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Dec 17, 2020
2 parents e154186 + 8119214 commit c6d5321
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 18 deletions.
11 changes: 11 additions & 0 deletions commit.c
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions commit.h
Expand Up @@ -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 */
Expand Down
121 changes: 114 additions & 7 deletions merge-ort.c
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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,
Expand All @@ -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 changes: 10 additions & 0 deletions merge-ort.h
Expand Up @@ -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,
Expand Down
11 changes: 0 additions & 11 deletions merge-recursive.c
Expand Up @@ -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
Expand Down

0 comments on commit c6d5321

Please sign in to comment.