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