Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
7fd521d
merge: add test case repository for multiple ref ('octopus') merge
jar-of-salt Sep 3, 2025
359fbfa
merge: Remove unnecessary hooks files
jar-of-salt Sep 3, 2025
c1d0895
merge octopus: add repo for testing octopus merge
jar-of-salt Sep 3, 2025
9ccd6aa
merge octopus: add initial failing test for the octopus merge feature
jar-of-salt Sep 3, 2025
ab94b67
merge octopus: setup failing test prior to merge octopus work
jar-of-salt Sep 3, 2025
1ccc258
merge octopus: enabling low-level support for multiple index joining
jar-of-salt Sep 4, 2025
cfd1f41
merge octopus: rewrite to use function that accepts multiple iterators
jar-of-salt Sep 4, 2025
c9c842a
merge octopus: create git_merge__iterators that allows multiple iter…
jar-of-salt Sep 5, 2025
9a96b06
merge octopus: create to support octopus merge
jar-of-salt Sep 5, 2025
9fa5c7f
merge octopus: fix initial test to accurately reflect goal repository…
jar-of-salt Sep 7, 2025
fe3ab54
merge octopus: enable multiple merge conflicts to be inserted for oct…
jar-of-salt Sep 7, 2025
77ae254
merge octopus: set to fail on merge conflict for octopus merge
jar-of-salt Sep 8, 2025
720ebca
merge octopus: set to fail on file merge conflict for octopus merge
jar-of-salt Sep 8, 2025
80e3d15
merge octopus: add todo for test case
jar-of-salt Sep 8, 2025
d7e5cb2
merge octopus: add missing semicolon
jar-of-salt Sep 8, 2025
b03ba10
merge octopus: disallow merge conflicts; clean up octopus-specific er…
jar-of-salt Sep 8, 2025
176ce1f
merge octopus: revert changes to queue difference (it was working for…
jar-of-salt Sep 9, 2025
f837350
merge octopus: update basic merge octopus test case
jar-of-salt Sep 9, 2025
82366b2
merge octopus: move octopus test to its own directory
jar-of-salt Sep 9, 2025
174bcd0
merge octopus: handle resetting iterators for octopus comparison
jar-of-salt Sep 10, 2025
43f4085
merge octopus: add failure test repo
jar-of-salt Sep 10, 2025
14c7ed8
merge octopus: add failure test case
jar-of-salt Sep 10, 2025
5988753
merge octopus: clean up octopus failure test
jar-of-salt Sep 10, 2025
9bc84be
merge octopus: remove unused functions, fix some warnings
jar-of-salt Sep 10, 2025
9512a07
merge octopus: update 'git_merge_diff_list__find_differences' to acce…
jar-of-salt Sep 10, 2025
1a1f479
merge octopus: use tabs!
jar-of-salt Sep 10, 2025
5f83bd6
merge octopus: fix formatting mistakes
jar-of-salt Sep 10, 2025
6a70f8a
merge octopus: update 'git_merge__iterators' to take multiple 'theirs…
jar-of-salt Sep 10, 2025
6f991b8
merge octopus: update 'git_merge_diff_list__find_differences' and 'gi…
jar-of-salt Sep 10, 2025
f7e58c0
merge octopus: update calls to 'git_merge__iterators' to reflect new …
jar-of-salt Sep 10, 2025
1865474
merge octopus: remove unused function from test
jar-of-salt Sep 10, 2025
a7d1b7d
merge octopus: update function call to match new signature
jar-of-salt Sep 10, 2025
a7beb3b
merge octopus: remove resolved TODOs
jar-of-salt Sep 10, 2025
86b370c
merge octopus: cast type in call to 'iterator_for_annotated_commit' t…
jar-of-salt Sep 10, 2025
4ac905b
merge octopus: clean up unnecessary git files for octopus test resources
jar-of-salt Sep 10, 2025
73cd43a
merge octopus: add explanatory comment regarding iterator resets in '…
jar-of-salt Sep 10, 2025
77003bd
merge octopus: add free for 'theirs' iterators in 'git_merge__iterators'
jar-of-salt Sep 10, 2025
6e2ffda
merge octopus: update 'merge_annotated_commits' to support multiple '…
jar-of-salt Sep 10, 2025
34c8fa4
merge octopus: revert unnecessary fssary format edits
jar-of-salt Sep 10, 2025
793fe9d
merge octopus: revert unnecessary format edits
jar-of-salt Sep 10, 2025
f59ea8e
merge octopus: remove complete TODOs
jar-of-salt Sep 10, 2025
9273c3b
merge octopus: correct merge base calculation for octopus case
jar-of-salt Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 166 additions & 35 deletions src/libgit2/merge.c
Original file line number Diff line number Diff line change
Expand Up @@ -1831,12 +1831,47 @@ int git_merge_diff_list__find_differences(
git_merge_diff_list *diff_list,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *their_iter)
git_iterator **their_iters,
size_t their_iters_len)
{
git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter };
struct merge_diff_find_data find_data = { diff_list };
size_t i, j, iters_len = 1 + their_iters_len;
git_iterator** iterators;
git_iterator* curr_iterators[3];
struct merge_diff_find_data find_data;
int error = 0;

iterators = git__calloc(iters_len, sizeof(git_iterator*));
GIT_ERROR_CHECK_ALLOC(iterators);

iterators[0] = our_iter;

for(i = 0; i < their_iters_len; i++) {
iterators[i + 1] = their_iters[i];
}

for (i = 0; i < iters_len - 1; i++) {
for(j = i + 1; j < iters_len; j++) {
find_data = (struct merge_diff_find_data){ diff_list };

/* Reset the ancestor iterator, since it is always walked. */
git_iterator_reset(ancestor_iter);
/* Reset the "ours" iterator since it may have previously been walked. */
git_iterator_reset(iterators[i]);
/* "theirs" will get reset once it takes the "ours" spot on the next loop */

curr_iterators[0] = ancestor_iter;
curr_iterators[1] = iterators[i];
curr_iterators[2] = iterators[j];

if ((error = git_iterator_walk(curr_iterators, 3, queue_difference, &find_data)) < 0)
goto done;
}
}

return git_iterator_walk(iterators, 3, queue_difference, &find_data);
done:
git__free(iterators);

return error;
}

git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
Expand Down Expand Up @@ -2101,19 +2136,21 @@ int git_merge__iterators(
git_repository *repo,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *theirs_iter,
git_iterator **theirs_iters,
size_t theirs_iters_len,
const git_merge_options *given_opts)
{
git_iterator *empty_ancestor = NULL,
*empty_ours = NULL,
*empty_theirs = NULL;
*empty_ours = NULL;
git_iterator **empty_theirs_iters;
git_merge_diff_list *diff_list;
git_merge_options opts;
git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT;
git_merge_diff *conflict;
git_vector changes;
size_t i;
int error = 0;
bool is_octopus = theirs_iters_len > 1;

GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(repo);
Expand All @@ -2138,15 +2175,27 @@ int git_merge__iterators(
file_opts.marker_size = GIT_MERGE_CONFLICT_MARKER_SIZE + 2;
}

/* set fail on merge conflict for octopus merge */
if (is_octopus) {
opts.flags |= GIT_MERGE_FAIL_ON_CONFLICT;
file_opts.flags &= ~GIT_MERGE_FILE_ACCEPT_CONFLICTS;
}

diff_list = git_merge_diff_list__alloc(repo);
GIT_ERROR_CHECK_ALLOC(diff_list);

ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter);
our_iter = iterator_given_or_empty(&empty_ours, our_iter);
theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter);

empty_theirs_iters = git__calloc(theirs_iters_len, sizeof(git_iterator*));
GIT_ERROR_CHECK_ALLOC(empty_theirs_iters);
for (i = 0; i < theirs_iters_len; i++) {
theirs_iters[i] = iterator_given_or_empty(&empty_theirs_iters[i],
theirs_iters[i]);
}

if ((error = git_merge_diff_list__find_differences(
diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 ||
diff_list, ancestor_iter, our_iter, theirs_iters, theirs_iters_len)) < 0 ||
(error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0)
goto done;

Expand All @@ -2160,9 +2209,13 @@ int git_merge__iterators(
&resolved, diff_list, conflict, &opts, &file_opts)) < 0)
goto done;

if (!resolved) {
if (!resolved) {
if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) {
git_error_set(GIT_ERROR_MERGE, "merge conflicts exist");
if (is_octopus)
git_error_set(GIT_ERROR_MERGE, "Automated merge did not work.\n"
"Should not be doing an octopus.");
else
git_error_set(GIT_ERROR_MERGE, "merge conflicts exist");
error = GIT_EMERGECONFLICT;
goto done;
}
Expand All @@ -2183,7 +2236,9 @@ int git_merge__iterators(
git_merge_diff_list__free(diff_list);
git_iterator_free(empty_ancestor);
git_iterator_free(empty_ours);
git_iterator_free(empty_theirs);
for (i = 0; i < theirs_iters_len; i++)
git_iterator_free(empty_theirs_iters[i]);
git__free(empty_theirs_iters);

return error;
}
Expand Down Expand Up @@ -2233,7 +2288,7 @@ int git_merge_trees(
goto done;

error = git_merge__iterators(
out, repo, ancestor_iter, our_iter, their_iter, merge_opts);
out, repo, ancestor_iter, our_iter, &their_iter, 1, merge_opts);

done:
git_iterator_free(ancestor_iter);
Expand All @@ -2248,7 +2303,8 @@ static int merge_annotated_commits(
git_annotated_commit **base_out,
git_repository *repo,
git_annotated_commit *our_commit,
git_annotated_commit *their_commit,
const git_annotated_commit **their_commits,
size_t their_commits_len,
size_t recursion_level,
const git_merge_options *opts);

Expand Down Expand Up @@ -2297,8 +2353,9 @@ static int create_virtual_base(
virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT;
virtual_opts.flags |= GIT_MERGE_VIRTUAL_BASE;

if ((merge_annotated_commits(&index, NULL, repo, one, two,
recursion_level + 1, &virtual_opts)) < 0)
if ((merge_annotated_commits(&index, NULL, repo, one,
(const git_annotated_commit **)&two, 1,
recursion_level + 1, &virtual_opts)) < 0)
return -1;

result = git__calloc(1, sizeof(git_annotated_commit));
Expand Down Expand Up @@ -2389,6 +2446,55 @@ static int compute_base(
return error;
}

static int compute_base_octopus(
git_annotated_commit **out,
git_repository *repo,
const git_annotated_commit *one,
const git_annotated_commit **twos,
size_t twos_len,
const git_merge_options *given_opts)
{
git_array_oid_t head_ids = GIT_ARRAY_INIT;
git_oid base_id;
git_annotated_commit *base = NULL;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
size_t i;
int error;

*out = NULL;

if (given_opts)
memcpy(&opts, given_opts, sizeof(git_merge_options));

/* With more than two commits, merge_bases_many finds the base of
* the first commit and a hypothetical merge of the others. Since
* "one" may itself be a virtual commit, which insert_head_ids
* substitutes multiple ancestors for, it needs to be added
* after "two" which is always a single real commit.
*/
for (i = 0; i < twos_len; i++) {
if ((error = insert_head_ids(&head_ids, twos[i])) < 0)
goto done;

}
if ((error = insert_head_ids(&head_ids, one)) < 0 ||
(error = git_merge_base_octopus(&base_id, repo,
head_ids.size, head_ids.ptr)) < 0)
goto done;

if ((error = git_annotated_commit_lookup(&base, repo, &base_id)) < 0)
goto done;

done:
if (error == 0)
*out = base;
else
git_annotated_commit_free(base);

git_array_clear(head_ids);
return error;
}

static int iterator_for_annotated_commit(
git_iterator **out,
git_annotated_commit *commit)
Expand Down Expand Up @@ -2419,28 +2525,50 @@ static int merge_annotated_commits(
git_annotated_commit **base_out,
git_repository *repo,
git_annotated_commit *ours,
git_annotated_commit *theirs,
const git_annotated_commit **their_commits,
size_t their_commits_len,
size_t recursion_level,
const git_merge_options *opts)
{
git_annotated_commit *base = NULL;
git_annotated_commit *base = NULL, *prev_base = ours;
git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL;
int error;
size_t i;
git_iterator **their_iters;
bool is_octopus = their_commits_len > 1;

if ((error = compute_base(&base, repo, ours, theirs, opts,
recursion_level)) < 0) {
their_iters = git__calloc(their_commits_len, sizeof(git_iterator*));

/* TODO: write test confirming that the base is as expected*/
if (is_octopus) {
if ((error = compute_base_octopus(&base, repo, ours, their_commits,
their_commits_len, opts)) < 0)
goto done;
} else if ((error = compute_base(&base, repo, prev_base, their_commits[0],
opts, recursion_level)) < 0) {
if (error != GIT_ENOTFOUND)
goto done;

git_error_clear();
}

if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 ||
(error = iterator_for_annotated_commit(&our_iter, ours)) < 0 ||
(error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 ||
(error = git_merge__iterators(index_out, repo, base_iter, our_iter,
their_iter, opts)) < 0)
(error = iterator_for_annotated_commit(&our_iter, ours)) < 0)
goto done;

if (is_octopus) {
/* TODO: remove ancestor commits */
}

for (i = 0; i < their_commits_len; i++) {
if((error = iterator_for_annotated_commit(&their_iter,
(git_annotated_commit *)their_commits[i])) < 0)
goto done;
their_iters[i] = their_iter;
}

if ((error = git_merge__iterators(index_out, repo, base_iter, our_iter,
their_iters, their_commits_len, opts)) < 0)
goto done;

if (base_out) {
Expand All @@ -2449,14 +2577,17 @@ static int merge_annotated_commits(
}

done:
/* TODO: check for memory leaks */
git_annotated_commit_free(base);
git_iterator_free(base_iter);
git_iterator_free(our_iter);
git_iterator_free(their_iter);
for(i = 0; i < their_commits_len; i++) {
git_iterator_free(their_iters[i]);
}
git__free(their_iters);
return error;
}


int git_merge_commits(
git_index **out,
git_repository *repo,
Expand All @@ -2471,7 +2602,8 @@ int git_merge_commits(
(error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0)
goto done;

error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts);
error = merge_annotated_commits(out, &base, repo, ours,
(const git_annotated_commit **)&theirs, 1, 0, opts);

done:
git_annotated_commit_free(ours);
Expand Down Expand Up @@ -3347,10 +3479,10 @@ int git_merge(
GIT_ASSERT_ARG(repo);
GIT_ASSERT_ARG(their_heads && their_heads_len > 0);

if (their_heads_len != 1) {
git_error_set(GIT_ERROR_MERGE, "can only merge a single branch");
return -1;
}
/* if (their_heads_len != 1) { */
/* git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); */
/* return -1; */
/* } */

if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0)
goto done;
Expand All @@ -3363,7 +3495,7 @@ int git_merge(
goto done;

if ((error = git_repository_index(&repo_index, repo) < 0) ||
(error = git_index_read(repo_index, 0) < 0))
(error = git_index_read(repo_index, 0) < 0))
goto done;

/* Write the merge setup files to the repository. */
Expand All @@ -3372,10 +3504,9 @@ int git_merge(
their_heads_len)) < 0)
goto done;

/* TODO: octopus */

/* Run octopus merge if there is more than one `their_heads` */
if ((error = merge_annotated_commits(&index, &base, repo, our_head,
(git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 ||
their_heads, their_heads_len, 0, merge_opts)) < 0 ||
(error = git_merge__check_result(repo, index)) < 0 ||
(error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0)
goto done;
Expand Down
6 changes: 4 additions & 2 deletions src/libgit2/merge.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ int git_merge_diff_list__find_differences(
git_merge_diff_list *merge_diff_list,
git_iterator *ancestor_iterator,
git_iterator *ours_iter,
git_iterator *theirs_iter);
git_iterator **theirs_iters,
size_t theirs_iters_len);

int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts);

Expand All @@ -148,7 +149,8 @@ int git_merge__iterators(
git_repository *repo,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *their_iter,
git_iterator **their_iters,
size_t their_iters_len,
const git_merge_options *given_opts);

int git_merge__check_result(git_repository *repo, git_index *index_new);
Expand Down
4 changes: 2 additions & 2 deletions src/libgit2/stash.c
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,7 @@ static int merge_indexes(
(error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0)
goto done;

error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
error = git_merge__iterators(out, repo, ancestor, ours, &theirs, 1, NULL);

done:
git_iterator_free(ancestor);
Expand All @@ -912,7 +912,7 @@ static int merge_index_and_tree(
(error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0)
goto done;

error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
error = git_merge__iterators(out, repo, ancestor, ours, &theirs, 1, NULL);

done:
git_iterator_free(ancestor);
Expand Down
Loading