diff --git a/LibGit2Sharp.Tests/BlameFixture.cs b/LibGit2Sharp.Tests/BlameFixture.cs index c9c7fab63..b0fcf77fe 100644 --- a/LibGit2Sharp.Tests/BlameFixture.cs +++ b/LibGit2Sharp.Tests/BlameFixture.cs @@ -75,7 +75,7 @@ public void CanStopBlame() // (be3563a comes after 9fd738e8) var blame = repo.Blame("new.txt", new BlameOptions {StoppingAt = "be3563a"}); Assert.True(blame[0].FinalCommit.Sha.StartsWith("be3563a")); - } + } } } } diff --git a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs index 04d584610..5ced715e0 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs @@ -615,7 +615,7 @@ public void CanIncludeUnmodifiedEntriesWhenEnabled() Assert.Equal(2, changes.Count()); Assert.Equal(1, changes.Unmodified.Count()); Assert.Equal(1, changes.Modified.Count()); - } + } } [Fact] diff --git a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs index e47697f3e..9913aefc8 100644 --- a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs +++ b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs @@ -481,5 +481,56 @@ public void CanCreateATagAnnotationWithAnEmptyMessage() Assert.Equal(string.Empty, tagAnnotation.Message); } } + + [Theory] + [InlineData("c47800c", "9fd738e", "5b5b025", 1, 2)] + [InlineData("9fd738e", "c47800c", "5b5b025", 2, 1)] + public void CanCalculateHistoryDivergence( + string sinceSha, string untilSha, + string expectedAncestorSha, int? expectedAheadBy, int? expectedBehindBy) + { + using (var repo = new Repository(BareTestRepoPath)) + { + var since = repo.Lookup(sinceSha); + var until = repo.Lookup(untilSha); + + HistoryDivergence div = repo.ObjectDatabase.CalculateHistoryDivergence(since, until); + + Assert.Equal(expectedAheadBy, div.AheadBy); + Assert.Equal(expectedBehindBy, div.BehindBy); + Assert.Equal(expectedAncestorSha, div.CommonAncestor.Id.ToString(7)); + } + } + + [Theory] + [InlineData("c47800c", "41bc8c6907", 3, 2)] + public void CanCalculateHistoryDivergenceWhenNoAncestorIsShared( + string sinceSha, string untilSha, + int? expectedAheadBy, int? expectedBehindBy) + { + using (var repo = new Repository(BareTestRepoPath)) + { + var since = repo.Lookup(sinceSha); + var until = repo.Lookup(untilSha); + + HistoryDivergence div = repo.ObjectDatabase.CalculateHistoryDivergence(since, until); + + Assert.Equal(expectedAheadBy, div.AheadBy); + Assert.Equal(expectedBehindBy, div.BehindBy); + Assert.Null(div.CommonAncestor); + } + } + + [Fact] + public void CalculatingHistoryDivergenceWithBadParamsThrows() + { + using (var repo = new Repository(BareTestRepoPath)) + { + Assert.Throws( + () => repo.ObjectDatabase.CalculateHistoryDivergence(repo.Head.Tip, null)); + Assert.Throws( + () => repo.ObjectDatabase.CalculateHistoryDivergence(null, repo.Head.Tip)); + } + } } } diff --git a/LibGit2Sharp.Tests/TreeFixture.cs b/LibGit2Sharp.Tests/TreeFixture.cs index 679345f5a..ced6f2788 100644 --- a/LibGit2Sharp.Tests/TreeFixture.cs +++ b/LibGit2Sharp.Tests/TreeFixture.cs @@ -216,9 +216,9 @@ public void CanParseSymlinkTreeEntries() Tree t = repo.ObjectDatabase.CreateTree(td); var te = t["A symlink"]; - + Assert.NotNull(te); - + Assert.Equal(Mode.SymbolicLink, te.Mode); Assert.Equal(linkContent, te.Target); } diff --git a/LibGit2Sharp/BlameHunkCollection.cs b/LibGit2Sharp/BlameHunkCollection.cs index baea545a9..18a3e24ba 100644 --- a/LibGit2Sharp/BlameHunkCollection.cs +++ b/LibGit2Sharp/BlameHunkCollection.cs @@ -13,7 +13,7 @@ namespace LibGit2Sharp public class BlameHunkCollection : IEnumerable { private readonly IRepository repo; - private readonly List hunks = new List(); + private readonly List hunks = new List(); /// /// For easy mocking diff --git a/LibGit2Sharp/BranchTrackingDetails.cs b/LibGit2Sharp/BranchTrackingDetails.cs index 0acce526e..ad7153c88 100644 --- a/LibGit2Sharp/BranchTrackingDetails.cs +++ b/LibGit2Sharp/BranchTrackingDetails.cs @@ -1,5 +1,4 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; +using System; namespace LibGit2Sharp { @@ -8,10 +7,7 @@ namespace LibGit2Sharp /// public class BranchTrackingDetails { - private readonly Repository repo; - private readonly Branch branch; - private readonly Lazy> aheadBehind; - private readonly Lazy commonAncestor; + private readonly HistoryDivergence historyDivergence; /// /// Needed for mocking purposes. @@ -21,11 +17,13 @@ protected BranchTrackingDetails() internal BranchTrackingDetails(Repository repo, Branch branch) { - this.repo = repo; - this.branch = branch; + if (!branch.IsTracking || branch.Tip == null || branch.TrackedBranch.Tip == null) + { + historyDivergence = new NullHistoryDivergence(); + return; + } - aheadBehind = new Lazy>(ResolveAheadBehind); - commonAncestor = new Lazy(ResolveCommonAncestor); + historyDivergence = repo.ObjectDatabase.CalculateHistoryDivergence(branch.Tip, branch.TrackedBranch.Tip); } /// @@ -37,7 +35,7 @@ internal BranchTrackingDetails(Repository repo, Branch branch) /// public virtual int? AheadBy { - get { return aheadBehind.Value.Item1; } + get { return historyDivergence.AheadBy; } } /// @@ -49,7 +47,7 @@ public virtual int? AheadBy /// public virtual int? BehindBy { - get { return aheadBehind.Value.Item2; } + get { return historyDivergence.BehindBy; } } /// @@ -61,29 +59,7 @@ public virtual int? BehindBy /// public virtual Commit CommonAncestor { - get { return commonAncestor.Value; } - } - - private Tuple ResolveAheadBehind() - { - return branch.IsTracking - ? Proxy.git_graph_ahead_behind(repo.Handle, branch.TrackedBranch.Tip, branch.Tip) - : new Tuple(null, null); - } - - private Commit ResolveCommonAncestor() - { - if (!branch.IsTracking) - { - return null; - } - - if (branch.Tip == null || branch.TrackedBranch.Tip == null) - { - return null; - } - - return repo.Commits.FindCommonAncestor(branch.Tip, branch.TrackedBranch.Tip); + get { return historyDivergence.CommonAncestor; } } } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 2c1f628db..d75488119 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -836,7 +836,7 @@ internal static extern int git_refspec_rtransform( UIntPtr outlen, GitRefSpecHandle refSpec, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - + [DllImport(libgit2)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_string( diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 73aa7db05..2f113e464 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2731,7 +2731,7 @@ public static IList RemoteLsHelper(IntPtr heads, UIntPtr size) list.Add((GitRemoteHead)Marshal.PtrToStructure(rawHeads[i], typeof (GitRemoteHead))); } return list; - } + } } private static bool RepositoryStateChecker(RepositorySafeHandle repo, Func checker) diff --git a/LibGit2Sharp/HistoryDivergence.cs b/LibGit2Sharp/HistoryDivergence.cs new file mode 100644 index 000000000..09caa99b9 --- /dev/null +++ b/LibGit2Sharp/HistoryDivergence.cs @@ -0,0 +1,81 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Compat; + +namespace LibGit2Sharp +{ + /// + /// Holds information about the potential ancestor + /// and distance from it and two specified s. + /// + public class HistoryDivergence + { + private readonly Lazy commonAncestor; + + /// + /// Needed for mocking purposes. + /// + protected HistoryDivergence() + { } + + internal HistoryDivergence(Repository repo, Commit one, Commit another) + { + commonAncestor = new Lazy(() => repo.Commits.FindCommonAncestor(one, another)); + Tuple div = Proxy.git_graph_ahead_behind(repo.Handle, another, one); + + One = one; + Another = another; + AheadBy = div.Item1; + BehindBy = div.Item2; + } + + /// + /// Gets the being used as a reference. + /// + public virtual Commit One { get; private set; } + + /// + /// Gets the being compared against . + /// + public virtual Commit Another { get; private set; } + + /// + /// Gets the number of commits that are reachable from , + /// but not from . + /// + /// This property will return null when + /// and do not share a common ancestor. + /// + /// + public virtual int? AheadBy { get; private set; } + + /// + /// Gets the number of commits that are reachable from , + /// but not from . + /// + /// This property will return null when + /// and do not share a common ancestor. + /// + /// + public virtual int? BehindBy { get; private set; } + + /// + /// Returns the best possible common ancestor of + /// and or null if none found. + /// + public virtual Commit CommonAncestor + { + get + { + return commonAncestor.Value; + } + } + } + + internal class NullHistoryDivergence : HistoryDivergence + { + public override Commit CommonAncestor + { + get { return null; } + } + } +} diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index d067dba05..af87d0931 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -83,6 +83,7 @@ + diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 0385a4f77..a31681eaa 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -235,5 +235,20 @@ public virtual TagAnnotation CreateTagAnnotation(string name, GitObject target, return repo.Lookup(tagId); } + + /// + /// Returns the merge base (best common ancestor) of the given commits + /// and the distance between each of these commits and this base. + /// + /// The being used as a reference. + /// The being compared against . + /// A instance of . + public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit another) + { + Ensure.ArgumentNotNull(one, "one"); + Ensure.ArgumentNotNull(another, "another"); + + return new HistoryDivergence(repo, one, another); + } } } diff --git a/LibGit2Sharp/RemoteUpdater.cs b/LibGit2Sharp/RemoteUpdater.cs index aaef72057..74b989a3e 100644 --- a/LibGit2Sharp/RemoteUpdater.cs +++ b/LibGit2Sharp/RemoteUpdater.cs @@ -99,10 +99,10 @@ public virtual ICollection FetchRefSpecs /// be used during a Push operation /// /// Changing the list updates the . - public virtual ICollection PushRefSpecs - { + public virtual ICollection PushRefSpecs + { get { return pushRefSpecs; } - set { pushRefSpecs.ReplaceAll(value); } + set { pushRefSpecs.ReplaceAll(value); } } private class UpdatingCollection : ICollection diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index bfee7db0b..ef28f947f 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -559,7 +559,7 @@ public static string Clone(string sourceUrl, string workdirPath, Credentials credentials = null) { CheckoutCallbacks checkoutCallbacks = CheckoutCallbacks.GenerateCheckoutCallbacks(onCheckoutProgress, null); - + var callbacks = new RemoteCallbacks(null, onTransferProgress, null, credentials); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index 719301115..f897bcd6c 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -353,7 +353,7 @@ internal static IEnumerable Committishes(this Repository repo, object { ObjectId singleReturnValue = null; - if (identifier is string) + if (identifier is string) { singleReturnValue = DereferenceToCommit(repo, identifier as string); } diff --git a/LibGit2Sharp/SimilarityOptions.cs b/LibGit2Sharp/SimilarityOptions.cs index 040649c36..13d26abf2 100644 --- a/LibGit2Sharp/SimilarityOptions.cs +++ b/LibGit2Sharp/SimilarityOptions.cs @@ -128,7 +128,7 @@ public static SimilarityOptions Default /// The mode for detecting renames and copies /// public RenameDetectionMode RenameDetectionMode { get; set; } - + /// /// The mode for handling whitespace when comparing files ///