2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.vcsUtil;
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;
50 import java.io.IOException;
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");
58 public final static String MAX_VCS_LOADED_SIZE_KB = "idea.max.vcs.loaded.size.kb";
59 private static final int ourMaxLoadedFileSize = computeLoadedFileSize();
61 public static int getMaxVcsLoadedFileSize() {
62 return ourMaxLoadedFileSize;
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);
69 return userLimitKb != null ? Integer.parseInt(userLimitKb) * 1024 : result;
71 catch (NumberFormatException ignored) {
76 public static void markFileAsDirty(final Project project, final VirtualFile file) {
77 VcsDirtyScopeManager.getInstance(project).fileDirty(file);
80 public static void markFileAsDirty(final Project project, final FilePath path) {
81 VcsDirtyScopeManager.getInstance(project).fileDirty(path);
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 );
89 public static void refreshFiles(Project project, HashSet<FilePath> paths) {
90 for (FilePath path : paths) {
91 VirtualFile vFile = path.getVirtualFile();
93 if (vFile.isDirectory()) {
94 markFileAsDirty(project, vFile);
97 vFile.refresh(true, vFile.isDirectory());
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
108 public static boolean isFileUnderVcs(Project project, String file) {
109 return getVcsFor(project, getFilePath(file)) != null;
112 public static boolean isFileUnderVcs(Project project, FilePath file) {
113 return getVcsFor(project, file) != null;
117 * File is considered to be a valid vcs file if it resides under the content
118 * root controlled by the given vcs.
120 public static boolean isFileForVcs(@NotNull VirtualFile file, Project project, AbstractVcs host) {
121 return getVcsFor(project, file) == host;
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;
131 public static boolean isFileForVcs(String path, Project project, AbstractVcs host) {
132 return getVcsFor(project, getFilePath(path)) == host;
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() {
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;
153 public static AbstractVcs getVcsFor(final Project project, @NotNull final VirtualFile file) {
154 final AbstractVcs[] vcss = new AbstractVcs[1];
156 ApplicationManager.getApplication().runReadAction(new Runnable() {
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() )
163 ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project );
164 vcss[ 0 ] = (mgr != null) ? mgr.getVcsFor(file) : null;
172 public static VirtualFile getVcsRootFor(final Project project, final FilePath file) {
173 final VirtualFile[] roots = new VirtualFile[1];
175 ApplicationManager.getApplication().runReadAction(new Runnable() {
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() )
182 ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project );
183 roots[ 0 ] = (mgr != null) ? mgr.getVcsRootFor( file ) : null;
191 public static VirtualFile getVcsRootFor(final Project project, final VirtualFile file) {
192 final VirtualFile[] roots = new VirtualFile[1];
194 ApplicationManager.getApplication().runReadAction(new Runnable() {
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() )
201 ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project );
202 roots[ 0 ] = (mgr != null) ? mgr.getVcsRootFor( file ) : null;
209 public static void refreshFiles(final FilePath[] roots, final Runnable runnable) {
210 ApplicationManager.getApplication().assertIsDispatchThread();
211 refreshFiles(collectFilesToRefresh(roots), runnable);
214 public static void refreshFiles(final File[] roots, final Runnable runnable) {
215 ApplicationManager.getApplication().assertIsDispatchThread();
216 refreshFiles(collectFilesToRefresh(roots), runnable);
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();
227 private static void refreshFiles(final List<VirtualFile> filesToRefresh, final Runnable runnable) {
228 RefreshQueue.getInstance().refresh(true, true, runnable, filesToRefresh);
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);
238 LOG.info("Failed to find VirtualFile for one of refresh roots: " + root.getAbsolutePath());
245 private static VirtualFile findFileFor(final File root) {
247 while (current != null) {
248 final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(root);
249 if (vFile != null) return vFile;
250 current = current.getParentFile();
257 public static VirtualFile getVirtualFile(final String path) {
258 return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
261 public VirtualFile compute() {
262 return LocalFileSystem.getInstance().findFileByPath(path.replace(File.separatorChar, '/'));
268 public static VirtualFile getVirtualFile(final File file) {
269 return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
272 public VirtualFile compute() {
273 return LocalFileSystem.getInstance().findFileByIoFile(file);
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);
289 public static String getFileContent(final String path) {
290 return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
292 public String compute() {
293 VirtualFile vFile = getVirtualFile(path);
294 assert vFile != null;
295 return FileDocumentManager.getInstance().getDocument(vFile).getText();
301 public static byte[] getFileByteContent(@NotNull File file) {
303 return FileUtil.loadFileBytes(file);
305 catch (IOException e) {
311 public static FilePath getFilePath(String path) {
312 return getFilePath(new File(path));
315 public static FilePath getFilePath(@NotNull VirtualFile file) {
316 return VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
319 public static FilePath getFilePath(@NotNull File file) {
320 return VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
323 public static FilePath getFilePath(@NotNull String path, boolean isDirectory) {
324 return VcsContextFactory.SERVICE.getInstance().createFilePath(path, isDirectory);
327 public static FilePath getFilePathOnNonLocal(String path, boolean isDirectory) {
328 return VcsContextFactory.SERVICE.getInstance().createFilePathOnNonLocal(path, isDirectory);
331 public static FilePath getFilePath(@NotNull File file, boolean isDirectory) {
332 return VcsContextFactory.SERVICE.getInstance().createFilePathOn(file, isDirectory);
335 public static FilePath getFilePathForDeletedFile(@NotNull String path, boolean isDirectory) {
336 return VcsContextFactory.SERVICE.getInstance().createFilePathOnDeleted(new File(path), isDirectory);
340 public static FilePath getFilePath(@NotNull VirtualFile parent, @NotNull String name) {
341 return VcsContextFactory.SERVICE.getInstance().createFilePathOn(parent, name);
345 public static FilePath getFilePath(@NotNull VirtualFile parent, @NotNull String fileName, boolean isDirectory) {
346 return VcsContextFactory.SERVICE.getInstance().createFilePath(parent, fileName, isDirectory);
350 * Shows message in the status bar.
352 * @param project Current project component
353 * @param message information message
355 public static void showStatusMessage(final Project project, final String message) {
356 SwingUtilities.invokeLater(new Runnable() {
359 if (project.isOpen()) {
360 StatusBar.Info.set(message, project);
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
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);
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.
389 public static boolean isChangeForNew(Change change) {
390 return (change.getBeforeRevision() == null) && (change.getAfterRevision() != null);
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.
398 public static boolean isChangeForDeleted(Change change) {
399 return (change.getBeforeRevision() != null) && (change.getAfterRevision() == null);
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());
409 * Sort file paths so that paths under the same root are placed from the
410 * innermost to the outermost (closest to the root).
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.
415 public static FilePath[] sortPathsFromInnermost(FilePath[] files) {
416 return sortPaths(files, -1);
420 * Sort file paths so that paths under the same root are placed from the
421 * outermost to the innermost (farest from the root).
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.
426 public static FilePath[] sortPathsFromOutermost(FilePath[] files) {
427 return sortPaths(files, 1);
430 private static FilePath[] sortPaths(FilePath[] files, final int sign) {
431 Arrays.sort(files, new Comparator<FilePath>() {
433 public int compare(@NotNull FilePath o1, @NotNull FilePath o2) {
434 return sign * o1.getPath().compareTo(o2.getPath());
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.
446 public static VirtualFile getOneVirtualFile(AnActionEvent e) {
447 VirtualFile[] files = getVirtualFiles(e);
448 return (files.length != 1) ? null : files[0];
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.
456 public static VirtualFile[] getVirtualFiles(AnActionEvent e) {
457 VirtualFile[] files = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
458 return (files == null) ? VirtualFile.EMPTY_ARRAY : files;
462 * Collects all files which are located in the passed directory.
464 * @throws IllegalArgumentException if <code>dir</code> isn't a directory.
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()));
474 final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
475 VfsUtilCore.visitChildrenRecursively(dir, new VirtualFileVisitor() {
477 public boolean visitFile(@NotNull VirtualFile file) {
478 if (file.isDirectory()) {
479 if (addDirectories) {
482 if (!recursive && !Comparing.equal(file, dir)) {
486 else if (fileTypeManager == null || file.getFileType() != FileTypes.UNKNOWN) {
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() {
503 catch (VcsException e) {
507 }, progressTitle, canBeCanceled, project);
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() {
520 app.runWriteAction(new Runnable() {
523 file[0] = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
529 app.invokeAndWait(action);
534 public static String getCanonicalLocalPath(String localPath) {
535 localPath = chopTrailingChars(localPath.trim().replace('\\', '/'), ourCharsToBeChopped);
536 if (localPath.length() == 2 && localPath.charAt(1) == ':') {
542 public static String getCanonicalPath( String path )
545 try { canonPath = new File( path ).getCanonicalPath(); }
546 catch( IOException e ){ canonPath = path; }
550 public static String getCanonicalPath( File file )
553 try { canonPath = file.getCanonicalPath(); }
554 catch (IOException e) { canonPath = file.getAbsolutePath(); }
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.
566 public static String chopTrailingChars(String source, char[] chars) {
567 StringBuilder sb = new StringBuilder(source);
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;
576 if (!atLeastOneCharWasChopped) {
580 return sb.toString();
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]);
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());
598 public static boolean isAspectAvailableByDefault(String id) {
599 return isAspectAvailableByDefault(id, true);
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);
607 public static void setAspectAvailability(String aspectID, boolean showByDefault) {
608 PropertiesComponent.getInstance().setValue(ANNO_ASPECT + aspectID, String.valueOf(showByDefault));
611 public static boolean isPathRemote(String path) {
612 final int idx = path.indexOf("://");
614 final int idx2 = path.indexOf(":\\\\");
623 public static String getPathForProgressPresentation(@NotNull final File file) {
624 return file.getName() + " (" + file.getParent() + ")";
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()
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()));
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");
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 + "]");
664 LOG.debug("Removing [" + path + "] -> <None> mapping");
669 mappings.add(new VcsDirectoryMapping(path, vcs));