Skip to content

Commit e9b93ec

Browse files
author
Jeremy Koritzinsky
committed
Added support for cherry-picking commits in a bare repository via the object database.
1 parent 0b00a5b commit e9b93ec

File tree

6 files changed

+184
-1
lines changed

6 files changed

+184
-1
lines changed

LibGit2Sharp.Tests/CherryPickFixture.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,42 @@ public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflict
126126
}
127127
}
128128

129+
[Fact]
130+
public void CanCherryPickCommit()
131+
{
132+
string path = SandboxMergeTestRepo();
133+
using (var repo = new Repository(path))
134+
{
135+
var ours = repo.Head.Tip;
136+
137+
Commit commitToMerge = repo.Branches["fast_forward"].Tip;
138+
139+
var result = repo.ObjectDatabase.CherryPickCommit(commitToMerge, ours, 0, null);
140+
141+
Assert.Equal(CherryPickStatus.CherryPicked, result.Status);
142+
Assert.Equal(0, result.Conflicts.Count());
143+
}
144+
}
145+
146+
[Fact]
147+
public void CherryPickWithConflictsReturnsConflicts()
148+
{
149+
const string conflictBranchName = "conflicts";
150+
151+
string path = SandboxMergeTestRepo();
152+
using (var repo = new Repository(path))
153+
{
154+
Branch branch = repo.Branches[conflictBranchName];
155+
Assert.NotNull(branch);
156+
157+
var result = repo.ObjectDatabase.CherryPickCommit(branch.Tip, repo.Head.Tip, 0, null);
158+
159+
Assert.Equal(CherryPickStatus.Conflicts, result.Status);
160+
Assert.NotEmpty(result.Conflicts);
161+
162+
}
163+
}
164+
129165
private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null)
130166
{
131167
Touch(repository.Info.WorkingDirectory, filename, content);

LibGit2Sharp/CherryPickTreeResult.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace LibGit2Sharp
7+
{
8+
/// <summary>
9+
/// Class to report the result of a cherry picked.
10+
/// </summary>
11+
public class CherryPickTreeResult
12+
{
13+
/// <summary>
14+
/// Needed for mocking purposes.
15+
/// </summary>
16+
protected CherryPickTreeResult()
17+
{ }
18+
19+
internal CherryPickTreeResult(Tree tree)
20+
{
21+
this.Tree = tree;
22+
this.Status = CherryPickStatus.CherryPicked;
23+
this.Conflicts = new List<Conflict>();
24+
}
25+
26+
internal CherryPickTreeResult(IEnumerable<Conflict> conflicts)
27+
{
28+
this.Conflicts = conflicts;
29+
this.Status = CherryPickStatus.Conflicts;
30+
}
31+
32+
/// <summary>
33+
/// The resulting tree of the cherry-pick.
34+
/// <para>This will return <code>null</code> if the cherry-pick has been unsuccessful due to conflicts.</para>
35+
/// </summary>
36+
public virtual Tree Tree { get; private set; }
37+
38+
/// <summary>
39+
/// The resulting conflicts from the cherry-pick.
40+
/// </summary>
41+
public virtual IEnumerable<Conflict> Conflicts { get; private set; }
42+
43+
/// <summary>
44+
/// The status of the cherry pick.
45+
/// </summary>
46+
public virtual CherryPickStatus Status { get; private set; }
47+
}
48+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,14 @@ internal static extern unsafe int git_treebuilder_insert(
18621862
[DllImport(libgit2)]
18631863
internal static extern unsafe int git_cherrypick(git_repository* repo, git_object* commit, GitCherryPickOptions options);
18641864

1865+
[DllImport(libgit2)]
1866+
internal static extern unsafe int git_cherrypick_commit(out git_index* index,
1867+
git_repository* repo,
1868+
git_object* cherrypick_commit,
1869+
git_object* our_commit,
1870+
uint mainline,
1871+
ref GitMergeOpts options);
1872+
18651873
[DllImport(libgit2)]
18661874
internal static extern int git_transaction_commit(IntPtr txn);
18671875

LibGit2Sharp/Core/Proxy.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,22 @@ internal static unsafe void git_cherrypick(RepositoryHandle repo, ObjectId commi
325325
Ensure.ZeroResult(res);
326326
}
327327
}
328+
329+
internal static unsafe IndexHandle git_cherrypick_commit(RepositoryHandle repo, ObjectHandle cherrypickCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop)
330+
{
331+
git_index* index;
332+
int res = NativeMethods.git_cherrypick_commit(out index, repo, cherrypickCommit, ourCommit, mainline, ref opts);
333+
if (res == (int)GitErrorCode.MergeConflict)
334+
{
335+
earlyStop = true;
336+
}
337+
else
338+
{
339+
earlyStop = false;
340+
Ensure.ZeroResult(res);
341+
}
342+
return new IndexHandle(index, true);
343+
}
328344
#endregion
329345

330346
#region git_clone_

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<Compile Include="CheckoutOptions.cs" />
7171
<Compile Include="CherryPickOptions.cs" />
7272
<Compile Include="CherryPickResult.cs" />
73+
<Compile Include="CherryPickTreeResult.cs" />
7374
<Compile Include="CloneOptions.cs" />
7475
<Compile Include="CommitFilter.cs" />
7576
<Compile Include="CommitOptions.cs" />
@@ -393,4 +394,4 @@
393394
</Target>
394395
-->
395396
<ItemGroup />
396-
</Project>
397+
</Project>

LibGit2Sharp/ObjectDatabase.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,80 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a
541541
return new HistoryDivergence(repo, one, another);
542542
}
543543

544+
/// <summary>
545+
/// Performs a cherry-pick of <paramref name="cherryPickCommit"/> onto <paramref name="ours"/> commit.
546+
/// </summary>
547+
/// <param name="cherryPickCommit">The commit to cherry-pick.</param>
548+
/// <param name="ours">The commit to cherry-pick onto.</param>
549+
/// <param name="mainline">Which commit to consider the parent for the diff when cherry-picking a merge commit.</param>
550+
/// <param name="options">The options for the merging in the cherry-pick operation.</param>
551+
/// <returns>A result containing a <see cref="Tree"/> if the cherry-pick was successful and a list of <see cref="Conflict"/>s if it is not.</returns>
552+
public virtual CherryPickTreeResult CherryPickCommit(Commit cherryPickCommit, Commit ours, int mainline, MergeOptions options)
553+
{
554+
Ensure.ArgumentNotNull(cherryPickCommit, "commit");
555+
Ensure.ArgumentNotNull(ours, "ours");
556+
557+
options = options ?? new MergeOptions();
558+
559+
// We throw away the index after looking at the conflicts, so we'll never need the REUC
560+
// entries to be there
561+
GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL | GitMergeFlag.GIT_MERGE_SKIP_REUC;
562+
if (options.FindRenames)
563+
{
564+
mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES;
565+
}
566+
if (options.FailOnConflict)
567+
{
568+
mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT;
569+
}
570+
571+
572+
var opts = new GitMergeOpts
573+
{
574+
Version = 1,
575+
MergeFileFavorFlags = options.MergeFileFavor,
576+
MergeTreeFlags = mergeFlags,
577+
RenameThreshold = (uint)options.RenameThreshold,
578+
TargetLimit = (uint)options.TargetLimit
579+
};
580+
581+
bool earlyStop;
582+
583+
using (var ourHandle = Proxy.git_object_lookup(repo.Handle, ours.Id, GitObjectType.Commit))
584+
using (var cherryPickCommitHandle = Proxy.git_object_lookup(repo.Handle, cherryPickCommit.Id, GitObjectType.Commit))
585+
using (var indexHandle = Proxy.git_cherrypick_commit(repo.Handle, cherryPickCommitHandle, ourHandle, (uint)mainline, opts, out earlyStop))
586+
{
587+
CherryPickTreeResult cherryPickResult;
588+
589+
// Stopped due to FailOnConflict so there's no index or conflict list
590+
if (earlyStop)
591+
{
592+
return new CherryPickTreeResult(new Conflict[] { });
593+
}
594+
595+
if (Proxy.git_index_has_conflicts(indexHandle))
596+
{
597+
List<Conflict> conflicts = new List<Conflict>();
598+
Conflict conflict;
599+
using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle))
600+
{
601+
while ((conflict = Proxy.git_index_conflict_next(iterator)) != null)
602+
{
603+
conflicts.Add(conflict);
604+
}
605+
}
606+
cherryPickResult = new CherryPickTreeResult(conflicts);
607+
}
608+
else
609+
{
610+
var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle);
611+
cherryPickResult = new CherryPickTreeResult(this.repo.Lookup<Tree>(treeId));
612+
}
613+
614+
return cherryPickResult;
615+
}
616+
}
617+
544618
/// <summary>
545619
/// Calculates the current shortest abbreviated <see cref="ObjectId"/>
546620
/// string representation for a <see cref="GitObject"/>.

0 commit comments

Comments
 (0)