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