From f64dbd13653d3bfaf94a5f333fe80207d6f21abe Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 17 Nov 2017 10:15:19 +0000 Subject: [PATCH 1/4] Update dependencies to latest versions --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 14 +++++++++----- LibGit2Sharp/LibGit2Sharp.csproj | 6 +++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index e98f150ac..6ffe97c52 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -18,12 +18,16 @@ - - + + - - - + + + + + + + diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 0b37bf6af..2530874b7 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -34,10 +34,14 @@ - + + + + + From 13ed9391a2ec4bf2bb804d1f6fa10da28aa577b0 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 17 Nov 2017 10:26:08 +0000 Subject: [PATCH 2/4] xunit: specify enum type instead of integer value Xunit 2.3 now considers it an error to pass a integer type when a enum was expected. --- LibGit2Sharp.Tests/RemoveFixture.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LibGit2Sharp.Tests/RemoveFixture.cs b/LibGit2Sharp.Tests/RemoveFixture.cs index a89977fce..6ceef6045 100644 --- a/LibGit2Sharp.Tests/RemoveFixture.cs +++ b/LibGit2Sharp.Tests/RemoveFixture.cs @@ -28,14 +28,14 @@ public class RemoveFixture : BaseFixture * 'git rm ' fails ("error: '' has local modifications"). */ [InlineData(false, "modified_unstaged_file.txt", false, FileStatus.ModifiedInWorkdir, true, true, FileStatus.NewInWorkdir | FileStatus.DeletedFromIndex)] - [InlineData(true, "modified_unstaged_file.txt", true, FileStatus.ModifiedInWorkdir, true, true, 0)] + [InlineData(true, "modified_unstaged_file.txt", true, FileStatus.ModifiedInWorkdir, true, true, FileStatus.Unaltered)] /*** * Test case: modified file in wd, the modifications have already been promoted to the index. * 'git rm --cached ' works (removes the file from the index) * 'git rm ' fails ("error: '' has changes staged in the index") */ [InlineData(false, "modified_staged_file.txt", false, FileStatus.ModifiedInIndex, true, true, FileStatus.NewInWorkdir | FileStatus.DeletedFromIndex)] - [InlineData(true, "modified_staged_file.txt", true, FileStatus.ModifiedInIndex, true, true, 0)] + [InlineData(true, "modified_staged_file.txt", true, FileStatus.ModifiedInIndex, true, true, FileStatus.Unaltered)] /*** * Test case: modified file in wd, the modifications have already been promoted to the index, and * the file does not exist in the HEAD. @@ -43,7 +43,7 @@ public class RemoveFixture : BaseFixture * 'git rm ' throws ("error: '' has changes staged in the index") */ [InlineData(false, "new_tracked_file.txt", false, FileStatus.NewInIndex, true, true, FileStatus.NewInWorkdir)] - [InlineData(true, "new_tracked_file.txt", true, FileStatus.NewInIndex, true, true, 0)] + [InlineData(true, "new_tracked_file.txt", true, FileStatus.NewInIndex, true, true, FileStatus.Unaltered)] public void CanRemoveAnUnalteredFileFromTheIndexWithoutRemovingItFromTheWorkingDirectory( bool removeFromWorkdir, string filename, bool throws, FileStatus initialStatus, bool existsBeforeRemove, bool existsAfterRemove, FileStatus lastStatus) { From 75dad9bb4f35d7eb6519c7c80049f85069b6643d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 27 Jul 2017 15:09:43 +0100 Subject: [PATCH 3/4] Make `Rebase` a set of `Commands` --- LibGit2Sharp.Tests/RebaseFixture.cs | 48 ++--- LibGit2Sharp/Commands/Rebase.cs | 315 ++++++++++++++++++++++++++++ LibGit2Sharp/IRepository.cs | 5 - LibGit2Sharp/Repository.cs | 13 -- 4 files changed, 339 insertions(+), 42 deletions(-) create mode 100644 LibGit2Sharp/Commands/Rebase.cs diff --git a/LibGit2Sharp.Tests/RebaseFixture.cs b/LibGit2Sharp.Tests/RebaseFixture.cs index 28c49738e..b6deba946 100644 --- a/LibGit2Sharp.Tests/RebaseFixture.cs +++ b/LibGit2Sharp.Tests/RebaseFixture.cs @@ -78,7 +78,7 @@ public void CanRebase(string initialBranchName, }, }; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, options); // Validation: Assert.True(afterRebaseStepCountCorrect, "Unexpected CompletedStepIndex value in RebaseStepCompleted"); @@ -130,7 +130,7 @@ public void CanRebaseBranchOntoItself() Commands.Checkout(repo, topicBranch2Name); Branch b = repo.Branches[topicBranch2Name]; - RebaseResult result = repo.Rebase.Start(b, b, null, Constants.Identity, new RebaseOptions()); + RebaseResult result = Commands.Rebase.Start(repo, b, b, null, Constants.Identity, new RebaseOptions()); Assert.Equal(0, result.TotalStepCount); Assert.Equal(RebaseStatus.Complete, result.Status); Assert.Equal(0, result.CompletedStepCount); @@ -251,7 +251,7 @@ public void VerifyRebaseDetailed(string attributes, string lineEnding, string[] }; - repo.Rebase.Start(null, upstreamBranch, null, Constants.Identity2, options); + Commands.Rebase.Start(repo, null, upstreamBranch, null, Constants.Identity2, options); Assert.Equal(true, wasCheckoutNotifyCalledForResetingHead); Assert.Equal(true, wasCheckoutProgressCalledForResetingHead); @@ -317,7 +317,7 @@ public void CanContinueRebase() CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, }; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, options); // Verify that we have a conflict. Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); @@ -345,7 +345,7 @@ public void CanContinueRebase() // Clear the flags: wasCheckoutProgressCalled = false; wasCheckoutNotifyCalled = false; - RebaseResult continuedRebaseResult = repo.Rebase.Continue(Constants.Identity, options); + RebaseResult continuedRebaseResult = Commands.Rebase.Continue(repo, Constants.Identity, options); Assert.NotNull(continuedRebaseResult); Assert.Equal(RebaseStatus.Complete, continuedRebaseResult.Status); @@ -377,7 +377,7 @@ public void ContinuingRebaseWithUnstagedChangesThrows() Branch upstream = repo.Branches[conflictBranch1Name]; Branch onto = repo.Branches[conflictBranch1Name]; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, null); // Verify that we have a conflict. Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); @@ -388,7 +388,7 @@ public void ContinuingRebaseWithUnstagedChangesThrows() Assert.Equal(3, rebaseResult.TotalStepCount); Assert.Throws(() => - repo.Rebase.Continue(Constants.Identity, null)); + Commands.Rebase.Continue(repo, Constants.Identity, null)); // Resolve the conflict foreach (Conflict conflict in repo.Index.Conflicts) @@ -404,7 +404,7 @@ public void ContinuingRebaseWithUnstagedChangesThrows() "Unstaged content"); Assert.Throws(() => - repo.Rebase.Continue(Constants.Identity, null)); + Commands.Rebase.Continue(repo, Constants.Identity, null)); Assert.True(repo.Index.IsFullyMerged); } @@ -431,7 +431,7 @@ public void CanSpecifyFileConflictStrategy() FileConflictStrategy = CheckoutFileConflictStrategy.Ours, }; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, options); // Verify that we have a conflict. Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); @@ -473,7 +473,7 @@ public void CanQueryRebaseOperation() Branch upstream = repo.Branches[conflictBranch1Name]; Branch onto = repo.Branches[conflictBranch1Name]; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, null); // Verify that we have a conflict. Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); @@ -482,10 +482,10 @@ public void CanQueryRebaseOperation() Assert.Equal(0, rebaseResult.CompletedStepCount); Assert.Equal(3, rebaseResult.TotalStepCount); - RebaseStepInfo info = repo.Rebase.GetCurrentStepInfo(); + RebaseStepInfo info = Commands.Rebase.GetCurrentStepInfo(repo); - Assert.Equal(0, repo.Rebase.GetCurrentStepIndex()); - Assert.Equal(3, repo.Rebase.GetTotalStepCount()); + Assert.Equal(0, Commands.Rebase.GetCurrentStepIndex(repo)); + Assert.Equal(3, Commands.Rebase.GetTotalStepCount(repo)); Assert.Equal(RebaseStepOperation.Pick, info.Type); } } @@ -506,7 +506,7 @@ public void CanAbortRebase() Branch upstream = repo.Branches[conflictBranch1Name]; Branch onto = repo.Branches[conflictBranch1Name]; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, null); // Verify that we have a conflict. Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); @@ -526,7 +526,7 @@ public void CanAbortRebase() CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, }; - repo.Rebase.Abort(options); + Commands.Rebase.Abort(repo, options); Assert.False(repo.RetrieveStatus().IsDirty, "Repository workdir is dirty after Rebase.Abort."); Assert.True(repo.Index.IsFullyMerged, "Repository index is not fully merged after Rebase.Abort."); Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); @@ -552,7 +552,7 @@ public void RebaseWhileAlreadyRebasingThrows() Branch upstream = repo.Branches[conflictBranch1Name]; Branch onto = repo.Branches[conflictBranch1Name]; - RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + RebaseResult rebaseResult = Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, null); // Verify that we have a conflict. Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); @@ -560,7 +560,7 @@ public void RebaseWhileAlreadyRebasingThrows() Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); Assert.Throws(() => - repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null)); + Commands.Rebase.Start(repo, branch, upstream, onto, Constants.Identity, null)); } } @@ -576,10 +576,10 @@ public void RebaseOperationsWithoutRebasingThrow() Commands.Checkout(repo, topicBranch1Name); Assert.Throws(() => - repo.Rebase.Continue(Constants.Identity, new RebaseOptions())); + Commands.Rebase.Continue(repo, Constants.Identity, new RebaseOptions())); Assert.Throws(() => - repo.Rebase.Abort()); + Commands.Rebase.Abort(repo)); } } @@ -593,7 +593,7 @@ public void CurrentStepInfoIsNullWhenNotRebasing() ConstructRebaseTestRepository(repo); Commands.Checkout(repo, topicBranch1Name); - Assert.Null(repo.Rebase.GetCurrentStepInfo()); + Assert.Null(Commands.Rebase.GetCurrentStepInfo(repo)); } } @@ -642,7 +642,7 @@ public void CanRebaseHandlePatchAlreadyApplied(string attributes, string lineEnd } }; - repo.Rebase.Start(null, upstreamBranch, null, Constants.Identity2, options); + Commands.Rebase.Start(repo, null, upstreamBranch, null, Constants.Identity2, options); ObjectId secondCommitExpectedTreeId = new ObjectId(expectedShaText); Signature secondCommitAuthorSignature = Constants.Signature; Identity secondCommitCommiterIdentity = Constants.Identity2; @@ -670,9 +670,9 @@ public void RebasingInBareRepositoryThrows() Branch rebaseUpstreamBranch = repo.Branches["refs/heads/test"]; Assert.NotNull(rebaseUpstreamBranch); - Assert.Throws(() => repo.Rebase.Start(null, rebaseUpstreamBranch, null, Constants.Identity, new RebaseOptions())); - Assert.Throws(() => repo.Rebase.Continue(Constants.Identity, new RebaseOptions())); - Assert.Throws(() => repo.Rebase.Abort()); + Assert.Throws(() => Commands.Rebase.Start(repo, null, rebaseUpstreamBranch, null, Constants.Identity, new RebaseOptions())); + Assert.Throws(() => Commands.Rebase.Continue(repo, Constants.Identity, new RebaseOptions())); + Assert.Throws(() => Commands.Rebase.Abort(repo)); } } diff --git a/LibGit2Sharp/Commands/Rebase.cs b/LibGit2Sharp/Commands/Rebase.cs new file mode 100644 index 000000000..82bfeb4c5 --- /dev/null +++ b/LibGit2Sharp/Commands/Rebase.cs @@ -0,0 +1,315 @@ +using System; +using LibGit2Sharp; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + public static partial class Commands + { + /// + /// Begin, continue or abort a rebase operation in a repository. + /// + public static class Rebase + { + /// + /// The type of operation to be performed in a rebase step. + /// + public enum RebaseStepOperation + { + /// + /// Commit is to be cherry-picked. + /// + Pick = 0, + + /// + /// Cherry-pick the commit and edit the commit message. + /// + Reword, + + /// + /// Cherry-pick the commit but allow user to edit changes. + /// + Edit, + + /// + /// Commit is to be squashed into previous commit. The commit + /// message will be merged with the previous message. + /// + Squash, + + /// + /// Commit is to be squashed into previous commit. The commit + /// message will be discarded. + /// + Fixup, + + // + // No commit to cherry-pick. Run the given command and continue + // if successful. + // + // Exec + } + + private unsafe static AnnotatedCommitHandle AnnotatedCommitHandleFromRefHandle(Repository repository, ReferenceHandle refHandle) + { + return (refHandle == null) ? + new AnnotatedCommitHandle(null, false) : + Proxy.git_annotated_commit_from_ref(repository.Handle, refHandle); + } + + /// + /// Start a rebase operation. + /// + /// The to start a rebase in. + /// The branch to rebase. + /// The starting commit to rebase. + /// The branch to rebase onto. + /// The of who added the change to the repository. + /// The that specify the rebase behavior. + /// true if completed successfully, false if conflicts encountered. + public static RebaseResult Start(Repository repository, Branch branch, Branch upstream, Branch onto, Identity committer, RebaseOptions options) + { + Ensure.ArgumentNotNull(upstream, "upstream"); + + options = options ?? new RebaseOptions(); + + EnsureNonBareRepo(repository); + + if (repository.Info.CurrentOperation != CurrentOperation.None) + { + throw new LibGit2SharpException("A {0} operation is already in progress.", + repository.Info.CurrentOperation); + } + + Func RefHandleFromBranch = (Branch b) => + { + return (b == null) ? + null : + repository.Refs.RetrieveReferencePtr(b.CanonicalName); + }; + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + checkout_options = checkoutOptionsWrapper.Options, + }; + + using (ReferenceHandle branchRefPtr = RefHandleFromBranch(branch)) + using (ReferenceHandle upstreamRefPtr = RefHandleFromBranch(upstream)) + using (ReferenceHandle ontoRefPtr = RefHandleFromBranch(onto)) + using (AnnotatedCommitHandle annotatedBranchCommitHandle = AnnotatedCommitHandleFromRefHandle(repository, branchRefPtr)) + using (AnnotatedCommitHandle upstreamRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(repository, upstreamRefPtr)) + using (AnnotatedCommitHandle ontoRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(repository, ontoRefPtr)) + using (RebaseHandle rebaseOperationHandle = Proxy.git_rebase_init(repository.Handle, + annotatedBranchCommitHandle, + upstreamRefAnnotatedCommitHandle, + ontoRefAnnotatedCommitHandle, + gitRebaseOptions)) + { + RebaseResult rebaseResult = RebaseOperationImpl.Run(rebaseOperationHandle, + repository, + committer, + options); + return rebaseResult; + } + } + } + + /// + /// Continue the current rebase. + /// + /// The to continue a rebase in. + /// The of who added the change to the repository. + /// The that specify the rebase behavior. + public static unsafe RebaseResult Continue(Repository repository, Identity committer, RebaseOptions options) + { + Ensure.ArgumentNotNull(committer, "committer"); + + options = options ?? new RebaseOptions(); + + EnsureNonBareRepo(repository); + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + checkout_options = checkoutOptionsWrapper.Options, + }; + + using (RebaseHandle rebase = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + // TODO: Should we check the pre-conditions for committing here + // for instance - what if we had failed on the git_rebase_finish call, + // do we want continue to be able to restart afterwords... + var rebaseCommitResult = Proxy.git_rebase_commit(rebase, null, committer); + + // Report that we just completed the step + if (options.RebaseStepCompleted != null) + { + // Get information on the current step + long currentStepIndex = Proxy.git_rebase_operation_current(rebase); + long totalStepCount = Proxy.git_rebase_operation_entrycount(rebase); + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebase, currentStepIndex); + + var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8NoCleanupMarshaler.FromNative(gitRebasestepInfo->exec)); + + if (rebaseCommitResult.WasPatchAlreadyApplied) + { + options.RebaseStepCompleted(new AfterRebaseStepInfo(stepInfo, currentStepIndex, totalStepCount)); + } + else + { + options.RebaseStepCompleted(new AfterRebaseStepInfo(stepInfo, + repository.Lookup(new ObjectId(rebaseCommitResult.CommitId)), + currentStepIndex, + totalStepCount)); + } + } + + RebaseResult rebaseResult = RebaseOperationImpl.Run(rebase, repository, committer, options); + return rebaseResult; + } + } + } + + /// + /// Abort the rebase operation. + /// + /// The to abort a rebase in. + public static void Abort(Repository repository) + { + Abort(repository, null); + } + + /// + /// Abort the rebase operation. + /// + /// The to abort a rebase in. + /// The that specify the rebase behavior. + public static void Abort(Repository repository, RebaseOptions options) + { + options = options ?? new RebaseOptions(); + + EnsureNonBareRepo(repository); + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + checkout_options = checkoutOptionsWrapper.Options, + }; + + using (RebaseHandle rebase = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + Proxy.git_rebase_abort(rebase); + } + } + } + + /// + /// The info on the current step. + /// The to get the information about. + /// + public static unsafe RebaseStepInfo GetCurrentStepInfo(Repository repository) + { + if (repository.Info.CurrentOperation != LibGit2Sharp.CurrentOperation.RebaseMerge) + { + return null; + } + + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + long currentStepIndex = Proxy.git_rebase_operation_current(rebaseHandle); + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebaseHandle, currentStepIndex); + var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); + return stepInfo; + } + } + + /// + /// Get info on the specified step + /// + /// The to get the information about. + /// The step number to get information about. + /// + public static unsafe RebaseStepInfo GetStepInfo(Repository repository, long stepIndex) + { + if (repository.Info.CurrentOperation != LibGit2Sharp.CurrentOperation.RebaseMerge) + { + return null; + } + + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebaseHandle, stepIndex); + var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); + return stepInfo; + } + } + + /// + /// Get the index of the current step in the rebase process. + /// + /// The to get the information about. + /// The index + public static long GetCurrentStepIndex(Repository repository) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + return Proxy.git_rebase_operation_current(rebaseHandle); + } + } + + /// + /// Get the number of steps in the rebase process. + /// + /// The to get the information about. + /// The number of steps in the rebase operation. + public static long GetTotalStepCount(Repository repository) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + return Proxy.git_rebase_operation_entrycount(rebaseHandle); + } + } + + private static void EnsureNonBareRepo(Repository repository) + { + if (repository.Info.IsBare) + { + throw new BareRepositoryException("Rebase operations in a bare repository are not supported."); + } + } + } + } +} diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index e97a84c92..d50703c51 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -188,11 +188,6 @@ public interface IRepository : IDisposable /// The of the merge. MergeResult Merge(string committish, Signature merger, MergeOptions options); - /// - /// Access to Rebase functionality. - /// - Rebase Rebase { get; } - /// /// Merge the reference that was recently fetched. This will merge /// the branch on the fetched remote that corresponded to the diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 84110f409..a156fe4a2 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -31,7 +31,6 @@ public sealed class Repository : IRepository private readonly NoteCollection notes; private readonly Lazy odb; private readonly Lazy network; - private readonly Lazy rebaseOperation; private readonly Stack toCleanup = new Stack(); private readonly Ignore ignore; private readonly SubmoduleCollection submodules; @@ -177,7 +176,6 @@ private Repository(string path, RepositoryOptions options, RepositoryRequiredPar notes = new NoteCollection(this); ignore = new Ignore(this); network = new Lazy(() => new Network(this)); - rebaseOperation = new Lazy(() => new Rebase(this)); pathCase = new Lazy(() => new PathCase(this)); submodules = new SubmoduleCollection(this); @@ -304,17 +302,6 @@ public Network Network get { return network.Value; } } - /// - /// Provides access to rebase functionality for a repository. - /// - public Rebase Rebase - { - get - { - return rebaseOperation.Value; - } - } - /// /// Gets the database. /// From e7254b858d197600c59b4f2d69b3eedc8fa9a0f7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Jul 2017 14:52:35 +0100 Subject: [PATCH 4/4] Rebase: introduce a Rebase class Introduce a Rebase class that maps to the rebase object in libgit2 more directly. --- LibGit2Sharp/Commands/Rebase.cs | 42 +--- LibGit2Sharp/Core/NativeMethods.cs | 4 +- LibGit2Sharp/Core/Proxy.cs | 5 +- LibGit2Sharp/ObjectDatabase.cs | 2 +- LibGit2Sharp/Rebase.cs | 326 ++++++++-------------------- LibGit2Sharp/RebaseOperationImpl.cs | 2 +- LibGit2Sharp/RebaseResult.cs | 6 - LibGit2Sharp/RebaseStepOperation.cs | 41 ++++ 8 files changed, 136 insertions(+), 292 deletions(-) create mode 100644 LibGit2Sharp/RebaseStepOperation.cs diff --git a/LibGit2Sharp/Commands/Rebase.cs b/LibGit2Sharp/Commands/Rebase.cs index 82bfeb4c5..fc2673124 100644 --- a/LibGit2Sharp/Commands/Rebase.cs +++ b/LibGit2Sharp/Commands/Rebase.cs @@ -12,45 +12,6 @@ public static partial class Commands /// public static class Rebase { - /// - /// The type of operation to be performed in a rebase step. - /// - public enum RebaseStepOperation - { - /// - /// Commit is to be cherry-picked. - /// - Pick = 0, - - /// - /// Cherry-pick the commit and edit the commit message. - /// - Reword, - - /// - /// Cherry-pick the commit but allow user to edit changes. - /// - Edit, - - /// - /// Commit is to be squashed into previous commit. The commit - /// message will be merged with the previous message. - /// - Squash, - - /// - /// Commit is to be squashed into previous commit. The commit - /// message will be discarded. - /// - Fixup, - - // - // No commit to cherry-pick. Run the given command and continue - // if successful. - // - // Exec - } - private unsafe static AnnotatedCommitHandle AnnotatedCommitHandleFromRefHandle(Repository repository, ReferenceHandle refHandle) { return (refHandle == null) ? @@ -70,6 +31,7 @@ private unsafe static AnnotatedCommitHandle AnnotatedCommitHandleFromRefHandle(R /// true if completed successfully, false if conflicts encountered. public static RebaseResult Start(Repository repository, Branch branch, Branch upstream, Branch onto, Identity committer, RebaseOptions options) { + Ensure.ArgumentNotNull(repository, "repository"); Ensure.ArgumentNotNull(upstream, "upstream"); options = options ?? new RebaseOptions(); @@ -145,7 +107,7 @@ public static unsafe RebaseResult Continue(Repository repository, Identity commi // TODO: Should we check the pre-conditions for committing here // for instance - what if we had failed on the git_rebase_finish call, // do we want continue to be able to restart afterwords... - var rebaseCommitResult = Proxy.git_rebase_commit(rebase, null, committer); + var rebaseCommitResult = Proxy.git_rebase_commit(rebase, null, committer, null); // Report that we just completed the step if (options.RebaseStepCompleted != null) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index d28a7aceb..c0ada0a14 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -208,8 +208,8 @@ internal static extern unsafe int git_rebase_commit( git_rebase* rebase, git_signature* author, git_signature* committer, - IntPtr message_encoding, - IntPtr message); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message_encoding, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message); [DllImport(libgit2)] internal static extern unsafe int git_rebase_abort( diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 02ae4a1c5..25a0dc966 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1792,7 +1792,8 @@ private static UIntPtr GIT_REBASE_NO_OPERATION public static unsafe GitRebaseCommitResult git_rebase_commit( RebaseHandle rebase, Identity author, - Identity committer) + Identity committer, + string message) { Ensure.ArgumentNotNull(rebase, "rebase"); Ensure.ArgumentNotNull(committer, "committer"); @@ -1802,7 +1803,7 @@ public static unsafe GitRebaseCommitResult git_rebase_commit( { GitRebaseCommitResult commitResult = new GitRebaseCommitResult(); - int result = NativeMethods.git_rebase_commit(ref commitResult.CommitId, rebase, authorHandle, committerHandle, IntPtr.Zero, IntPtr.Zero); + int result = NativeMethods.git_rebase_commit(ref commitResult.CommitId, rebase, authorHandle, committerHandle, null, message); if (result == (int)GitErrorCode.Applied) { diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 1f42d82dc..155e23b5e 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -819,7 +819,7 @@ public virtual MergeTreeResult MergeCommits(Commit ours, Commit theirs, MergeTre return mergeResult; } } - + /// /// Packs all the objects in the and write a pack (.pack) and index (.idx) files for them. /// diff --git a/LibGit2Sharp/Rebase.cs b/LibGit2Sharp/Rebase.cs index 00dc3f267..4b002459f 100644 --- a/LibGit2Sharp/Rebase.cs +++ b/LibGit2Sharp/Rebase.cs @@ -1,103 +1,27 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; -using System.Globalization; namespace LibGit2Sharp { - /// - /// The type of operation to be performed in a rebase step. - /// - public enum RebaseStepOperation - { - /// - /// Commit is to be cherry-picked. - /// - Pick = 0, - - /// - /// Cherry-pick the commit and edit the commit message. - /// - Reword, - - /// - /// Cherry-pick the commit but allow user to edit changes. - /// - Edit, - - /// - /// Commit is to be squashed into previous commit. The commit - /// message will be merged with the previous message. - /// - Squash, - - /// - /// Commit is to be squashed into previous commit. The commit - /// message will be discarded. - /// - Fixup, - - // - // No commit to cherry-pick. Run the given command and continue - // if successful. - // - // Exec - } - - /// - /// Encapsulates a rebase operation. - /// - public class Rebase + public class Rebase : IDisposable { - internal readonly Repository repository; - - /// - /// Needed for mocking purposes. - /// - protected Rebase() - { } - - internal Rebase(Repository repo) - { - this.repository = repo; - } + private readonly Repository repository; + private readonly RebaseHandle handle; + private bool disposed = false; - unsafe AnnotatedCommitHandle AnnotatedCommitHandleFromRefHandle(ReferenceHandle refHandle) - { - return (refHandle == null) ? - new AnnotatedCommitHandle(null, false) : - Proxy.git_annotated_commit_from_ref(this.repository.Handle, refHandle); - } - - /// - /// Start a rebase operation. - /// - /// The branch to rebase. - /// The starting commit to rebase. - /// The branch to rebase onto. - /// The of who added the change to the repository. - /// The that specify the rebase behavior. - /// true if completed successfully, false if conflicts encountered. - public virtual RebaseResult Start(Branch branch, Branch upstream, Branch onto, Identity committer, RebaseOptions options) + public Rebase(Repository repository, Branch branch, Branch upstream, Branch onto, RebaseOptions options) { + Ensure.ArgumentNotNull(repository, "repository"); Ensure.ArgumentNotNull(upstream, "upstream"); - options = options ?? new RebaseOptions(); - - EnsureNonBareRepo(); - - if (this.repository.Info.CurrentOperation != CurrentOperation.None) - { - throw new LibGit2SharpException("A {0} operation is already in progress.", - this.repository.Info.CurrentOperation); - } + this.repository = repository; - Func RefHandleFromBranch = (Branch b) => - { - return (b == null) ? - null : - this.repository.Refs.RetrieveReferencePtr(b.CanonicalName); - }; + options = options ?? new RebaseOptions(); using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) { @@ -107,211 +31,133 @@ public virtual RebaseResult Start(Branch branch, Branch upstream, Branch onto, I checkout_options = checkoutOptionsWrapper.Options, }; - using (ReferenceHandle branchRefPtr = RefHandleFromBranch(branch)) - using (ReferenceHandle upstreamRefPtr = RefHandleFromBranch(upstream)) - using (ReferenceHandle ontoRefPtr = RefHandleFromBranch(onto)) - using (AnnotatedCommitHandle annotatedBranchCommitHandle = AnnotatedCommitHandleFromRefHandle(branchRefPtr)) - using (AnnotatedCommitHandle upstreamRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(upstreamRefPtr)) - using (AnnotatedCommitHandle ontoRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(ontoRefPtr)) - using (RebaseHandle rebaseOperationHandle = Proxy.git_rebase_init(this.repository.Handle, - annotatedBranchCommitHandle, - upstreamRefAnnotatedCommitHandle, - ontoRefAnnotatedCommitHandle, - gitRebaseOptions)) + using (ReferenceHandle branchRefPtr = RefHandleFromBranch(repository, branch)) + using (ReferenceHandle upstreamRefPtr = RefHandleFromBranch(repository, upstream)) + using (ReferenceHandle ontoRefPtr = RefHandleFromBranch(repository, onto)) + using (AnnotatedCommitHandle annotatedBranchCommitHandle = AnnotatedCommitHandleFromRefHandle(repository, branchRefPtr)) + using (AnnotatedCommitHandle upstreamRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(repository, upstreamRefPtr)) + using (AnnotatedCommitHandle ontoRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(repository, ontoRefPtr)) { - RebaseResult rebaseResult = RebaseOperationImpl.Run(rebaseOperationHandle, - this.repository, - committer, - options); - return rebaseResult; + handle = Proxy.git_rebase_init(repository.Handle, + annotatedBranchCommitHandle, + upstreamRefAnnotatedCommitHandle, + ontoRefAnnotatedCommitHandle, + gitRebaseOptions); } } } - /// - /// Continue the current rebase. - /// - /// The of who added the change to the repository. - /// The that specify the rebase behavior. - public virtual unsafe RebaseResult Continue(Identity committer, RebaseOptions options) + private static ReferenceHandle RefHandleFromBranch(Repository repository, Branch branch) { - Ensure.ArgumentNotNull(committer, "committer"); + return (branch == null) ? + null : + repository.Refs.RetrieveReferencePtr(branch.CanonicalName); + } - options = options ?? new RebaseOptions(); + private unsafe static AnnotatedCommitHandle AnnotatedCommitHandleFromRefHandle(Repository repository, ReferenceHandle refHandle) + { + return (refHandle == null) ? + new AnnotatedCommitHandle(null, false) : + Proxy.git_annotated_commit_from_ref(repository.Handle, refHandle); + } - EnsureNonBareRepo(); + public virtual unsafe RebaseStepInfo Next() + { + git_rebase_operation* rebaseOp = Proxy.git_rebase_next(handle); - using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + if (rebaseOp == null) { - GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() - { - version = 1, - checkout_options = checkoutOptionsWrapper.Options, - }; - - using (RebaseHandle rebase = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) - { - // TODO: Should we check the pre-conditions for committing here - // for instance - what if we had failed on the git_rebase_finish call, - // do we want continue to be able to restart afterwords... - var rebaseCommitResult = Proxy.git_rebase_commit(rebase, null, committer); - - // Report that we just completed the step - if (options.RebaseStepCompleted != null) - { - // Get information on the current step - long currentStepIndex = Proxy.git_rebase_operation_current(rebase); - long totalStepCount = Proxy.git_rebase_operation_entrycount(rebase); - git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebase, currentStepIndex); + return null; + } - var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, - repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), - LaxUtf8NoCleanupMarshaler.FromNative(gitRebasestepInfo->exec)); + return new RebaseStepInfo(rebaseOp->type, + repository.Lookup(ObjectId.BuildFromPtr(&rebaseOp->id)), + LaxUtf8NoCleanupMarshaler.FromNative(rebaseOp->exec)); + } - if (rebaseCommitResult.WasPatchAlreadyApplied) - { - options.RebaseStepCompleted(new AfterRebaseStepInfo(stepInfo, currentStepIndex, totalStepCount)); - } - else - { - options.RebaseStepCompleted(new AfterRebaseStepInfo(stepInfo, - repository.Lookup(new ObjectId(rebaseCommitResult.CommitId)), - currentStepIndex, - totalStepCount)); - } - } + public virtual ObjectId Commit(Identity author, Identity committer, string message) + { + Ensure.ArgumentNotNull(committer, "committer"); - RebaseResult rebaseResult = RebaseOperationImpl.Run(rebase, repository, committer, options); - return rebaseResult; - } - } + Proxy.GitRebaseCommitResult rebaseResult = Proxy.git_rebase_commit(handle, author, committer, message); + return rebaseResult.WasPatchAlreadyApplied ? null : new ObjectId(rebaseResult.CommitId); } - /// - /// Abort the rebase operation. - /// public virtual void Abort() { - Abort(null); + Proxy.git_rebase_abort(handle); } /// - /// Abort the rebase operation. + /// Get the index of the current step in the rebase process. /// - /// The that specify the rebase behavior. - public virtual void Abort(RebaseOptions options) + /// The to get the information about. + /// The index + public virtual long CurrentStep { - options = options ?? new RebaseOptions(); - - EnsureNonBareRepo(); - - using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + get { - GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() - { - checkout_options = checkoutOptionsWrapper.Options, - }; - - using (RebaseHandle rebase = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) - { - Proxy.git_rebase_abort(rebase); - } + return Proxy.git_rebase_operation_current(handle); } } /// - /// The info on the current step. + /// Get the number of steps in the rebase process. /// - public virtual unsafe RebaseStepInfo GetCurrentStepInfo() + /// The to get the information about. + /// The number of steps in the rebase operation. + public virtual long TotalSteps { - if (repository.Info.CurrentOperation != LibGit2Sharp.CurrentOperation.RebaseMerge) - { - return null; - } - - GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() - { - version = 1, - }; - - using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + get { - long currentStepIndex = Proxy.git_rebase_operation_current(rebaseHandle); - git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebaseHandle, currentStepIndex); - var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, - repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), - LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); - return stepInfo; + return Proxy.git_rebase_operation_entrycount(handle); } } /// - /// Get info on the specified step + /// The info on the current step. + /// The to get the information about. /// - /// - /// - public virtual unsafe RebaseStepInfo GetStepInfo(long stepIndex) + public virtual unsafe RebaseStepInfo CurrentStepInfo { - if (repository.Info.CurrentOperation != LibGit2Sharp.CurrentOperation.RebaseMerge) + get { - return null; - } - - GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() - { - version = 1, - }; - - using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) - { - git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebaseHandle, stepIndex); - var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, - repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), - LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); - return stepInfo; + return GetStepInfo(CurrentStep); } } /// - /// + /// Get info on the specified step /// + /// The to get the information about. + /// The step number to get information about. /// - public virtual long GetCurrentStepIndex() + public virtual unsafe RebaseStepInfo GetStepInfo(long stepIndex) { - GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() - { - version = 1, - }; + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(handle, stepIndex); + return new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); + } - using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + protected virtual void Dispose(bool disposing) + { + if (!disposed) { - return Proxy.git_rebase_operation_current(rebaseHandle); + handle.SafeDispose(); + disposed = true; } } - /// - /// - /// - /// - public virtual long GetTotalStepCount() + public void Dispose() { - GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() - { - version = 1, - }; - - using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) - { - return Proxy.git_rebase_operation_entrycount(rebaseHandle); - } + Dispose(true); + GC.SuppressFinalize(this); } - private void EnsureNonBareRepo() + ~Rebase() { - if (this.repository.Info.IsBare) - { - throw new BareRepositoryException("Rebase operations in a bare repository are not supported."); - } + Dispose(false); } } } diff --git a/LibGit2Sharp/RebaseOperationImpl.cs b/LibGit2Sharp/RebaseOperationImpl.cs index c35564573..43d54d3fe 100644 --- a/LibGit2Sharp/RebaseOperationImpl.cs +++ b/LibGit2Sharp/RebaseOperationImpl.cs @@ -161,7 +161,7 @@ private static RebaseStepResult ApplyPickStep(RebaseHandle rebaseOperationHandle // commit and continue. if (repository.Index.IsFullyMerged) { - Proxy.GitRebaseCommitResult rebase_commit_result = Proxy.git_rebase_commit(rebaseOperationHandle, null, committer); + Proxy.GitRebaseCommitResult rebase_commit_result = Proxy.git_rebase_commit(rebaseOperationHandle, null, committer, null); if (rebase_commit_result.WasPatchAlreadyApplied) { diff --git a/LibGit2Sharp/RebaseResult.cs b/LibGit2Sharp/RebaseResult.cs index bee2254af..1b5564a4a 100644 --- a/LibGit2Sharp/RebaseResult.cs +++ b/LibGit2Sharp/RebaseResult.cs @@ -19,12 +19,6 @@ public enum RebaseStatus /// The rebase operation hit a conflict and stopped. /// Conflicts, - - /// - /// The rebase operation has hit a user requested stop point - /// (edit, reword, ect.) - /// - Stop, }; /// diff --git a/LibGit2Sharp/RebaseStepOperation.cs b/LibGit2Sharp/RebaseStepOperation.cs new file mode 100644 index 000000000..a2d55573e --- /dev/null +++ b/LibGit2Sharp/RebaseStepOperation.cs @@ -0,0 +1,41 @@ +namespace LibGit2Sharp +{ + /// + /// The type of operation to be performed in a rebase step. + /// + public enum RebaseStepOperation + { + /// + /// Commit is to be cherry-picked. + /// + Pick = 0, + + /// + /// Cherry-pick the commit and edit the commit message. + /// + Reword, + + /// + /// Cherry-pick the commit but allow user to edit changes. + /// + Edit, + + /// + /// Commit is to be squashed into previous commit. The commit + /// message will be merged with the previous message. + /// + Squash, + + /// + /// Commit is to be squashed into previous commit. The commit + /// message will be discarded. + /// + Fixup, + + /// + /// No commit to cherry-pick. Run the given command and continue + /// if successful. + /// + Exec + } +}