2 ` * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package git4idea.branch;
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.components.ServiceManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.ProgressIndicator;
23 import com.intellij.openapi.progress.Task;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.openapi.util.Key;
27 import com.intellij.openapi.vcs.VcsException;
28 import com.intellij.openapi.vcs.VcsNotifier;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.util.containers.ContainerUtil;
31 import com.intellij.vcs.log.*;
32 import com.intellij.vcs.log.data.VcsLogData;
33 import com.intellij.vcs.log.impl.HashImpl;
34 import com.intellij.vcs.log.impl.VcsLogUtil;
35 import com.intellij.vcs.log.ui.MergeCommitsHighlighter;
36 import com.intellij.vcs.log.ui.VcsLogHighlighterFactory;
37 import git4idea.GitBranch;
38 import git4idea.commands.GitCommand;
39 import git4idea.commands.GitLineHandler;
40 import git4idea.commands.GitLineHandlerAdapter;
41 import git4idea.repo.GitRepository;
42 import git4idea.repo.GitRepositoryManager;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
49 public class DeepComparator implements VcsLogHighlighter, Disposable {
50 private static final Logger LOG = Logger.getInstance(DeepComparator.class);
52 @NotNull private final Project myProject;
53 @NotNull private final GitRepositoryManager myRepositoryManager;
54 @NotNull private final VcsLogUi myUi;
56 @Nullable private MyTask myTask;
57 @Nullable private Set<CommitId> myNonPickedCommits;
59 public DeepComparator(@NotNull Project project, @NotNull GitRepositoryManager manager, @NotNull VcsLogUi ui, @NotNull Disposable parent) {
61 myRepositoryManager = manager;
63 Disposer.register(parent, this);
66 public void highlightInBackground(@NotNull String branchToCompare, @NotNull VcsLogDataProvider dataProvider) {
68 LOG.error("Shouldn't be possible");
72 Map<GitRepository, GitBranch> repositories = getRepositories(myUi.getDataPack().getLogProviders(), branchToCompare);
73 if (repositories.isEmpty()) {
78 myTask = new MyTask(myProject, repositories, dataProvider, branchToCompare);
83 private Map<GitRepository, GitBranch> getRepositories(@NotNull Map<VirtualFile, VcsLogProvider> providers,
84 @NotNull String branchToCompare) {
85 Map<GitRepository, GitBranch> repos = ContainerUtil.newHashMap();
86 for (VirtualFile root : providers.keySet()) {
87 GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
88 if (repository == null || repository.getCurrentBranch() == null ||
89 repository.getBranches().findBranchByName(branchToCompare) == null) {
92 repos.put(repository, repository.getCurrentBranch());
97 public void stopAndUnhighlight() {
102 private void stopTask() {
103 if (myTask != null) {
109 private void removeHighlighting() {
110 ApplicationManager.getApplication().assertIsDispatchThread();
111 myNonPickedCommits = null;
115 public void dispose() {
116 stopAndUnhighlight();
119 public boolean hasHighlightingOrInProgress() {
120 return myTask != null;
123 public static DeepComparator getInstance(@NotNull Project project, @NotNull VcsLogUi logUi) {
124 return ServiceManager.getService(project, DeepComparatorHolder.class).getInstance(logUi);
129 public VcsLogHighlighter.VcsCommitStyle getStyle(@NotNull VcsShortCommitDetails commitDetails, boolean isSelected) {
130 if (myNonPickedCommits == null) return VcsCommitStyle.DEFAULT;
131 return VcsCommitStyleFactory.foreground(!myNonPickedCommits.contains(new CommitId(commitDetails.getId(), commitDetails.getRoot()))
132 ? MergeCommitsHighlighter.MERGE_COMMIT_FOREGROUND
137 public void update(@NotNull VcsLogDataPack dataPack, boolean refreshHappened) {
138 if (myTask == null) { // no task in progress => not interested in refresh events
142 String comparedBranch = myTask.myComparedBranch;
143 VcsLogBranchFilter branchFilter = dataPack.getFilters().getBranchFilter();
144 if (branchFilter == null || !myTask.myComparedBranch.equals(VcsLogUtil.getSingleFilteredBranch(branchFilter, dataPack.getRefs()))) {
145 stopAndUnhighlight();
149 if (refreshHappened) {
150 Map<GitRepository, GitBranch> repositoriesWithCurrentBranches = myTask.myRepositoriesWithCurrentBranches;
151 VcsLogDataProvider provider = myTask.myProvider;
156 Map<GitRepository, GitBranch> repositories = getRepositories(dataPack.getLogProviders(), comparedBranch);
157 if (repositories.equals(repositoriesWithCurrentBranches)) {
158 // but not if current branch changed
159 highlightInBackground(comparedBranch, provider);
162 removeHighlighting();
167 public static class Factory implements VcsLogHighlighterFactory {
168 @NotNull private static final String ID = "CHERRY_PICKED_COMMITS";
172 public VcsLogHighlighter createHighlighter(@NotNull VcsLogData logDataManager, @NotNull VcsLogUi logUi) {
173 return getInstance(logDataManager.getProject(), logUi);
178 public String getId() {
184 public String getTitle() {
185 return "Cherry Picked Commits";
189 public boolean showMenuItem() {
194 private class MyTask extends Task.Backgroundable {
196 @NotNull private final Project myProject;
197 @NotNull private final Map<GitRepository, GitBranch> myRepositoriesWithCurrentBranches;
198 @NotNull private final VcsLogDataProvider myProvider;
199 @NotNull private final String myComparedBranch;
201 @NotNull private final Set<CommitId> myCollectedNonPickedCommits = ContainerUtil.newHashSet();
202 @Nullable private VcsException myException;
203 private boolean myCancelled;
205 public MyTask(@NotNull Project project,
206 @NotNull Map<GitRepository, GitBranch> repositoriesWithCurrentBranches,
207 @NotNull VcsLogDataProvider dataProvider,
208 @NotNull String branchToCompare) {
209 super(project, "Comparing Branches...");
211 myRepositoriesWithCurrentBranches = repositoriesWithCurrentBranches;
212 myProvider = dataProvider;
213 myComparedBranch = branchToCompare;
217 public void run(@NotNull ProgressIndicator indicator) {
219 for (Map.Entry<GitRepository, GitBranch> entry : myRepositoriesWithCurrentBranches.entrySet()) {
220 GitRepository repo = entry.getKey();
221 GitBranch currentBranch = entry.getValue();
222 myCollectedNonPickedCommits
223 .addAll(getNonPickedCommitsFromGit(myProject, repo.getRoot(), currentBranch.getName(), myComparedBranch));
226 catch (VcsException e) {
233 public void onSuccess() {
238 removeHighlighting();
240 if (myException != null) {
241 VcsNotifier.getInstance(myProject).notifyError("Couldn't compare with branch " + myComparedBranch, myException.getMessage());
244 myNonPickedCommits = myCollectedNonPickedCommits;
247 public void cancel() {
252 private Set<CommitId> getNonPickedCommitsFromGit(@NotNull Project project,
253 @NotNull final VirtualFile root,
254 @NotNull String currentBranch,
255 @NotNull String comparedBranch) throws VcsException {
256 GitLineHandler handler = new GitLineHandler(project, root, GitCommand.CHERRY);
257 handler.addParameters(currentBranch, comparedBranch); // upstream - current branch; head - compared branch
259 final Set<CommitId> pickedCommits = ContainerUtil.newHashSet();
260 handler.addLineListener(new GitLineHandlerAdapter() {
262 public void onLineAvailable(String line, Key outputType) {
263 // + 645caac042ff7fb1a5e3f7d348f00e9ceea5c317
264 // - c3b9b90f6c26affd7e597ebf65db96de8f7e5860
265 if (line.startsWith("+")) {
267 line = line.substring(2).trim();
268 int firstSpace = line.indexOf(' ');
269 if (firstSpace > 0) {
270 line = line.substring(0, firstSpace); // safety-check: take just the first word for sure
272 Hash hash = HashImpl.build(line);
273 pickedCommits.add(new CommitId(hash, root));
275 catch (Exception e) {
276 LOG.error("Couldn't parse line [" + line + "]");
281 handler.runInCurrentThread(null);
282 return pickedCommits;