/* * Copyright 2000-2010 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 org.community.intellij.plugins.communitycase.history.browser; import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.ObjectsConvertor; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.checkin.CheckinEnvironment; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vfs.newvfs.RefreshSessionImpl; import com.intellij.util.Consumer; import org.community.intellij.plugins.communitycase.Vcs; import java.util.*; public class CherryPicker { private final Vcs myVcs; private final List<Commit> myCommits; private final LowLevelAccess myAccess; private final List<VcsException> myExceptions; private final List<VcsException> myWarnings; private final List<FilePath> myDirtyFiles; private final List<String> myMessagesInOrder; private final Map<String, Collection<FilePath>> myFilesToMove; public CherryPicker(Vcs vcs, final List<Commit> commits, LowLevelAccess access) { myVcs = vcs; myCommits = commits; myAccess = access; myExceptions = new ArrayList<VcsException>(); myWarnings = new ArrayList<VcsException>(); myDirtyFiles = new ArrayList<FilePath>(); myMessagesInOrder = new ArrayList<String>(commits.size()); myFilesToMove = new HashMap<String, Collection<FilePath>>(); } public void execute() { final CheckinEnvironment ce = myVcs.getCheckinEnvironment(); for (int i = 0; i < myCommits.size(); i++) { cherryPickStep(ce, i); } // remove those that are in newer lists checkListsForSamePaths(); final RefreshSessionImpl refreshSession = new RefreshSessionImpl(true, false, new Runnable() { public void run() { findAndProcessChangedForVcs(); } }); refreshSession.addAllFiles(ObjectsConvertor.convert(myDirtyFiles, ObjectsConvertor.FILEPATH_TO_VIRTUAL, ObjectsConvertor.NOT_NULL)); refreshSession.launch(); showResults(); } private void findAndProcessChangedForVcs() { final ChangeListManager clm = PeriodicalTasksCloser.getInstance().safeGetComponent(myVcs.getProject(), ChangeListManager.class); clm.invokeAfterUpdate(new Runnable() { public void run() { moveToCorrectLists(clm); } }, InvokeAfterUpdateMode.SILENT, "", new Consumer<VcsDirtyScopeManager>() { public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) { vcsDirtyScopeManager.filePathsDirty(myDirtyFiles, null); } }, ModalityState.NON_MODAL); } private void showResults() { if (myExceptions.isEmpty()) { VcsBalloonProblemNotifier .showOverChangesView(myVcs.getProject(), "Successful cherry-pick into working tree, please commit changes", MessageType.INFO); } else { VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), "Errors in cherry-pick", MessageType.ERROR); } if ((! myExceptions.isEmpty()) || (! myWarnings.isEmpty())) { myExceptions.addAll(myWarnings); AbstractVcsHelper.getInstance(myVcs.getProject()).showErrors(myExceptions, "Cherry-pick problems"); } } private void moveToCorrectLists(ChangeListManager clm) { for (Map.Entry<String, Collection<FilePath>> entry : myFilesToMove.entrySet()) { final Collection<FilePath> filePaths = entry.getValue(); final String message = entry.getKey(); if (filePaths.isEmpty()) continue; final List<Change> changes = new ArrayList<Change>(filePaths.size()); for (FilePath filePath : filePaths) { changes.add(clm.getChange(filePath)); } if (! changes.isEmpty()) { final LocalChangeList cl = clm.addChangeList(message, null); clm.moveChangesTo(cl, changes.toArray(new Change[changes.size()])); } } } private void checkListsForSamePaths() { final GroupOfListsProcessor listsProcessor = new GroupOfListsProcessor(); listsProcessor.process(myMessagesInOrder, myFilesToMove); final Set<String> lostSet = listsProcessor.getHaveLostSomething(); markFilesMovesToNewerLists(myWarnings, lostSet, myFilesToMove); } private void cherryPickStep(CheckinEnvironment ce, int i) { final Commit commit = myCommits.get(i); final ShaHash hash = commit.getHash(); try { myAccess.cherryPick(hash); } catch (VcsException e) { myExceptions.add(e); } final List<Change> changes = commit.getChanges(); final Collection<FilePath> paths = ChangesUtil.getPaths(changes); String message = ce.getDefaultMessageFor(paths.toArray(new FilePath[paths.size()])); message = (message == null) ? new StringBuilder().append(commit.getDescription()).append("(cherry picked from commit ") .append(hash.getValue()).append(")").toString() : message; myMessagesInOrder.add(message); myFilesToMove.put(message, paths); myDirtyFiles.addAll(paths); } private void markFilesMovesToNewerLists(List<VcsException> exceptions, Set<String> lostSet, Map<String, Collection<FilePath>> 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<String> myHaveLostSomething; private GroupOfListsProcessor() { myHaveLostSomething = new HashSet<String>(); } public void process(final List<String> messagesInOrder, final Map<String, Collection<FilePath>> filesToMove) { // remove those that are in newer lists for (int i = 1; i < messagesInOrder.size(); i++) { final String message = messagesInOrder.get(i); final Collection<FilePath> 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); } } } } public Set<String> getHaveLostSomething() { return myHaveLostSomething; } } }