Skip to content

Commit 333646e

Browse files
author
Jeremy Koritzinsky
committed
Added support for revert in a bare repository.
1 parent e9b93ec commit 333646e

File tree

6 files changed

+214
-1
lines changed

6 files changed

+214
-1
lines changed

LibGit2Sharp.Tests/RevertFixture.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,5 +458,77 @@ 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+
// and the file whose contents we expect to be reverted.
517+
const string revertBranchName = "refs/heads/revert";
518+
519+
string path = SandboxRevertTestRepo();
520+
using (var repo = new Repository(path))
521+
{
522+
523+
524+
// Checkout the revert branch.
525+
Branch branch = Commands.Checkout(repo, revertBranchName);
526+
Assert.NotNull(branch);
527+
528+
// Revert tip commit.
529+
var result = repo.ObjectDatabase.RevertCommit(repo.Head.Tip, repo.Head.Tip, 0, null);
530+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
531+
}
532+
}
461533
}
462534
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 9 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,

LibGit2Sharp/Core/Proxy.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2670,6 +2670,21 @@ public static unsafe void git_revert(
26702670
}
26712671
}
26722672

2673+
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+
}
26732688
#endregion
26742689

26752690
#region git_revparse_

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
<Compile Include="RenameDetails.cs" />
162162
<Compile Include="RevertResult.cs" />
163163
<Compile Include="RevertOptions.cs" />
164+
<Compile Include="RevertTreeResult.cs" />
164165
<Compile Include="SecureUsernamePasswordCredentials.cs" />
165166
<Compile Include="StageOptions.cs" />
166167
<Compile Include="StatusOptions.cs" />

LibGit2Sharp/ObjectDatabase.cs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a
551551
/// <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>
552552
public virtual CherryPickTreeResult CherryPickCommit(Commit cherryPickCommit, Commit ours, int mainline, MergeOptions options)
553553
{
554-
Ensure.ArgumentNotNull(cherryPickCommit, "commit");
554+
Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit");
555555
Ensure.ArgumentNotNull(ours, "ours");
556556

557557
options = options ?? new MergeOptions();
@@ -880,5 +880,79 @@ private PackBuilderResults InternalPack(PackBuilderOptions options, Action<PackB
880880

881881
return results;
882882
}
883+
884+
/// <summary>
885+
/// Performs a revert of <paramref name="revertCommit"/> onto <paramref name="ours"/> commit.
886+
/// </summary>
887+
/// <param name="revertCommit">The commit to revert.</param>
888+
/// <param name="ours">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 RevertTreeResult RevertCommit(Commit revertCommit, Commit ours, int mainline, MergeOptions options)
893+
{
894+
Ensure.ArgumentNotNull(revertCommit, "revertCommit");
895+
Ensure.ArgumentNotNull(ours, "ours");
896+
897+
options = options ?? new MergeOptions();
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 ourHandle = Proxy.git_object_lookup(repo.Handle, ours.Id, GitObjectType.Commit))
924+
using (var cherryPickCommitHandle = Proxy.git_object_lookup(repo.Handle, revertCommit.Id, GitObjectType.Commit))
925+
using (var indexHandle = Proxy.git_revert_commit(repo.Handle, cherryPickCommitHandle, ourHandle, (uint)mainline, opts, out earlyStop))
926+
{
927+
RevertTreeResult revertTreeResult;
928+
929+
// Stopped due to FailOnConflict so there's no index or conflict list
930+
if (earlyStop)
931+
{
932+
return new RevertTreeResult(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 RevertTreeResult(conflicts);
947+
}
948+
else
949+
{
950+
var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle);
951+
revertTreeResult = new RevertTreeResult(this.repo.Lookup<Tree>(treeId));
952+
}
953+
954+
return revertTreeResult;
955+
}
956+
}
883957
}
884958
}

LibGit2Sharp/RevertTreeResult.cs

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

0 commit comments

Comments
 (0)