Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2sharp.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LibGit2Sharp.Tests/MergeFixture.cs190
-rw-r--r--LibGit2Sharp/Core/GitMergeOpts.cs33
-rw-r--r--LibGit2Sharp/Core/GitMergeResult.cs47
-rw-r--r--LibGit2Sharp/Core/GitMergeTreeOpts.cs57
-rw-r--r--LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs13
-rw-r--r--LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs13
-rw-r--r--LibGit2Sharp/Core/NativeMethods.cs49
-rw-r--r--LibGit2Sharp/Core/Proxy.cs83
-rw-r--r--LibGit2Sharp/IRepository.cs7
-rw-r--r--LibGit2Sharp/LibGit2Sharp.csproj6
-rw-r--r--LibGit2Sharp/MergeResult.cs71
-rw-r--r--LibGit2Sharp/Repository.cs74
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; }