Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / hg4idea / src / org / zmlx / hg4idea / HgVFSListener.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 org.zmlx.hg4idea;
17
18 import com.intellij.openapi.progress.ProgressIndicator;
19 import com.intellij.openapi.progress.Task;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.vcs.*;
22 import com.intellij.openapi.vcs.changes.ChangeListManagerImpl;
23 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
24 import com.intellij.openapi.vfs.VirtualFile;
25 import com.intellij.util.ui.VcsBackgroundTask;
26 import com.intellij.vcsUtil.VcsUtil;
27 import org.jetbrains.annotations.NotNull;
28 import org.zmlx.hg4idea.command.*;
29
30 import java.util.*;
31
32 /**
33  * Listens to VFS events (such as adding or deleting bunch of files) and performs necessary operations with the VCS.
34  * @author Kirill Likhodedov
35  */
36 public class HgVFSListener extends VcsVFSListener {
37
38   private final VcsDirtyScopeManager dirtyScopeManager;
39
40   protected HgVFSListener(final Project project, final HgVcs vcs) {
41     super(project, vcs);
42     dirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject);
43   }
44
45   @Override
46   protected String getAddTitle() {
47     return HgVcsMessages.message("hg4idea.add.title");
48   }
49
50   @Override
51   protected String getSingleFileAddTitle() {
52     return HgVcsMessages.message("hg4idea.add.single.title");
53   }
54
55   @Override
56   protected String getSingleFileAddPromptTemplate() {
57     return HgVcsMessages.message("hg4idea.add.body");
58   }
59
60   @Override
61   protected void executeAdd(List<VirtualFile> addedFiles, Map<VirtualFile, VirtualFile> copyFromMap) {
62     // if a file is copied from another repository, then 'hg add' should be used instead of 'hg copy'.
63     // Thus here we remove such files from the copyFromMap.
64     for (Iterator<Map.Entry<VirtualFile, VirtualFile>> it = copyFromMap.entrySet().iterator(); it.hasNext(); ) {
65       final Map.Entry<VirtualFile, VirtualFile> entry = it.next();
66       final VirtualFile rootFrom = HgUtil.getHgRootOrNull(myProject, entry.getKey());
67       final VirtualFile rootTo = HgUtil.getHgRootOrNull(myProject, entry.getValue());
68
69       if (rootTo == null || !rootTo.equals(rootFrom)) {
70         it.remove();
71       }
72     }
73
74     // exclude files which are added to a directory which is not version controlled
75     for (Iterator<VirtualFile> it = addedFiles.iterator(); it.hasNext(); ) {
76       if (HgUtil.getHgRootOrNull(myProject, it.next()) == null) {
77         it.remove();
78       }
79     }
80
81     // select files to add if there is something to select
82     if (!addedFiles.isEmpty() || !copyFromMap.isEmpty()) {
83       super.executeAdd(addedFiles, copyFromMap);
84     }
85   }
86
87   @Override
88   protected void performAdding(final Collection<VirtualFile> addedFiles, final Map<VirtualFile, VirtualFile> copyFromMap) {
89     (new Task.ConditionalModal(myProject,
90                                HgVcsMessages.message("hg4idea.add.progress"),
91                                false,
92                                VcsConfiguration.getInstance(myProject).getAddRemoveOption() ) {
93       @Override public void run(@NotNull ProgressIndicator aProgressIndicator) {
94         final ArrayList<HgFile> adds = new ArrayList<HgFile>();
95         final HashMap<HgFile, HgFile> copies = new HashMap<HgFile, HgFile>(); // from -> to
96
97         // separate adds from copies
98         for (VirtualFile file : addedFiles) {
99           if (file.isDirectory()) {
100             continue;
101           }
102
103           final VirtualFile copyFrom = copyFromMap.get(file);
104           if (copyFrom != null) {
105             copies.put(new HgFile(myProject, copyFrom), new HgFile(myProject, file));
106           } else {
107             adds.add(new HgFile(myProject, file));
108           }
109         }
110
111         // add for all files at once
112         if (!adds.isEmpty()) {
113           new HgAddCommand(myProject).execute(adds);
114         }
115
116         // copy needs to be run for each file separately
117         if (!copies.isEmpty()) {
118           for(Map.Entry<HgFile, HgFile> copy : copies.entrySet()) {
119             new HgCopyCommand(myProject).execute(copy.getKey(), copy.getValue());
120           }
121         }
122
123         for (VirtualFile file : addedFiles) {
124           dirtyScopeManager.fileDirty(file);
125         }
126       }
127     }).queue();
128   }
129
130   @Override
131   protected String getDeleteTitle() {
132     return HgVcsMessages.message("hg4idea.remove.multiple.title");
133   }
134
135   @Override
136   protected String getSingleFileDeleteTitle() {
137     return HgVcsMessages.message("hg4idea.remove.single.title");
138   }
139
140   @Override
141   protected String getSingleFileDeletePromptTemplate() {
142     return HgVcsMessages.message("hg4idea.remove.single.body");
143   }
144
145   @Override
146   protected VcsDeleteType needConfirmDeletion(VirtualFile file) {
147     //// newly added files (which were added to the repo but never committed) should be removed from the VCS,
148     //// but without user confirmation.
149     final FilePath filePath = VcsUtil.getFilePath(file.getPath());
150     final VirtualFile repo = HgUtil.getHgRootOrNull(myProject, filePath);
151     if (repo == null) {
152       return super.needConfirmDeletion(file);
153     }
154     final HgFile hgFile = new HgFile(repo, filePath);
155
156     final HgLogCommand logCommand = new HgLogCommand(myProject);
157     logCommand.setLogFile(true);
158     logCommand.setFollowCopies(false);
159     logCommand.setIncludeRemoved(true);
160     final List<HgFileRevision> localRevisions = logCommand.execute(hgFile, -1, true);
161     // file is newly added, if it doesn't have a history or if the last history action was deleting this file.
162     if (localRevisions == null || localRevisions.isEmpty() || localRevisions.get(0).getDeletedFiles().contains(hgFile.getRelativePath())) {
163       return VcsDeleteType.SILENT;
164     }
165     return VcsDeleteType.CONFIRM;
166   }
167
168   protected void executeDelete() {
169     final List<FilePath> filesToDelete = new ArrayList<FilePath>(myDeletedWithoutConfirmFiles);
170     final List<FilePath> deletedFiles = new ArrayList<FilePath>(myDeletedFiles);
171     myDeletedWithoutConfirmFiles.clear();
172     myDeletedFiles.clear();
173
174     // skip unversioned files and files which are not under Mercurial
175     final ChangeListManagerImpl changeListManager = ChangeListManagerImpl.getInstanceImpl(myProject);
176     skipUnversionedAndNotUnderHg(changeListManager, filesToDelete);
177     skipUnversionedAndNotUnderHg(changeListManager, deletedFiles);
178
179     // confirm removal from the VCS if needed
180     if (myRemoveOption.getValue() != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) {
181       if (myRemoveOption.getValue() == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY || deletedFiles.isEmpty()) {
182         filesToDelete.addAll(deletedFiles);
183       }
184       else {
185         Collection<FilePath> filePaths = selectFilePathsToDelete(deletedFiles);
186         if (filePaths != null) {
187           filesToDelete.addAll(filePaths);
188         }
189       }
190     }
191
192     if (!filesToDelete.isEmpty()) {
193       performDeletion(filesToDelete);
194     }
195   }
196
197     /**
198      * Changes the given collection of files by filtering out unversioned files and
199      * files which are not under Mercurial repository.
200      * @param changeListManager instance of the ChangeListManagerImpl to retrieve unversioned files from it.
201      * @param filesToFilter     files to be filtered.
202      */
203   private void skipUnversionedAndNotUnderHg(ChangeListManagerImpl changeListManager, Collection<FilePath> filesToFilter) {
204     for (Iterator<FilePath> iter = filesToFilter.iterator(); iter.hasNext(); ) {
205       final FilePath filePath = iter.next();
206       if (HgUtil.getHgRootOrNull(myProject, filePath) == null || changeListManager.isUnversioned(filePath.getVirtualFile())) {
207         iter.remove();
208       }
209     }
210   }
211
212   @Override
213   protected void performDeletion( final List<FilePath> filesToDelete) {
214     (new Task.ConditionalModal(myProject,
215                                         HgVcsMessages.message("hg4idea.remove.progress"),
216                                         false,
217                                         VcsConfiguration.getInstance(myProject).getAddRemoveOption()) {
218       @Override public void run( @NotNull ProgressIndicator aProgressIndicator ) {
219         final ArrayList<HgFile> deletes = new ArrayList<HgFile>();
220         for (FilePath file : filesToDelete) {
221           if (file.isDirectory()) {
222             continue;
223           }
224
225           deletes.add(new HgFile(VcsUtil.getVcsRootFor(myProject, file), file));
226         }
227
228         if (!deletes.isEmpty()) {
229           new HgRemoveCommand(myProject).execute(deletes);
230         }
231
232         for (HgFile file : deletes) {
233           dirtyScopeManager.fileDirty(file.toFilePath());
234         }
235       }
236
237     }).queue();
238   }
239
240   @Override
241   protected void performMoveRename(List<MovedFileInfo> movedFiles) {
242     (new VcsBackgroundTask<MovedFileInfo>(myProject,
243                                         HgVcsMessages.message("hg4idea.move.progress"),
244                                         VcsConfiguration.getInstance(myProject).getAddRemoveOption(),
245                                         movedFiles) {
246       protected void process(final MovedFileInfo file) throws VcsException {
247         final FilePath source = VcsUtil.getFilePath(file.myOldPath);
248         final FilePath target = VcsUtil.getFilePath(file.myNewPath);
249         (new HgMoveCommand(myProject)).execute(new HgFile(VcsUtil.getVcsRootFor(myProject, source), source), new HgFile(VcsUtil.getVcsRootFor(myProject, target), target));
250         dirtyScopeManager.fileDirty(source);
251         dirtyScopeManager.fileDirty(target);
252       }
253
254     }).queue();
255   }
256
257   @Override
258   protected boolean isDirectoryVersioningSupported() {
259     return false;
260   }
261 }