diff options
-rw-r--r-- | LibGit2Sharp.Tests/MergeFixture.cs | 190 | ||||
-rw-r--r-- | LibGit2Sharp/Core/GitMergeOpts.cs | 33 | ||||
-rw-r--r-- | LibGit2Sharp/Core/GitMergeResult.cs | 47 | ||||
-rw-r--r-- | LibGit2Sharp/Core/GitMergeTreeOpts.cs | 57 | ||||
-rw-r--r-- | LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs | 13 | ||||
-rw-r--r-- | LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs | 13 | ||||
-rw-r--r-- | LibGit2Sharp/Core/NativeMethods.cs | 49 | ||||
-rw-r--r-- | LibGit2Sharp/Core/Proxy.cs | 83 | ||||
-rw-r--r-- | LibGit2Sharp/IRepository.cs | 7 | ||||
-rw-r--r-- | LibGit2Sharp/LibGit2Sharp.csproj | 6 | ||||
-rw-r--r-- | LibGit2Sharp/MergeResult.cs | 71 | ||||
-rw-r--r-- | LibGit2Sharp/Repository.cs | 74 |
12 files changed, 642 insertions, 1 deletions
diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index 7bb5d54d..32bef84e 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -80,5 +81,192 @@ namespace LibGit2Sharp.Tests Assert.Null(mergedHeads[1].Tip); } } + + [Fact] + public void CanMergeRepoNonFastForward() + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + var originalTreeCount = firstBranch.Tip.Tree.Count; + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + + secondBranch.Checkout(); + + // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). + AddFileCommitToRepo(repo, secondBranchFileName); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); + + Assert.Equal(repo.Head.Tip, mergeResult.Commit); + Assert.Equal(originalTreeCount + 3, mergeResult.Commit.Tree.Count); // Expecting original tree count plussed by the 3 added files. + Assert.Equal(2, mergeResult.Commit.Parents.Count()); // Merge commit should have 2 parents + } + } + + [Fact] + public void IsUpToDateMerge() + { + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + + secondBranch.Checkout(); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.UpToDate, mergeResult.Status); + } + } + + [Fact] + public void CanFastForwardRepos() + { + const string firstBranchFileName = "first branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + // Reset the index and the working tree. + repo.Reset(ResetMode.Hard); + + // Clean the working directory. + repo.RemoveUntrackedFiles(); + + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + + secondBranch.Checkout(); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); + Assert.Equal(repo.Branches["FirstBranch"].Tip, mergeResult.Commit); + Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip); + Assert.Equal(0, repo.Index.RetrieveStatus().Count()); + } + } + + [Fact] + public void ConflictingMergeRepos() + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch + + secondBranch.Checkout(); + // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). + AddFileCommitToRepo(repo, secondBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "The second branches comment"); // Change file in second branch + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + Assert.Null(mergeResult.Commit); + Assert.Equal(1, repo.Index.Conflicts.Count()); + + var conflict = repo.Index.Conflicts.First(); + var changes = repo.Diff.Compare(repo.Lookup<Blob>(conflict.Theirs.Id), repo.Lookup<Blob>(conflict.Ours.Id)); + + Assert.False(changes.IsBinaryComparison); + } + } + + [Fact] + public void ConflictingMergeReposBinary() + { + const string firstBranchFileName = "first branch file.bin"; + const string secondBranchFileName = "second branch file.bin"; + const string sharedBranchFileName = "first+second branch file.bin"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "\0The first branches comment\0"); // Change file in first branch + + secondBranch.Checkout(); + // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). + AddFileCommitToRepo(repo, secondBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "\0The second branches comment\0"); // Change file in second branch + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + Assert.Equal(1, repo.Index.Conflicts.Count()); + + Conflict conflict = repo.Index.Conflicts.First(); + + var changes = repo.Diff.Compare(repo.Lookup<Blob>(conflict.Theirs.Id), repo.Lookup<Blob>(conflict.Ours.Id)); + + Assert.True(changes.IsBinaryComparison); + } + } + + private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null) + { + Touch(repository.Info.WorkingDirectory, filename, content); + + repository.Index.Stage(filename); + + return repository.Commit("New commit", Constants.Signature, Constants.Signature); + } } } diff --git a/LibGit2Sharp/Core/GitMergeOpts.cs b/LibGit2Sharp/Core/GitMergeOpts.cs new file mode 100644 index 00000000..1b371a37 --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeOpts.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + internal enum GitMergeFlags + { + /// <summary> + /// Default + /// </summary> + GIT_MERGE_DEFAULT = 0, + + /// <summary> + /// Do not fast-forward. + /// </summary> + GIT_MERGE_NO_FASTFORWARD = 1, + + /// <summary> + /// Only perform fast-forward. + /// </summary> + GIT_MERGE_FASTFORWARD_ONLY = 2, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GitMergeOpts + { + public uint Version; + + public GitMergeFlags MergeFlags; + public GitMergeTreeOpts MergeTreeOpts; + public GitCheckoutOpts CheckoutOpts; + } +} diff --git a/LibGit2Sharp/Core/GitMergeResult.cs b/LibGit2Sharp/Core/GitMergeResult.cs new file mode 100644 index 00000000..e335cabf --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeResult.cs @@ -0,0 +1,47 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + internal class GitMergeResult + { + internal GitMergeResult(GitMergeResultHandle handle) + { + IsUpToDate = Proxy.git_merge_result_is_uptodate(handle); + IsFastForward = Proxy.git_merge_result_is_fastforward(handle); + + if (IsFastForward) + { + FastForwardId = Proxy.git_merge_result_fastforward_oid(handle); + } + } + + public virtual bool IsUpToDate { get; private set; } + + public virtual bool IsFastForward { get; private set; } + + /// <summary> + /// The ID that a fast-forward merge should advance to. + /// </summary> + public virtual ObjectId FastForwardId { get; private set; } + + public virtual MergeStatus Status + { + get + { + if (IsUpToDate) + { + return MergeStatus.UpToDate; + } + else if (IsFastForward) + { + return MergeStatus.FastForward; + } + else + { + return MergeStatus.NonFastForward; + } + } + } + } +} diff --git a/LibGit2Sharp/Core/GitMergeTreeOpts.cs b/LibGit2Sharp/Core/GitMergeTreeOpts.cs new file mode 100644 index 00000000..dfcbe597 --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeTreeOpts.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [Flags] + internal enum GitMergeTreeFlags + { + /// <summary> + /// No options. + /// </summary> + GIT_MERGE_TREE_NORMAL = 0, + + /// <summary> + /// GIT_MERGE_TREE_FIND_RENAMES in libgit2 + /// </summary> + GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), + } + + internal enum GitMergeAutomergeFlags + { + GIT_MERGE_AUTOMERGE_NORMAL = 0, + GIT_MERGE_AUTOMERGE_NONE = 1, + GIT_MERGE_AUTOMERGE_FAVOR_OURS = 2, + GIT_MERGE_AUTOMERGE_FAVOR_THEIRS = 3, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GitMergeTreeOpts + { + public uint Version; + + public GitMergeTreeFlags MergeTreeFlags; + + /// <summary> + /// Similarity to consider a file renamed. + /// </summary> + public uint RenameThreshold; + + /// <summary> + /// Maximum similarity sources to examine (overrides + /// 'merge.renameLimit' config (default 200) + /// </summary> + public uint TargetLimit; + + /// <summary> + /// Pluggable similarityMetric; pass IntPtr.Zero + /// to use internal metric. + /// </summary> + public IntPtr SimilarityMetric; + + /// <summary> + /// Flags for automerging content. + /// </summary> + public GitMergeAutomergeFlags MergeAutomergeFlags; + } +} diff --git a/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs b/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs new file mode 100644 index 00000000..f49e30e5 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core.Handles +{ + internal class GitMergeHeadHandle : SafeHandleBase + { + protected override bool ReleaseHandleImpl() + { + Proxy.git_merge_head_free(handle); + return true; + } + } +} diff --git a/LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs b/LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs new file mode 100644 index 00000000..f13b03e6 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core.Handles +{ + internal class GitMergeResultHandle : SafeHandleBase + { + protected override bool ReleaseHandleImpl() + { + Proxy.git_merge_result_free(handle); + return true; + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 6b1accb2..9b2f9215 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -577,6 +577,55 @@ namespace LibGit2Sharp.Core GitObjectSafeHandle two); [DllImport(libgit2)] + internal static extern int git_merge_head_from_ref( + out GitMergeHeadHandle mergehead, + RepositorySafeHandle repo, + ReferenceSafeHandle reference); + + [DllImport(libgit2)] + internal static extern int git_merge_head_from_fetchhead( + out GitMergeHeadHandle mergehead, + RepositorySafeHandle repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string branch_name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote_url, + ref GitOid oid); + + [DllImport(libgit2)] + internal static extern int git_merge_head_from_oid( + out GitMergeHeadHandle mergehead, + RepositorySafeHandle repo, + ref GitOid oid); + + [DllImport(libgit2)] + internal static extern int git_merge( + out GitMergeResultHandle mergeResult, + RepositorySafeHandle repo, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] [In] IntPtr[] their_heads, + UIntPtr their_heads_len, + ref GitMergeOpts given_opts); + + [DllImport(libgit2)] + internal static extern int git_merge_result_is_uptodate( + GitMergeResultHandle merge_result); + + [DllImport(libgit2)] + internal static extern int git_merge_result_is_fastforward( + GitMergeResultHandle merge_result); + + [DllImport(libgit2)] + internal static extern int git_merge_result_fastforward_oid( + out GitOid oid, + GitMergeResultHandle merge_result); + + [DllImport(libgit2)] + internal static extern void git_merge_result_free( + IntPtr merge_result); + + [DllImport(libgit2)] + internal static extern void git_merge_head_free( + IntPtr merge_head); + + [DllImport(libgit2)] internal static extern int git_message_prettify( byte[] message_out, // NB: This is more properly a StringBuilder, but it's UTF8 UIntPtr buffer_size, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 78c5a2be..01377e04 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -956,6 +956,89 @@ namespace LibGit2Sharp.Core } } + public static GitMergeHeadHandle git_merge_head_from_oid(RepositorySafeHandle repo, GitOid oid) + { + using (ThreadAffinity()) + { + GitMergeHeadHandle their_head; + + int res = NativeMethods.git_merge_head_from_oid(out their_head, repo, ref oid); + + Ensure.ZeroResult(res); + + return their_head; + } + } + + public static GitMergeResultHandle git_merge(RepositorySafeHandle repo, GitMergeHeadHandle[] heads, GitMergeOpts options) + { + using (ThreadAffinity()) + { + GitMergeResultHandle ret; + + IntPtr[] their_heads = new IntPtr[heads.Length]; + for (int i = 0; i < heads.Length; i++) + { + their_heads[i] = heads[i].DangerousGetHandle(); + } + + int res = NativeMethods.git_merge( + out ret, + repo, + their_heads, + (UIntPtr)their_heads.Length, + ref options); + + Ensure.ZeroResult(res); + + return ret; + } + } + + public static bool git_merge_result_is_uptodate(GitMergeResultHandle handle) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_merge_result_is_uptodate(handle); + Ensure.BooleanResult(res); + + return (res == 1); + } + } + + public static bool git_merge_result_is_fastforward(GitMergeResultHandle handle) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_merge_result_is_fastforward(handle); + Ensure.BooleanResult(res); + + return (res == 1); + } + } + + public static GitOid git_merge_result_fastforward_oid(GitMergeResultHandle handle) + { + using (ThreadAffinity()) + { + GitOid oid; + int res = NativeMethods.git_merge_result_fastforward_oid(out oid, handle); + Ensure.ZeroResult(res); + + return oid; + } + } + + public static void git_merge_result_free(IntPtr handle) + { + NativeMethods.git_merge_result_free(handle); + } + + public static void git_merge_head_free(IntPtr handle) + { + NativeMethods.git_merge_head_free(handle); + } + #endregion #region git_message_ diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index 9c3c9f31..952e1f64 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -194,6 +194,13 @@ namespace LibGit2Sharp IEnumerable<MergeHead> MergeHeads { get; } /// <summary> + /// Merges the given commit into HEAD. + /// </summary> + /// <param name="commit">The commit to use as a reference for the changes that should be merged into HEAD.</param> + /// <param name="merger">If the merge generates a merge commit (i.e. a non-fast forward merge), the <see cref="Signature"/> of who made the merge.</param> + MergeResult Merge(Commit commit, Signature merger); + + /// <summary> /// Manipulate the currently ignored files. /// </summary> Ignore Ignore { get; } diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 54fc4b9c..26e5aec3 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -78,19 +78,25 @@ <Compile Include="Core\Handles\PatchSafeHandle.cs" /> <Compile Include="Core\IntPtrExtensions.cs" /> <Compile Include="FetchOptions.cs" /> + <Compile Include="MergeResult.cs" /> <Compile Include="RefSpec.cs" /> <Compile Include="RefSpecCollection.cs" /> <Compile Include="Core\EncodingMarshaler.cs" /> + <Compile Include="Core\GitMergeOpts.cs" /> + <Compile Include="Core\GitMergeTreeOpts.cs" /> <Compile Include="Core\Handles\BranchIteratorSafeHandle.cs" /> <Compile Include="Core\Handles\ConfigurationIteratorSafeHandle.cs" /> <Compile Include="Core\GitBlame.cs" /> <Compile Include="Core\Handles\BlameSafeHandle.cs" /> + <Compile Include="Core\Handles\GitMergeHeadHandle.cs" /> + <Compile Include="Core\Handles\GitMergeResultHandle.cs" /> <Compile Include="Core\PushTransferProgressCallbacks.cs" /> <Compile Include="Core\PackbuilderCallbacks.cs" /> <Compile Include="HistoryDivergence.cs" /> <Compile Include="PushOptions.cs" /> <Compile Include="Core\GitBuf.cs" /> <Compile Include="FilteringOptions.cs" /> + <Compile Include="Core\GitMergeResult.cs" /> <Compile Include="ResetMode.cs" /> <Compile Include="NoteCollectionExtensions.cs" /> <Compile Include="RefSpecDirection.cs" /> diff --git a/LibGit2Sharp/MergeResult.cs b/LibGit2Sharp/MergeResult.cs new file mode 100644 index 00000000..fa209336 --- /dev/null +++ b/LibGit2Sharp/MergeResult.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// <summary> + /// Class to report the result of a merge. + /// </summary> + public class MergeResult + { + /// <summary> + /// Needed for mocking purposes. + /// </summary> + protected MergeResult() + { } + + internal MergeResult(MergeStatus status, Commit commit = null) + { + this.Status = status; + this.Commit = commit; + } + + /// <summary> + /// The status of the merge. + /// </summary> + public virtual MergeStatus Status + { + get; + private set; + } + + /// <summary> + /// The resulting commit of the merge. For fast-forward merges, this is the + /// commit that merge was fast forwarded to. + /// <para>This will return <code>null</code> if the merge has been unsuccessful due to conflicts.</para> + /// </summary> + public virtual Commit Commit + { + get; + private set; + } + } + + /// <summary> + /// The status of what happened as a result of a merge. + /// </summary> + public enum MergeStatus + { + /// <summary> + /// Merge was up-to-date. + /// </summary> + UpToDate, + + /// <summary> + /// Fast-forward merge. + /// </summary> + FastForward, + + /// <summary> + /// A non fast-forward merge. + /// </summary> + NonFastForward, + + /// <summary> + /// Merge resulted in conflicts. + /// </summary> + Conflicts, + } +} diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 33f9d795..092aa38d 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -1032,6 +1032,80 @@ namespace LibGit2Sharp } } + /// <summary> + /// Merges the given commit into HEAD as well as performing a Fast Forward if possible. + /// </summary> + /// <param name="commit">The commit to use as a reference for the changes that should be merged into HEAD.</param> + /// <param name="merger">If the merge generates a merge commit (i.e. a non-fast forward merge), the <see cref="Signature"/> of who made the merge.</param> + /// <returns>The result of the performed merge <see cref="MergeResult"/>.</returns> + public MergeResult Merge(Commit commit, Signature merger) + { + using (GitMergeHeadHandle mergeHeadHandle = Proxy.git_merge_head_from_oid(Handle, commit.Id.Oid)) + { + GitMergeOpts opts = new GitMergeOpts() + { + Version = 1, + MergeTreeOpts = { Version = 1 }, + CheckoutOpts = { version = 1 }, + }; + + + // Perform the merge in libgit2 and get the result back. + GitMergeResult gitMergeResult; + using (GitMergeResultHandle mergeResultHandle = Proxy.git_merge(Handle, new GitMergeHeadHandle[] { mergeHeadHandle }, opts)) + { + gitMergeResult = new GitMergeResult(mergeResultHandle); + } + + // Handle the result of the merge performed in libgit2 + // and commit the result / update the working directory as necessary. + MergeResult mergeResult; + switch(gitMergeResult.Status) + { + case MergeStatus.UpToDate: + mergeResult = new MergeResult(MergeStatus.UpToDate); + break; + case MergeStatus.FastForward: + Commit fastForwardCommit = this.Lookup<Commit>(gitMergeResult.FastForwardId); + FastForward(fastForwardCommit); + mergeResult = new MergeResult(MergeStatus.FastForward, fastForwardCommit); + break; + case MergeStatus.NonFastForward: + { + if (Index.IsFullyMerged) + { + // Commit the merge + Commit mergeCommit = this.Commit(Info.Message, author: merger, committer: merger); + mergeResult = new MergeResult(MergeStatus.NonFastForward, mergeCommit); + } + else + { + mergeResult = new MergeResult(MergeStatus.Conflicts); + } + } + break; + default: + throw new NotImplementedException(string.Format("Unknown MergeStatus: {0}", gitMergeResult.Status)); + } + + return mergeResult; + } + } + + private void FastForward(Commit fastForwardCommit) + { + var checkoutOpts = new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.None, + }; + + CheckoutTree(fastForwardCommit.Tree, null, checkoutOpts); + + Refs.UpdateTarget("HEAD", fastForwardCommit.Id.Sha); + + // TODO: Update Reflog... + } + internal StringComparer PathComparer { get { return pathCase.Value.Comparer; } |