diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java index 86ce4101d3c6..82ab5a033673 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java @@ -444,7 +444,7 @@ public class DvcsUtil { } @Nullable - private static VirtualFile guessVcsRoot(@NotNull Project project, @Nullable VirtualFile file) { + public static VirtualFile guessVcsRoot(@NotNull Project project, @Nullable VirtualFile file) { VirtualFile root = null; if (file != null) { root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file); diff --git a/plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java b/plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java index 751186030aef..952724556e93 100644 --- a/plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java +++ b/plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java @@ -44,7 +44,7 @@ abstract class GitAbstractRebaseAction extends DumbAwareAction { super.update(e); Project project = e.getProject(); if (project == null || !hasGitRepositories(project)) { - e.getPresentation().setVisible(false); + e.getPresentation().setEnabledAndVisible(false); } else { e.getPresentation().setEnabledAndVisible(hasRebaseInProgress(project)); diff --git a/plugins/git4idea/src/git4idea/actions/GitRebase.java b/plugins/git4idea/src/git4idea/actions/GitRebase.java index a092544bdda2..51b2665e3667 100644 --- a/plugins/git4idea/src/git4idea/actions/GitRebase.java +++ b/plugins/git4idea/src/git4idea/actions/GitRebase.java @@ -15,38 +15,58 @@ */ package git4idea.actions; +import com.intellij.dvcs.DvcsUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import git4idea.commands.GitLineHandler; -import git4idea.i18n.GitBundle; +import com.intellij.util.containers.ContainerUtil; import git4idea.rebase.GitRebaseDialog; +import git4idea.rebase.GitRebaseUtils; +import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; -/** - * Git rebase action - */ -public class GitRebase extends GitRebaseActionBase { +import static com.intellij.dvcs.DvcsUtil.sortRepositories; +import static git4idea.GitUtil.*; +import static git4idea.rebase.GitRebaseUtils.getRebasingRepositories; +import static java.util.Collections.singletonList; - /** - * {@inheritDoc} - */ - @NotNull - protected String getActionName() { - return GitBundle.getString("rebase.action.name"); +public class GitRebase extends DumbAwareAction { + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + Project project = e.getProject(); + if (project == null || !hasGitRepositories(project)) { + e.getPresentation().setEnabledAndVisible(false); + } + else { + e.getPresentation().setVisible(true); + e.getPresentation().setEnabled(getRebasingRepositories(project).size() < getRepositories(project).size()); + } } - /** - * {@inheritDoc} - */ - @Nullable - protected GitLineHandler createHandler(Project project, List gitRoots, VirtualFile defaultRoot) { - GitRebaseDialog dialog = new GitRebaseDialog(project, gitRoots, defaultRoot); - if (!dialog.showAndGet()) { - return null; + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final Project project = e.getRequiredData(CommonDataKeys.PROJECT); + ArrayList repositories = ContainerUtil.newArrayList(getRepositories(project)); + repositories.removeAll(getRebasingRepositories(project)); + List roots = ContainerUtil.newArrayList(getRootsFromRepositories(sortRepositories(repositories))); + VirtualFile defaultRoot = DvcsUtil.guessVcsRoot(project, e.getData(CommonDataKeys.VIRTUAL_FILE)); + final GitRebaseDialog dialog = new GitRebaseDialog(project, roots, defaultRoot); + if (dialog.showAndGet()) { + ProgressManager.getInstance().run(new Task.Backgroundable(project, "Rebasing...") { + public void run(@NotNull ProgressIndicator indicator) { + GitRebaseUtils.rebase(project, singletonList(dialog.getSelectedRepository()), dialog.getSelectedParams(), indicator); + } + }); } - return dialog.handler(); } } diff --git a/plugins/git4idea/src/git4idea/actions/GitRebaseActionBase.java b/plugins/git4idea/src/git4idea/actions/GitRebaseActionBase.java deleted file mode 100644 index 21458445aa28..000000000000 --- a/plugins/git4idea/src/git4idea/actions/GitRebaseActionBase.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package git4idea.actions; - -import com.intellij.dvcs.DvcsUtil; -import com.intellij.openapi.application.AccessToken; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.VcsNotifier; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import git4idea.GitUtil; -import git4idea.commands.Git; -import git4idea.commands.GitCommandResult; -import git4idea.commands.GitLineHandler; -import git4idea.i18n.GitBundle; -import git4idea.rebase.GitInteractiveRebaseEditorHandler; -import git4idea.rebase.GitRebaseEditorService; -import git4idea.rebase.GitRebaseLineListener; -import git4idea.repo.GitRepositoryManager; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Set; - -/** - * The base class for rebase actions that use editor - */ -public abstract class GitRebaseActionBase extends GitRepositoryAction { - /** - * {@inheritDoc} - */ - protected void perform(@NotNull final Project project, - @NotNull final List gitRoots, - @NotNull final VirtualFile defaultRoot, - final Set affectedRoots, - final List exceptions) throws VcsException { - final GitLineHandler h = createHandler(project, gitRoots, defaultRoot); - if (h == null) { - return; - } - final VirtualFile root = h.workingDirectoryFile(); - GitRebaseEditorService service = GitRebaseEditorService.getInstance(); - final GitInteractiveRebaseEditorHandler editor = new GitInteractiveRebaseEditorHandler(service, project, root, h); - final GitRebaseLineListener resultListener = new GitRebaseLineListener(); - h.addLineListener(resultListener); - configureEditor(editor); - affectedRoots.add(root); - - service.configureHandler(h, editor.getHandlerNo()); - new Task.Backgroundable(project, GitBundle.getString("rebasing.title"), true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - AccessToken token = DvcsUtil.workingTreeChangeStarted(project); - try { - GitCommandResult result = ServiceManager.getService(Git.class).runCommand(h); - editor.close(); - GitRepositoryManager manager = GitUtil.getRepositoryManager(project); - manager.updateRepository(root); - VfsUtil.markDirtyAndRefresh(false, true, false, root); - notifyAboutResult(result, resultListener, editor.wasNoopSituationDetected(), exceptions, project); - } - finally { - DvcsUtil.workingTreeChangeFinished(project, token); - } - } - }.queue(); - } - - private static void notifyAboutResult(@NotNull GitCommandResult commandResult, - @NotNull GitRebaseLineListener resultListener, - boolean noopSituation, - @NotNull List exceptions, - @NotNull Project project) { - final GitRebaseLineListener.Result result = resultListener.getResult(); - String messageId; - String message = null; - boolean isError = true; - switch (result.status) { - case CONFLICT: - messageId = "rebase.result.conflict"; - break; - case ERROR: - messageId = "rebase.result.error"; - message = commandResult.getErrorOutputAsHtmlString(); - break; - case CANCELLED: - // we do not need to show a message if editing was cancelled. - exceptions.clear(); - return; - case EDIT: - isError = false; - messageId = "rebase.result.amend"; - break; - case FINISHED: - isError = false; - messageId = "rebase.result.success"; - if (noopSituation) { - message = "Current branch was reset to the base branch"; - } - break; - default: - throw new IllegalStateException("Unsupported rebase result: " + result.status); - } - - String title = GitBundle.message(messageId + ".title"); - if (message == null) { - message = GitBundle.message(messageId, result.current, result.total); - } - - if (isError) { - VcsNotifier.getInstance(project).notifyError(title, message); - } - else { - VcsNotifier.getInstance(project).notifySuccess(title, message); - } - } - - /** - * This method could be overridden to supply additional information to the editor. - * - * @param editor the editor to configure - */ - protected void configureEditor(GitInteractiveRebaseEditorHandler editor) { - } - - /** - * Create line handler that represents a git operation - * - * @param project the context project - * @param gitRoots the git roots - * @param defaultRoot the default root - * @return the line handler or null - */ - @Nullable - protected abstract GitLineHandler createHandler(Project project, List gitRoots, VirtualFile defaultRoot); -} diff --git a/plugins/git4idea/src/git4idea/branch/GitRebaseParams.java b/plugins/git4idea/src/git4idea/branch/GitRebaseParams.java index 200f0ff6e4fb..23d87057c288 100644 --- a/plugins/git4idea/src/git4idea/branch/GitRebaseParams.java +++ b/plugins/git4idea/src/git4idea/branch/GitRebaseParams.java @@ -18,31 +18,71 @@ package git4idea.branch; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; +import static com.intellij.openapi.util.text.StringUtil.nullize; +import static java.util.Arrays.asList; + public class GitRebaseParams { - @NotNull private final String myNewBase; + @Nullable private final String myBranch; + @Nullable private final String myNewBase; + @NotNull private final String myUpstream; + private final boolean myInteractive; + private final boolean myPreserveMerges; - public GitRebaseParams(@NotNull String newBase) { - myNewBase = newBase; + public GitRebaseParams(@NotNull String upstream) { + this(null, null, upstream, false, false); + } + public GitRebaseParams(@Nullable String branch, + @Nullable String newBase, + @NotNull String upstream, + boolean interactive, + boolean preserveMerges) { + myBranch = nullize(branch, true); + myNewBase = nullize(newBase, true); + myUpstream = upstream; + myInteractive = interactive; + myPreserveMerges = preserveMerges; } @NotNull public List asCommandLineArguments() { List args = ContainerUtil.newArrayList(); - args.add(myNewBase); + if (myInteractive) { + args.add("--interactive"); + } + if (myPreserveMerges) { + args.add("--preserve-merges"); + } + if (myNewBase != null) { + args.addAll(asList("--onto", myNewBase)); + } + args.add(myUpstream); + if (myBranch != null) { + args.add(myBranch); + } return args; } - @NotNull + @Nullable public String getNewBase() { return myNewBase; } + @NotNull + public String getUpstream() { + return myUpstream; + } + @Override public String toString() { return StringUtil.join(asCommandLineArguments(), " "); } + + public boolean isInteractive() { + return myInteractive; + } } diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index b8cac21d0954..4bced6045417 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -15,6 +15,7 @@ */ package git4idea.commands; +import com.google.common.annotations.VisibleForTesting; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; @@ -567,10 +568,12 @@ public class GitImpl implements Git { public GitCommandResult rebase(@NotNull GitRepository repository, @NotNull GitRebaseParams parameters, @NotNull GitLineHandlerListener... listeners) { - GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REBASE); + Project project = repository.getProject(); + VirtualFile root = repository.getRoot(); + GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE); handler.addParameters(parameters.asCommandLineArguments()); addListeners(handler, listeners); - return run(handler); + return parameters.isInteractive() ? runWithEditor(project, root, handler, true) : run(handler); } @NotNull @@ -595,9 +598,9 @@ public class GitImpl implements Git { } @NotNull - private static GitCommandResult rebaseResume(@NotNull GitRepository repository, - @NotNull GitRebaseResumeMode rebaseMode, - @NotNull GitLineHandlerListener[] listeners) { + private GitCommandResult rebaseResume(@NotNull GitRepository repository, + @NotNull GitRebaseResumeMode rebaseMode, + @NotNull GitLineHandlerListener[] listeners) { Project project = repository.getProject(); VirtualFile root = repository.getRoot(); GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE); @@ -607,10 +610,10 @@ public class GitImpl implements Git { } @NotNull - private static GitCommandResult runWithEditor(@NotNull Project project, - @NotNull VirtualFile root, - @NotNull GitLineHandler handler, - boolean commitListAware) { + private GitCommandResult runWithEditor(@NotNull Project project, + @NotNull VirtualFile root, + @NotNull GitLineHandler handler, + boolean commitListAware) { GitInteractiveRebaseEditorHandler editor = configureEditor(project, root, handler, commitListAware); try { return run(handler); @@ -620,11 +623,12 @@ public class GitImpl implements Git { } } + @VisibleForTesting @NotNull - private static GitInteractiveRebaseEditorHandler configureEditor(@NotNull Project project, - @NotNull VirtualFile root, - @NotNull GitLineHandler handler, - boolean commitListAware) { + protected GitInteractiveRebaseEditorHandler configureEditor(@NotNull Project project, + @NotNull VirtualFile root, + @NotNull GitLineHandler handler, + boolean commitListAware) { GitRebaseEditorService service = GitRebaseEditorService.getInstance(); GitInteractiveRebaseEditorHandler editor = new GitInteractiveRebaseEditorHandler(service, project, root, handler); if (!commitListAware) { diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java index fb24ce229fef..6186503255a6 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java @@ -26,14 +26,14 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.DocumentAdapter; import git4idea.*; import git4idea.branch.GitBranchUtil; -import git4idea.commands.GitCommand; -import git4idea.commands.GitLineHandler; +import git4idea.branch.GitRebaseParams; import git4idea.config.GitConfigUtil; import git4idea.config.GitRebaseSettings; import git4idea.i18n.GitBundle; import git4idea.merge.GitMergeUtil; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; import git4idea.ui.GitReferenceValidator; import git4idea.util.GitUIUtil; import org.jetbrains.annotations.NotNull; @@ -46,6 +46,9 @@ import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; +import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces; +import static com.intellij.util.ObjectUtils.assertNotNull; + /** * The dialog that allows initiating git rebase activity */ @@ -53,6 +56,8 @@ public class GitRebaseDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance(GitRebaseDialog.class); + @NotNull private final GitRepositoryManager myRepositoryManager; + /** * Git root selector */ @@ -151,6 +156,7 @@ public class GitRebaseDialog extends DialogWrapper { init(); myProject = project; mySettings = ServiceManager.getService(myProject, GitRebaseSettings.class); + myRepositoryManager = GitUtil.getRepositoryManager(myProject); final Runnable validateRunnable = new Runnable() { public void run() { validateFields(); @@ -210,39 +216,6 @@ public class GitRebaseDialog extends DialogWrapper { } } - public GitLineHandler handler() { - GitLineHandler h = new GitLineHandler(myProject, gitRoot(), GitCommand.REBASE); - h.setStdoutSuppressed(false); - if (myInteractiveCheckBox.isSelected() && myInteractiveCheckBox.isEnabled()) { - h.addParameters("-i"); - } - h.addParameters("-v"); - if (!myDoNotUseMergeCheckBox.isSelected()) { - if (myMergeStrategyComboBox.getSelectedItem().equals(GitMergeUtil.DEFAULT_STRATEGY)) { - h.addParameters("-m"); - } - else { - h.addParameters("-s", myMergeStrategyComboBox.getSelectedItem().toString()); - } - } - if (myPreserveMergesCheckBox.isSelected()) { - h.addParameters("-p"); - } - String from = GitUIUtil.getTextField(myFromComboBox).getText(); - String onto = GitUIUtil.getTextField(myOntoComboBox).getText(); - if (from.length() == 0) { - h.addParameters(onto); - } - else { - h.addParameters("--onto", onto, from); - } - final String selectedBranch = (String)myBranchComboBox.getSelectedItem(); - if (myCurrentBranch != null && !myCurrentBranch.getName().equals(selectedBranch)) { - h.addParameters(selectedBranch); - } - return h; - } - @Override protected void doOKAction() { try { @@ -457,6 +430,31 @@ public class GitRebaseDialog extends DialogWrapper { return (VirtualFile)myGitRootComboBox.getSelectedItem(); } + @NotNull + public GitRepository getSelectedRepository() { + return assertNotNull(myRepositoryManager.getRepositoryForRoot(gitRoot())); + } + + @NotNull + public GitRebaseParams getSelectedParams() { + String selectedBranch = (String)myBranchComboBox.getSelectedItem(); + String branch = myCurrentBranch != null && !myCurrentBranch.getName().equals(selectedBranch) ? selectedBranch : null; + + String from = GitUIUtil.getTextField(myFromComboBox).getText(); + String onto = GitUIUtil.getTextField(myOntoComboBox).getText(); + String upstream; + String newBase; + if (isEmptyOrSpaces(from)) { + upstream = onto; + newBase = null; + } + else { + upstream = from; + newBase = onto; + } + + return new GitRebaseParams(branch, newBase, upstream, myInteractiveCheckBox.isSelected(), myPreserveMergesCheckBox.isSelected()); + } /** * {@inheritDoc} diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseProblemDetector.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseProblemDetector.java index 87144ab69f18..6c45b960c5eb 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaseProblemDetector.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaseProblemDetector.java @@ -42,10 +42,12 @@ public class GitRebaseProblemDetector extends GitLineHandlerAdapter { "you have unstaged changes", "your index contains uncommitted changes" }; + private static final String STOPPED_FOR_EDITING = "You can amend the commit now"; private volatile boolean myMergeConflict; private volatile boolean myNoChangeError; private volatile boolean myDirtyTree; + private volatile boolean myStoppedForEditing; public boolean isNoChangeError() { return myNoChangeError; @@ -59,6 +61,10 @@ public class GitRebaseProblemDetector extends GitLineHandlerAdapter { return myDirtyTree; } + public boolean hasStoppedForEditing() { + return myStoppedForEditing; + } + @Override public void onLineAvailable(String line, Key outputType) { for (String conflictIndicator : REBASE_CONFLICT_INDICATORS) { @@ -79,5 +85,9 @@ public class GitRebaseProblemDetector extends GitLineHandlerAdapter { return; } } + + if (StringUtil.containsIgnoreCase(line, STOPPED_FOR_EDITING)) { + myStoppedForEditing = true; + } } } diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseProcess.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseProcess.java index 1fb3c24c39eb..b60c3bdbd127 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaseProcess.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaseProcess.java @@ -200,6 +200,10 @@ public class GitRebaseProcess { boolean somethingRebased = customMode != null || progressListener.getResult().current > 1; if (result.success()) { + if (rebaseDetector.hasStoppedForEditing()) { + showStoppedForEditingMessage(repository); + return new GitRebaseStatus(GitRebaseStatus.Type.SUSPENDED, skippedCommits); + } LOG.debug("Successfully rebased " + repoName); return GitSuccessfulRebase.parseFromOutput(result.getOutput(), skippedCommits); } @@ -346,7 +350,7 @@ public class GitRebaseProcess { }); SuccessType commonType = getItemIfAllTheSame(successTypes, SuccessType.REBASED); GitRebaseParams params = myRebaseSpec.getParams(); - String message = commonType.formatMessage(rebasedBranch, params == null ? null : params.getNewBase()); + String message = commonType.formatMessage(rebasedBranch, params == null ? null : notNull(params.getNewBase(), params.getUpstream())); message += mentionSkippedCommits(skippedCommits); myNotifier.notifyMinorInfo("Rebase Successful", message, new NotificationListener.Adapter() { @Override @@ -391,6 +395,12 @@ public class GitRebaseProcess { return ResolveConflictResult.UNRESOLVED_REMAIN; } + private void showStoppedForEditingMessage(@NotNull GitRepository repository) { + String description = "Once you are satisfied with your changes you may continue"; + myNotifier.notifyImportantInfo("Rebase Stopped for Editing", description, + new RebaseNotificationListener(repository, MultiMap.empty())); + } + private void showFatalError(@NotNull final String error, @NotNull final GitRepository currentRepository, boolean somethingWasRebased, @@ -507,10 +517,10 @@ public class GitRebaseProcess { private class RebaseNotificationListener extends NotificationListener.Adapter { @NotNull private final GitRepository myCurrentRepository; - private final MultiMap mySkippedCommits; + @NotNull private final MultiMap mySkippedCommits; RebaseNotificationListener(@NotNull GitRepository currentRepository, - MultiMap skippedCommits) { + @NotNull MultiMap skippedCommits) { myCurrentRepository = currentRepository; mySkippedCommits = skippedCommits; } diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseStatus.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseStatus.java index 7bfbd9acb74f..f23f1a02fefa 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaseStatus.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaseStatus.java @@ -29,7 +29,7 @@ class GitRebaseStatus { SUCCESS, /** * Rebase started, and some commits were already applied, - * but then rebase stopped because of conflicts, or because of an error.
+ * but then rebase stopped because of conflicts, or to edit during interactive rebase, or because of an error.
* Such rebase can be retried/continued by calling `git rebase --continue/--skip`, or * it can be aborted by calling `git rebase --abort`. */ diff --git a/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt b/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt index 66aca707a3c5..48cecfbf0d58 100644 --- a/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt +++ b/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt @@ -18,6 +18,7 @@ package git4idea.rebase import com.intellij.dvcs.DvcsUtil import com.intellij.openapi.progress.EmptyProgressIndicator import com.intellij.openapi.ui.Messages +import com.intellij.util.LineSeparator import git4idea.branch.GitRebaseParams import git4idea.repo.GitRepository import git4idea.test.GitExecutor.file @@ -174,7 +175,7 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() { myRepo.`diverge feature and master`() val localChange = LocalChange(myRepo, "new.txt").generate() - object : GitTestingRebaseProcess(myProject, GitRebaseParams("master"), myRepo) { + object : GitTestingRebaseProcess(myProject, simpleParams("master"), myRepo) { override fun getDirtyRoots(repositories: Collection): Collection { return listOf(myRepo) } @@ -190,7 +191,7 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() { myRepo.`diverge feature and master`() val localChange = LocalChange(myRepo, "new.txt").generate() - object : GitTestingRebaseProcess(myProject, GitRebaseParams("master"), myRepo) { + object : GitTestingRebaseProcess(myProject, simpleParams("master"), myRepo) { override fun getDirtyRoots(repositories: Collection): Collection { return emptyList() } @@ -392,12 +393,48 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() { """) } + fun `test interactive rebase stopped for editing`() { + build { + master { + 0() + 1() + } + feature(1) { + 2() + 3() + } + } + + + myGit.setInteractiveRebaseEditor { + it.lines().mapIndexed { i, s -> + if (i != 0) s + else s.replace("pick", "edit") + }.joinToString(LineSeparator.getSystemLineSeparator().separatorString) + } + + GitTestingRebaseProcess(myProject, GitRebaseParams(null, null, "master", true, false), myRepo).rebase() + + assertSuccessfulNotification("Rebase Stopped for Editing", "Once you are satisfied with your changes you may continue") + assertEquals("The repository must be in the 'SUSPENDED' state", myRepo, myGitRepositoryManager.ongoingRebaseSpec!!.ongoingRebase) + + GitRebaseUtils.continueRebase(myProject) + + assertSuccessfulNotification("Rebased feature on master") + myRepo.`assert feature rebased on master`() + assertNoRebaseInProgress(myRepo) + } + private fun build(f: RepoBuilder.() -> Unit) { build(myRepo, f) } private fun rebaseOnMaster() { - GitTestingRebaseProcess(myProject, GitRebaseParams("master"), myRepo).rebase() + GitTestingRebaseProcess(myProject, simpleParams("master"), myRepo).rebase() + } + + private fun simpleParams(newBase: String): GitRebaseParams { + return GitRebaseParams(newBase) } } diff --git a/plugins/git4idea/tests/git4idea/test/GitPlatformTest.kt b/plugins/git4idea/tests/git4idea/test/GitPlatformTest.kt index 278ec57a717c..f54b10aa499d 100644 --- a/plugins/git4idea/tests/git4idea/test/GitPlatformTest.kt +++ b/plugins/git4idea/tests/git4idea/test/GitPlatformTest.kt @@ -216,8 +216,12 @@ abstract class GitPlatformTest : PlatformTestCase() { hookFile.setExecutable(true, false) } + protected fun assertSuccessfulNotification(title: String, message: String) { + GitTestUtil.assertNotification(NotificationType.INFORMATION, title, message, myVcsNotifier.lastNotification) + } + protected fun assertSuccessfulNotification(message: String) { - GitTestUtil.assertNotification(NotificationType.INFORMATION, "Rebase Successful", message, myVcsNotifier.lastNotification) + assertSuccessfulNotification("Rebase Successful", message) } protected fun assertWarningNotification(title: String, message: String) { diff --git a/plugins/git4idea/tests/git4idea/test/TestGit.kt b/plugins/git4idea/tests/git4idea/test/TestGit.kt index 14a7fa297d11..69605b12f7d7 100644 --- a/plugins/git4idea/tests/git4idea/test/TestGit.kt +++ b/plugins/git4idea/tests/git4idea/test/TestGit.kt @@ -15,12 +15,20 @@ */ package git4idea.test +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile import git4idea.branch.GitRebaseParams import git4idea.commands.GitCommandResult import git4idea.commands.GitImpl +import git4idea.commands.GitLineHandler import git4idea.commands.GitLineHandlerListener +import git4idea.rebase.GitInteractiveRebaseEditorHandler +import git4idea.rebase.GitRebaseEditorService import git4idea.repo.GitRemote import git4idea.repo.GitRepository +import java.io.File /** * Any unknown error that could be returned by Git. @@ -28,9 +36,11 @@ import git4idea.repo.GitRepository val UNKNOWN_ERROR_TEXT: String = "unknown error" class TestGitImpl : GitImpl() { + private val LOG = Logger.getInstance(TestGitImpl::class.java) - private var myRebaseShouldFail: (GitRepository) -> Boolean = { false } - private var myPushHandler: (GitRepository) -> GitCommandResult? = { null } + @Volatile private var myRebaseShouldFail: (GitRepository) -> Boolean = { false } + @Volatile private var myPushHandler: (GitRepository) -> GitCommandResult? = { null } + @Volatile private var myInteractiveRebaseEditor: ((String) -> String)? = null override fun push(repository: GitRepository, remote: GitRemote, @@ -67,6 +77,28 @@ class TestGitImpl : GitImpl() { } } + override fun configureEditor(project: Project, root: VirtualFile, handler: GitLineHandler, + commitListAware: Boolean): GitInteractiveRebaseEditorHandler { + if (myInteractiveRebaseEditor == null) return super.configureEditor(project, root, handler, commitListAware) + + val service = GitRebaseEditorService.getInstance() + val editor = object: GitInteractiveRebaseEditorHandler(service, project, root, handler) { + override fun editCommits(path: String?): Int { + try { + val file = File(path) + FileUtil.writeToFile(file, myInteractiveRebaseEditor!!(FileUtil.loadFile(file))) + } + catch(e: Exception) { + LOG.error(e) + return 1 + } + return 0 + } + } + service.configureHandler(handler, editor.handlerNo) + return editor + } + fun setShouldRebaseFail(shouldFail: (GitRepository) -> Boolean) { myRebaseShouldFail = shouldFail } @@ -75,9 +107,14 @@ class TestGitImpl : GitImpl() { myPushHandler = pushHandler; } + fun setInteractiveRebaseEditor(editor: (String) -> String) { + myInteractiveRebaseEditor = editor + } + fun reset() { myRebaseShouldFail = { false } myPushHandler = { null } + myInteractiveRebaseEditor = null } private fun failOrCall(repository: GitRepository, delegate: () -> GitCommandResult): GitCommandResult {