diff --git a/LibGit2Sharp.Tests/CommitAncestorFixture.cs b/LibGit2Sharp.Tests/CommitAncestorFixture.cs index c752f7415..85bb293f9 100644 --- a/LibGit2Sharp.Tests/CommitAncestorFixture.cs +++ b/LibGit2Sharp.Tests/CommitAncestorFixture.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -8,6 +10,8 @@ namespace LibGit2Sharp.Tests { public class CommitAncestorFixture : BaseFixture { + private static int orphans; + /* * BareTestRepoPath structure * @@ -27,7 +31,7 @@ public class CommitAncestorFixture : BaseFixture * | * * commit 8496071c1b46c854b31185ea97743be6a877447 * - */ + */ [Theory] [InlineData("5b5b025afb0b4c913b4c338a42934a3863bf3644", "c47800c", "9fd738e")] @@ -85,6 +89,24 @@ 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(); + using (var repo = new Repository(path)) + { + var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); + + var ancestors = repo.ObjectDatabase.FindMergeBases(commits).ToArray(); + + Assert.NotNull(ancestors); + Assert.Equal(results, ancestors.Select(a => a.Id.Sha)); + } + } + [Theory] [InlineData("4c062a6", "0000000")] [InlineData("0000000", "4c062a6")] @@ -123,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); diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/a.txt b/LibGit2Sharp.Tests/Resources/crosshistory_wd/a.txt new file mode 100644 index 000000000..9096eb599 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/a.txt differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/b.txt b/LibGit2Sharp.Tests/Resources/crosshistory_wd/b.txt new file mode 100644 index 000000000..b2257a703 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/b.txt differ 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 000000000..1b923896e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/index differ 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 000000000..1d120b107 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/03/50717031bebea6b2600b89a33159935541cee1 differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/15/2325a8a96b610627aaae41a00391c3644079e4 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/15/2325a8a96b610627aaae41a00391c3644079e4 new file mode 100644 index 000000000..d26fee2a5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/15/2325a8a96b610627aaae41a00391c3644079e4 differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/2c/e209a66a1f9c973b3c06e7fc4bd5fc431b964a b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/2c/e209a66a1f9c973b3c06e7fc4bd5fc431b964a new file mode 100644 index 000000000..7a3cc9925 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/2c/e209a66a1f9c973b3c06e7fc4bd5fc431b964a differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/65/8844f325a396f77d223d66464b93b75f107c87 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/65/8844f325a396f77d223d66464b93b75f107c87 new file mode 100644 index 000000000..0546640bb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/65/8844f325a396f77d223d66464b93b75f107c87 differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/6a/caa5c7a6876fe73611ba4a46966786efe794a5 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/6a/caa5c7a6876fe73611ba4a46966786efe794a5 new file mode 100644 index 000000000..ae49fe036 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/6a/caa5c7a6876fe73611ba4a46966786efe794a5 differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/90/96eb59927d50983135dae48bc56f885f156d47 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/90/96eb59927d50983135dae48bc56f885f156d47 new file mode 100644 index 000000000..9f556a0c1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/90/96eb59927d50983135dae48bc56f885f156d47 differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/a6/9878c70d9cce9a1fbec1712d1fcd4f609e689b b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/a6/9878c70d9cce9a1fbec1712d1fcd4f609e689b new file mode 100644 index 000000000..59e9482e1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/a6/9878c70d9cce9a1fbec1712d1fcd4f609e689b differ 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 000000000..038e29dab Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/b2/257a703ced76328aa58972de648e403051c41a differ diff --git a/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/c9/a20513b0648832b41b0f875ab16c92e4b7dae7 b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/c9/a20513b0648832b41b0f875ab16c92e4b7dae7 new file mode 100644 index 000000000..02946305e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/crosshistory_wd/dot_git/objects/c9/a20513b0648832b41b0f875ab16c92e4b7dae7 differ 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 @@ +xK +@ @])rKc] b&ZR#WnŃ'z1!lQ  Q X i$ն0ʔE'B^26QTUgz + /// 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..9f94e81ab 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 new ObjectId[0]; + } + + 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..4490375fb 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 . + /// + /// The s for which to find the merge bases. + /// An enumerable collection containing all best possible merge bases. + public virtual IEnumerable 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.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