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