Skip to content

Commit b2b6139

Browse files
author
Edward Thomson
committed
Introduce ObjectDatabase.MergeCommits
Provide an API to merge two commits and return the resultant result. This Index may be analyzed looking for conflicts or otherwise to determine the success of the merge. This Index must be disposed when the caller has finished with it. Thus, Index now implements IDisposable and Indexes that are not the repository index (ie, generated by merge) are expected to be disposed. The repository's index is not expected to be disposed and is protected from disposal.
1 parent eaf7817 commit b2b6139

File tree

10 files changed

+418
-63
lines changed

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)