[Git] Fix merge dialog sides for unstash and cherry-pick. Relates to IDEA-51187
[idea/community.git] / plugins / git4idea / src / git4idea / history / browser / LowLevelAccessImpl.java
1
2 /*
3  * Copyright 2000-2010 JetBrains s.r.o.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package git4idea.history.browser;
18
19 import com.intellij.notification.Notification;
20 import com.intellij.notification.NotificationListener;
21 import com.intellij.notification.NotificationType;
22 import com.intellij.notification.Notifications;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.Getter;
26 import com.intellij.openapi.util.Key;
27 import com.intellij.openapi.vcs.FilePathImpl;
28 import com.intellij.openapi.vcs.VcsException;
29 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
30 import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.AsynchConsumer;
34 import git4idea.GitBranch;
35 import git4idea.GitTag;
36 import git4idea.GitVcs;
37 import git4idea.commands.GitCommand;
38 import git4idea.commands.GitLineHandler;
39 import git4idea.commands.GitLineHandlerAdapter;
40 import git4idea.config.GitConfigUtil;
41 import git4idea.history.GitHistoryUtils;
42 import git4idea.history.wholeTree.CommitHashPlusParents;
43 import git4idea.merge.GitMergeConflictResolver;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import javax.swing.event.HyperlinkEvent;
48 import java.util.*;
49 import java.util.concurrent.atomic.AtomicBoolean;
50
51 public class LowLevelAccessImpl implements LowLevelAccess {
52   private final static Logger LOG = Logger.getInstance("#git4idea.history.browser.LowLevelAccessImpl");
53   private final Project myProject;
54   private final VirtualFile myRoot;
55
56   public LowLevelAccessImpl(final Project project, final VirtualFile root) {
57     myProject = project;
58     myRoot = root;
59   }
60
61   @Override
62   public VirtualFile getRoot() {
63     return myRoot;
64   }
65
66   public void loadHashesWithParents(final @NotNull Collection<String> startingPoints,
67                                     @NotNull final Collection<ChangesFilter.Filter> filters,
68                                     final AsynchConsumer<CommitHashPlusParents> consumer,
69                                     Getter<Boolean> isCanceled, int useMaxCnt) throws VcsException {
70     final List<String> parameters = new ArrayList<String>();
71     ChangesFilter.filtersToParameters(filters, parameters);
72
73     if (! startingPoints.isEmpty()) {
74       for (String startingPoint : startingPoints) {
75         parameters.add(startingPoint);
76       }
77     } else {
78       parameters.add("--all");
79     }
80     if (useMaxCnt > 0) {
81       parameters.add("--max-count=" + useMaxCnt);
82     }
83
84     GitHistoryUtils.hashesWithParents(myProject, new FilePathImpl(myRoot), consumer, isCanceled, ArrayUtil.toStringArray(parameters));
85   }
86
87   @Override
88   public List<GitCommit> getCommitDetails(final Collection<String> commitIds, SymbolicRefs refs) throws VcsException {
89     return GitHistoryUtils.commitsDetails(myProject, new FilePathImpl(myRoot), refs, commitIds);
90   }
91
92   public void loadCommits(final Collection<String> startingPoints, final Date beforePoint, final Date afterPoint,
93                              final Collection<ChangesFilter.Filter> filtersIn, final AsynchConsumer<GitCommit> consumer,
94                              int maxCnt, SymbolicRefs refs) throws VcsException {
95     final Collection<ChangesFilter.Filter> filters = new ArrayList<ChangesFilter.Filter>(filtersIn);
96     if (beforePoint != null) {
97       filters.add(new ChangesFilter.BeforeDate(new Date(beforePoint.getTime() - 1)));
98     }
99     if (afterPoint != null) {
100       filters.add(new ChangesFilter.AfterDate(afterPoint));
101     }
102
103     loadCommits(startingPoints, Collections.<String>emptyList(), filters, consumer, maxCnt, null, refs);
104   }
105
106   public SymbolicRefs getRefs() throws VcsException {
107     final SymbolicRefs refs = new SymbolicRefs();
108     loadAllTags(refs.getTags());
109     final List<GitBranch> allBranches = new ArrayList<GitBranch>();
110     final GitBranch current = GitBranch.list(myProject, myRoot, true, true, allBranches, null);
111     for (GitBranch branch : allBranches) {
112       if (branch.isRemote()) {
113         String name = branch.getName();
114         name = name.startsWith("remotes/") ? name.substring("remotes/".length()) : name;
115         refs.addRemote(name);
116       } else {
117         refs.addLocal(branch.getName());
118       }
119     }
120     refs.setCurrent(current);
121     if (current != null) {
122       refs.setTrackedRemote(current.getTrackedRemoteName(myProject, myRoot));
123     }
124     refs.setUsername(GitConfigUtil.getValue(myProject, myRoot, GitConfigUtil.USER_NAME));
125     // todo
126     /*GitStashUtils.loadStashStack(myProject, myRoot, new Consumer<StashInfo>() {
127       @Override
128       public void consume(StashInfo stashInfo) {
129
130       }
131     });*/
132     return refs;
133   }
134
135   public void loadCommits(final @NotNull Collection<String> startingPoints, @NotNull final Collection<String> endPoints,
136                           @NotNull final Collection<ChangesFilter.Filter> filters,
137                           @NotNull final AsynchConsumer<GitCommit> consumer,
138                           int useMaxCnt,
139                           Getter<Boolean> isCanceled, SymbolicRefs refs)
140     throws VcsException {
141
142     final List<String> parameters = new ArrayList<String>();
143     if (useMaxCnt > 0) {
144       parameters.add("--max-count=" + useMaxCnt);
145     }
146
147     ChangesFilter.filtersToParameters(filters, parameters);
148
149     if (! startingPoints.isEmpty()) {
150       for (String startingPoint : startingPoints) {
151         parameters.add(startingPoint);
152       }
153     } else {
154       parameters.add("--all");
155     }
156
157     for (String endPoint : endPoints) {
158       parameters.add("^" + endPoint);
159     }
160
161     GitHistoryUtils.historyWithLinks(myProject, new FilePathImpl(myRoot),
162                                      refs, consumer, isCanceled, ArrayUtil.toStringArray(parameters));
163   }
164
165   public List<String> getBranchesWithCommit(final SHAHash hash) throws VcsException {
166     final List<String> result = new ArrayList<String>();
167     GitBranch.listAsStrings(myProject, myRoot, true, true, result, hash.getValue());
168     //GitBranch.listAsStrings(myProject, myRoot, true, false, result, hash.getValue());
169     return result;
170   }
171
172   public Collection<String> getTagsWithCommit(final SHAHash hash) throws VcsException {
173     final List<String> result = new ArrayList<String>();
174     GitTag.listAsStrings(myProject, myRoot, result, hash.getValue());
175     return result;
176   }
177
178   @Nullable
179   public GitBranch loadLocalBranches(Collection<String> sink) throws VcsException {
180     return GitBranch.listAsStrings(myProject, myRoot, false, true, sink, null);
181   }
182
183   @Nullable
184   public GitBranch loadRemoteBranches(Collection<String> sink) throws VcsException {
185     return GitBranch.listAsStrings(myProject, myRoot, true, false, sink, null);
186   }
187
188   public void loadAllBranches(List<String> sink) throws VcsException {
189     GitBranch.listAsStrings(myProject, myRoot, true, false, sink, null);
190     GitBranch.listAsStrings(myProject, myRoot, false, true, sink, null);
191   }
192
193   public void loadAllTags(Collection<String> sink) throws VcsException {
194     GitTag.listAsStrings(myProject, myRoot, sink, null);
195   }
196
197   public boolean cherryPick(GitCommit commit) throws VcsException {
198     final GitLineHandler handler = new GitLineHandler(myProject, myRoot, GitCommand.CHERRY_PICK);
199     handler.addParameters("-x", "-n", commit.getHash().getValue());
200     handler.endOptions();
201     handler.setNoSSH(true);
202
203     final AtomicBoolean conflict = new AtomicBoolean();
204
205     handler.addLineListener(new GitLineHandlerAdapter() {
206       public void onLineAvailable(String line, Key outputType) {
207         if (line.contains("after resolving the conflicts, mark the corrected paths")) {
208           conflict.set(true);
209         }
210       }
211     });
212     handler.runInCurrentThread(null);
213
214     if (conflict.get()) {
215       boolean allConflictsResolved = new CherryPickConflictResolver(myProject, commit.getShortHash().getString(), commit.getAuthor(), commit.getSubject()).merge(Collections.singleton(myRoot));
216       return allConflictsResolved;
217     } else {
218       final List<VcsException> errors = handler.errors();
219       if (!errors.isEmpty()) {
220         throw errors.get(0);
221       } else { // no conflicts, no errors
222         return true;
223       }
224     }
225   }
226
227   private static class CherryPickConflictResolver extends GitMergeConflictResolver {
228
229     private String myCommitHash;
230     private String myCommitAuthor;
231     private String myCommitMessage;
232
233     public CherryPickConflictResolver(Project project, String commitHash, String commitAuthor, String commitMessage) {
234       super(project, false, new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage), "Cherry-picked with conflicts", "");
235       myCommitHash = commitHash;
236       myCommitAuthor = commitAuthor;
237       myCommitMessage = commitMessage;
238     }
239
240     @Override
241     protected void notifyUnresolvedRemain(final Collection<VirtualFile> roots) {
242       Notifications.Bus.notify(new Notification(GitVcs.IMPORTANT_ERROR_NOTIFICATION, "Conflicts were not resolved during cherry-pick",
243                                                 "Cherry-pick is not complete, you have unresolved merges in your working tree<br/>" +
244                                                 "<a href='resolve'>Resolve</a> conflicts.",
245                                                 NotificationType.WARNING, new NotificationListener() {
246           @Override
247           public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
248             if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
249               if (event.getDescription().equals("resolve")) {
250                 new CherryPickConflictResolver(myProject, myCommitHash, myCommitAuthor, myCommitMessage).justMerge(roots);
251               }
252             }
253           }
254       }));
255     }
256   }
257
258   private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer {
259
260     private String myCommitHash;
261     private String myCommitAuthor;
262     private String myCommitMessage;
263
264     public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) {
265       myCommitHash = commitHash;
266       myCommitAuthor = commitAuthor;
267       myCommitMessage = commitMessage;
268     }
269
270     @Override
271     public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
272       return "<html>Conflicts during cherry-picking commit <code>" + myCommitHash + "</code> made by " + myCommitAuthor + "<br/>" +
273              "<code>\"" + myCommitMessage + "\"</code></html>";
274     }
275
276     @Override
277     public String getLeftPanelTitle(VirtualFile file) {
278       return "Local changes";
279     }
280
281     @Override
282     public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
283       return "<html>Changes from cherry-pick <code>" + myCommitHash + "</code>";
284     }
285   }
286
287 }