Skip to content
Open
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
66 changes: 66 additions & 0 deletions LibGit2Sharp.Tests/MergeFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,72 @@ private Commit AddFileCommitToRepo(IRepository repository, string filename, stri
return repository.Commit("New commit", Constants.Signature, Constants.Signature);
}


[Theory]
[InlineData("master", MergeAnalysis.Unborn | MergeAnalysis.FastForward, true)]
[InlineData("fast_forward", MergeAnalysis.Normal | MergeAnalysis.FastForward, false)]
[InlineData("conflicts", MergeAnalysis.Normal, false)]

public void CanAnalyzeMerge(string branchName, MergeAnalysis analysis, bool makeUnborn)
{
string path = SandboxMergeTestRepo();
using (var repo = new Repository(path))
{
if (makeUnborn)
{
repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn");
}

// test both Commit and Reference overload
MergeAnalysisResult result = repo.AnalyzeMerge(repo.Branches[branchName].Tip);
MergeAnalysisResult result2 = repo.AnalyzeMerge(repo.Refs["refs/heads/" + branchName]);

Assert.Equal(analysis, result.Analysis);
Assert.Equal(analysis, result2.Analysis);
}
}

[Theory]
[InlineData("false", MergePreference.NoFastForward)]
[InlineData("only", MergePreference.FastForwardOnly)]
[InlineData(null, MergePreference.Default)]
public void CanRetrieveMergePreference(string value, MergePreference preference)
{
string path = SandboxMergeTestRepo();
using (var repo = new Repository(path))
{
if (value != null)
{
repo.Config.Set("merge.ff", value);
}

// it doesn't matter too much which branch we ask about, we want the preference
MergeAnalysisResult result = repo.AnalyzeMerge(repo.Branches["fast_forward"].Tip);

Assert.Equal(preference, result.Preference);
}
}

[Fact]
public void CanAnalyzeFastForward()
{
string path = SandboxMergeTestRepo();
using (var repo = new Repository(path))
{
// test both Commit and Reference overload
MergeAnalysisResult result = repo.AnalyzeMerge(repo.Branches["fast_forward"].Tip);
MergeAnalysisResult result2 = repo.AnalyzeMerge(repo.Refs["refs/heads/fast_forward"]);

Assert.True(result.Analysis.HasFlag(MergeAnalysis.FastForward));
Assert.True(result.Analysis.HasFlag(MergeAnalysis.Normal));
Assert.False(result.Analysis.HasFlag(MergeAnalysis.Unborn));
Assert.False(result.Analysis.HasFlag(MergeAnalysis.UpToDate));

Assert.Equal(result, result2);
}
}


// Commit IDs of the checked in merge_testrepo
private const string masterBranchInitialId = "83cebf5389a4adbcb80bda6b68513caee4559802";
private const string fastForwardBranchInitialId = "4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53";
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/MetaFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class MetaFixture
private static readonly HashSet<Type> explicitOnlyInterfaces = new HashSet<Type>
{
typeof(IBelongToARepository), typeof(IDiffResult),
typeof(IAnnotatedCommit),
};

[Fact]
Expand Down
65 changes: 65 additions & 0 deletions LibGit2Sharp/AnnotatedCommit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;

namespace LibGit2Sharp
{
/// <summary>
/// A commit with information about its source. Input for merge and rebase functions
/// </summary>
public class AnnotatedCommit : IDisposable, IAnnotatedCommit
{
internal readonly Repository repository;
internal AnnotatedCommitHandle Handle { get; private set; }

/// <summary>
/// Initialize a <see cref="LibGit2Sharp.AnnotatedCommit"/> from extended SHA-1 syntax
/// </summary>
/// <param name="repository">The repository in which the commit lives</param>
/// <param name="revspec">A string in extended SHA-1 syntax to look up the object</param>
public AnnotatedCommit(Repository repository, string revspec)
{
this.repository = repository;
this.Handle = Proxy.git_annotated_commit_from_revspec(repository.Handle, revspec);
}

/// <summary>
/// Initialize a <see cref="LibGit2Sharp.AnnotatedCommit"/> from extended SHA-1 syntax
/// </summary>
/// <param name="repository">The repository in which the commit lives</param>
/// <param name="reference">A reference pointing to the commit</param>
public AnnotatedCommit(Repository repository, Reference reference)
{
this.repository = repository;
using (var refHandle = Proxy.git_reference_lookup(repository.Handle, reference.CanonicalName, true))
{
this.Handle = Proxy.git_annotated_commit_from_ref(repository.Handle, refHandle);
}
}

/// <summary>
/// Initialize a <see cref="LibGit2Sharp.AnnotatedCommit"/> from extended SHA-1 syntax
/// </summary>
/// <param name="repository">The repository in which the commit lives</param>
/// <param name="commit">A commit</param>
public AnnotatedCommit(Repository repository, Commit commit)
{
this.repository = repository;
this.Handle = Proxy.git_annotated_commit_lookup(repository.Handle, commit.Id.Oid);
}

/// <summary>
/// Releases all resource used by the <see cref="LibGit2Sharp.AnnotatedCommit"/> object.
/// </summary>
public void Dispose()
{
Handle.Dispose();
}

AnnotatedCommit IAnnotatedCommit.GetAnnotatedCommit()
{
return this;
}
}
}

7 changes: 6 additions & 1 deletion LibGit2Sharp/Commit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace LibGit2Sharp
/// A Commit
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class Commit : GitObject
public class Commit : GitObject, IAnnotatedCommit
{
private readonly GitObjectLazyGroup group1;
private readonly GitObjectLazyGroup group2;
Expand Down Expand Up @@ -138,6 +138,11 @@ private string DebuggerDisplay
}
}

AnnotatedCommit IAnnotatedCommit.GetAnnotatedCommit()
{
return repo.LookupAnnotatedCommit(this);
}

private class ParentsCollection : ICollection<Commit>
{
private readonly Lazy<ICollection<Commit>> _parents;
Expand Down
60 changes: 60 additions & 0 deletions LibGit2Sharp/Core/DisposableArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Linq;
using System.Collections.Generic;

namespace LibGit2Sharp.Core
{
/// <summary>
/// An array containing disposables, for convenience when dealing with handles
/// </summary>
internal class DisposableArray<T> : IDisposable where T : IDisposable
{
readonly T[] array;

/// <summary>
/// Create a wrapper for the given array so the contents will be disposed when this class is disposed.
/// </summary>
/// <param name="handles">The array of dispsables</param>
public DisposableArray(T[] handles)
{
array = handles;
}

/// <summary>
/// Create a wrapper for the given array so the contents will be disposed when this class is disposed.
/// <para>
/// The enumerable is first made into an array
/// </para>
/// </summary>
/// <param name="handles">Handles.</param>
public DisposableArray(IEnumerable<T> handles)
{
array = handles.ToArray();
}

/// <summary>
/// The underlying array
/// </summary>
public T[] Array { get { return array; } }

/// <summary>
/// Return the underlying array so we can use this wherever the methods expect an array
/// </summary>
public static implicit operator T[](DisposableArray<T> da)
{
return da.array;
}

/// <summary>
/// Call Dispose on each of the elements of the array
/// </summary>
public void Dispose()
{
foreach (var handle in array)
{
handle.Dispose();
}
}
}
}

16 changes: 16 additions & 0 deletions LibGit2Sharp/IAnnotatedCommit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace LibGit2Sharp
{
/// <summary>
/// Interface to retrieve an annotated commit from another type
/// </summary>
public interface IAnnotatedCommit
{
/// <summary>
/// Retrieve an annotated commit from this object
/// </summary>
AnnotatedCommit GetAnnotatedCommit();
}
}

29 changes: 29 additions & 0 deletions LibGit2Sharp/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,17 @@ public interface IRepository : IDisposable
/// <returns>The <see cref="MergeResult"/> of the merge.</returns>
MergeResult Merge(string committish, Signature merger, MergeOptions options);

/// <summary>
/// Analyze the possibilities of updating HEAD with the given commit(s).
/// <para>
/// It expects objects convertible to annotated commits, so <see cref="LibGit2Sharp.Reference"/> and
/// <see cref="LibGit2Sharp.Commit"/> also work as inputs.
/// </para>
/// </summary>
/// <param name="commits">Commits to merge into HEAD</param>
/// <returns>Which update methods are possible and which preference the user has specified</returns>
MergeAnalysisResult AnalyzeMerge(params IAnnotatedCommit[] commits);

/// <summary>
/// Access to Rebase functionality.
/// </summary>
Expand Down Expand Up @@ -421,5 +432,23 @@ public interface IRepository : IDisposable
/// <param name="reference">The reference mentioned in the revision (if any)</param>
/// <param name="obj">The object which the revision resolves to</param>
void RevParse(string revision, out Reference reference, out GitObject obj);

/// <summary>
/// Initialize a <see cref="LibGit2Sharp.AnnotatedCommit"/> from extended SHA-1 syntax
/// </summary>
/// <param name="revspec">A string in extended SHA-1 syntax to look up the object</param>
AnnotatedCommit LookupAnnotatedCommit(string revspec);

/// <summary>
/// Initialize a <see cref="LibGit2Sharp.AnnotatedCommit"/> from extended SHA-1 syntax
/// </summary>
/// <param name="reference">A reference pointing to the commit</param>
AnnotatedCommit LookupAnnotatedCommit(Reference reference);

/// <summary>
/// Initialize a <see cref="LibGit2Sharp.AnnotatedCommit"/> from extended SHA-1 syntax
/// </summary>
/// <param name="commit">A commit</param>
AnnotatedCommit LookupAnnotatedCommit(Commit commit);
}
}
6 changes: 6 additions & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,13 @@
<Compile Include="IndexReucEntry.cs" />
<Compile Include="IndexReucEntryCollection.cs" />
<Compile Include="IndexNameEntry.cs" />
<Compile Include="MergeAnalysis.cs" />
<Compile Include="MergeAnalysisResult.cs" />
<Compile Include="MergeAndCheckoutOptionsBase.cs" />
<Compile Include="CheckoutConflictException.cs" />
<Compile Include="MergeOptions.cs" />
<Compile Include="MergeOptionsBase.cs" />
<Compile Include="MergePreference.cs" />
<Compile Include="MergeResult.cs" />
<Compile Include="MergeTreeOptions.cs" />
<Compile Include="MergeTreeResult.cs" />
Expand Down Expand Up @@ -355,6 +358,9 @@
<Compile Include="Commands\Stage.cs" />
<Compile Include="Commands\Remove.cs" />
<Compile Include="Commands\Checkout.cs" />
<Compile Include="Core\DisposableArray.cs" />
<Compile Include="IAnnotatedCommit.cs" />
<Compile Include="AnnotatedCommit.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="CustomDictionary.xml" />
Expand Down
44 changes: 44 additions & 0 deletions LibGit2Sharp/MergeAnalysis.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LibGit2Sharp
{
/// <summary>
/// Description of the possibilities for merging one or multiple branches into HEAD
/// </summary>
[Flags]
public enum MergeAnalysis
{
/// <summary>
/// No update possible. This is a dummy to serve as default value.
/// </summary>
None = 0,
/// <summary>
/// A "normal" merge; both HEAD and the given merge input have diverged
/// from their common ancestor. The divergent commits must be merged.
/// </summary>
Normal = (1 << 0),

/// <summary>
/// All given merge inputs are reachable from HEAD, meaning the
/// repository is up-to-date and no merge needs to be performed.
/// </summary>
UpToDate = (1 << 1),

/// <summary>
/// The given merge input is a fast-forward from HEAD and no merge
/// needs to be performed. Instead, the client can check out the
/// given merge input.
/// </summary>
FastForward = (1 << 2),

/// <summary>
/// The HEAD of the current repository is "unborn" and does not point to
/// a valid commit. No merge can be performed, but the caller may wish
/// to simply set HEAD to the target commit(s).
/// </summary>
Unborn = (1 << 3),
}
}
Loading