vcs: Moved "SortByVcsRoots" logic to "VcsUtil"
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / diff / impl / patch / formove / TriggerAdditionOrDeletion.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 com.intellij.openapi.diff.impl.patch.formove;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.registry.Registry;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.openapi.vcs.*;
23 import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
24 import com.intellij.openapi.vfs.LocalFileSystem;
25 import com.intellij.openapi.vfs.VfsUtilCore;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.util.FilePathByPathComparator;
28 import com.intellij.util.ObjectUtils;
29 import com.intellij.util.Processor;
30 import com.intellij.util.containers.ContainerUtil;
31 import com.intellij.util.containers.MultiMap;
32 import org.jetbrains.annotations.NotNull;
33
34 import java.util.*;
35
36 import static com.intellij.util.Functions.identity;
37 import static com.intellij.vcsUtil.VcsUtil.groupByRoots;
38
39 public class TriggerAdditionOrDeletion {
40   private final Collection<FilePath> myExisting;
41   private final Collection<FilePath> myDeleted;
42   private final Set<FilePath> myAffected;
43   private final Project myProject;
44   private final boolean mySilentAddDelete;
45   private final ProjectLevelVcsManager myVcsManager;
46   private final AbstractVcsHelper myVcsHelper;
47   private static final Logger LOG = Logger.getInstance(TriggerAdditionOrDeletion.class);
48   private final VcsFileListenerContextHelper myVcsFileListenerContextHelper;
49
50   private MultiMap<VcsRoot, FilePath> myPreparedAddition;
51   private MultiMap<VcsRoot, FilePath> myPreparedDeletion;
52
53   public TriggerAdditionOrDeletion(final Project project) {
54     myProject = project;
55     mySilentAddDelete = Registry.is("vcs.add.remove.silent");
56     myExisting = new HashSet<>();
57     myDeleted = new HashSet<>();
58     myVcsManager = ProjectLevelVcsManager.getInstance(myProject);
59     myVcsHelper = AbstractVcsHelper.getInstance(myProject);
60     myAffected = new HashSet<>();
61     myVcsFileListenerContextHelper = VcsFileListenerContextHelper.getInstance(myProject);
62   }
63
64   public void addExisting(final Collection<FilePath> files) {
65     myExisting.addAll(files);
66   }
67
68   public void addDeleted(final Collection<FilePath> files) {
69     myDeleted.addAll(files);
70   }
71
72   public void prepare() {
73     if (myExisting.isEmpty() && myDeleted.isEmpty()) return;
74
75     if (! myExisting.isEmpty()) {
76       processAddition();
77     }
78     if (! myDeleted.isEmpty()) {
79       processDeletion();
80     }
81   }
82
83   public void processIt() {
84     if (myPreparedDeletion != null) {
85       for (Map.Entry<VcsRoot, Collection<FilePath>> entry : myPreparedDeletion.entrySet()) {
86         final VcsRoot vcsRoot = entry.getKey();
87         final AbstractVcs vcs = ObjectUtils.assertNotNull(vcsRoot.getVcs());
88         final CheckinEnvironment localChangesProvider = vcs.getCheckinEnvironment();
89         if (localChangesProvider == null) continue;
90         final Collection<FilePath> filePaths = entry.getValue();
91         if (vcs.fileListenerIsSynchronous()) {
92           myAffected.addAll(filePaths);
93           continue;
94         }
95         askUserIfNeeded(vcsRoot.getVcs(), (List<FilePath>)filePaths, VcsConfiguration.StandardConfirmation.REMOVE);
96         myAffected.addAll(filePaths);
97         localChangesProvider.scheduleMissingFileForDeletion((List<FilePath>)filePaths);
98       }
99     }
100     if (myPreparedAddition != null) {
101       final List<FilePath> incorrectFilePath = new ArrayList<>();
102       for (Map.Entry<VcsRoot, Collection<FilePath>> entry : myPreparedAddition.entrySet()) {
103         final VcsRoot vcsRoot = entry.getKey();
104         final AbstractVcs vcs = ObjectUtils.assertNotNull(vcsRoot.getVcs());
105         final CheckinEnvironment localChangesProvider = vcs.getCheckinEnvironment();
106         if (localChangesProvider == null) continue;
107         final Collection<FilePath> filePaths = entry.getValue();
108         if (vcs.fileListenerIsSynchronous()) {
109           myAffected.addAll(filePaths);
110           continue;
111         }
112         askUserIfNeeded(vcsRoot.getVcs(), (List<FilePath>)filePaths, VcsConfiguration.StandardConfirmation.ADD);
113         myAffected.addAll(filePaths);
114         final List<VirtualFile> virtualFiles = new ArrayList<>();
115         ContainerUtil.process(filePaths, new Processor<FilePath>() {
116           @Override
117           public boolean process(FilePath path) {
118             VirtualFile vf = path.getVirtualFile();
119             if (vf == null) {
120               incorrectFilePath.add(path);
121             }
122             else {
123               virtualFiles.add(vf);
124             }
125             return true;
126           }
127         });
128         //virtual files collection shouldn't contain 'null' vf
129         localChangesProvider.scheduleUnversionedFilesForAddition(virtualFiles);
130       }
131       //if some errors occurred  -> notify
132       if (!incorrectFilePath.isEmpty()) {
133         notifyAndLogFiles("Apply new files error", incorrectFilePath);
134       }
135     }
136   }
137
138   private void notifyAndLogFiles(@NotNull String topic, @NotNull List<FilePath> incorrectFilePath) {
139     String message = "The following " + StringUtil.pluralize("file", incorrectFilePath.size()) + " may be processed incorrectly by VCS.\n" +
140                      "Please check it manually: " + incorrectFilePath;
141     LOG.warn(message);
142     VcsNotifier.getInstance(myProject).notifyImportantWarning(topic, message);
143   }
144
145   public Set<FilePath> getAffected() {
146     return myAffected;
147   }
148
149   private void processDeletion() {
150     Map<VcsRoot, List<FilePath>> map = groupByRoots(myProject, myDeleted, identity());
151
152     myPreparedDeletion = new MultiMap<>();
153     for (VcsRoot vcsRoot : map.keySet()) {
154       if (vcsRoot != null && vcsRoot.getVcs() != null) {
155         final CheckinEnvironment localChangesProvider = vcsRoot.getVcs().getCheckinEnvironment();
156         if (localChangesProvider == null) continue;
157         final boolean takeDirs = vcsRoot.getVcs().areDirectoriesVersionedItems();
158
159         final Collection<FilePath> files = map.get(vcsRoot);
160         final List<FilePath> toBeDeleted = new LinkedList<>();
161         for (FilePath file : files) {
162           final FilePath parent = file.getParentPath();
163           if ((takeDirs || (! file.isDirectory())) && parent != null && parent.getIOFile().exists()) {
164             toBeDeleted.add(file);
165           }
166         }
167         if (toBeDeleted.isEmpty()) return;
168         if (! vcsRoot.getVcs().fileListenerIsSynchronous()) {
169           for (FilePath filePath : toBeDeleted) {
170             myVcsFileListenerContextHelper.ignoreDeleted(filePath);
171           }
172         }
173         myPreparedDeletion.put(vcsRoot, toBeDeleted);
174       }
175     }
176   }
177
178   private void processAddition() {
179     Map<VcsRoot, List<FilePath>> map = groupByRoots(myProject, myExisting, identity());
180
181     myPreparedAddition = new MultiMap<>();
182     for (VcsRoot vcsRoot : map.keySet()) {
183       if (vcsRoot != null && vcsRoot.getVcs() != null) {
184         final CheckinEnvironment localChangesProvider = vcsRoot.getVcs().getCheckinEnvironment();
185         if (localChangesProvider == null) continue;
186         final boolean takeDirs = vcsRoot.getVcs().areDirectoriesVersionedItems();
187
188         final Collection<FilePath> files = map.get(vcsRoot);
189         final List<FilePath> toBeAdded;
190         if (takeDirs) {
191           final RecursiveCheckAdder adder = new RecursiveCheckAdder(vcsRoot.getPath());
192           for (FilePath file : files) {
193             adder.process(file);
194           }
195           toBeAdded = adder.getToBeAdded();
196         } else {
197           toBeAdded = new LinkedList<>();
198           for (FilePath file : files) {
199             if (! file.isDirectory()) {
200               toBeAdded.add(file);
201             }
202           }
203         }
204         if (toBeAdded.isEmpty()) {
205           return;
206         }
207         Collections.sort(toBeAdded, FilePathByPathComparator.getInstance());
208         if (! vcsRoot.getVcs().fileListenerIsSynchronous()) {
209           for (FilePath filePath : toBeAdded) {
210             myVcsFileListenerContextHelper.ignoreAdded(filePath.getVirtualFile());
211           }
212         }
213         myPreparedAddition.put(vcsRoot, toBeAdded);
214       }
215     }
216   }
217
218   private void askUserIfNeeded(final AbstractVcs vcs, @NotNull  final List<FilePath> filePaths, @NotNull VcsConfiguration.StandardConfirmation type) {
219     if (mySilentAddDelete) return;
220     final VcsShowConfirmationOption confirmationOption = myVcsManager.getStandardConfirmation(type, vcs);
221     if (VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY.equals(confirmationOption.getValue())) {
222       filePaths.clear();
223     }
224     else if (VcsShowConfirmationOption.Value.SHOW_CONFIRMATION.equals(confirmationOption.getValue())) {
225       String operation = type == VcsConfiguration.StandardConfirmation.ADD ? "addition" : "deletion";
226       String preposition = type == VcsConfiguration.StandardConfirmation.ADD ? " to " : " from ";
227       final Collection<FilePath> files = myVcsHelper.selectFilePathsToProcess(filePaths, "Select files to " +
228                                                                                          StringUtil.decapitalize(type.getId()) +
229                                                                                          preposition +
230                                                                                          vcs.getDisplayName(), null,
231                                                                               "Schedule for " + operation,
232                                                                               "Do you want to schedule the following file for " +
233                                                                               operation +
234                                                                               preposition +
235                                                                               vcs.getDisplayName() +
236                                                                               "\n{0}", confirmationOption);
237       if (files == null) {
238         filePaths.clear();
239       }
240       else {
241         filePaths.retainAll(files);
242       }
243     }
244   }
245
246   private static class RecursiveCheckAdder {
247     private final Set<FilePath> myToBeAdded;
248     private final VirtualFile myRoot;
249
250     private RecursiveCheckAdder(final VirtualFile root) {
251       myRoot = root;
252       myToBeAdded = new HashSet<>();
253     }
254
255     public void process(final FilePath path) {
256       FilePath current = path;
257       while (current != null) {
258         VirtualFile vf = current.getVirtualFile();
259         if (vf == null) {
260           vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(current.getPath());
261         }
262         if (vf == null) {
263           return;
264         }
265         if (!VfsUtilCore.isAncestor(myRoot, vf, true)) return;
266
267         myToBeAdded.add(current);
268         current = current.getParentPath();
269       }
270     }
271
272     public List<FilePath> getToBeAdded() {
273       return new ArrayList<>(myToBeAdded);
274     }
275   }
276 }