f606bf50eecadfa2aabd059bbc097129f2421498
[idea/community.git] / platform / vcs-api / src / com / intellij / vcsUtil / VcsUtil.java
1 /*
2  * Copyright 2000-2016 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.vcsUtil;
17
18 import com.intellij.ide.util.PropertiesComponent;
19 import com.intellij.openapi.actionSystem.AnActionEvent;
20 import com.intellij.openapi.actionSystem.CommonDataKeys;
21 import com.intellij.openapi.application.Application;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.components.ServiceManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.fileEditor.FileDocumentManager;
26 import com.intellij.openapi.fileTypes.FileTypeManager;
27 import com.intellij.openapi.fileTypes.FileTypes;
28 import com.intellij.openapi.progress.ProgressManager;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.openapi.util.Computable;
32 import com.intellij.openapi.util.Ref;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.openapi.vcs.*;
36 import com.intellij.openapi.vcs.actions.VcsContextFactory;
37 import com.intellij.openapi.vcs.changes.Change;
38 import com.intellij.openapi.vcs.changes.ContentRevision;
39 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
40 import com.intellij.openapi.vcs.roots.VcsRootDetector;
41 import com.intellij.openapi.vfs.*;
42 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
43 import com.intellij.openapi.wm.StatusBar;
44 import com.intellij.util.containers.ContainerUtilRt;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import javax.swing.*;
49 import java.io.File;
50 import java.io.IOException;
51 import java.util.*;
52
53 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
54 public class VcsUtil {
55   protected static final char[] ourCharsToBeChopped = new char[]{'/', '\\'};
56   private static final Logger LOG = Logger.getInstance("#com.intellij.vcsUtil.VcsUtil");
57
58   public final static String MAX_VCS_LOADED_SIZE_KB = "idea.max.vcs.loaded.size.kb";
59   private static final int ourMaxLoadedFileSize = computeLoadedFileSize();
60
61   public static int getMaxVcsLoadedFileSize() {
62     return ourMaxLoadedFileSize;
63   }
64
65   private static int computeLoadedFileSize() {
66     int result = (int)PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD;
67     String userLimitKb = System.getProperty(MAX_VCS_LOADED_SIZE_KB);
68     try {
69       return userLimitKb != null ? Integer.parseInt(userLimitKb) * 1024 : result;
70     }
71     catch (NumberFormatException ignored) {
72       return result;
73     }
74   }
75
76   public static void markFileAsDirty(final Project project, final VirtualFile file) {
77     VcsDirtyScopeManager.getInstance(project).fileDirty(file);
78   }
79
80   public static void markFileAsDirty(final Project project, final FilePath path) {
81       VcsDirtyScopeManager.getInstance(project).fileDirty(path);
82   }
83
84   public static void markFileAsDirty(final Project project, final String path) {
85     final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(new File(path));
86     markFileAsDirty( project, filePath );
87   }
88
89   public static void refreshFiles(Project project, HashSet<FilePath> paths) {
90     for (FilePath path : paths) {
91       VirtualFile vFile = path.getVirtualFile();
92       if (vFile != null) {
93         if (vFile.isDirectory()) {
94           markFileAsDirty(project, vFile);
95         }
96         else {
97           vFile.refresh(true, vFile.isDirectory());
98         }
99       }
100     }
101   }
102
103   /**
104    * @param project Project component
105    * @param file    File to check
106    * @return true if the given file resides under the root associated with any
107    */
108   public static boolean isFileUnderVcs(Project project, String file) {
109     return getVcsFor(project, getFilePath(file)) != null;
110   }
111
112   public static boolean isFileUnderVcs(Project project, FilePath file) {
113     return getVcsFor(project, file) != null;
114   }
115
116   /**
117    * File is considered to be a valid vcs file if it resides under the content
118    * root controlled by the given vcs.
119    */
120   public static boolean isFileForVcs(@NotNull VirtualFile file, Project project, AbstractVcs host) {
121     return getVcsFor(project, file) == host;
122   }
123
124   //  NB: do not reduce this method to the method above since PLVcsMgr uses
125   //      different methods for computing its predicate (since FilePath can
126   //      refer to the deleted files).
127   public static boolean isFileForVcs(FilePath path, Project project, AbstractVcs host) {
128     return getVcsFor(project, path) == host;
129   }
130
131   public static boolean isFileForVcs(String path, Project project, AbstractVcs host) {
132     return getVcsFor(project, getFilePath(path)) == host;
133   }
134
135   @Nullable
136   public static AbstractVcs getVcsFor(@NotNull final Project project, final FilePath file) {
137     final AbstractVcs[] vcss = new AbstractVcs[1];
138     ApplicationManager.getApplication().runReadAction(new Runnable() {
139       @Override
140       public void run() {
141         //  IDEADEV-17916, when e.g. ContentRevision.getContent is called in
142         //  a future task after the component has been disposed.
143         if (!project.isDisposed()) {
144           ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance(project);
145           vcss[0] = (mgr != null) ? mgr.getVcsFor(file) : null;
146         }
147       }
148     });
149     return vcss[0];
150   }
151
152   @Nullable
153   public static AbstractVcs getVcsFor(final Project project, @NotNull final VirtualFile file) {
154     final AbstractVcs[] vcss = new AbstractVcs[1];
155
156     ApplicationManager.getApplication().runReadAction(new Runnable() {
157       @Override
158       public void run() {
159         //  IDEADEV-17916, when e.g. ContentRevision.getContent is called in
160         //  a future task after the component has been disposed.
161         if( !project.isDisposed() )
162         {
163           ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project );
164           vcss[ 0 ] = (mgr != null) ? mgr.getVcsFor(file) : null;
165         }
166       }
167     });
168     return vcss[0];
169   }
170
171   @Nullable
172   public static VirtualFile getVcsRootFor(final Project project, final FilePath file) {
173     final VirtualFile[] roots = new VirtualFile[1];
174
175     ApplicationManager.getApplication().runReadAction(new Runnable() {
176       @Override
177       public void run() {
178         //  IDEADEV-17916, when e.g. ContentRevision.getContent is called in
179         //  a future task after the component has been disposed.
180         if( !project.isDisposed() )
181         {
182           ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project );
183           roots[ 0 ] = (mgr != null) ? mgr.getVcsRootFor( file ) : null;
184         }
185       }
186     });
187     return roots[0];
188   }
189
190   @Nullable
191   public static VirtualFile getVcsRootFor(final Project project, final VirtualFile file) {
192     final VirtualFile[] roots = new VirtualFile[1];
193
194     ApplicationManager.getApplication().runReadAction(new Runnable() {
195       @Override
196       public void run() {
197         //  IDEADEV-17916, when e.g. ContentRevision.getContent is called in
198         //  a future task after the component has been disposed.
199         if( !project.isDisposed() )
200         {
201           ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project );
202           roots[ 0 ] = (mgr != null) ? mgr.getVcsRootFor( file ) : null;
203         }
204       }
205     });
206     return roots[0];
207   }
208
209   public static void refreshFiles(final FilePath[] roots, final Runnable runnable) {
210     ApplicationManager.getApplication().assertIsDispatchThread();
211     refreshFiles(collectFilesToRefresh(roots), runnable);
212   }
213
214   public static void refreshFiles(final File[] roots, final Runnable runnable) {
215     ApplicationManager.getApplication().assertIsDispatchThread();
216     refreshFiles(collectFilesToRefresh(roots), runnable);
217   }
218
219   private static File[] collectFilesToRefresh(final FilePath[] roots) {
220     final File[] result = new File[roots.length];
221     for (int i = 0; i < roots.length; i++) {
222       result[i] = roots[i].getIOFile();
223     }
224     return result;
225   }
226
227   private static void refreshFiles(final List<VirtualFile> filesToRefresh, final Runnable runnable) {
228     RefreshQueue.getInstance().refresh(true, true, runnable, filesToRefresh);
229   }
230
231   private static List<VirtualFile> collectFilesToRefresh(final File[] roots) {
232     final ArrayList<VirtualFile> result = new ArrayList<>();
233     for (File root : roots) {
234       VirtualFile vFile = findFileFor(root);
235       if (vFile != null) {
236         result.add(vFile);
237       } else {
238         LOG.info("Failed to find VirtualFile for one of refresh roots: " + root.getAbsolutePath());
239       }
240     }
241     return result;
242   }
243
244   @Nullable
245   private static VirtualFile findFileFor(final File root) {
246     File current = root;
247     while (current != null) {
248       final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(root);
249       if (vFile != null) return vFile;
250       current = current.getParentFile();
251     }
252
253     return null;
254   }
255
256   @Nullable
257   public static VirtualFile getVirtualFile(final String path) {
258     return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
259       @Override
260       @Nullable
261       public VirtualFile compute() {
262         return LocalFileSystem.getInstance().findFileByPath(path.replace(File.separatorChar, '/'));
263       }
264     });
265   }
266
267   @Nullable
268   public static VirtualFile getVirtualFile(final File file) {
269     return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
270       @Override
271       @Nullable
272       public VirtualFile compute() {
273         return LocalFileSystem.getInstance().findFileByIoFile(file);
274       }
275     });
276   }
277
278   @Nullable
279   public static VirtualFile getVirtualFileWithRefresh(final File file) {
280     if (file == null) return null;
281     final LocalFileSystem lfs = LocalFileSystem.getInstance();
282     VirtualFile result = lfs.findFileByIoFile(file);
283     if (result == null) {
284       result = lfs.refreshAndFindFileByIoFile(file);
285     }
286     return result;
287   }
288
289   public static String getFileContent(final String path) {
290     return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
291       @Override
292       public String compute() {
293         VirtualFile vFile = getVirtualFile(path);
294         assert vFile != null;
295         return FileDocumentManager.getInstance().getDocument(vFile).getText();
296       }
297     });
298   }
299
300   @Nullable
301   public static byte[] getFileByteContent(@NotNull File file) {
302     try {
303       return FileUtil.loadFileBytes(file);
304     }
305     catch (IOException e) {
306       LOG.info(e);
307       return null;
308     }
309   }
310
311   public static FilePath getFilePath(String path) {
312     return getFilePath(new File(path));
313   }
314
315   public static FilePath getFilePath(@NotNull VirtualFile file) {
316     return VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
317   }
318
319   public static FilePath getFilePath(@NotNull File file) {
320     return VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
321   }
322
323   public static FilePath getFilePath(@NotNull String path, boolean isDirectory) {
324     return VcsContextFactory.SERVICE.getInstance().createFilePath(path, isDirectory);
325   }
326
327   public static FilePath getFilePathOnNonLocal(String path, boolean isDirectory) {
328     return VcsContextFactory.SERVICE.getInstance().createFilePathOnNonLocal(path, isDirectory);
329   }
330
331   public static FilePath getFilePath(@NotNull File file, boolean isDirectory) {
332     return VcsContextFactory.SERVICE.getInstance().createFilePathOn(file, isDirectory);
333   }
334
335   public static FilePath getFilePathForDeletedFile(@NotNull String path, boolean isDirectory) {
336     return VcsContextFactory.SERVICE.getInstance().createFilePathOnDeleted(new File(path), isDirectory);
337   }
338
339   @NotNull
340   public static FilePath getFilePath(@NotNull VirtualFile parent, @NotNull String name) {
341     return VcsContextFactory.SERVICE.getInstance().createFilePathOn(parent, name);
342   }
343
344   @NotNull
345   public static FilePath getFilePath(@NotNull VirtualFile parent, @NotNull String fileName, boolean isDirectory) {
346     return VcsContextFactory.SERVICE.getInstance().createFilePath(parent, fileName, isDirectory);
347   }
348
349   /**
350    * Shows message in the status bar.
351    *
352    * @param project Current project component
353    * @param message information message
354    */
355   public static void showStatusMessage(final Project project, final String message) {
356     SwingUtilities.invokeLater(new Runnable() {
357       @Override
358       public void run() {
359         if (project.isOpen()) {
360           StatusBar.Info.set(message, project);
361         }
362       }
363     });
364   }
365
366   /**
367    * @param change "Change" description.
368    * @return Return true if the "Change" object is created for "Rename" operation:
369    *         in this case name of files for "before" and "after" revisions must not
370    *         coniside.
371    */
372   public static boolean isRenameChange(Change change) {
373     boolean isRenamed = false;
374     ContentRevision before = change.getBeforeRevision();
375     ContentRevision after = change.getAfterRevision();
376     if (before != null && after != null) {
377       String prevFile = getCanonicalLocalPath(before.getFile().getPath());
378       String newFile = getCanonicalLocalPath(after.getFile().getPath());
379       isRenamed = !prevFile.equals(newFile);
380     }
381     return isRenamed;
382   }
383
384   /**
385    * @param change "Change" description.
386    * @return Return true if the "Change" object is created for "New" operation:
387    *         "before" revision is obviously NULL, while "after" revision is not.
388    */
389   public static boolean isChangeForNew(Change change) {
390     return (change.getBeforeRevision() == null) && (change.getAfterRevision() != null);
391   }
392
393   /**
394    * @param change "Change" description.
395    * @return Return true if the "Change" object is created for "Delete" operation:
396    *         "before" revision is NOT NULL, while "after" revision is NULL.
397    */
398   public static boolean isChangeForDeleted(Change change) {
399     return (change.getBeforeRevision() != null) && (change.getAfterRevision() == null);
400   }
401
402   public static boolean isChangeForFolder(Change change) {
403     ContentRevision revB = change.getBeforeRevision();
404     ContentRevision revA = change.getAfterRevision();
405     return (revA != null && revA.getFile().isDirectory()) || (revB != null && revB.getFile().isDirectory());
406   }
407
408   /**
409    * Sort file paths so that paths under the same root are placed from the
410    * innermost to the outermost (closest to the root).
411    *
412    * @param files An array of file paths to be sorted. Sorting is done over the parameter.
413    * @return Sorted array of the file paths.
414    */
415   public static FilePath[] sortPathsFromInnermost(FilePath[] files) {
416     return sortPaths(files, -1);
417   }
418
419   /**
420    * Sort file paths so that paths under the same root are placed from the
421    * outermost to the innermost (farest from the root).
422    *
423    * @param files An array of file paths to be sorted. Sorting is done over the parameter.
424    * @return Sorted array of the file paths.
425    */
426   public static FilePath[] sortPathsFromOutermost(FilePath[] files) {
427     return sortPaths(files, 1);
428   }
429
430   private static FilePath[] sortPaths(FilePath[] files, final int sign) {
431     Arrays.sort(files, new Comparator<FilePath>() {
432       @Override
433       public int compare(@NotNull FilePath o1, @NotNull FilePath o2) {
434         return sign * o1.getPath().compareTo(o2.getPath());
435       }
436     });
437     return files;
438   }
439
440   /**
441    * @param e ActionEvent object
442    * @return <code>VirtualFile</code> available in the current context.
443    *         Returns not <code>null</code> if and only if exectly one file is available.
444    */
445   @Nullable
446   public static VirtualFile getOneVirtualFile(AnActionEvent e) {
447     VirtualFile[] files = getVirtualFiles(e);
448     return (files.length != 1) ? null : files[0];
449   }
450
451   /**
452    * @param e ActionEvent object
453    * @return <code>VirtualFile</code>s available in the current context.
454    *         Returns empty array if there are no available files.
455    */
456   public static VirtualFile[] getVirtualFiles(AnActionEvent e) {
457     VirtualFile[] files = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
458     return (files == null) ? VirtualFile.EMPTY_ARRAY : files;
459   }
460
461   /**
462    * Collects all files which are located in the passed directory.
463    *
464    * @throws IllegalArgumentException if <code>dir</code> isn't a directory.
465    */
466   public static void collectFiles(final VirtualFile dir,
467                                   final List<VirtualFile> files,
468                                   final boolean recursive,
469                                   final boolean addDirectories) {
470     if (!dir.isDirectory()) {
471       throw new IllegalArgumentException(VcsBundle.message("exception.text.file.should.be.directory", dir.getPresentableUrl()));
472     }
473
474     final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
475     VfsUtilCore.visitChildrenRecursively(dir, new VirtualFileVisitor() {
476       @Override
477       public boolean visitFile(@NotNull VirtualFile file) {
478         if (file.isDirectory()) {
479           if (addDirectories) {
480             files.add(file);
481           }
482           if (!recursive && !Comparing.equal(file, dir)) {
483             return false;
484           }
485         }
486         else if (fileTypeManager == null || file.getFileType() != FileTypes.UNKNOWN) {
487           files.add(file);
488         }
489         return true;
490       }
491     });
492   }
493
494   public static boolean runVcsProcessWithProgress(final VcsRunnable runnable, String progressTitle, boolean canBeCanceled, Project project)
495     throws VcsException {
496     final Ref<VcsException> ex = new Ref<>();
497     boolean result = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
498       @Override
499       public void run() {
500         try {
501           runnable.run();
502         }
503         catch (VcsException e) {
504           ex.set(e);
505         }
506       }
507     }, progressTitle, canBeCanceled, project);
508     if (!ex.isNull()) {
509       throw ex.get();
510     }
511     return result;
512   }
513
514   public static VirtualFile waitForTheFile(final String path) {
515     final VirtualFile[] file = new VirtualFile[1];
516     final Application app = ApplicationManager.getApplication();
517     Runnable action = new Runnable() {
518       @Override
519       public void run() {
520         app.runWriteAction(new Runnable() {
521           @Override
522           public void run() {
523             file[0] = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
524           }
525         });
526       }
527     };
528
529     app.invokeAndWait(action);
530
531     return file[0];
532   }
533
534   public static String getCanonicalLocalPath(String localPath) {
535     localPath = chopTrailingChars(localPath.trim().replace('\\', '/'), ourCharsToBeChopped);
536     if (localPath.length() == 2 && localPath.charAt(1) == ':') {
537       localPath += '/';
538     }
539     return localPath;
540   }
541
542   public static String getCanonicalPath( String path )
543   {
544     String canonPath;
545     try {  canonPath = new File( path ).getCanonicalPath();  }
546     catch( IOException e ){  canonPath = path;  }
547     return canonPath;
548   }
549
550   public static String getCanonicalPath( File file )
551   {
552     String canonPath;
553     try {  canonPath = file.getCanonicalPath();  }
554     catch (IOException e) {  canonPath = file.getAbsolutePath();  }
555     return canonPath;
556   }
557
558   /**
559    * @param source Source string
560    * @param chars  Symbols to be trimmed
561    * @return string without all specified chars at the end. For example,
562    *         <code>chopTrailingChars("c:\\my_directory\\//\\",new char[]{'\\'}) is <code>"c:\\my_directory\\//"</code>,
563    *         <code>chopTrailingChars("c:\\my_directory\\//\\",new char[]{'\\','/'}) is <code>"c:\my_directory"</code>.
564    *         Actually this method can be used to normalize file names to chop trailing separator chars.
565    */
566   public static String chopTrailingChars(String source, char[] chars) {
567     StringBuilder sb = new StringBuilder(source);
568     while (true) {
569       boolean atLeastOneCharWasChopped = false;
570       for (int i = 0; i < chars.length && sb.length() > 0; i++) {
571         if (sb.charAt(sb.length() - 1) == chars[i]) {
572           sb.deleteCharAt(sb.length() - 1);
573           atLeastOneCharWasChopped = true;
574         }
575       }
576       if (!atLeastOneCharWasChopped) {
577         break;
578       }
579     }
580     return sb.toString();
581   }
582
583   public static VirtualFile[] paths2VFiles(String[] paths) {
584     VirtualFile[] files = new VirtualFile[paths.length];
585     for (int i = 0; i < paths.length; i++) {
586       files[i] = getVirtualFile(paths[i]);
587     }
588
589     return files;
590   }
591
592   private static final String ANNO_ASPECT = "show.vcs.annotation.aspect.";
593   //public static boolean isAspectAvailableByDefault(LineAnnotationAspect aspect) {
594   //  if (aspect.getId() == null) return aspect.isShowByDefault();
595   //  return PropertiesComponent.getInstance().getBoolean(ANNO_ASPECT + aspect.getId(), aspect.isShowByDefault());
596   //}
597
598   public static boolean isAspectAvailableByDefault(String id) {
599     return isAspectAvailableByDefault(id, true);
600   }
601
602   public static boolean isAspectAvailableByDefault(@Nullable String id, boolean defaultValue) {
603     if (id == null) return false;
604     return PropertiesComponent.getInstance().getBoolean(ANNO_ASPECT + id, defaultValue);
605   }
606
607   public static void setAspectAvailability(String aspectID, boolean showByDefault) {
608     PropertiesComponent.getInstance().setValue(ANNO_ASPECT + aspectID, String.valueOf(showByDefault));
609   }
610
611   public static boolean isPathRemote(String path) {
612     final int idx = path.indexOf("://");
613     if (idx == -1) {
614       final int idx2 = path.indexOf(":\\\\");
615       if (idx2 == -1) {
616         return false;
617       }
618       return idx2 > 0;
619     }
620     return idx > 0;
621   }
622
623   public static String getPathForProgressPresentation(@NotNull final File file) {
624     return file.getName() + " (" + file.getParent() + ")";
625   }
626
627   @NotNull
628   public static Collection<VcsDirectoryMapping> findRoots(@NotNull VirtualFile rootDir, @NotNull Project project)
629     throws IllegalArgumentException {
630     if (!rootDir.isDirectory()) {
631       throw new IllegalArgumentException(
632         "Can't find VCS at the target file system path. Reason: expected to find a directory there but it's not. The path: "
633         + rootDir.getParent()
634       );
635     }
636     Collection<VcsRoot> roots = ServiceManager.getService(project, VcsRootDetector.class).detect(rootDir);
637     Collection<VcsDirectoryMapping> result = ContainerUtilRt.newArrayList();
638     for (VcsRoot vcsRoot : roots) {
639       VirtualFile vFile = vcsRoot.getPath();
640       AbstractVcs rootVcs = vcsRoot.getVcs();
641       if (rootVcs != null && vFile != null) {
642         result.add(new VcsDirectoryMapping(vFile.getPath(), rootVcs.getName()));
643       }
644     }
645     return result;
646   }
647
648   @NotNull
649   public static List<VcsDirectoryMapping> addMapping(@NotNull List<VcsDirectoryMapping> existingMappings,
650                                                      @NotNull String path,
651                                                      @NotNull String vcs) {
652     List<VcsDirectoryMapping> mappings = new ArrayList<>(existingMappings);
653     for (Iterator<VcsDirectoryMapping> iterator = mappings.iterator(); iterator.hasNext(); ) {
654       VcsDirectoryMapping mapping = iterator.next();
655       if (mapping.isDefaultMapping() && StringUtil.isEmptyOrSpaces(mapping.getVcs())) {
656         LOG.debug("Removing <Project> -> <None> mapping");
657         iterator.remove();
658       }
659       else if (FileUtil.pathsEqual(mapping.getDirectory(), path)) {
660         if (!StringUtil.isEmptyOrSpaces(mapping.getVcs())) {
661           LOG.warn("Substituting existing mapping [" + path + "] -> [" + mapping.getVcs() + "] with [" + vcs + "]");
662         }
663         else {
664           LOG.debug("Removing [" + path + "] -> <None> mapping");
665         }
666         iterator.remove();
667       }
668     }
669     mappings.add(new VcsDirectoryMapping(path, vcs));
670     return mappings;
671   }
672 }