8000 Added support for cherry-picking commits in a bare repository via the… · XavierDa/libgit2sharp-ssh@d59c673 · GitHub
[go: up one dir, main page]

Skip to content

Commit d59c673

Browse files
author
Jeremy Koritzinsky
committed
Added support for cherry-picking commits in a bare repository via the object database.
Added support for revert in a bare repository. Updated CanRevertInObjectDatabase to not use a checkout and to just use commit refs. Changed methods to use `MergeTreeResult` and fixed incorrect name in argument validation.
1 parent 0b00a5b commit d59c673

File tree

6 files changed

+298
-1
lines changed

6 files changed

+298
-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(MergeTreeStatus.Succeeded, 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(MergeTreeStatus.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.Tests/RevertFixture.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,5 +458,70 @@ public void RevertOrphanedBranchThrows()
458458
Assert.Throws<UnbornBranchException>(() => repo.Revert(commitToRevert, Constants.Signature));
459459
}
460460
}
461+
462+
[Fact]
463+
public void RevertWithNothingToRevertInObjectDatabaseSucceeds()
464+
{
465+
// The branch name to perform the revert on
466+
const string revertBranchName = "refs/heads/revert";
467+
468+
string path = SandboxRevertTestRepo();
469+
using (var repo = new Repository(path))
470+
{
471+
// Checkout the revert branch.
472+
Branch branch = Commands.Checkout(repo, revertBranchName);
473+
Assert.NotNull(branch);
474+
475+
Commit commitToRevert = repo.Head.Tip;
476+
477+
// Revert tip commit.
478+
RevertResult result = repo.Revert(commitToRevert, Constants.Signature);
479+
Assert.NotNull(result);
480+
Assert.Equal(RevertStatus.Reverted, result.Status);
481+
482+
var revertResult = repo.ObjectDatabase.RevertCommit(commitToRevert, repo.Branches[revertBranchName].Tip, 0, null);
483+
484+
Assert.NotNull(revertResult);
485+
Assert.Equal(MergeTreeStatus.Succeeded, revertResult.Status);
486+
}
487+
}
488+
489+
[Fact]
490+
public void RevertWithConflictReportsConflict()
491+
{
492+
// The branch name to perform the revert on,
493+
// and the file whose contents we expect to be reverted.
494+
const string revertBranchName = "refs/heads/revert";
495+
496+
string path = SandboxRevertTestRepo();
497+
using (var repo = new Repository(path))
498+
{
499+
// The commit to revert - we know that reverting this
500+
// specific commit will generate conflicts.
501+
Commit commitToRevert = repo.Lookup<Commit>("cb4f7f0eca7a0114cdafd8537332aa17de36a4e9");
502+
Assert.NotNull(commitToRevert);
503+
504+
// Perform the revert and verify there were conflicts.
505+
var result = repo.ObjectDatabase.RevertCommit(commitToRevert, repo.Branches[revertBranchName].Tip, 0, null);
506+
Assert.NotNull(result);
507+
Assert.Equal(MergeTreeStatus.Conflicts, result.Status);
508+
Assert.Null(result.Tree);
509+
}
510+
}
511+
512+
[Fact]
513+
public void CanRevertInObjectDatabase()
514+
{
515+
// The branch name to perform the revert on
516+
const string revertBranchName = "refs/heads/revert";
517+
518+
string path = SandboxRevertTestRepo();
519+
using (var repo = new Repository(path))
520+
{
521+
// Revert tip commit.
522+
var result = repo.ObjectDatabase.RevertCommit(repo.Branches[revertBranchName].Tip, repo.Branches[revertBranchName].Tip, 0, null);
523+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
524+
}
525+
}
461526
}
462527
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,15 @@ internal static extern unsafe int git_revert(
15031503
git_object* commit,
15041504
GitRevertOpts opts);
15051505

1506+
[DllImport(libgit2)]
1507+
internal static extern unsafe int git_revert_commit(
1508+
out git_index* index,
1509+
git_repository* repo,
1510+
git_object* revert_commit,
1511+
git_object* our_commit,
1512+
uint mainline,
1513+
ref GitMergeOpts opts);
1514+
15061515
[DllImport(libgit2)]
15071516
internal static extern unsafe int git_revparse_ext(
15081517
out git_object* obj,
@@ -1862,6 +1871,14 @@ internal static extern unsafe int git_treebuilder_insert(
18621871
[DllImport(libgit2)]
18631872
internal static extern unsafe int git_cherrypick(git_repository* repo, git_object* commit, GitCherryPickOptions options);
18641873

1874+
[DllImport(libgit2)]
1875+
internal static extern unsafe int git_cherrypick_commit(out git_index* index,
1876+
git_repository* repo,
1877+
git_object* cherrypick_commit,
1878+
git_object* our_commit,
1879+
uint mainline,
1880+
ref GitMergeOpts options);
1881+
18651882
[DllImport(libgit2)]
18661883
internal static extern int git_transaction_commit(IntPtr txn);
18671884

LibGit2Sharp/Core/Proxy.cs

Lines changed: 31 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_
@@ -2654,6 +2670,21 @@ public static unsafe void git_revert(
26542670
}
26552671
}
26562672

26 1241 73+
internal static unsafe IndexHandle git_revert_commit(RepositoryHandle repo, ObjectHandle revertCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop)
2674+
{
2675+
git_index* index;
2676+
int res = NativeMethods.git_revert_commit(out index, repo, revertCommit, ourCommit, mainline, ref opts);
2677+
if (res == (int)GitErrorCode.MergeConflict)
2678+
{
2679+
earlyStop = true;
2680+
}
2681+
else
2682+
{
2683+
earlyStop = false;
2684+
Ensure.ZeroResult(res);
2685+
}
2686+
return new IndexHandle(index, true);
2687+
}
26572688
#endregion
26582689

26592690
#region git_revparse_

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,4 @@
393393
</Target>
394394
-->
395395
<ItemGroup />
396-
</Project>
396+
</Project>

LibGit2Sharp/ObjectDatabase.cs

Lines changed: 148 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="cherryPickOnto"/> commit.
546+
/// </summary>
547+
/// <param name="cherryPickCommit">The commit to cherry-pick.</param>
548+
/// <param name="cherryPickOnto">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 MergeTreeResult CherryPickCommit(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options)
553+
{
554+
Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit");
555+
Ensure.ArgumentNotNull(cherryPickOnto, "ours");
556+
557+
options = options ?? new MergeTreeOptions();
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 cherryPickOntoHandle = Proxy.git_object_lookup(repo.Handle, cherryPickOnto.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, cherryPickOntoHandle, (uint)mainline, opts, out earlyStop))
586+
{
587+
MergeTreeResult cherryPickResult;
588+
589+
// Stopped due to FailOnConflict so there's no index or conflict list
590+
if (earlyStop)
591+
{
592+
return new MergeTreeResult(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 MergeTreeResult(conflicts);
607+
}
608+
else
609+
{
610+
var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle);
611+
cherryPickResult = new MergeTreeResult(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"/>.
@@ -806,5 +880,79 @@ private PackBuilderResults InternalPack(PackBuilderOptions options, Action<PackB
806880

807881
return results;
808882
}
883+
884+
/// <summary>
885+
/// Performs a revert of <paramref name="revertCommit"/> onto <paramref name="revertOnto"/> commit.
886+
/// </summary>
887+
/// <param name="revertCommit">The commit to revert.</param>
888+
/// <param name="revertOnto">The commit to revert onto.</param>
889+
/// <param name="mainline">Which commit to consider the parent for the diff when reverting a merge commit.</param>
890+
/// <param name="options">The options for the merging in the revert operation.</param>
891+
/// <returns>A result containing a <see cref="Tree"/> if the revert was successful and a list of <see cref="Conflict"/>s if it is not.</returns>
892+
public virtual MergeTreeResult RevertCommit(Commit revertCommit, Commit revertOnto, int mainline, MergeTreeOptions options)
893+
{
894+
Ensure.ArgumentNotNull(revertCommit, "revertCommit");
895+
Ensure.ArgumentNotNull(revertOnto, "revertOnto");
896+
897+
options = options ?? new MergeTreeOptions();
898+
899+
// We throw away the index after looking at the conflicts, so we'll never need the REUC
900+
// entries to be there
901+
GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL | GitMergeFlag.GIT_MERGE_SKIP_REUC;
902+
if (options.FindRenames)
903+
{
904+
mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES;
905+
}
906+
if (options.FailOnConflict)
907+
{
908+
mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT;
909+
}
910+
911+
912+
var opts = new GitMergeOpts
913+
{
914+
Version = 1,
915+
MergeFileFavorFlags = options.MergeFileFavor,
916+
MergeTreeFlags = mergeFlags,
917+
RenameThreshold = (uint)options.RenameThreshold,
918+
TargetLimit = (uint)options.TargetLimit
919+
};
920+
921+
bool earlyStop;
922+
923+
using (var revertOntoHandle = Proxy.git_object_lookup(repo.Handle, revertOnto.Id, GitObjectType.Commit))
924+
using (var revertCommitHandle = Proxy.git_object_lookup(repo.Handle, revertCommit.Id, GitObjectType.Commit))
925+
using (var indexHandle = Proxy.git_revert_commit(repo.Handle, revertCommitHandle, revertOntoHandle, (uint)mainline, opts, out earlyStop))
926+
{
927+
MergeTreeResult revertTreeResult;
928+
929+
// Stopped due to FailOnConflict so there's no index or conflict list
930+
if (earlyStop)
931+
{
932+
return new MergeTreeResult(new Conflict[] { });
933+
}
934+
935+
if (Proxy.git_index_has_conflicts(indexHandle))
936+
{
937+
List<Conflict> conflicts = new List<Conflict>();
938+
Conflict conflict;
939+
using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle))
940+
{
941+
while ((conflict = Proxy.git_index_conflict_next(iterator)) != null)
942+
{
943+
conflicts.Add(conflict);
944+
}
945+
}
946+
revertTreeResult = new MergeTreeResult(conflicts);
947+
}
948+
else
949+
{
950+
var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle);
951+
revertTreeResult = new MergeTreeResult(this.repo.Lookup<Tree>(treeId));
952+
}
953+
954+
return revertTreeResult;
955+
}
956+
}
809957
}
810958
}

0 commit comments

Comments
 (0)
0