From 67262703f98f00eb42ce1313964c9f4711d2f863 Mon Sep 17 00:00:00 2001 From: John Gietzen Date: Thu, 9 Jul 2015 13:52:48 -0700 Subject: [PATCH 1/3] Added FindMergeBases on the ObjectDatabase class, equivalent to `git merge-base --all`. --- LibGit2Sharp.Tests/CommitAncestorFixture.cs | 24 ++++++++++ .../Resources/crosshistory_wd/a.txt | Bin 0 -> 4 bytes .../Resources/crosshistory_wd/b.txt | Bin 0 -> 4 bytes .../Resources/crosshistory_wd/dot_git/HEAD | 1 + .../Resources/crosshistory_wd/dot_git/config | 8 ++++ .../Resources/crosshistory_wd/dot_git/index | Bin 0 -> 176 bytes .../03/50717031bebea6b2600b89a33159935541cee1 | Bin 0 -> 158 bytes .../15/2325a8a96b610627aaae41a00391c3644079e4 | Bin 0 -> 196 bytes .../2c/e209a66a1f9c973b3c06e7fc4bd5fc431b964a | Bin 0 -> 50 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 0 -> 15 bytes .../65/8844f325a396f77d223d66464b93b75f107c87 | Bin 0 -> 75 bytes .../6a/caa5c7a6876fe73611ba4a46966786efe794a5 | Bin 0 -> 49 bytes .../90/96eb59927d50983135dae48bc56f885f156d47 | Bin 0 -> 19 bytes .../a6/9878c70d9cce9a1fbec1712d1fcd4f609e689b | Bin 0 -> 129 bytes .../b2/257a703ced76328aa58972de648e403051c41a | Bin 0 -> 19 bytes .../c9/a20513b0648832b41b0f875ab16c92e4b7dae7 | Bin 0 -> 207 bytes .../eb/c932c47f8800bffdd150c560e111d87bb74f4f | 2 + .../crosshistory_wd/dot_git/refs/heads/a | 1 + .../crosshistory_wd/dot_git/refs/heads/b | 1 + .../crosshistory_wd/dot_git/refs/heads/master | 1 + LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs | 7 +++ LibGit2Sharp/Core/GitOidArray.cs | 28 ++++++++++++ LibGit2Sharp/Core/GitOidArrayNative.cs | 43 ++++++++++++++++++ LibGit2Sharp/Core/NativeMethods.cs | 11 +++++ LibGit2Sharp/Core/Proxy.cs | 23 ++++++++++ LibGit2Sharp/LibGit2Sharp.csproj | 2 + LibGit2Sharp/ObjectDatabase.cs | 32 +++++++++++++ 27 files changed, 184 insertions(+) create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/a.txt create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/b.txt create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/HEAD create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/config create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/index create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/03/50717031bebea6b2600b89a33159935541cee1 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/15/2325a8a96b610627aaae41a00391c3644079e4 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/2c/e209a66a1f9c973b3c06e7fc4bd5fc431b964a create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/65/8844f325a396f77d223d66464b93b75f107c87 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/6a/caa5c7a6876fe73611ba4a46966786efe794a5 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/90/96eb59927d50983135dae48bc56f885f156d47 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/a6/9878c70d9cce9a1fbec1712d1fcd4f609e689b create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/b2/257a703ced76328aa58972de648e403051c41a create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/c9/a20513b0648832b41b0f875ab16c92e4b7dae7 create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/eb/c932c47f8800bffdd150c560e111d87bb74f4f create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/refs/heads/a create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/refs/heads/b create mode 100644 LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/refs/heads/master create mode 100644 LibGit2Sharp/Core/GitOidArray.cs create mode 100644 LibGit2Sharp/Core/GitOidArrayNative.cs diff --git a/LibGit2Sharp.Tests/CommitAncestorFixture.cs b/LibGit2Sharp.Tests/CommitAncestorFixture.cs index c752f7415..322141984 100644 --- a/LibGit2Sharp.Tests/CommitAncestorFixture.cs +++ b/LibGit2Sharp.Tests/CommitAncestorFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -85,6 +86,29 @@ public void FindCommonAncestorForCommitsAsEnumerable(string result, string[] sha } } + [Theory] + [InlineData(new[] { "0350717031bebea6b2600b89a33159935541cee1", "ebc932c47f8800bffdd150c560e111d87bb74f4f" }, new[] { "152325a", "c9a2051" })] + public void FindCommonAncestorsForCommitsAsEnumerable(string[] results, string[] shas) + { + string path = SandboxCrossHistoryRepo(); + using (var repo = new Repository(path)) + { + var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); + + List ancestors = repo.ObjectDatabase.FindMergeBases(commits); + + if (results == null) + { + Assert.Null(ancestors); + } + else + { + Assert.NotNull(ancestors); + Assert.Equal(results, ancestors.Select(a => a.Id.Sha)); + } + } + } + [Theory] [InlineData("4c062a6", "0000000")] [InlineData("0000000", "4c062a6")] diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/a.txt b/LibGit2Sharp.Tests/Resources/crosshistory_wd/a.txt new file mode 100644 index 0000000000000000000000000000000000000000..9096eb59927d50983135dae48bc56f885f156d47 GIT binary patch literal 4 LcmezWFOdNN2fPAb literal 0 HcmV?d00001 diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/b.txt b/LibGit2Sharp.Tests/Resources/crosshistory_wd/b.txt new file mode 100644 index 0000000000000000000000000000000000000000..b2257a703ced76328aa58972de648e403051c41a GIT binary patch literal 4 LcmezWFNpyF2fhMe literal 0 HcmV?d00001 diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/HEAD new file mode 100644 index 000000000..0d9c7d109 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/a diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/index new file mode 100644 index 0000000000000000000000000000000000000000..1b923896e9435384489abb028c327272074522bc GIT binary patch literal 176 zcmZ?q402{*U|<5_(0S*>fiwe<&H>Q~(6|J_W?-2x?RDg&+JG5`rnjDSAI2oj$JR-%wnglne?#$s9 VrZ+4y21;kx(!32XzmLgo0ss&?D}n$3 literal 0 HcmV?d00001 diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/03/50717031bebea6b2600b89a33159935541cee1 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/03/50717031bebea6b2600b89a33159935541cee1 new file mode 100644 index 0000000000000000000000000000000000000000..1d120b1079e05f60b5303bdfc65ff4d0c7f59a83 GIT binary patch literal 158 zcmV;P0Ac@l0iBLP3c^4P06p(3@&U`HZj)UQ(UagW(rjuy*h-BczFyHEc$|TO(fNA4 zIJ9kA`C|h-^#-QIrw}0AjsMRX; MjXyoS0n0WKX#CDM=D5Y#KYEwyev0J$y`XYlTA-?KvY_@9Rnt zTTJLgv}QF^>C*pvmi4)L?4>6V=y!@Ff%bxNYpE-C}Ef|?RDg&+JG5`rnjDSAI@W&P>0!cZwZ*^8w``27XSy Fg#fA#5hyI=2-bZLV-Dq93U+G>sYS^||wD jdtacVvm=jzEgaD!n*2Me{NtK?En4CW_K5ic-99>ZUuruP literal 0 HcmV?d00001 diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/b2/257a703ced76328aa58972de648e403051c41a b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/b2/257a703ced76328aa58972de648e403051c41a new file mode 100644 index 0000000000000000000000000000000000000000..038e29dabea8018c323c443111fd84c6bc0773c0 GIT binary patch literal 19 acmbvL8d`i}PM@5zXe3g={DUNH5SFzT0C6C1iR3cg=ZY;a8EBHSY*F6HNF>VMkhO~+9|Ms8(;Lq5Q*=5hLj>+QUQz0D4? Jd;?HiTAVtvWfTAa literal 0 HcmV?d00001 diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/eb/c932c47f8800bffdd150c560e111d87bb74f4f b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/eb/c932c47f8800bffdd150c560e111d87bb74f4f new file mode 100644 index 000000000..ea12ee63e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/eb/c932c47f8800bffdd150c560e111d87bb74f4f @@ -0,0 +1,2 @@ +x��K +�@ @]�)rK�c�] �b&�Z�R#��W�n�Ń'��z1!�lQ� � Q� �X� i$ն�0�ʔ���E'�B��^26Q�TU�g������z�� + /// A pointer to an array of ids. + /// + public IntPtr Ids; + + /// + /// The number of ids in the array. + /// + public UIntPtr Length; + + /// + /// Resets the GitOidArray to default values. + /// + public void Reset() + { + Ids = IntPtr.Zero; + Length = UIntPtr.Zero; + } + } +} diff --git a/LibGit2Sharp/Core/GitOidArrayNative.cs b/LibGit2Sharp/Core/GitOidArrayNative.cs new file mode 100644 index 000000000..ea00e3d96 --- /dev/null +++ b/LibGit2Sharp/Core/GitOidArrayNative.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + /// + /// A git_oidarray where the id array and ids themselves were allocated + /// with libgit2's allocator. Only libgit2 can free this git_oidarray. + /// + [StructLayout(LayoutKind.Sequential)] + internal class GitOidArrayNative : IDisposable + { + public GitOidArray Array; + + /// + /// Reads each GitOid from the array. + /// + public GitOid[] ReadOids() + { + var count = checked((int)Array.Length.ToUInt32()); + + GitOid[] toReturn = new GitOid[count]; + + for (int i = 0; i < count; i++) + { + toReturn[i] = (Array.Ids + i * Marshal.SizeOf(typeof(GitOid))).MarshalAs(); + } + + return toReturn; + } + + public void Dispose() + { + if (Array.Ids != IntPtr.Zero) + { + NativeMethods.git_oidarray_free(ref Array); + } + + // Now that we've freed the memory, zero out the structure. + Array.Reset(); + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e3389acbf..f695263b0 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -714,6 +714,13 @@ internal static extern int git_merge_base_many( int length, [In] GitOid[] input_array); + [DllImport(libgit2)] + internal static extern int git_merge_bases_many( + out GitOidArray mergeBases, + RepositorySafeHandle repo, + int length, + [In] GitOid[] input_array); + [DllImport(libgit2)] internal static extern int git_merge_base_octopus( out GitOid mergeBase, @@ -721,6 +728,10 @@ internal static extern int git_merge_base_octopus( int length, [In] GitOid[] input_array); + [DllImport(libgit2)] + internal static extern void git_oidarray_free( + ref GitOidArray array); + [DllImport(libgit2)] internal static extern int git_annotated_commit_from_ref( out GitAnnotatedCommitHandle annotatedCommit, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index f442d7b71..19f769976 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1126,6 +1126,29 @@ public static ObjectId git_merge_base_many(RepositorySafeHandle repo, GitOid[] c return ret; } + public static ObjectId[] git_merge_bases_many(RepositorySafeHandle repo, GitOid[] commitIds) + { + var array = new GitOidArrayNative(); + + try + { + int res = NativeMethods.git_merge_bases_many(out array.Array, repo, commitIds.Length, commitIds); + + if (res == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.ZeroResult(res); + + return Array.ConvertAll(array.ReadOids(), id => (ObjectId)id); + } + finally + { + array.Dispose(); + } + } + public static ObjectId git_merge_base_octopus(RepositorySafeHandle repo, GitOid[] commitIds) { GitOid ret; diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 9a00d0dad..902a51c0c 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -71,6 +71,8 @@ + + diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index d855e2c6f..459cb3789 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -586,6 +586,38 @@ public virtual Commit FindMergeBase(IEnumerable commits, MergeBaseFindin return id == null ? null : repo.Lookup(id); } + /// + /// Find all best possible merge bases given two or more according to the . + /// + /// The s for which to find the merge bases. + /// The merge bases or null if none found. + public virtual List FindMergeBases(IEnumerable commits) + { + Ensure.ArgumentNotNull(commits, "commits"); + + List ids = new List(8); + int count = 0; + + foreach (var commit in commits) + { + if (commit == null) + { + throw new ArgumentException("Enumerable contains null at position: " + count.ToString(CultureInfo.InvariantCulture), "commits"); + } + ids.Add(commit.Id.Oid); + count++; + } + + if (count < 2) + { + throw new ArgumentException("The enumerable must contains at least two commits.", "commits"); + } + + var baseIds = Proxy.git_merge_bases_many(repo.Handle, ids.ToArray()); + + return baseIds == null ? null : baseIds.Select(id => repo.Lookup(id)).ToList(); + } + /// /// Perform a three-way merge of two commits, looking up their /// commit ancestor. The returned index will contain the results From e470838231aa9288434af82bab50b02239d9091b Mon Sep 17 00:00:00 2001 From: John Gietzen Date: Tue, 14 Jul 2015 23:18:54 -0700 Subject: [PATCH 2/3] Improve the signature of FindMergeBases and its underlying methods. --- LibGit2Sharp.Tests/CommitAncestorFixture.cs | 13 +++---------- LibGit2Sharp/Core/Proxy.cs | 2 +- LibGit2Sharp/ObjectDatabase.cs | 8 ++++---- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/LibGit2Sharp.Tests/CommitAncestorFixture.cs b/LibGit2Sharp.Tests/CommitAncestorFixture.cs index 322141984..774488e3a 100644 --- a/LibGit2Sharp.Tests/CommitAncestorFixture.cs +++ b/LibGit2Sharp.Tests/CommitAncestorFixture.cs @@ -95,17 +95,10 @@ public void FindCommonAncestorsForCommitsAsEnumerable(string[] results, string[] { var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); - List ancestors = repo.ObjectDatabase.FindMergeBases(commits); + var ancestors = repo.ObjectDatabase.FindMergeBases(commits).ToArray(); - if (results == null) - { - Assert.Null(ancestors); - } - else - { - Assert.NotNull(ancestors); - Assert.Equal(results, ancestors.Select(a => a.Id.Sha)); - } + Assert.NotNull(ancestors); + Assert.Equal(results, ancestors.Select(a => a.Id.Sha)); } } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 19f769976..9f94e81ab 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1136,7 +1136,7 @@ public static ObjectId[] git_merge_bases_many(RepositorySafeHandle repo, GitOid[ if (res == (int)GitErrorCode.NotFound) { - return null; + return new ObjectId[0]; } Ensure.ZeroResult(res); diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 459cb3789..4490375fb 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -587,11 +587,11 @@ public virtual Commit FindMergeBase(IEnumerable commits, MergeBaseFindin } /// - /// Find all best possible merge bases given two or more according to the . + /// Find all best possible merge bases given two or more . /// /// The s for which to find the merge bases. - /// The merge bases or null if none found. - public virtual List FindMergeBases(IEnumerable commits) + /// An enumerable collection containing all best possible merge bases. + public virtual IEnumerable FindMergeBases(IEnumerable commits) { Ensure.ArgumentNotNull(commits, "commits"); @@ -615,7 +615,7 @@ public virtual List FindMergeBases(IEnumerable commits) var baseIds = Proxy.git_merge_bases_many(repo.Handle, ids.ToArray()); - return baseIds == null ? null : baseIds.Select(id => repo.Lookup(id)).ToList(); + return baseIds.Select(id => repo.Lookup(id)).ToList(); } /// From ecd8fcf319699744164a0d6972ab2f7d89d8b96a Mon Sep 17 00:00:00 2001 From: John Gietzen Date: Tue, 14 Jul 2015 23:20:21 -0700 Subject: [PATCH 3/3] Add coverage for more FindMergeBases scenarios. --- LibGit2Sharp.Tests/CommitAncestorFixture.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/LibGit2Sharp.Tests/CommitAncestorFixture.cs b/LibGit2Sharp.Tests/CommitAncestorFixture.cs index 774488e3a..85bb293f9 100644 --- a/LibGit2Sharp.Tests/CommitAncestorFixture.cs +++ b/LibGit2Sharp.Tests/CommitAncestorFixture.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -9,6 +10,8 @@ namespace LibGit2Sharp.Tests { public class CommitAncestorFixture : BaseFixture { + private static int orphans; + /* * BareTestRepoPath structure * @@ -28,7 +31,7 @@ public class CommitAncestorFixture : BaseFixture * | * * commit 8496071c1b46c854b31185ea97743be6a877447 * - */ + */ [Theory] [InlineData("5b5b025afb0b4c913b4c338a42934a3863bf3644", "c47800c", "9fd738e")] @@ -88,6 +91,8 @@ public void FindCommonAncestorForCommitsAsEnumerable(string result, string[] sha [Theory] [InlineData(new[] { "0350717031bebea6b2600b89a33159935541cee1", "ebc932c47f8800bffdd150c560e111d87bb74f4f" }, new[] { "152325a", "c9a2051" })] + [InlineData(new[] { "0350717031bebea6b2600b89a33159935541cee1", "ebc932c47f8800bffdd150c560e111d87bb74f4f" }, new[] { "152325a", "c9a2051", "-" })] + [InlineData(new string[0], new[] { "-", "-" })] public void FindCommonAncestorsForCommitsAsEnumerable(string[] results, string[] shas) { string path = SandboxCrossHistoryRepo(); @@ -140,7 +145,7 @@ private static Commit CreateOrphanedCommit(IRepository repo) Commit orphanedCommit = repo.ObjectDatabase.CreateCommit( random.Author, random.Committer, - "This is a test commit created by 'CommitFixture.CannotFindCommonAncestorForCommmitsWithoutCommonAncestor'", + string.Format("This is a test commit (#{0}) created by 'CommitFixture.CannotFindCommonAncestorForCommmitsWithoutCommonAncestor'", Interlocked.Increment(ref orphans)), random.Tree, Enumerable.Empty(), false);