mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
git: rebase: make rebase dialog reuse the existing common rebase process
This fixes: IDEA-81093, IDEA-55479, IDEA-55672, IDEA-76778, IDEA-55672
This commit is contained in:
@@ -444,7 +444,7 @@ public class DvcsUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static VirtualFile guessVcsRoot(@NotNull Project project, @Nullable VirtualFile file) {
|
public static VirtualFile guessVcsRoot(@NotNull Project project, @Nullable VirtualFile file) {
|
||||||
VirtualFile root = null;
|
VirtualFile root = null;
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file);
|
root = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ abstract class GitAbstractRebaseAction extends DumbAwareAction {
|
|||||||
super.update(e);
|
super.update(e);
|
||||||
Project project = e.getProject();
|
Project project = e.getProject();
|
||||||
if (project == null || !hasGitRepositories(project)) {
|
if (project == null || !hasGitRepositories(project)) {
|
||||||
e.getPresentation().setVisible(false);
|
e.getPresentation().setEnabledAndVisible(false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
e.getPresentation().setEnabledAndVisible(hasRebaseInProgress(project));
|
e.getPresentation().setEnabledAndVisible(hasRebaseInProgress(project));
|
||||||
|
|||||||
@@ -15,38 +15,58 @@
|
|||||||
*/
|
*/
|
||||||
package git4idea.actions;
|
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.project.Project;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import git4idea.commands.GitLineHandler;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import git4idea.i18n.GitBundle;
|
|
||||||
import git4idea.rebase.GitRebaseDialog;
|
import git4idea.rebase.GitRebaseDialog;
|
||||||
|
import git4idea.rebase.GitRebaseUtils;
|
||||||
|
import git4idea.repo.GitRepository;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
import static com.intellij.dvcs.DvcsUtil.sortRepositories;
|
||||||
* Git rebase action
|
import static git4idea.GitUtil.*;
|
||||||
*/
|
import static git4idea.rebase.GitRebaseUtils.getRebasingRepositories;
|
||||||
public class GitRebase extends GitRebaseActionBase {
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
/**
|
public class GitRebase extends DumbAwareAction {
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
@Override
|
||||||
@NotNull
|
public void update(@NotNull AnActionEvent e) {
|
||||||
protected String getActionName() {
|
super.update(e);
|
||||||
return GitBundle.getString("rebase.action.name");
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* {@inheritDoc}
|
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||||
*/
|
final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
|
||||||
@Nullable
|
ArrayList<GitRepository> repositories = ContainerUtil.newArrayList(getRepositories(project));
|
||||||
protected GitLineHandler createHandler(Project project, List<VirtualFile> gitRoots, VirtualFile defaultRoot) {
|
repositories.removeAll(getRebasingRepositories(project));
|
||||||
GitRebaseDialog dialog = new GitRebaseDialog(project, gitRoots, defaultRoot);
|
List<VirtualFile> roots = ContainerUtil.newArrayList(getRootsFromRepositories(sortRepositories(repositories)));
|
||||||
if (!dialog.showAndGet()) {
|
VirtualFile defaultRoot = DvcsUtil.guessVcsRoot(project, e.getData(CommonDataKeys.VIRTUAL_FILE));
|
||||||
return null;
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<VirtualFile> gitRoots,
|
|
||||||
@NotNull final VirtualFile defaultRoot,
|
|
||||||
final Set<VirtualFile> affectedRoots,
|
|
||||||
final List<VcsException> 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<VcsException> 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<VirtualFile> gitRoots, VirtualFile defaultRoot);
|
|
||||||
}
|
|
||||||
@@ -18,31 +18,71 @@ package git4idea.branch;
|
|||||||
import com.intellij.openapi.util.text.StringUtil;
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.intellij.openapi.util.text.StringUtil.nullize;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
public class GitRebaseParams {
|
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) {
|
public GitRebaseParams(@NotNull String upstream) {
|
||||||
myNewBase = newBase;
|
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
|
@NotNull
|
||||||
public List<String> asCommandLineArguments() {
|
public List<String> asCommandLineArguments() {
|
||||||
List<String> args = ContainerUtil.newArrayList();
|
List<String> 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;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@Nullable
|
||||||
public String getNewBase() {
|
public String getNewBase() {
|
||||||
return myNewBase;
|
return myNewBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getUpstream() {
|
||||||
|
return myUpstream;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return StringUtil.join(asCommandLineArguments(), " ");
|
return StringUtil.join(asCommandLineArguments(), " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInteractive() {
|
||||||
|
return myInteractive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package git4idea.commands;
|
package git4idea.commands;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.util.Computable;
|
import com.intellij.openapi.util.Computable;
|
||||||
@@ -567,10 +568,12 @@ public class GitImpl implements Git {
|
|||||||
public GitCommandResult rebase(@NotNull GitRepository repository,
|
public GitCommandResult rebase(@NotNull GitRepository repository,
|
||||||
@NotNull GitRebaseParams parameters,
|
@NotNull GitRebaseParams parameters,
|
||||||
@NotNull GitLineHandlerListener... listeners) {
|
@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());
|
handler.addParameters(parameters.asCommandLineArguments());
|
||||||
addListeners(handler, listeners);
|
addListeners(handler, listeners);
|
||||||
return run(handler);
|
return parameters.isInteractive() ? runWithEditor(project, root, handler, true) : run(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -595,9 +598,9 @@ public class GitImpl implements Git {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static GitCommandResult rebaseResume(@NotNull GitRepository repository,
|
private GitCommandResult rebaseResume(@NotNull GitRepository repository,
|
||||||
@NotNull GitRebaseResumeMode rebaseMode,
|
@NotNull GitRebaseResumeMode rebaseMode,
|
||||||
@NotNull GitLineHandlerListener[] listeners) {
|
@NotNull GitLineHandlerListener[] listeners) {
|
||||||
Project project = repository.getProject();
|
Project project = repository.getProject();
|
||||||
VirtualFile root = repository.getRoot();
|
VirtualFile root = repository.getRoot();
|
||||||
GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE);
|
GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE);
|
||||||
@@ -607,10 +610,10 @@ public class GitImpl implements Git {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static GitCommandResult runWithEditor(@NotNull Project project,
|
private GitCommandResult runWithEditor(@NotNull Project project,
|
||||||
@NotNull VirtualFile root,
|
@NotNull VirtualFile root,
|
||||||
@NotNull GitLineHandler handler,
|
@NotNull GitLineHandler handler,
|
||||||
boolean commitListAware) {
|
boolean commitListAware) {
|
||||||
GitInteractiveRebaseEditorHandler editor = configureEditor(project, root, handler, commitListAware);
|
GitInteractiveRebaseEditorHandler editor = configureEditor(project, root, handler, commitListAware);
|
||||||
try {
|
try {
|
||||||
return run(handler);
|
return run(handler);
|
||||||
@@ -620,11 +623,12 @@ public class GitImpl implements Git {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
@NotNull
|
@NotNull
|
||||||
private static GitInteractiveRebaseEditorHandler configureEditor(@NotNull Project project,
|
protected GitInteractiveRebaseEditorHandler configureEditor(@NotNull Project project,
|
||||||
@NotNull VirtualFile root,
|
@NotNull VirtualFile root,
|
||||||
@NotNull GitLineHandler handler,
|
@NotNull GitLineHandler handler,
|
||||||
boolean commitListAware) {
|
boolean commitListAware) {
|
||||||
GitRebaseEditorService service = GitRebaseEditorService.getInstance();
|
GitRebaseEditorService service = GitRebaseEditorService.getInstance();
|
||||||
GitInteractiveRebaseEditorHandler editor = new GitInteractiveRebaseEditorHandler(service, project, root, handler);
|
GitInteractiveRebaseEditorHandler editor = new GitInteractiveRebaseEditorHandler(service, project, root, handler);
|
||||||
if (!commitListAware) {
|
if (!commitListAware) {
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ import com.intellij.openapi.vfs.VirtualFile;
|
|||||||
import com.intellij.ui.DocumentAdapter;
|
import com.intellij.ui.DocumentAdapter;
|
||||||
import git4idea.*;
|
import git4idea.*;
|
||||||
import git4idea.branch.GitBranchUtil;
|
import git4idea.branch.GitBranchUtil;
|
||||||
import git4idea.commands.GitCommand;
|
import git4idea.branch.GitRebaseParams;
|
||||||
import git4idea.commands.GitLineHandler;
|
|
||||||
import git4idea.config.GitConfigUtil;
|
import git4idea.config.GitConfigUtil;
|
||||||
import git4idea.config.GitRebaseSettings;
|
import git4idea.config.GitRebaseSettings;
|
||||||
import git4idea.i18n.GitBundle;
|
import git4idea.i18n.GitBundle;
|
||||||
import git4idea.merge.GitMergeUtil;
|
import git4idea.merge.GitMergeUtil;
|
||||||
import git4idea.repo.GitRemote;
|
import git4idea.repo.GitRemote;
|
||||||
import git4idea.repo.GitRepository;
|
import git4idea.repo.GitRepository;
|
||||||
|
import git4idea.repo.GitRepositoryManager;
|
||||||
import git4idea.ui.GitReferenceValidator;
|
import git4idea.ui.GitReferenceValidator;
|
||||||
import git4idea.util.GitUIUtil;
|
import git4idea.util.GitUIUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -46,6 +46,9 @@ import java.awt.event.ActionListener;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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
|
* 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);
|
private static final Logger LOG = Logger.getInstance(GitRebaseDialog.class);
|
||||||
|
|
||||||
|
@NotNull private final GitRepositoryManager myRepositoryManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Git root selector
|
* Git root selector
|
||||||
*/
|
*/
|
||||||
@@ -151,6 +156,7 @@ public class GitRebaseDialog extends DialogWrapper {
|
|||||||
init();
|
init();
|
||||||
myProject = project;
|
myProject = project;
|
||||||
mySettings = ServiceManager.getService(myProject, GitRebaseSettings.class);
|
mySettings = ServiceManager.getService(myProject, GitRebaseSettings.class);
|
||||||
|
myRepositoryManager = GitUtil.getRepositoryManager(myProject);
|
||||||
final Runnable validateRunnable = new Runnable() {
|
final Runnable validateRunnable = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
validateFields();
|
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
|
@Override
|
||||||
protected void doOKAction() {
|
protected void doOKAction() {
|
||||||
try {
|
try {
|
||||||
@@ -457,6 +430,31 @@ public class GitRebaseDialog extends DialogWrapper {
|
|||||||
return (VirtualFile)myGitRootComboBox.getSelectedItem();
|
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}
|
* {@inheritDoc}
|
||||||
|
|||||||
@@ -42,10 +42,12 @@ public class GitRebaseProblemDetector extends GitLineHandlerAdapter {
|
|||||||
"you have unstaged changes",
|
"you have unstaged changes",
|
||||||
"your index contains uncommitted 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 myMergeConflict;
|
||||||
private volatile boolean myNoChangeError;
|
private volatile boolean myNoChangeError;
|
||||||
private volatile boolean myDirtyTree;
|
private volatile boolean myDirtyTree;
|
||||||
|
private volatile boolean myStoppedForEditing;
|
||||||
|
|
||||||
public boolean isNoChangeError() {
|
public boolean isNoChangeError() {
|
||||||
return myNoChangeError;
|
return myNoChangeError;
|
||||||
@@ -59,6 +61,10 @@ public class GitRebaseProblemDetector extends GitLineHandlerAdapter {
|
|||||||
return myDirtyTree;
|
return myDirtyTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasStoppedForEditing() {
|
||||||
|
return myStoppedForEditing;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLineAvailable(String line, Key outputType) {
|
public void onLineAvailable(String line, Key outputType) {
|
||||||
for (String conflictIndicator : REBASE_CONFLICT_INDICATORS) {
|
for (String conflictIndicator : REBASE_CONFLICT_INDICATORS) {
|
||||||
@@ -79,5 +85,9 @@ public class GitRebaseProblemDetector extends GitLineHandlerAdapter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtil.containsIgnoreCase(line, STOPPED_FOR_EDITING)) {
|
||||||
|
myStoppedForEditing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,6 +200,10 @@ public class GitRebaseProcess {
|
|||||||
boolean somethingRebased = customMode != null || progressListener.getResult().current > 1;
|
boolean somethingRebased = customMode != null || progressListener.getResult().current > 1;
|
||||||
|
|
||||||
if (result.success()) {
|
if (result.success()) {
|
||||||
|
if (rebaseDetector.hasStoppedForEditing()) {
|
||||||
|
showStoppedForEditingMessage(repository);
|
||||||
|
return new GitRebaseStatus(GitRebaseStatus.Type.SUSPENDED, skippedCommits);
|
||||||
|
}
|
||||||
LOG.debug("Successfully rebased " + repoName);
|
LOG.debug("Successfully rebased " + repoName);
|
||||||
return GitSuccessfulRebase.parseFromOutput(result.getOutput(), skippedCommits);
|
return GitSuccessfulRebase.parseFromOutput(result.getOutput(), skippedCommits);
|
||||||
}
|
}
|
||||||
@@ -346,7 +350,7 @@ public class GitRebaseProcess {
|
|||||||
});
|
});
|
||||||
SuccessType commonType = getItemIfAllTheSame(successTypes, SuccessType.REBASED);
|
SuccessType commonType = getItemIfAllTheSame(successTypes, SuccessType.REBASED);
|
||||||
GitRebaseParams params = myRebaseSpec.getParams();
|
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);
|
message += mentionSkippedCommits(skippedCommits);
|
||||||
myNotifier.notifyMinorInfo("Rebase Successful", message, new NotificationListener.Adapter() {
|
myNotifier.notifyMinorInfo("Rebase Successful", message, new NotificationListener.Adapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -391,6 +395,12 @@ public class GitRebaseProcess {
|
|||||||
return ResolveConflictResult.UNRESOLVED_REMAIN;
|
return ResolveConflictResult.UNRESOLVED_REMAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showStoppedForEditingMessage(@NotNull GitRepository repository) {
|
||||||
|
String description = "Once you are satisfied with your changes you may <a href='continue'>continue</a>";
|
||||||
|
myNotifier.notifyImportantInfo("Rebase Stopped for Editing", description,
|
||||||
|
new RebaseNotificationListener(repository, MultiMap.<GitRepository, GitRebaseUtils.CommitInfo>empty()));
|
||||||
|
}
|
||||||
|
|
||||||
private void showFatalError(@NotNull final String error,
|
private void showFatalError(@NotNull final String error,
|
||||||
@NotNull final GitRepository currentRepository,
|
@NotNull final GitRepository currentRepository,
|
||||||
boolean somethingWasRebased,
|
boolean somethingWasRebased,
|
||||||
@@ -507,10 +517,10 @@ public class GitRebaseProcess {
|
|||||||
|
|
||||||
private class RebaseNotificationListener extends NotificationListener.Adapter {
|
private class RebaseNotificationListener extends NotificationListener.Adapter {
|
||||||
@NotNull private final GitRepository myCurrentRepository;
|
@NotNull private final GitRepository myCurrentRepository;
|
||||||
private final MultiMap<GitRepository, GitRebaseUtils.CommitInfo> mySkippedCommits;
|
@NotNull private final MultiMap<GitRepository, GitRebaseUtils.CommitInfo> mySkippedCommits;
|
||||||
|
|
||||||
RebaseNotificationListener(@NotNull GitRepository currentRepository,
|
RebaseNotificationListener(@NotNull GitRepository currentRepository,
|
||||||
MultiMap<GitRepository, GitRebaseUtils.CommitInfo> skippedCommits) {
|
@NotNull MultiMap<GitRepository, GitRebaseUtils.CommitInfo> skippedCommits) {
|
||||||
myCurrentRepository = currentRepository;
|
myCurrentRepository = currentRepository;
|
||||||
mySkippedCommits = skippedCommits;
|
mySkippedCommits = skippedCommits;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class GitRebaseStatus {
|
|||||||
SUCCESS,
|
SUCCESS,
|
||||||
/**
|
/**
|
||||||
* Rebase started, and some commits were already applied,
|
* Rebase started, and some commits were already applied,
|
||||||
* but then rebase stopped because of conflicts, or because of an error.<br/>
|
* but then rebase stopped because of conflicts, or to edit during interactive rebase, or because of an error.<br/>
|
||||||
* Such rebase can be retried/continued by calling `git rebase --continue/--skip`, or
|
* Such rebase can be retried/continued by calling `git rebase --continue/--skip`, or
|
||||||
* it can be aborted by calling `git rebase --abort`.
|
* it can be aborted by calling `git rebase --abort`.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package git4idea.rebase
|
|||||||
import com.intellij.dvcs.DvcsUtil
|
import com.intellij.dvcs.DvcsUtil
|
||||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||||
import com.intellij.openapi.ui.Messages
|
import com.intellij.openapi.ui.Messages
|
||||||
|
import com.intellij.util.LineSeparator
|
||||||
import git4idea.branch.GitRebaseParams
|
import git4idea.branch.GitRebaseParams
|
||||||
import git4idea.repo.GitRepository
|
import git4idea.repo.GitRepository
|
||||||
import git4idea.test.GitExecutor.file
|
import git4idea.test.GitExecutor.file
|
||||||
@@ -174,7 +175,7 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() {
|
|||||||
myRepo.`diverge feature and master`()
|
myRepo.`diverge feature and master`()
|
||||||
val localChange = LocalChange(myRepo, "new.txt").generate()
|
val localChange = LocalChange(myRepo, "new.txt").generate()
|
||||||
|
|
||||||
object : GitTestingRebaseProcess(myProject, GitRebaseParams("master"), myRepo) {
|
object : GitTestingRebaseProcess(myProject, simpleParams("master"), myRepo) {
|
||||||
override fun getDirtyRoots(repositories: Collection<GitRepository>): Collection<GitRepository> {
|
override fun getDirtyRoots(repositories: Collection<GitRepository>): Collection<GitRepository> {
|
||||||
return listOf(myRepo)
|
return listOf(myRepo)
|
||||||
}
|
}
|
||||||
@@ -190,7 +191,7 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() {
|
|||||||
myRepo.`diverge feature and master`()
|
myRepo.`diverge feature and master`()
|
||||||
val localChange = LocalChange(myRepo, "new.txt").generate()
|
val localChange = LocalChange(myRepo, "new.txt").generate()
|
||||||
|
|
||||||
object : GitTestingRebaseProcess(myProject, GitRebaseParams("master"), myRepo) {
|
object : GitTestingRebaseProcess(myProject, simpleParams("master"), myRepo) {
|
||||||
override fun getDirtyRoots(repositories: Collection<GitRepository>): Collection<GitRepository> {
|
override fun getDirtyRoots(repositories: Collection<GitRepository>): Collection<GitRepository> {
|
||||||
return emptyList()
|
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 <a href='continue'>continue</a>")
|
||||||
|
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) {
|
private fun build(f: RepoBuilder.() -> Unit) {
|
||||||
build(myRepo, f)
|
build(myRepo, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rebaseOnMaster() {
|
private fun rebaseOnMaster() {
|
||||||
GitTestingRebaseProcess(myProject, GitRebaseParams("master"), myRepo).rebase()
|
GitTestingRebaseProcess(myProject, simpleParams("master"), myRepo).rebase()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun simpleParams(newBase: String): GitRebaseParams {
|
||||||
|
return GitRebaseParams(newBase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,8 +216,12 @@ abstract class GitPlatformTest : PlatformTestCase() {
|
|||||||
hookFile.setExecutable(true, false)
|
hookFile.setExecutable(true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun assertSuccessfulNotification(title: String, message: String) {
|
||||||
|
GitTestUtil.assertNotification(NotificationType.INFORMATION, title, message, myVcsNotifier.lastNotification)
|
||||||
|
}
|
||||||
|
|
||||||
protected fun assertSuccessfulNotification(message: String) {
|
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) {
|
protected fun assertWarningNotification(title: String, message: String) {
|
||||||
|
|||||||
@@ -15,12 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package git4idea.test
|
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.branch.GitRebaseParams
|
||||||
import git4idea.commands.GitCommandResult
|
import git4idea.commands.GitCommandResult
|
||||||
import git4idea.commands.GitImpl
|
import git4idea.commands.GitImpl
|
||||||
|
import git4idea.commands.GitLineHandler
|
||||||
import git4idea.commands.GitLineHandlerListener
|
import git4idea.commands.GitLineHandlerListener
|
||||||
|
import git4idea.rebase.GitInteractiveRebaseEditorHandler
|
||||||
|
import git4idea.rebase.GitRebaseEditorService
|
||||||
import git4idea.repo.GitRemote
|
import git4idea.repo.GitRemote
|
||||||
import git4idea.repo.GitRepository
|
import git4idea.repo.GitRepository
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any unknown error that could be returned by Git.
|
* Any unknown error that could be returned by Git.
|
||||||
@@ -28,9 +36,11 @@ import git4idea.repo.GitRepository
|
|||||||
val UNKNOWN_ERROR_TEXT: String = "unknown error"
|
val UNKNOWN_ERROR_TEXT: String = "unknown error"
|
||||||
|
|
||||||
class TestGitImpl : GitImpl() {
|
class TestGitImpl : GitImpl() {
|
||||||
|
private val LOG = Logger.getInstance(TestGitImpl::class.java)
|
||||||
|
|
||||||
private var myRebaseShouldFail: (GitRepository) -> Boolean = { false }
|
@Volatile private var myRebaseShouldFail: (GitRepository) -> Boolean = { false }
|
||||||
private var myPushHandler: (GitRepository) -> GitCommandResult? = { null }
|
@Volatile private var myPushHandler: (GitRepository) -> GitCommandResult? = { null }
|
||||||
|
@Volatile private var myInteractiveRebaseEditor: ((String) -> String)? = null
|
||||||
|
|
||||||
override fun push(repository: GitRepository,
|
override fun push(repository: GitRepository,
|
||||||
remote: GitRemote,
|
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) {
|
fun setShouldRebaseFail(shouldFail: (GitRepository) -> Boolean) {
|
||||||
myRebaseShouldFail = shouldFail
|
myRebaseShouldFail = shouldFail
|
||||||
}
|
}
|
||||||
@@ -75,9 +107,14 @@ class TestGitImpl : GitImpl() {
|
|||||||
myPushHandler = pushHandler;
|
myPushHandler = pushHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setInteractiveRebaseEditor(editor: (String) -> String) {
|
||||||
|
myInteractiveRebaseEditor = editor
|
||||||
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
myRebaseShouldFail = { false }
|
myRebaseShouldFail = { false }
|
||||||
myPushHandler = { null }
|
myPushHandler = { null }
|
||||||
|
myInteractiveRebaseEditor = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun failOrCall(repository: GitRepository, delegate: () -> GitCommandResult): GitCommandResult {
|
private fun failOrCall(repository: GitRepository, delegate: () -> GitCommandResult): GitCommandResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user