diff --git a/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java b/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java index 216db2ad3aa8..b057ad1aa2ca 100644 --- a/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java +++ b/plugins/git4idea/src/git4idea/commands/GitSimpleEventDetector.java @@ -26,6 +26,8 @@ public class GitSimpleEventDetector implements GitLineHandlerListener { private boolean myHappened; public enum Event { + CHERRY_PICK_CONFLICT("fter resolving the conflicts"), // a is uppercase in 1.7.0.5 and lowercase in 1.7.9.2 + LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK("would be overwritten by merge"), UNMERGED_PREVENTING_CHECKOUT("you need to resolve your current index first"), UNMERGED_PREVENTING_MERGE("is not possible because you have unmerged files"), BRANCH_NOT_FULLY_MERGED("is not fully merged"), diff --git a/plugins/git4idea/src/git4idea/history/browser/CherryPicker.java b/plugins/git4idea/src/git4idea/history/browser/CherryPicker.java index 77112b2c263f..b0983b944769 100644 --- a/plugins/git4idea/src/git4idea/history/browser/CherryPicker.java +++ b/plugins/git4idea/src/git4idea/history/browser/CherryPicker.java @@ -15,85 +15,184 @@ */ package git4idea.history.browser; -import com.intellij.lifecycle.PeriodicalTasksCloser; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.checkin.CheckinEnvironment; -import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; -import git4idea.GitVcs; import git4idea.PlatformFacade; import git4idea.commands.Git; +import git4idea.commands.GitCommandResult; +import git4idea.commands.GitSimpleEventDetector; +import git4idea.merge.GitConflictResolver; import git4idea.repo.GitRepository; -import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; +import javax.swing.event.HyperlinkEvent; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; -import static com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier.showOverChangesView; +import static git4idea.commands.GitSimpleEventDetector.Event.CHERRY_PICK_CONFLICT; +import static git4idea.commands.GitSimpleEventDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK; public class CherryPicker { - private static final Logger LOG = Logger.getInstance(CherryPicker.class); + @NotNull private final Project myProject; + @NotNull private final Git myGit; + @NotNull private final PlatformFacade myPlatformFacade; + @NotNull private final ChangeListManager myChangeListManager; + private final boolean myAutoCommit; - private GitVcs myVcs; - private List myCommits; - @NotNull private CheckinEnvironment myCheckinEnvironment; - private LowLevelAccess myAccess; - - private List myExceptions; - private List myWarnings; - private boolean myConflictsExist; - private ChangeListManager myChangeListManager; - - private List myCherryPickedData; - - public CherryPicker(GitVcs vcs, final List commits, LowLevelAccess access) { - myVcs = vcs; - myCommits = commits; - myAccess = access; - - myChangeListManager = PeriodicalTasksCloser.getInstance().safeGetComponent(myVcs.getProject(), ChangeListManager.class); - CheckinEnvironment ce = myVcs.getCheckinEnvironment(); - LOG.assertTrue(ce != null); - myCheckinEnvironment = ce; - - myExceptions = new ArrayList(); - myWarnings = new ArrayList(); - myCherryPickedData = new ArrayList(); + public CherryPicker(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade platformFacade, boolean autoCommit) { + myProject = project; + myGit = git; + myPlatformFacade = platformFacade; + myAutoCommit = autoCommit; + myChangeListManager = myPlatformFacade.getChangeListManager(myProject); } - public CherryPicker(Project project, Git git, PlatformFacade platformFacade, GitRepositoryManager repositoryManager, boolean autoCommit) { - } - - public void cherryPick(Map> commitsInRoots) { - - } - - public void execute() { - for (GitCommit commit : myCommits) { - cherryPickStep(commit); + public void cherryPick(@NotNull Map> commitsInRoots) { + List successfulCommits = new ArrayList(); + for (Map.Entry> entry : commitsInRoots.entrySet()) { + if (!cherryPick(entry.getKey(), entry.getValue(), successfulCommits)) { + return; + } } - - // remove those that are in newer lists - checkListsForSamePaths(); - - refreshChangedFiles(); - findAndProcessChangedForVcs(); - - showResults(); + notifySuccess(successfulCommits); } - private void refreshChangedFiles() { - for (FilePath file : getAllChangedFiles()) { - VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(file.getPath()); + private boolean cherryPick(@NotNull GitRepository repository, @NotNull List commits, + @NotNull List successfulCommits) { + if (myAutoCommit) { + GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(CHERRY_PICK_CONFLICT); + GitSimpleEventDetector localChangesOverwrittenDetector = new GitSimpleEventDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK); + for (GitCommit commit : commits) { + GitCommandResult result = myGit.cherryPick(repository, commit.getHash().getValue(), true, + conflictDetector, localChangesOverwrittenDetector); + if (result.success()) { + successfulCommits.add(commit); + } + else if (conflictDetector.hasHappened()) { + boolean mergeCompleted = new CherryPickConflictResolver(myProject, myGit, myPlatformFacade, repository.getRoot(), + commit.getShortHash().getString(), commit.getAuthor(), + commit.getSubject()).merge(); + if (mergeCompleted) { + boolean committed = updateChangeListManagerAndShowCommitDialogIfNeeded(commit, true); + if (!committed) { + notifyConflictWarning(commit, successfulCommits); + return false; + } + else { + successfulCommits.add(commit); + } + } + else { + updateChangeListManagerAndShowCommitDialogIfNeeded(commit, false); + notifyConflictWarning(commit, successfulCommits); + return false; + } + } + else { + // including localChangesOverwrittenDetector.hasHappened() - no special handler for now + notifyError(result.getErrorOutputAsHtmlString(), commit, successfulCommits); + return false; + } + } + } + return true; + } + + private void notifyConflictWarning(GitCommit commit, List successfulCommits) { + String description = commitDetails(commit); + description += getSuccessfulCommitDetailsIfAny(successfulCommits, description); + myPlatformFacade.getNotificator(myProject).notifyWeakWarning("Cherry-picked with conflicts", description); + } + + private boolean updateChangeListManagerAndShowCommitDialogIfNeeded(@NotNull final GitCommit commit, boolean showCommitDialog) { + final Collection paths = ChangesUtil.getPaths(commit.getChanges()); + refreshChangedFiles(paths); + final String commitMessage = createCommitMessage(commit, paths); + LocalChangeList changeList = createChangeListAfterUpdate(commit.getChanges(), paths, commitMessage); + if (showCommitDialog) { + return showCommitDialog(commit, changeList, commitMessage); + } + return false; + } + + @NotNull + private LocalChangeList createChangeListAfterUpdate(@NotNull final List changes, @NotNull final Collection paths, + @NotNull final String commitMessage) { + final AtomicReference changeList = new AtomicReference(); + myChangeListManager.invokeAfterUpdate(new Runnable() { + public void run() { + changeList.set(createChangeList(changes, commitMessage)); + } + }, InvokeAfterUpdateMode.SILENT, "", new Consumer() { + public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) { + vcsDirtyScopeManager.filePathsDirty(paths, null); + } + }, ModalityState.NON_MODAL); + + return changeList.get(); + } + + @NotNull + private String createCommitMessage(@NotNull GitCommit commit, @NotNull Collection paths) { + CheckinEnvironment ce = myPlatformFacade.getVcs(myProject).getCheckinEnvironment(); + String message = ce == null ? null : ce.getDefaultMessageFor(ArrayUtil.toObjectArray(paths, FilePath.class)); + message = message == null ? commit.getDescription() + "\n(cherry-picked from " + commit.getShortHash().getString() + ")" : message; + return message; + } + + private boolean showCommitDialog(@NotNull GitCommit commit, @NotNull LocalChangeList changeList, @NotNull String commitMessage) { + return myPlatformFacade.getVcsHelper(myProject).commitChanges(commit.getChanges(), changeList, commitMessage); + } + + private void notifyError(@NotNull String content, @NotNull GitCommit failedCommit, @NotNull List successfulCommits) { + String description = "Cherry-pick failed for " + commitDetails(failedCommit) + "
" + content; + description += getSuccessfulCommitDetailsIfAny(successfulCommits, description); + myPlatformFacade.getNotificator(myProject).notifyError("Cherry-pick failed", description); + } + + @NotNull + private static String getSuccessfulCommitDetailsIfAny(@NotNull List successfulCommits, @NotNull String description) { + if (!successfulCommits.isEmpty()) { + description += "
However it succeeded for the following " + StringUtil.pluralize("commit", successfulCommits.size()) + ":
"; + description = getCommitsDetails(successfulCommits); + } + return description; + } + + private void notifySuccess(@NotNull List successfulCommits) { + String description = getCommitsDetails(successfulCommits); + myPlatformFacade.getNotificator(myProject).notifySuccess("Cherry-pick successful", description); + } + + @NotNull + private static String getCommitsDetails(@NotNull List successfulCommits) { + String description = ""; + for (GitCommit commit : successfulCommits) { + description += commitDetails(commit); + } + return description; + } + + @NotNull + private static String commitDetails(@NotNull GitCommit commit) { + return commit.getShortHash().toString() + " " + commit.getSubject(); + } + + private void refreshChangedFiles(@NotNull Collection filePaths) { + for (FilePath file : filePaths) { + VirtualFile vf = myPlatformFacade.getLocalFileSystem().refreshAndFindFileByPath(file.getPath()); if (vf != null) { vf.refresh(false, false); } @@ -101,190 +200,92 @@ public class CherryPicker { } @NotNull - private Collection getAllChangedFiles() { - Collection files = new ArrayList(); - for (CherryPickedData data : myCherryPickedData) { - files.addAll(data.getFiles()); + private LocalChangeList createChangeList(@NotNull List changes, @NotNull String commitMessage) { + if (!changes.isEmpty()) { + final LocalChangeList changeList = myChangeListManager.addChangeList(commitMessage, null); + myChangeListManager.moveChangesTo(changeList, changes.toArray(new Change[changes.size()])); + myChangeListManager.setDefaultChangeList(changeList); + return changeList; } - return files; + return myChangeListManager.getDefaultChangeList(); } - private void findAndProcessChangedForVcs() { - myChangeListManager.invokeAfterUpdate(new Runnable() { - public void run() { - moveToCorrectLists(); - } - }, InvokeAfterUpdateMode.SILENT, "", new Consumer() { - public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) { - vcsDirtyScopeManager.filePathsDirty(getAllChangedFiles(), null); - } - }, ModalityState.NON_MODAL); - } + private static class CherryPickConflictResolver extends GitConflictResolver { - private void showResults() { - final Project project = myVcs.getProject(); - if (myExceptions.isEmpty() && !myConflictsExist) { - showOverChangesView(project, "Successful cherry-pick into working tree, please commit changes", MessageType.INFO); - } else { - if (myExceptions.isEmpty()) { - showOverChangesView(project, "Unresolved conflicts while cherry-picking. Resolve conflicts, then commit changes", - MessageType.WARNING); - } else { - showOverChangesView(project, "Errors in cherry-pick", MessageType.ERROR); - } + @NotNull private final VirtualFile myRoot; + @NotNull private final String myCommitHash; + @NotNull private final String myCommitAuthor; + @NotNull private final String myCommitMessage; + @NotNull private final Git myGit; + @NotNull private final PlatformFacade myPlatformFacade; + + public CherryPickConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull PlatformFacade facade, @NotNull VirtualFile root, + @NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) { + super(project, git, facade, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage)); + myGit = git; + myPlatformFacade = facade; + myRoot = root; + myCommitHash = commitHash; + myCommitAuthor = commitAuthor; + myCommitMessage = commitMessage; } - if ((! myExceptions.isEmpty()) || (! myWarnings.isEmpty())) { - myExceptions.addAll(myWarnings); - AbstractVcsHelper.getInstance(project).showErrors(myExceptions, "Cherry-pick problems"); + + private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) { + Params params = new Params(); + params.setErrorNotificationTitle("Cherry-picked with conflicts"); + params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage)); + return params; + } + + @Override + protected void notifyUnresolvedRemain() { + myPlatformFacade.getNotificator(myProject).notifyStrongWarning("Conflicts were not resolved during cherry-pick", + "Cherry-pick is not complete, you have unresolved merges in your working tree
" + + "Resolve conflicts.", + new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, + @NotNull HyperlinkEvent event) { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (event.getDescription().equals("resolve")) { + new CherryPickConflictResolver(myProject, myGit, + myPlatformFacade, myRoot, + myCommitHash, myCommitAuthor, + myCommitMessage) + .mergeNoProceed(); + } + } + } + }); } } - private void moveToCorrectLists() { - for (CherryPickedData pickedData : myCherryPickedData) { - final Collection filePaths = pickedData.getFiles(); - final String message = pickedData.getCommitMessage(); + private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer { - if (filePaths.isEmpty()) continue; + private String myCommitHash; + private String myCommitAuthor; + private String myCommitMessage; - final List changes = pathsToChanges(filePaths); - pickedData.setChanges(changes); - if (!changes.isEmpty()) { - final LocalChangeList cl = myChangeListManager.addChangeList(message, null); - pickedData.setChangeList(cl); - myChangeListManager.moveChangesTo(cl, changes.toArray(new Change[changes.size()])); - } - } - } - - @NotNull - private List pathsToChanges(@NotNull Collection filePaths) { - final List changes = new ArrayList(filePaths.size()); - for (FilePath filePath : filePaths) { - changes.add(myChangeListManager.getChange(filePath)); - } - return changes; - } - - private void checkListsForSamePaths() { - List myMessagesInOrder = new ArrayList(myCherryPickedData.size()); - Map> myFilesToMove = new HashMap>(myCherryPickedData.size()); - for (CherryPickedData data : myCherryPickedData) { - myMessagesInOrder.add(data.getCommitMessage()); - myFilesToMove.put(data.getCommitMessage(), data.getFiles()); - } - final GroupOfListsProcessor listsProcessor = new GroupOfListsProcessor(); - listsProcessor.process(myMessagesInOrder, myFilesToMove); - final Set lostSet = listsProcessor.getHaveLostSomething(); - markFilesMovesToNewerLists(myWarnings, lostSet, myFilesToMove); - } - - private void cherryPickStep(@NotNull GitCommit commit) { - try { - if (!myAccess.cherryPick(commit)) { - myConflictsExist = true; - } - } - catch (VcsException e) { - myExceptions.add(e); - } - final List changes = commit.getChanges(); - - final Collection paths = ChangesUtil.getPaths(changes); - String message = myCheckinEnvironment.getDefaultMessageFor(paths.toArray(new FilePath[paths.size()])); - message = (message == null) ? commit.getDescription() + " (cherry picked from commit " + commit.getShortHash() + ")" : message; - - myCherryPickedData.add(new CherryPickedData(message, paths)); - } - - private static void markFilesMovesToNewerLists(List exceptions, Set lostSet, - Map> filesToMove) { - if (! lostSet.isEmpty()) { - final StringBuilder sb = new StringBuilder("Some changes are moved from following list(s) to other:"); - boolean first = true; - for (String s : lostSet) { - if (filesToMove.get(s).isEmpty()) { - final VcsException exc = - new VcsException("Changelist not created since all files moved to other cherry-pick(s): '" + s + "'"); - exc.setIsWarning(true); - exceptions.add(exc); - continue; - } - sb.append(s); - if (! first) { - sb.append(", "); - } - first = false; - } - if (! first) { - final VcsException exc = new VcsException(sb.toString()); - exc.setIsWarning(true); - exceptions.add(exc); - } - } - } - - private static class GroupOfListsProcessor { - private final Set myHaveLostSomething; - - private GroupOfListsProcessor() { - myHaveLostSomething = new HashSet(); + public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) { + myCommitHash = commitHash; + myCommitAuthor = commitAuthor; + myCommitMessage = commitMessage; } - public void process(final List messagesInOrder, final Map> filesToMove) { - // remove those that are in newer lists - for (int i = 1; i < messagesInOrder.size(); i++) { - final String message = messagesInOrder.get(i); - final Collection currentFiles = filesToMove.get(message); - - for (int j = 0; j < i; j++) { - final String previous = messagesInOrder.get(j); - final boolean somethingChanged = filesToMove.get(previous).removeAll(currentFiles); - if (somethingChanged) { - myHaveLostSomething.add(previous); - } - } - } + @Override + public String getMultipleFileMergeDescription(Collection files) { + return "Conflicts during cherry-picking commit " + myCommitHash + " made by " + myCommitAuthor + "
" + + "\"" + myCommitMessage + "\""; } - public Set getHaveLostSomething() { - return myHaveLostSomething; - } - } - - private static class CherryPickedData { - - private final String myCommitMessage; - private final Collection myFiles; - private LocalChangeList myChangeList; - private Collection myChanges; - - private CherryPickedData(@NotNull String message, @NotNull Collection files) { - myCommitMessage = message; - myFiles = files; + @Override + public String getLeftPanelTitle(VirtualFile file) { + return "Local changes"; } - public Collection getChanges() { - return myChanges; - } - - public LocalChangeList getChangeList() { - return myChangeList; - } - - public String getCommitMessage() { - return myCommitMessage; - } - - public Collection getFiles() { - return myFiles; - } - - public void setChanges(List changes) { - myChanges = changes; - } - - public void setChangeList(LocalChangeList changeList) { - myChangeList = changeList; + @Override + public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) { + return "Changes from cherry-pick " + myCommitHash + ""; } } diff --git a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java index 2771f3046361..2aca5e7395b7 100644 --- a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java +++ b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccess.java @@ -56,14 +56,6 @@ public interface LowLevelAccess { void loadAllTags(final Collection sink) throws VcsException; - /** - * Cherry-picks the specified commit. - * Doesn't autocommit - instead puts the changes into a separate changelist. - * In the case of merge conflict provides the Conflict Resolver dialog. - * @return true if all conflicts were resolved or there were no merge conflicts; false if unresolved files remain. - * @throws VcsException - */ - boolean cherryPick(GitCommit hash) throws VcsException; void loadHashesWithParents(final @NotNull Collection startingPoints, @NotNull final Collection filters, final AsynchConsumer consumer, Getter isCanceled, int useMaxCnt, final boolean topoOrder) throws VcsException; diff --git a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java index 002925c5e3fb..8f8ea572719f 100644 --- a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java +++ b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java @@ -212,103 +212,4 @@ public class LowLevelAccessImpl implements LowLevelAccess { GitTag.listAsStrings(myProject, myRoot, sink, null); } - public boolean cherryPick(GitCommit commit) throws VcsException { - final GitLineHandler handler = new GitLineHandler(myProject, myRoot, GitCommand.CHERRY_PICK); - handler.addParameters("-x", "-n", commit.getHash().getValue()); - handler.endOptions(); - handler.setNoSSH(true); - - final AtomicBoolean conflict = new AtomicBoolean(); - - handler.addLineListener(new GitLineHandlerAdapter() { - public void onLineAvailable(String line, Key outputType) { - if (line.toLowerCase().contains("after resolving the conflicts")) { - conflict.set(true); - } - } - }); - handler.runInCurrentThread(null); - - if (conflict.get()) { - return new CherryPickConflictResolver(myProject, myRoot, commit.getShortHash().getString(), commit.getAuthor(), commit.getSubject()).merge(); - } else { - final List errors = handler.errors(); - if (!errors.isEmpty()) { - throw errors.get(0); - } else { // no conflicts, no errors - return true; - } - } - } - - private static class CherryPickConflictResolver extends GitConflictResolver { - - private VirtualFile myRoot; - private String myCommitHash; - private String myCommitAuthor; - private String myCommitMessage; - - public CherryPickConflictResolver(Project project, VirtualFile root, String commitHash, String commitAuthor, String commitMessage) { - super(project, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class), Collections.singleton(root), - makeParams(commitHash, commitAuthor, commitMessage)); - myRoot = root; - myCommitHash = commitHash; - myCommitAuthor = commitAuthor; - myCommitMessage = commitMessage; - } - - private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) { - Params params = new Params(); - params.setErrorNotificationTitle("Cherry-picked with conflicts"); - params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage)); - return params; - } - - @Override - protected void notifyUnresolvedRemain() { - GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification("Conflicts were not resolved during cherry-pick", - "Cherry-pick is not complete, you have unresolved merges in your working tree
" + - "Resolve conflicts.", - NotificationType.WARNING, new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - if (event.getDescription().equals("resolve")) { - new CherryPickConflictResolver(myProject, myRoot, myCommitHash, myCommitAuthor, myCommitMessage).mergeNoProceed(); - } - } - } - }).notify(myProject); - } - } - - private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer { - - private String myCommitHash; - private String myCommitAuthor; - private String myCommitMessage; - - public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) { - myCommitHash = commitHash; - myCommitAuthor = commitAuthor; - myCommitMessage = commitMessage; - } - - @Override - public String getMultipleFileMergeDescription(Collection files) { - return "Conflicts during cherry-picking commit " + myCommitHash + " made by " + myCommitAuthor + "
" + - "\"" + myCommitMessage + "\""; - } - - @Override - public String getLeftPanelTitle(VirtualFile file) { - return "Local changes"; - } - - @Override - public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) { - return "Changes from cherry-pick " + myCommitHash + ""; - } - } - } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java index 33387f731093..a7ab311b7ccd 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java @@ -19,6 +19,7 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.impl.SimpleDataContext; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.CaptionIcon; import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; @@ -58,10 +59,13 @@ import com.intellij.util.ui.UIUtil; import com.intellij.vcsUtil.VcsUtil; import git4idea.GitUtil; import git4idea.GitVcs; +import git4idea.PlatformFacade; import git4idea.branch.GitBranchOperationsProcessor; import git4idea.changes.GitChangeUtils; +import git4idea.commands.Git; import git4idea.history.browser.*; import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; import git4idea.ui.branch.GitBranchUiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -1611,12 +1615,10 @@ public class GitLogUI implements Disposable { final Application application = ApplicationManager.getApplication(); application.executeOnPooledThread(new Runnable() { public void run() { - for (VirtualFile file : commits.keySet()) { - final List part = (List)commits.get(file); - // earliest first!!! - Collections.reverse(part); - new CherryPicker(GitVcs.getInstance(myProject), part, new LowLevelAccessImpl(myProject, file)).execute(); - } + boolean autoCommit = true; + Map> commitsInRoots = prepareCommitsForCherryPick(commits); + new CherryPicker(myProject, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class), autoCommit).cherryPick( + commitsInRoots); application.invokeLater(new Runnable() { public void run() { @@ -1629,6 +1631,22 @@ public class GitLogUI implements Disposable { }); } + private Map> prepareCommitsForCherryPick(MultiMap commits) { + Map> commitsInRoots = new HashMap>(); + GitRepositoryManager repositoryManager = ServiceManager.getService(myProject, GitRepositoryManager.class); + for (Map.Entry> entry : commits.entrySet()) { + List sortedCommits = new ArrayList(entry.getValue()); + // earliest first!!! + Collections.reverse(sortedCommits); + GitRepository repository = repositoryManager.getRepositoryForRoot(entry.getKey()); + if (repository == null) { + continue; + } + commitsInRoots.put(repository, sortedCommits); + } + return commitsInRoots; + } + // newest first @NotNull private MultiMap getSelectedCommitsAndCheck() { diff --git a/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy b/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy index 1dc94ba3a4d8..c4344bc7d98b 100644 --- a/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy +++ b/plugins/git4idea/tests/git4idea/cherrypick/GitAutoCommittingCherryPickTest.groovy @@ -92,7 +92,11 @@ Otherwise, please use 'git reset' GitCommit commit = commit() invokeCherryPick(commit) assertMergeDialogShown() - assertChangeLists([DEFAULT, commit.getSubject()], commit.getSubject()) + assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit)) + } + + String newCommitMessage(GitCommit commit) { + "${commit.description}\n(cherry-picked from ${commit.hash.value})" } @Test @@ -151,7 +155,7 @@ Otherwise, please use 'git reset' invokeCherryPick(commit) assertMergeDialogShown() assertTrue "Commit dialog was not shown", commitDialogShown - assertChangeLists([DEFAULT, commit.getSubject()], commit.getSubject()) + assertChangeLists([DEFAULT, newCommitMessage(commit)], newCommitMessage(commit)) } @Test diff --git a/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy b/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy index 7c24cf8a4af0..8f9812628a1f 100644 --- a/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy +++ b/plugins/git4idea/tests/git4idea/cherrypick/GitCherryPickTest.groovy @@ -80,7 +80,8 @@ hint: and commit the result with 'git commit' AbstractHash hash = AbstractHash.create(new SHA().toString()) List changes = new ArrayList(); changes.add(new Change(null, new MockContentRevision(new FilePathImpl(new MockVirtualFile("name")), VcsRevisionNumber.NULL))); - new GitCommit(hash, SHAHash.emulate(hash), "John Smith", null, null, commitMessage, null, null, null, null, null, null, null, null, changes, 0) + new GitCommit(hash, SHAHash.emulate(hash), "John Smith", null, null, commitMessage, commitMessage, null, null, null, null, null, null, + null, changes, 0) } void assertOnlyDefaultChangelist() { @@ -113,8 +114,8 @@ hint: and commit the result with 'git commit' ChangeListManager changeListManager = myPlatformFacade.getChangeListManager(myProject) List lists = changeListManager.changeLists Collection listNames = lists.collect { it.name } - assertEquals changeLists.toSet(), listNames.toSet() - assertEquals activeChangelist, changeListManager.defaultChangeList.name + assertEquals "Change lists are different", changeLists.toSet(), listNames.toSet() + assertEquals "Wrong active changelist", activeChangelist, changeListManager.defaultChangeList.name } String notificationContent(GitCommit commit) {