Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / git4idea / src / git4idea / history / browser / CherryPicker.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package git4idea.history.browser;
17
18 import com.intellij.lifecycle.PeriodicalTasksCloser;
19 import com.intellij.openapi.application.ModalityState;
20 import com.intellij.openapi.ui.MessageType;
21 import com.intellij.openapi.vcs.AbstractVcsHelper;
22 import com.intellij.openapi.vcs.FilePath;
23 import com.intellij.openapi.vcs.VcsException;
24 import com.intellij.openapi.vcs.changes.*;
25 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
26 import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
27 import com.intellij.openapi.vfs.LocalFileSystem;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.util.Consumer;
30 import git4idea.GitVcs;
31
32 import java.util.*;
33
34 public class CherryPicker {
35   private final GitVcs myVcs;
36   private final List<GitCommit> myCommits;
37   private final LowLevelAccess myAccess;
38
39   private final List<VcsException> myExceptions;
40   private final List<VcsException> myWarnings;
41   private final List<FilePath> myDirtyFiles;
42   private final List<String> myMessagesInOrder;
43   private final Map<String, Collection<FilePath>> myFilesToMove;
44
45   public CherryPicker(GitVcs vcs, final List<GitCommit> commits, LowLevelAccess access) {
46     myVcs = vcs;
47     myCommits = commits;
48     myAccess = access;
49
50     myExceptions = new ArrayList<VcsException>();
51     myWarnings = new ArrayList<VcsException>();
52
53     myDirtyFiles = new ArrayList<FilePath>();
54     myMessagesInOrder = new ArrayList<String>(commits.size());
55     myFilesToMove = new HashMap<String, Collection<FilePath>>();
56   }
57
58   public void execute() {
59     final CheckinEnvironment ce = myVcs.getCheckinEnvironment();
60
61     for (int i = 0; i < myCommits.size(); i++) {
62       cherryPickStep(ce, i);
63     }
64
65     // remove those that are in newer lists
66     checkListsForSamePaths();
67
68     for (FilePath file : myDirtyFiles) {
69       VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(file.getPath());
70       if (vf != null) {
71         vf.refresh(false, false);
72       }
73     }
74     findAndProcessChangedForVcs();
75
76     showResults();
77   }
78
79   private void findAndProcessChangedForVcs() {
80     final ChangeListManager clm = PeriodicalTasksCloser.getInstance().safeGetComponent(myVcs.getProject(), ChangeListManager.class);
81     clm.invokeAfterUpdate(new Runnable() {
82       public void run() {
83         moveToCorrectLists(clm);
84       }
85     }, InvokeAfterUpdateMode.SILENT, "", new Consumer<VcsDirtyScopeManager>() {
86       public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) {
87         vcsDirtyScopeManager.filePathsDirty(myDirtyFiles, null);
88       }
89     }, ModalityState.NON_MODAL);
90   }
91
92   private void showResults() {
93     if (myExceptions.isEmpty()) {
94       VcsBalloonProblemNotifier
95         .showOverChangesView(myVcs.getProject(), "Successful cherry-pick into working tree, please commit changes", MessageType.INFO);
96     } else {
97       VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), "Errors in cherry-pick", MessageType.ERROR);
98     }
99     if ((! myExceptions.isEmpty()) || (! myWarnings.isEmpty())) {
100       myExceptions.addAll(myWarnings);
101       AbstractVcsHelper.getInstance(myVcs.getProject()).showErrors(myExceptions, "Cherry-pick problems");
102     }
103   }
104
105   private void moveToCorrectLists(ChangeListManager clm) {
106     for (Map.Entry<String, Collection<FilePath>> entry : myFilesToMove.entrySet()) {
107       final Collection<FilePath> filePaths = entry.getValue();
108       final String message = entry.getKey();
109
110       if (filePaths.isEmpty()) continue;
111
112       final List<Change> changes = new ArrayList<Change>(filePaths.size());
113       for (FilePath filePath : filePaths) {
114         changes.add(clm.getChange(filePath));
115       }
116       if (! changes.isEmpty()) {
117         final LocalChangeList cl = clm.addChangeList(message, null);
118         clm.moveChangesTo(cl, changes.toArray(new Change[changes.size()]));
119       }
120     }
121   }
122
123   private void checkListsForSamePaths() {
124     final GroupOfListsProcessor listsProcessor = new GroupOfListsProcessor();
125     listsProcessor.process(myMessagesInOrder, myFilesToMove);
126     final Set<String> lostSet = listsProcessor.getHaveLostSomething();
127     markFilesMovesToNewerLists(myWarnings, lostSet, myFilesToMove);
128   }
129
130   private void cherryPickStep(CheckinEnvironment ce, int i) {
131     final GitCommit commit = myCommits.get(i);
132     final SHAHash hash = commit.getHash();
133     try {
134       myAccess.cherryPick(hash);
135     }
136     catch (VcsException e) {
137       myExceptions.add(e);
138     }
139     final List<Change> changes = commit.getChanges();
140
141     final Collection<FilePath> paths = ChangesUtil.getPaths(changes);
142     String message = ce.getDefaultMessageFor(paths.toArray(new FilePath[paths.size()]));
143     message = (message == null) ? new StringBuilder().append(commit.getDescription()).append("(cherry picked from commit ")
144       .append(hash.getValue()).append(")").toString() : message;
145
146     myMessagesInOrder.add(message);
147     myFilesToMove.put(message, paths);
148     myDirtyFiles.addAll(paths);
149   }
150
151   private void markFilesMovesToNewerLists(List<VcsException> exceptions, Set<String> lostSet, Map<String, Collection<FilePath>> filesToMove) {
152     if (! lostSet.isEmpty()) {
153       final StringBuilder sb = new StringBuilder("Some changes are moved from following list(s) to other:");
154       boolean first = true;
155       for (String s : lostSet) {
156         if (filesToMove.get(s).isEmpty()) {
157           final VcsException exc =
158             new VcsException("Changelist not created since all files moved to other cherry-pick(s): '" + s + "'");
159           exc.setIsWarning(true);
160           exceptions.add(exc);
161           continue;
162         }
163         sb.append(s);
164         if (! first) {
165           sb.append(", ");
166         }
167         first = false;
168       }
169       if (! first) {
170         final VcsException exc = new VcsException(sb.toString());
171         exc.setIsWarning(true);
172         exceptions.add(exc);
173       }
174     }
175   }
176
177   private static class GroupOfListsProcessor {
178     private final Set<String> myHaveLostSomething;
179
180     private GroupOfListsProcessor() {
181       myHaveLostSomething = new HashSet<String>();
182     }
183
184     public void process(final List<String> messagesInOrder, final Map<String, Collection<FilePath>> filesToMove) {
185       // remove those that are in newer lists
186       for (int i = 1; i < messagesInOrder.size(); i++) {
187         final String message = messagesInOrder.get(i);
188         final Collection<FilePath> currentFiles = filesToMove.get(message);
189
190         for (int j = 0; j < i; j++) {
191           final String previous = messagesInOrder.get(j);
192           final boolean somethingChanged = filesToMove.get(previous).removeAll(currentFiles);
193           if (somethingChanged) {
194             myHaveLostSomething.add(previous);
195           }
196         }
197       }
198     }
199
200     public Set<String> getHaveLostSomething() {
201       return myHaveLostSomething;
202     }
203   }
204 }