diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index 7ce3ff496..5f3e3e9eb 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -902,6 +902,100 @@ public void CanIgnoreWhitespaceChangeMergeConflict(string branchName) } } + [Fact] + public void CanTreeMergeTreeIntoSameTree() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Branches["master"].Tip; + + var result = repo.ObjectDatabase.MergeTrees(master.Tree, master.Tree, master.Tree, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CanTreeMergeFastForwardTreeWithoutConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("fast_forward"); + var ancestor = repo.ObjectDatabase.FindMergeBase(master, branch); + + var result = repo.ObjectDatabase.MergeTrees(ancestor.Tree, master.Tree, branch.Tree, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.NotNull(result.Tree); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CanIdentifyConflictsInMergeTrees() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("conflicts"); + var ancestor = repo.ObjectDatabase.FindMergeBase(master, branch); + + var result = repo.ObjectDatabase.MergeTrees(ancestor.Tree, master.Tree, branch.Tree, null); + + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + + Assert.Null(result.Tree); + Assert.Single(result.Conflicts); + + var conflict = result.Conflicts.First(); + Assert.Equal(new ObjectId("8e9daea300fbfef6c0da9744c6214f546d55b279"), conflict.Ancestor.Id); + Assert.Equal(new ObjectId("610b16886ca829cebd2767d9196f3c4378fe60b5"), conflict.Ours.Id); + Assert.Equal(new ObjectId("3dd9738af654bbf1c363f6c3bbc323bacdefa179"), conflict.Theirs.Id); + } + } + + [Theory] + [InlineData("conflicts_spaces")] + [InlineData("conflicts_tabs")] + public void CanConflictOnWhitespaceChangeMergeTreesConflict(string branchName) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var mergeResult = repo.Merge(branchName, Constants.Signature, new MergeOptions()); + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + var master = repo.Branches["master"].Tip; + var branch = repo.Branches[branchName].Tip; + var ancestor = repo.ObjectDatabase.FindMergeBase(master, branch); + var mergeTreeResult = repo.ObjectDatabase.MergeTrees(ancestor.Tree, master.Tree, branch.Tree, new MergeTreeOptions()); + Assert.Equal(MergeTreeStatus.Conflicts, mergeTreeResult.Status); + } + } + + [Theory] + [InlineData("conflicts_spaces")] + [InlineData("conflicts_tabs")] + public void CanIgnoreWhitespaceChangeMergeTreesConflict(string branchName) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var mergeResult = repo.Merge(branchName, Constants.Signature, new MergeOptions() { IgnoreWhitespaceChange = true }); + Assert.NotEqual(MergeStatus.Conflicts, mergeResult.Status); + + var master = repo.Branches["master"].Tip; + var branch = repo.Branches[branchName].Tip; + var ancestor = repo.ObjectDatabase.FindMergeBase(master, branch); + var mergeTreeResult = repo.ObjectDatabase.MergeTrees(ancestor.Tree, master.Tree, branch.Tree, new MergeTreeOptions() { IgnoreWhitespaceChange = true }); + Assert.NotEqual(MergeTreeStatus.Conflicts, mergeTreeResult.Status); + Assert.Empty(mergeTreeResult.Conflicts); + } + } + [Fact] public void CanMergeIntoIndex() { diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e8e59843e..0e8bdc368 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1987,6 +1987,15 @@ internal static extern int git_transport_smart_credentials( internal static extern int git_transport_unregister( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_trees( + out git_index* outIndex, + git_repository* repo, + git_object* ancestorTree, + git_object* ourTree, + git_object* theirTree, + ref GitMergeOpts options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe uint git_tree_entry_filemode(git_tree_entry* entry); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 18e952e68..5e0d6db6c 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -3249,6 +3249,24 @@ public static int git_transport_smart_credentials(out IntPtr cred, IntPtr transp #region git_tree_ + public static unsafe IndexHandle git_merge_trees(RepositoryHandle repo, ObjectHandle ancestorTree, + ObjectHandle ourTree, ObjectHandle theirTree, GitMergeOpts opts, out bool earlyStop) + { + git_index* index; + int res = NativeMethods.git_merge_trees(out index, repo, ancestorTree, ourTree, theirTree, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + + return new IndexHandle(index, true); + } + public static unsafe Mode git_tree_entry_attributes(git_tree_entry* entry) { return (Mode)NativeMethods.git_tree_entry_filemode(entry); diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 42b65d7d0..26710d023 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -808,6 +808,74 @@ public virtual MergeTreeResult MergeCommits(Commit ours, Commit theirs, MergeTre } } + /// + /// Perform a three-way merge of two trees relative to the provided ancestor. + /// The returned will contain the results + /// of the merge and can be examined for conflicts. + /// + /// The ancestor tree + /// The first tree + /// The second tree + /// The controlling the merge + /// The containing the merged trees and any conflicts + public virtual MergeTreeResult MergeTrees(Tree ancestor, Tree ours, Tree theirs, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(ancestor, "ancestor"); + Ensure.ArgumentNotNull(ours, "ours"); + Ensure.ArgumentNotNull(theirs, "theirs"); + + var modifiedOptions = new MergeTreeOptions(); + + // We throw away the index after looking at the conflicts, so we'll never need the REUC + // entries to be there + modifiedOptions.SkipReuc = true; + + if (options != null) + { + modifiedOptions.FailOnConflict = options.FailOnConflict; + modifiedOptions.FindRenames = options.FindRenames; + modifiedOptions.IgnoreWhitespaceChange = options.IgnoreWhitespaceChange; + modifiedOptions.MergeFileFavor = options.MergeFileFavor; + modifiedOptions.RenameThreshold = options.RenameThreshold; + modifiedOptions.TargetLimit = options.TargetLimit; + } + + bool earlyStop; + using (var indexHandle = MergeTrees(ancestor, ours, theirs, modifiedOptions, out earlyStop)) + { + MergeTreeResult mergeResult; + + // Stopped due to FailOnConflict so there's no index or conflict list + if (earlyStop) + { + return new MergeTreeResult(new Conflict[] { }); + } + + if (Proxy.git_index_has_conflicts(indexHandle)) + { + List conflicts = new List(); + Conflict conflict; + + using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle)) + { + while ((conflict = Proxy.git_index_conflict_next(iterator)) != null) + { + conflicts.Add(conflict); + } + } + + mergeResult = new MergeTreeResult(conflicts); + } + else + { + var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle); + mergeResult = new MergeTreeResult(this.repo.Lookup(treeId)); + } + + return mergeResult; + } + } + /// /// Packs all the objects in the and write a pack (.pack) and index (.idx) files for them. /// @@ -940,6 +1008,50 @@ private IndexHandle MergeCommits(Commit ours, Commit theirs, MergeTreeOptions op } } + /// + /// Perform a three-way merge of two trees relative to the provided ancestor. + /// The returned will contain the results + /// of the merge and can be examined for conflicts. + /// + /// The ancestor tree + /// The first tree + /// The second tree + /// The controlling the merge + /// True if the merge stopped early due to conflicts + /// The containing the merged trees and any conflicts + private IndexHandle MergeTrees(Tree ancestor, Tree ours, Tree theirs, MergeTreeOptions options, out bool earlyStop) + { + GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL; + if (options.SkipReuc) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC; + } + if (options.FindRenames) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES; + } + if (options.FailOnConflict) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + + var mergeOptions = new GitMergeOpts + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = mergeFlags, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + }; + using (var ancestorHandle = Proxy.git_object_lookup(repo.Handle, ancestor.Id, GitObjectType.Tree)) + using (var oursHandle = Proxy.git_object_lookup(repo.Handle, ours.Id, GitObjectType.Tree)) + using (var theirHandle = Proxy.git_object_lookup(repo.Handle, theirs.Id, GitObjectType.Tree)) + { + var indexHandle = Proxy.git_merge_trees(repo.Handle, ancestorHandle, oursHandle, theirHandle, mergeOptions, out earlyStop); + return indexHandle; + } + } + /// /// Performs a cherry-pick of onto commit. /// pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy