Skip to content

Commit 63da0d3

Browse files
author
Edward Thomson
committed
Introduce ObjectDatabase.MergeCommits
Provide an API to merge two commits and return the result. If successful, a new tree will be written to the object database that reflects the results of the merge. If the merge had conflicts, a ConflictCollection will be provided.
1 parent eaf7817 commit 63da0d3

10 files changed

+418
-63
lines changed

LibGit2Sharp.Tests/MergeFixture.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,106 @@ public void CanMergeIntoOrphanedBranch()
748748
}
749749
}
750750

751+
752+
[Fact]
753+
public void CanMergeTreeIntoSameTree()
754+
{
755+
string path = SandboxMergeTestRepo();
756+
using (var repo = new Repository(path))
757+
{
758+
var master = repo.Branches["master"].Tip;
759+
760+
var result = repo.ObjectDatabase.MergeCommits(master, master, null);
761+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
762+
Assert.Equal(0, result.Conflicts.Count());
763+
}
764+
}
765+
766+
[Fact]
767+
public void CanMergeTreeIntoTreeFromUnbornBranch()
768+
{
769+
string path = SandboxMergeTestRepo();
770+
using (var repo = new Repository(path))
771+
{
772+
repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn");
773+
774+
Touch(repo.Info.WorkingDirectory, "README", "Yeah!\n");
775+
repo.Index.Clear();
776+
repo.Stage("README");
777+
778+
repo.Commit("A new world, free of the burden of the history", Constants.Signature, Constants.Signature);
779+
780+
var master = repo.Branches["master"].Tip;
781+
var branch = repo.Branches["unborn"].Tip;
782+
783+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
784+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
785+
Assert.NotNull(result.Tree);
786+
Assert.Equal(0, result.Conflicts.Count());
787+
}
788+
}
789+
790+
[Fact]
791+
public void CanMergeCommitsAndDetectConflicts()
792+
{
793+
string path = SandboxMergeTestRepo();
794+
using (var repo = new Repository(path))
795+
{
796+
repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn");
797+
798+
repo.Index.Replace(repo.Lookup<Commit>("conflicts"));
799+
800+
repo.Commit("A conflicting world, free of the burden of the history", Constants.Signature, Constants.Signature);
801+
802+
var master = repo.Branches["master"].Tip;
803+
var branch = repo.Branches["unborn"].Tip;
804+
805+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
806+
Assert.Equal(MergeTreeStatus.Conflicts, result.Status);
807+
Assert.Null(result.Tree);
808+
Assert.NotEqual(0, result.Conflicts.Count());
809+
}
810+
}
811+
812+
[Fact]
813+
public void CanMergeFastForwardTreeWithoutConflicts()
814+
{
815+
string path = SandboxMergeTestRepo();
816+
using (var repo = new Repository(path))
817+
{
818+
var master = repo.Lookup<Commit>("master");
819+
var branch = repo.Lookup<Commit>("fast_forward");
820+
821+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
822+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
823+
Assert.NotNull(result.Tree);
824+
Assert.Equal(0, result.Conflicts.Count());
825+
}
826+
}
827+
828+
[Fact]
829+
public void CanIdentifyConflictsInMergeCommits()
830+
{
831+
string path = SandboxMergeTestRepo();
832+
using (var repo = new Repository(path))
833+
{
834+
var master = repo.Lookup<Commit>("master");
835+
var branch = repo.Lookup<Commit>("conflicts");
836+
837+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
838+
839+
Assert.Equal(MergeTreeStatus.Conflicts, result.Status);
840+
841+
Assert.Null(result.Tree);
842+
Assert.Equal(1, result.Conflicts.Count());
843+
844+
var conflict = result.Conflicts.First();
845+
Assert.Equal(new ObjectId("8e9daea300fbfef6c0da9744c6214f546d55b279"), conflict.Ancestor.Id);
846+
Assert.Equal(new ObjectId("610b16886ca829cebd2767d9196f3c4378fe60b5"), conflict.Ours.Id);
847+
Assert.Equal(new ObjectId("3dd9738af654bbf1c363f6c3bbc323bacdefa179"), conflict.Theirs.Id);
848+
}
849+
}
850+
751851
private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null)
752852
{
753853
Touch(repository.Info.WorkingDirectory, filename, content);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace LibGit2Sharp.Core.Handles
2+
{
3+
internal class ConflictIteratorSafeHandle : SafeHandleBase
4+
{
5+
protected override bool ReleaseHandleImpl()
6+
{
7+
Proxy.git_index_conflict_iterator_free(handle);
8+
return true;
9+
}
10+
}
11+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,22 @@ internal static extern int git_index_conflict_get(
542542
IndexSafeHandle index,
543543
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path);
544544

545+
[DllImport(libgit2)]
546+
internal static extern int git_index_conflict_iterator_new(
547+
out ConflictIteratorSafeHandle iterator,
548+
IndexSafeHandle index);
549+
550+
[DllImport(libgit2)]
551+
internal static extern int git_index_conflict_next(
552+
out IndexEntrySafeHandle ancestor,
553+
out IndexEntrySafeHandle ours,
554+
out IndexEntrySafeHandle theirs,
555+
ConflictIteratorSafeHandle iterator);
556+
557+
[DllImport(libgit2)]
558+
internal static extern void git_index_conflict_iterator_free(
559+
IntPtr iterator);
560+
545561
[DllImport(libgit2)]
546562
internal static extern UIntPtr git_index_entrycount(IndexSafeHandle index);
547563

@@ -602,6 +618,9 @@ internal static extern IndexReucEntrySafeHandle git_index_reuc_get_bypath(
602618
[DllImport(libgit2)]
603619
internal static extern int git_index_write_tree(out GitOid treeOid, IndexSafeHandle index);
604620

621+
[DllImport(libgit2)]
622+
internal static extern int git_index_write_tree_to(out GitOid treeOid, IndexSafeHandle index, RepositorySafeHandle repo);
623+
605624
[DllImport(libgit2)]
606625
internal static extern int git_index_read_tree(IndexSafeHandle index, GitObjectSafeHandle tree);
607626

@@ -661,12 +680,11 @@ internal static extern int git_merge(
661680
ref GitCheckoutOpts checkout_opts);
662681

663682
[DllImport(libgit2)]
664-
internal static extern int git_merge_trees(
683+
internal static extern int git_merge_commits(
665684
out IndexSafeHandle index,
666685
RepositorySafeHandle repo,
667-
GitObjectSafeHandle ancestor_tree,
668-
GitObjectSafeHandle our_tree,
669-
GitObjectSafeHandle their_tree,
686+
GitObjectSafeHandle our_commit,
687+
GitObjectSafeHandle their_commit,
670688
ref GitMergeOpts merge_opts);
671689

672690
[DllImport(libgit2)]

LibGit2Sharp/Core/Proxy.cs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,50 @@ public static Conflict git_index_conflict_get(
939939
IndexEntry.BuildFromPtr(theirs));
940940
}
941941

942+
public static ConflictIteratorSafeHandle git_index_conflict_iterator_new(IndexSafeHandle index)
943+
{
944+
using (ThreadAffinity())
945+
{
946+
ConflictIteratorSafeHandle iter;
947+
int res = NativeMethods.git_index_conflict_iterator_new(out iter, index);
948+
Ensure.ZeroResult(res);
949+
950+
return iter;
951+
}
952+
}
953+
954+
public static Conflict git_index_conflict_next(ConflictIteratorSafeHandle iterator)
955+
{
956+
IndexEntrySafeHandle ancestor, ours, theirs;
957+
958+
using (ThreadAffinity())
959+
{
960+
int res = NativeMethods.git_index_conflict_next(out ancestor, out ours, out theirs, iterator);
961+
962+
if (res == (int)GitErrorCode.IterOver)
963+
{
964+
return null;
965+
}
966+
967+
Ensure.ZeroResult(res);
968+
969+
using (ancestor)
970+
using (ours)
971+
using (theirs)
972+
{
973+
return new Conflict(
974+
IndexEntry.BuildFromPtr(ancestor),
975+
IndexEntry.BuildFromPtr(ours),
976+
IndexEntry.BuildFromPtr(theirs));
977+
}
978+
}
979+
}
980+
981+
public static void git_index_conflict_iterator_free(IntPtr iterator)
982+
{
983+
NativeMethods.git_index_conflict_iterator_free(iterator);
984+
}
985+
942986
public static int git_index_entrycount(IndexSafeHandle index)
943987
{
944988
UIntPtr count = NativeMethods.git_index_entrycount(index);
@@ -1053,12 +1097,24 @@ public static void git_index_write(IndexSafeHandle index)
10531097
}
10541098
}
10551099

1056-
public static ObjectId git_tree_create_fromindex(Index index)
1100+
public static ObjectId git_index_write_tree(IndexSafeHandle index)
1101+
{
1102+
using (ThreadAffinity())
1103+
{
1104+
GitOid treeOid;
1105+
int res = NativeMethods.git_index_write_tree(out treeOid, index);
1106+
Ensure.ZeroResult(res);
1107+
1108+
return treeOid;
1109+
}
1110+
}
1111+
1112+
public static ObjectId git_index_write_tree_to(IndexSafeHandle index, RepositorySafeHandle repo)
10571113
{
10581114
using (ThreadAffinity())
10591115
{
10601116
GitOid treeOid;
1061-
int res = NativeMethods.git_index_write_tree(out treeOid, index.Handle);
1117+
int res = NativeMethods.git_index_write_tree_to(out treeOid, index, repo);
10621118
Ensure.ZeroResult(res);
10631119

10641120
return treeOid;
@@ -1087,13 +1143,12 @@ public static void git_index_clear(Index index)
10871143

10881144
#region git_merge_
10891145

1090-
public static IndexSafeHandle git_merge_trees(RepositorySafeHandle repo, GitObjectSafeHandle ancestorTree, GitObjectSafeHandle ourTree, GitObjectSafeHandle theirTree)
1146+
public static IndexSafeHandle git_merge_commits(RepositorySafeHandle repo, GitObjectSafeHandle ourCommit, GitObjectSafeHandle theirCommit, GitMergeOpts opts)
10911147
{
10921148
using (ThreadAffinity())
10931149
{
10941150
IndexSafeHandle index;
1095-
GitMergeOpts opts = new GitMergeOpts { Version = 1 };
1096-
int res = NativeMethods.git_merge_trees(out index, repo, ancestorTree, ourTree, theirTree, ref opts);
1151+
int res = NativeMethods.git_merge_commits(out index, repo, ourCommit, theirCommit, ref opts);
10971152
Ensure.ZeroResult(res);
10981153

10991154
return index;

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<Compile Include="CommitSortStrategies.cs" />
7070
<Compile Include="CompareOptions.cs" />
7171
<Compile Include="Core\Platform.cs" />
72+
<Compile Include="Core\Handles\ConflictIteratorSafeHandle.cs" />
7273
<Compile Include="DescribeOptions.cs" />
7374
<Compile Include="DescribeStrategy.cs" />
7475
<Compile Include="Core\GitDescribeFormatOptions.cs" />
@@ -101,6 +102,8 @@
101102
<Compile Include="IndexNameEntry.cs" />
102103
<Compile Include="MergeOptions.cs" />
103104
<Compile Include="MergeResult.cs" />
105+
<Compile Include="MergeTreeOptions.cs" />
106+
<Compile Include="MergeTreeResult.cs" />
104107
<Compile Include="NotFoundException.cs" />
105108
<Compile Include="GitObjectMetadata.cs" />
106109
<Compile Include="PatchEntryChanges.cs" />

LibGit2Sharp/MergeOptions.cs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -132,41 +132,4 @@ public enum FastForwardStrategy
132132
/// </summary>
133133
FastForwardOnly = 2, /* GIT_MERGE_FASTFORWARD_ONLY */
134134
}
135-
136-
/// <summary>
137-
/// Enum specifying how merge should deal with conflicting regions
138-
/// of the files.
139-
/// </summary>
140-
public enum MergeFileFavor
141-
{
142-
/// <summary>
143-
/// When a region of a file is changed in both branches, a conflict
144-
/// will be recorded in the index so that the checkout operation can produce
145-
/// a merge file with conflict markers in the working directory.
146-
/// This is the default.
147-
/// </summary>
148-
Normal = 0,
149-
150-
/// <summary>
151-
/// When a region of a file is changed in both branches, the file
152-
/// created in the index will contain the "ours" side of any conflicting
153-
/// region. The index will not record a conflict.
154-
/// </summary>
155-
Ours = 1,
156-
157-
/// <summary>
158-
/// When a region of a file is changed in both branches, the file
159-
/// created in the index will contain the "theirs" side of any conflicting
160-
/// region. The index will not record a conflict.
161-
/// </summary>
162-
Theirs = 2,
163-
164-
/// <summary>
165-
/// When a region of a file is changed in both branches, the file
166-
/// created in the index will contain each unique line from each side,
167-
/// which has the result of combining both files. The index will not
168-
/// record a conflict.
169-
/// </summary>
170-
Union = 3,
171-
}
172135
}

0 commit comments

Comments
 (0)