fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / dvcs-impl / src / com / intellij / dvcs / cherrypick / VcsCherryPickManager.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.dvcs.cherrypick;
3
4 import com.google.common.collect.Lists;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.components.ServiceManager;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.progress.BackgroundTaskQueue;
9 import com.intellij.openapi.progress.ProgressIndicator;
10 import com.intellij.openapi.progress.Task;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.vcs.AbstractVcs;
13 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
14 import com.intellij.openapi.vcs.VcsKey;
15 import com.intellij.openapi.vcs.VcsNotifier;
16 import com.intellij.openapi.vcs.changes.ChangeListManager;
17 import com.intellij.openapi.vcs.changes.ChangeListManagerEx;
18 import com.intellij.util.containers.ContainerUtil;
19 import com.intellij.util.containers.MultiMap;
20 import com.intellij.vcs.log.CommitId;
21 import com.intellij.vcs.log.VcsFullCommitDetails;
22 import com.intellij.vcs.log.VcsLog;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.util.*;
27
28 public class VcsCherryPickManager {
29   private static final Logger LOG = Logger.getInstance(VcsCherryPickManager.class);
30   @NotNull private final Project myProject;
31   @NotNull private final Set<CommitId> myIdsInProgress = ContainerUtil.newConcurrentSet();
32   @NotNull private final BackgroundTaskQueue myTaskQueue;
33
34   public VcsCherryPickManager(@NotNull Project project) {
35     myProject = project;
36     myTaskQueue = new BackgroundTaskQueue(project, "Cherry-picking");
37   }
38
39   public void cherryPick(@NotNull VcsLog log) {
40     log.requestSelectedDetails( details -> myTaskQueue.run(new CherryPickingTask(ContainerUtil.reverse(details))));
41   }
42
43   public boolean isCherryPickAlreadyStartedFor(@NotNull List<? extends CommitId> commits) {
44     for (CommitId commit : commits) {
45       if (myIdsInProgress.contains(commit)) {
46         return true;
47       }
48     }
49     return false;
50   }
51
52   @Nullable
53   private VcsCherryPicker getCherryPickerForCommit(@NotNull VcsFullCommitDetails commitDetails) {
54     AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(commitDetails.getRoot());
55     if (vcs == null) return null;
56     VcsKey key = vcs.getKeyInstanceMethod();
57     return getCherryPickerFor(key);
58   }
59
60   @Nullable
61   public VcsCherryPicker getCherryPickerFor(@NotNull final VcsKey key) {
62     return ContainerUtil.find(VcsCherryPicker.EXTENSION_POINT_NAME.getExtensions(myProject),
63                               picker -> picker.getSupportedVcs().equals(key));
64   }
65
66   private class CherryPickingTask extends Task.Backgroundable {
67     @NotNull private final List<? extends VcsFullCommitDetails> myAllDetailsInReverseOrder;
68     @NotNull private final ChangeListManagerEx myChangeListManager;
69
70     CherryPickingTask(@NotNull List<? extends VcsFullCommitDetails> detailsInReverseOrder) {
71       super(VcsCherryPickManager.this.myProject, "Cherry-Picking");
72       myAllDetailsInReverseOrder = detailsInReverseOrder;
73       myChangeListManager = (ChangeListManagerEx)ChangeListManager.getInstance(myProject);
74       myChangeListManager.blockModalNotifications();
75     }
76
77     @Nullable
78     private VcsCherryPicker getCherryPickerOrReportError(@NotNull VcsFullCommitDetails details) {
79       CommitId commitId = new CommitId(details.getId(), details.getRoot());
80       if (myIdsInProgress.contains(commitId)) {
81         showError("Cherry pick process is already started for commit " +
82                   commitId.getHash().toShortString() +
83                   " from root " +
84                   commitId.getRoot().getName());
85         return null;
86       }
87       myIdsInProgress.add(commitId);
88
89       VcsCherryPicker cherryPicker = getCherryPickerForCommit(details);
90       if (cherryPicker == null) {
91         showError(
92           "Cherry pick is not supported for commit " + details.getId().toShortString() + " from root " + details.getRoot().getName());
93         return null;
94       }
95       return cherryPicker;
96     }
97
98     public void showError(@NotNull String message) {
99       VcsNotifier.getInstance(myProject).notifyWeakError(message);
100       LOG.warn(message);
101     }
102
103     @Override
104     public void run(@NotNull ProgressIndicator indicator) {
105       try {
106         boolean isOk = true;
107         MultiMap<VcsCherryPicker, VcsFullCommitDetails> groupedCommits = createArrayMultiMap();
108         for (VcsFullCommitDetails details : myAllDetailsInReverseOrder) {
109           VcsCherryPicker cherryPicker = getCherryPickerOrReportError(details);
110           if (cherryPicker == null) {
111             isOk = false;
112             break;
113           }
114           groupedCommits.putValue(cherryPicker, details);
115         }
116
117         if (isOk) {
118           for (Map.Entry<VcsCherryPicker, Collection<VcsFullCommitDetails>> entry : groupedCommits.entrySet()) {
119             entry.getKey().cherryPick(Lists.newArrayList(entry.getValue()));
120           }
121         }
122       }
123       finally {
124         ApplicationManager.getApplication().invokeLater(() -> {
125           myChangeListManager.unblockModalNotifications();
126           for (VcsFullCommitDetails details : myAllDetailsInReverseOrder) {
127             myIdsInProgress.remove(new CommitId(details.getId(), details.getRoot()));
128           }
129         });
130       }
131     }
132
133     @NotNull
134     public MultiMap<VcsCherryPicker, VcsFullCommitDetails> createArrayMultiMap() {
135       return new MultiMap<VcsCherryPicker, VcsFullCommitDetails>() {
136         @NotNull
137         @Override
138         protected Collection<VcsFullCommitDetails> createCollection() {
139           return new ArrayList<>();
140         }
141       };
142     }
143   }
144
145   public static VcsCherryPickManager getInstance(@NotNull Project project) {
146     return ServiceManager.getService(project, VcsCherryPickManager.class);
147   }
148 }