Skip to content

Exposed git_merge_base as CommitCollection.GetMergeBase(...) #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions LibGit2Sharp.Tests/CommitFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,167 @@ public void CanCorrectlyCountCommitsWhenSwitchingToAnotherBranch()
}
}

/*
* BareTestRepoPath structure
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


* commit 4c062a6361ae6959e06292c1fa5e2822d9c96345
|
* commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644
|\
| |
| * commit c47800c7266a2be04c571c04d5a6614691ea99bd
| |
* | commit 9fd738e8f7967c078dceed8190330fc8648ee56a
| |
* | commit 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
|/
|
* commit 5b5b025afb0b4c913b4c338a42934a3863bf3644
|
* commit 8496071c1b46c854b31185ea97743be6a877447

*/
[Fact]
public void CanCorrectlyFindCommonAncestorForTwoCommits()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("c47800c7266a2be04c571c04d5a6614691ea99bd");

Commit second = repo.Lookup<Commit>("9fd738e8f7967c078dceed8190330fc8648ee56a");

Commit ancestor = repo.Commits.FindCommonAncestor(first, second);

Assert.NotNull(ancestor);

ancestor.Id.Sha.ShouldEqual("5b5b025afb0b4c913b4c338a42934a3863bf3644");
}
}

[Fact]
public void CanCorrectlyFindCommonAncestorForTwoCommitsAsEnumerable()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("c47800c7266a2be04c571c04d5a6614691ea99bd");

Commit second = repo.Lookup<Commit>("9fd738e8f7967c078dceed8190330fc8648ee56a");

Commit ancestor = repo.Commits.FindCommonAncestor(new[] { first, second });

Assert.NotNull(ancestor);

ancestor.Id.Sha.ShouldEqual("5b5b025afb0b4c913b4c338a42934a3863bf3644");
}
}

[Fact]
public void CanCorrectlyFindCommonAncestorForSeveralCommits()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("4c062a6361ae6959e06292c1fa5e2822d9c96345");

Commit second = repo.Lookup<Commit>("be3563ae3f795b2b4353bcce3a527ad0a4f7f644");

Commit third = repo.Lookup<Commit>("c47800c7266a2be04c571c04d5a6614691ea99bd");

Commit fourth = repo.Lookup<Commit>("5b5b025afb0b4c913b4c338a42934a3863bf3644");

Commit ancestor = repo.Commits.FindCommonAncestor(new[] { first, second, third, fourth });

Assert.NotNull(ancestor);

ancestor.Id.Sha.ShouldEqual("5b5b025afb0b4c913b4c338a42934a3863bf3644");
}
}

[Fact]
public void CannotFindCommonAncestorForTwoCommmitsWithoutCommonAncestor()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("4c062a6361ae6959e06292c1fa5e2822d9c96345");

Commit second = repo.Lookup<Commit>("be3563ae3f795b2b4353bcce3a527ad0a4f7f644");

Commit third = repo.Lookup<Commit>("c47800c7266a2be04c571c04d5a6614691ea99bd");

Commit fourth = repo.Lookup<Commit>("5b5b025afb0b4c913b4c338a42934a3863bf3644");

// create new commit with no parents
TreeDefinition newTreeDef = TreeDefinition.From(first.Tree);
Tree newTree = repo.ObjectDatabase.CreateTree(newTreeDef);
Commit orphanedCommit = repo.ObjectDatabase.CreateCommit(
"This is a test commit created by 'CommitFixture.CannotFindCommonAncestorForCommmitsWithoutCommonAncestor'",
first.Author,
first.Committer,
newTree,
Enumerable.Empty<Commit>());

Commit ancestor = repo.Commits.FindCommonAncestor(new[] { first, second, orphanedCommit, third, fourth });

Assert.Null(ancestor);
}
}

[Fact]
public void CannotFindCommonAncestorForSeveralCommmitsWithoutCommonAncestor()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("4c062a6361ae6959e06292c1fa5e2822d9c96345");

// create new commit with no parents
TreeDefinition newTreeDef = TreeDefinition.From(first.Tree);
Tree newTree = repo.ObjectDatabase.CreateTree(newTreeDef);
Commit orphanedCommit = repo.ObjectDatabase.CreateCommit(
"This is a test commit created by 'CommitFixture.CannotFindCommonAncestorForCommmitsWithoutCommonAncestor'",
first.Author,
first.Committer,
newTree,
Enumerable.Empty<Commit>());

Commit ancestor = repo.Commits.FindCommonAncestor(first, orphanedCommit);

Assert.Null(ancestor);
}
}

public void FindCommonAncestorForSingleCommitThrows()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("4c062a6361ae6959e06292c1fa5e2822d9c96345");

Assert.Throws<ArgumentException>(() => repo.Commits.FindCommonAncestor(new[] { first }));
}
}

public void FindCommonAncestorForEnumerableWithNullCommitThrows()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("4c062a6361ae6959e06292c1fa5e2822d9c96345");

Commit second = repo.Lookup<Commit>("be3563ae3f795b2b4353bcce3a527ad0a4f7f644");

Assert.Throws<ArgumentException>(() => repo.Commits.FindCommonAncestor(new[] { first, second, null }));
}
}

public void FindCommonAncestorForWithNullCommitThrows()
{
using (var repo = new Repository(BareTestRepoPath))
{
Commit first = repo.Lookup<Commit>("4c062a6361ae6959e06292c1fa5e2822d9c96345");

Assert.Throws<ArgumentException>(() => repo.Commits.FindCommonAncestor(first, null));

Assert.Throws<ArgumentException>(() => repo.Commits.FindCommonAncestor(null, first));
}
}

[Fact]
public void CanEnumerateCommits()
{
Expand Down
55 changes: 55 additions & 0 deletions LibGit2Sharp/CommitCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,61 @@ private static bool PointsAtTheHead(string shaOrRefName)
return ("HEAD".Equals(shaOrRefName, StringComparison.Ordinal) || "refs/heads/master".Equals(shaOrRefName, StringComparison.Ordinal));
}

/// <summary>
/// Find as good common ancestors as possible for a merge given two <see cref="Commit"/>.
/// </summary>
/// <param name="first">The first <see cref="Commit"/> for which to find the common ancestor.</param>
/// <param name="second">The second <see cref="Commit"/> for which to find the common ancestor.</param>
/// <returns>The common ancestor or null if none found.</returns>
public Commit FindCommonAncestor(Commit first, Commit second)
{
Ensure.ArgumentNotNull(first, "first");
Ensure.ArgumentNotNull(second, "second");
GitOid ret;
using (var osw1 = new ObjectSafeWrapper(first.Id, this.repo))
using (var osw2 = new ObjectSafeWrapper(second.Id, this.repo))
{
int result = NativeMethods.git_merge_base(out ret, this.repo.Handle, osw1.ObjectPtr, osw2.ObjectPtr);
if (result == (int)GitErrorCode.GIT_ENOTFOUND)
return null;

Ensure.Success(result);
return this.repo.Lookup<Commit>(new ObjectId(ret));
}
}

/// <summary>
/// Find as good common ancestors as possible for a merge given two or more <see cref="Commit"/>.
/// </summary>
/// <param name="commits">The <see cref="Commit"/> for which to find the common ancestor.</param>
/// <returns>The common ancestor or null if none found.</returns>
public Commit FindCommonAncestor(IEnumerable<Commit> commits)
{
Ensure.ArgumentNotNull(commits, "commits");
Commit ret = null;
int count = 0;
foreach (var commit in commits)
{
if (commit == null)
throw new ArgumentException("Enumerable contains null at position: " + count.ToString(CultureInfo.InvariantCulture), "commits");

count++;

if (ret == null)
ret = commit;
else
{
ret = this.FindCommonAncestor(ret, commit);
if (ret == null)
break;
}
}
if (count < 2)
throw new ArgumentException("The enumerable must contains at least two commits.", "commits");

return ret;
}

/// <summary>
/// Stores the content of the <see cref = "Repository.Index" /> as a new <see cref = "Commit" /> into the repository.
/// The tip of the <see cref = "Repository.Head"/> will be used as the parent of this new Commit.
Expand Down
7 changes: 7 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,13 @@ public static extern int git_index_open(
[DllImport(libgit2)]
public static extern int git_index_write(IndexSafeHandle index);

[DllImport(libgit2)]
public static extern int git_merge_base(
out GitOid mergeBase,
RepositorySafeHandle repo,
GitObjectSafeHandle one,
GitObjectSafeHandle two);

[DllImport(libgit2)]
public static extern int git_odb_exists(ObjectDatabaseSafeHandle odb, ref GitOid id);

Expand Down
20 changes: 19 additions & 1 deletion LibGit2Sharp/IQueryableCommitCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace LibGit2Sharp
using System.Collections.Generic;

namespace LibGit2Sharp
{
public interface IQueryableCommitCollection : ICommitCollection //TODO: Find a name that's more explicit than IQueryableCommitCollection
{
Expand All @@ -20,5 +22,21 @@ public interface IQueryableCommitCollection : ICommitCollection //TODO: Find a n
/// <param name = "amendPreviousCommit">True to amend the current <see cref = "Commit"/> pointed at by <see cref = "Repository.Head"/>, false otherwise.</param>
/// <returns>The generated <see cref = "Commit" />.</returns>
Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit);


/// <summary>
/// Find as good common ancestors as possible for a merge given two <see cref="Commit"/>.
/// </summary>
/// <param name="first">The first <see cref="Commit"/> for which to find the common ancestor.</param>
/// <param name="second">The second <see cref="Commit"/> for which to find the common ancestor.</param>
/// <returns>The common ancestor or null if none found.</returns>
Commit FindCommonAncestor(Commit first, Commit second);

/// <summary>
/// Find as good common ancestors as possible for a merge given two or more <see cref="Commit"/>.
/// </summary>
/// <param name="commits">The <see cref="Commit"/> for which to find the common ancestor.</param>
/// <returns>The common ancestor or null if none found.</returns>
Commit FindCommonAncestor(IEnumerable<Commit> commits);
}
}