Skip to content

Commit 2614ae8

Browse files
committed
Expose merge analysis
This allows us to ask the library about what kind of actions are available for merging into HEAD.
1 parent ccee01a commit 2614ae8

File tree

8 files changed

+323
-0
lines changed

8 files changed

+323
-0
lines changed

LibGit2Sharp.Tests/MergeFixture.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,72 @@ private Commit AddFileCommitToRepo(IRepository repository, string filename, stri
874874
return repository.Commit("New commit", Constants.Signature, Constants.Signature);
875875
}
876876

877+
878+
[Theory]
879+
[InlineData("master", MergeAnalysis.Unborn | MergeAnalysis.FastForward, true)]
880+
[InlineData("fast_forward", MergeAnalysis.Normal | MergeAnalysis.FastForward, false)]
881+
[InlineData("conflicts", MergeAnalysis.Normal, false)]
882+
883+
public void CanAnalyzeMerge(string branchName, MergeAnalysis analysis, bool makeUnborn)
884+
{
885+
string path = SandboxMergeTestRepo();
886+
using (var repo = new Repository(path))
887+
{
888+
if (makeUnborn)
889+
{
890+
repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn");
891+
}
892+
893+
// test both Commit and Reference overload
894+
MergeAnalysisResult result = repo.AnalyzeMerge(repo.Branches[branchName].Tip);
895+
MergeAnalysisResult result2 = repo.AnalyzeMerge(repo.Refs["refs/heads/" + branchName]);
896+
897+
Assert.Equal(analysis, result.Analysis);
898+
Assert.Equal(analysis, result2.Analysis);
899+
}
900+
}
901+
902+
[Theory]
903+
[InlineData("false", MergePreference.NoFastForward)]
904+
[InlineData("only", MergePreference.FastForwardOnly)]
905+
[InlineData(null, MergePreference.Default)]
906+
public void CanRetrieveMergePreference(string value, MergePreference preference)
907+
{
908+
string path = SandboxMergeTestRepo();
909+
using (var repo = new Repository(path))
910+
{
911+
if (value != null)
912+
{
913+
repo.Config.Set("merge.ff", value);
914+
}
915+
916+
// it doesn't matter too much which branch we ask about, we want the preference
917+
MergeAnalysisResult result = repo.AnalyzeMerge(repo.Branches["fast_forward"].Tip);
918+
919+
Assert.Equal(preference, result.Preference);
920+
}
921+
}
922+
923+
[Fact]
924+
public void CanAnalyzeFastForward()
925+
{
926+
string path = SandboxMergeTestRepo();
927+
using (var repo = new Repository(path))
928+
{
929+
// test both Commit and Reference overload
930+
MergeAnalysisResult result = repo.AnalyzeMerge(repo.Branches["fast_forward"].Tip);
931+
MergeAnalysisResult result2 = repo.AnalyzeMerge(repo.Refs["refs/heads/fast_forward"]);
932+
933+
Assert.True(result.Analysis.HasFlag(MergeAnalysis.FastForward));
934+
Assert.True(result.Analysis.HasFlag(MergeAnalysis.Normal));
935+
Assert.False(result.Analysis.HasFlag(MergeAnalysis.Unborn));
936+
Assert.False(result.Analysis.HasFlag(MergeAnalysis.UpToDate));
937+
938+
Assert.Equal(result, result2);
939+
}
940+
}
941+
942+
877943
// Commit IDs of the checked in merge_testrepo
878944
private const string masterBranchInitialId = "83cebf5389a4adbcb80bda6b68513caee4559802";
879945
private const string fastForwardBranchInitialId = "4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53";

LibGit2Sharp/Core/DisposableArray.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Linq;
3+
using System.Collections.Generic;
4+
5+
namespace LibGit2Sharp.Core
6+
{
7+
/// <summary>
8+
/// An array containing disposables, for convenience when dealing with handles
9+
/// </summary>
10+
internal class DisposableArray<T> : IDisposable where T : IDisposable
11+
{
12+
readonly T[] array;
13+
14+
/// <summary>
15+
/// Create a wrapper for the given array so the contents will be disposed when this class is disposed.
16+
/// </summary>
17+
/// <param name="handles">The array of dispsables</param>
18+
public DisposableArray(T[] handles)
19+
{
20+
array = handles;
21+
}
22+
23+
/// <summary>
24+
/// Create a wrapper for the given array so the contents will be disposed when this class is disposed.
25+
/// <para>
26+
/// The enumerable is first made into an array
27+
/// </para>
28+
/// </summary>
29+
/// <param name="handles">Handles.</param>
30+
public DisposableArray(IEnumerable<T> handles)
31+
{
32+
array = handles.ToArray();
33+
}
34+
35+
/// <summary>
36+
/// The underlying array
37+
/// </summary>
38+
public T[] Array { get { return array; } }
39+
40+
/// <summary>
41+
/// Return the underlying array so we can use this wherever the methods expect an array
42+
/// </summary>
43+
public static implicit operator T[](DisposableArray<T> da)
44+
{
45+
return da.array;
46+
}
47+
48+
/// <summary>
49+
/// Call Dispose on each of the elements of the array
50+
/// </summary>
51+
public void Dispose()
52+
{
53+
foreach (var handle in array)
54+
{
55+
handle.Dispose();
56+
}
57+
}
58+
}
59+
}
60+

LibGit2Sharp/IRepository.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,21 @@ public interface IRepository : IDisposable
226226
/// <returns>The <see cref="MergeResult"/> of the merge.</returns>
227227
MergeResult Merge(string committish, Signature merger, MergeOptions options);
228228

229+
/// <summary>
230+
/// Analyze the possibilities of updating HEAD with the given commit(s).
231+
/// </summary>
232+
/// <param name="commits">Commits to merge into HEAD</param>
233+
/// <returns>Which update methods are possible and which preference the user has specified</returns>
234+
MergeAnalysisResult AnalyzeMerge(params Commit[] commits);
235+
236+
237+
/// <summary>
238+
/// Analyze the possibilities of updating HEAD with the given reference(s)
239+
/// </summary>
240+
/// <param name="references">References to merge into HEAD</param>
241+
/// <returns>Which update methods are possible and which preference the user has specified</returns>
242+
MergeAnalysisResult AnalyzeMerge(params Reference[] references);
243+
229244
/// <summary>
230245
/// Access to Rebase functionality.
231246
/// </summary>

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,13 @@
119119
<Compile Include="IndexReucEntry.cs" />
120120
<Compile Include="IndexReucEntryCollection.cs" />
121121
<Compile Include="IndexNameEntry.cs" />
122+
<Compile Include="MergeAnalysis.cs" />
123+
<Compile Include="MergeAnalysisResult.cs" />
122124
<Compile Include="MergeAndCheckoutOptionsBase.cs" />
123125
<Compile Include="CheckoutConflictException.cs" />
124126
<Compile Include="MergeOptions.cs" />
125127
<Compile Include="MergeOptionsBase.cs" />
128+
<Compile Include="MergePreference.cs" />
126129
<Compile Include="MergeResult.cs" />
127130
<Compile Include="MergeTreeOptions.cs" />
128131
<Compile Include="MergeTreeResult.cs" />
@@ -355,6 +358,7 @@
355358
<Compile Include="Commands\Stage.cs" />
356359
<Compile Include="Commands\Remove.cs" />
357360
<Compile Include="Commands\Checkout.cs" />
361+
<Compile Include="Core\DisposableArray.cs" />
358362
</ItemGroup>
359363
<ItemGroup>
360364
<CodeAnalysisDictionary Include="CustomDictionary.xml" />

LibGit2Sharp/MergeAnalysis.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace LibGit2Sharp
7+
{
8+
/// <summary>
9+
/// Description of the possibilities for merging one or multiple branches into HEAD
10+
/// </summary>
11+
[Flags]
12+
public enum MergeAnalysis
13+
{
14+
/// <summary>
15+
/// No update possible. This is a dummy to serve as default value.
16+
/// </summary>
17+
None = 0,
18+
/// <summary>
19+
/// A "normal" merge; both HEAD and the given merge input have diverged
20+
/// from their common ancestor. The divergent commits must be merged.
21+
/// </summary>
22+
Normal = (1 << 0),
23+
24+
/// <summary>
25+
/// All given merge inputs are reachable from HEAD, meaning the
26+
/// repository is up-to-date and no merge needs to be performed.
27+
/// </summary>
28+
UpToDate = (1 << 1),
29+
30+
/// <summary>
31+
/// The given merge input is a fast-forward from HEAD and no merge
32+
/// needs to be performed. Instead, the client can check out the
33+
/// given merge input.
34+
/// </summary>
35+
FastForward = (1 << 2),
36+
37+
/// <summary>
38+
/// The HEAD of the current repository is "unborn" and does not point to
39+
/// a valid commit. No merge can be performed, but the caller may wish
40+
/// to simply set HEAD to the target commit(s).
41+
/// </summary>
42+
Unborn = (1 << 3),
43+
}
44+
}

LibGit2Sharp/MergeAnalysisResult.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using LibGit2Sharp.Core;
3+
4+
namespace LibGit2Sharp
5+
{
6+
/// <summary>
7+
/// The result of analyzing the possibilities of merging a commit or branch into HEAD.
8+
/// </summary>
9+
public struct MergeAnalysisResult
10+
{
11+
/// <summary>
12+
/// The result of the analysis done on the commits given.
13+
/// </summary>
14+
public MergeAnalysis Analysis;
15+
/// <summary>
16+
/// The user's preference for the type of merge to perform.
17+
/// </summary>
18+
public MergePreference Preference;
19+
20+
static MergeAnalysis MergeAnalysisFromGitMergeAnalysis(GitMergeAnalysis analysisIn)
21+
{
22+
MergeAnalysis analysis = default(MergeAnalysis);
23+
24+
if (analysisIn.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_NORMAL))
25+
{
26+
analysis |= MergeAnalysis.Normal;
27+
}
28+
if (analysisIn.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_UP_TO_DATE))
29+
{
30+
analysis |= MergeAnalysis.UpToDate;
31+
}
32+
if (analysisIn.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_FASTFORWARD))
33+
{
34+
analysis |= MergeAnalysis.FastForward;
35+
}
36+
37+
if (analysisIn.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_UNBORN))
38+
{
39+
analysis |= MergeAnalysis.Unborn;
40+
}
41+
42+
return analysis;
43+
}
44+
45+
static MergePreference MergePreferenceFromGitMergePreference(GitMergePreference preference)
46+
{
47+
switch (preference)
48+
{
49+
case GitMergePreference.GIT_MERGE_PREFERENCE_NONE:
50+
return MergePreference.Default;
51+
case GitMergePreference.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY:
52+
return MergePreference.FastForwardOnly;
53+
case GitMergePreference.GIT_MERGE_PREFERENCE_NO_FASTFORWARD:
54+
return MergePreference.NoFastForward;
55+
default:
56+
throw new InvalidOperationException(String.Format("Unknown merge preference: {0}", preference));
57+
}
58+
}
59+
60+
internal MergeAnalysisResult(GitMergeAnalysis analysis, GitMergePreference preference)
61+
{
62+
Analysis = MergeAnalysisFromGitMergeAnalysis(analysis);
63+
Preference = MergePreferenceFromGitMergePreference(preference);
64+
}
65+
}
66+
}

LibGit2Sharp/MergePreference.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace LibGit2Sharp
7+
{
8+
/// <summary>
9+
/// Indicates the user's stated preference for merges
10+
/// </summary>
11+
public enum MergePreference
12+
{
13+
/// <summary>
14+
/// No configuration was found that suggests a preferred behavior for
15+
/// merge.
16+
/// </summary>
17+
Default = 0,
18+
19+
/// <summary>
20+
/// There is a `merge.ff=false` configuration setting, suggesting that
21+
/// the user does not want to allow a fast-forward merge.
22+
/// </summary>
23+
NoFastForward = (1 << 0),
24+
25+
/// <summary>
26+
/// There is a `merge.ff=only` configuration setting, suggesting that
27+
/// the user only wants fast-forward merges.
28+
/// </summary>
29+
FastForwardOnly = (1 << 1),
30+
}
31+
}

LibGit2Sharp/Repository.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,43 @@ public CherryPickResult CherryPick(Commit commit, Signature committer, CherryPic
14121412
return result;
14131413
}
14141414

1415+
private MergeAnalysisResult AnalyzeMerge(AnnotatedCommitHandle[] annotatedCommits)
1416+
{
1417+
GitMergeAnalysis mergeAnalysis;
1418+
GitMergePreference mergePreference;
1419+
1420+
Proxy.git_merge_analysis(Handle, annotatedCommits, out mergeAnalysis, out mergePreference);
1421+
return new MergeAnalysisResult(mergeAnalysis, mergePreference);
1422+
}
1423+
1424+
/// <summary>
1425+
/// Analyze the possibilities of updating HEAD with the given commit(s).
1426+
/// </summary>
1427+
/// <param name="commits">Commits to merge into HEAD</param>
1428+
/// <returns>Which update methods are possible and which preference the user has specified</returns>
1429+
public MergeAnalysisResult AnalyzeMerge(params Commit[] commits)
1430+
{
1431+
using (var handles = new DisposableArray<AnnotatedCommitHandle>(commits.Select(commit =>
1432+
Proxy.git_annotated_commit_lookup(Handle, commit.Id.Oid)).ToArray()))
1433+
{
1434+
return AnalyzeMerge(handles);
1435+
}
1436+
}
1437+
1438+
/// <summary>
1439+
/// Analyze the possibilities of updating HEAD with the given reference(s)
1440+
/// </summary>
1441+
/// <param name="references">References to merge into HEAD</param>
1442+
/// <returns>Which update methods are possible and which preference the user has specified</returns>
1443+
public MergeAnalysisResult AnalyzeMerge(params Reference[] references)
1444+
{
1445+
using (var refHandles = new DisposableArray<ReferenceHandle>(references.Select(r => refs.RetrieveReferencePtr(r.CanonicalName))))
1446+
using (var handles = new DisposableArray<AnnotatedCommitHandle>(refHandles.Array.Select(rh => Proxy.git_annotated_commit_from_ref(Handle, rh))))
1447+
{
1448+
return AnalyzeMerge(handles);
1449+
}
1450+
}
1451+
14151452
private FastForwardStrategy FastForwardStrategyFromMergePreference(GitMergePreference preference)
14161453
{
14171454
switch (preference)

0 commit comments

Comments
 (0)