diff --git a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml index 274036923c65..defeb7de5f76 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml @@ -2,6 +2,8 @@ + extends S return null; } - @Nullable - public VcsRootChecker getRootChecker() { - return null; - } - @Nullable public VcsRootSettings createEmptyVcsRootSettings() { return null; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootChecker.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootChecker.java index b268b6dafaec..2f8231fb1f64 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootChecker.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootChecker.java @@ -15,9 +15,10 @@ */ package com.intellij.openapi.vcs; +import com.intellij.openapi.extensions.ExtensionPointName; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; - -import java.util.Collection; +import org.jetbrains.annotations.Nullable; /** * Checks VCS roots, revealing invalid roots (registered in the settings, but not related to real VCS roots on disk) @@ -25,18 +26,31 @@ import java.util.Collection; * * @author Kirill Likhodedov */ -public interface VcsRootChecker { +public abstract class VcsRootChecker { + + @NonNls public static final ExtensionPointName EXTENSION_POINT_NAME = + new ExtensionPointName("com.intellij.vcsRootChecker"); /** - * @return Paths to VCS roots which are not registered in the Settings | Version Control. + * @param path path to check if it is vcs root directory + * @return true if it is vcs root */ - @NotNull - Collection getUnregisteredRoots(); + public boolean isRoot(@NotNull String path) { + return false; + } /** + * @return - return vcs for current checker + */ + public abstract VcsKey getSupportedVcs(); + + /** + * Check if the "dot" directory changed during scan * - * @param directory root to be checked. - * @return true if the given directory is not a VCS root. + * @param path - path to check + * @return true if it is a DOT_DIR */ - boolean isInvalidMapping(@NotNull VcsDirectoryMapping mapping); + public boolean isVcsDir(@Nullable String path) { + return false; + } } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootError.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootError.java index 5fde942a9b12..ada11256da9a 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootError.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsRootError.java @@ -20,24 +20,24 @@ import org.jetbrains.annotations.NotNull; /** * Incorrect VCS root definition: either an unregistered, or an incorrectly registered VCS root. * - * @see VcsRootChecker - * @see AbstractVcs#getRootChecker - * * @author Kirill Likhodedov + * @see VcsRootChecker */ public class VcsRootError { private final @NotNull Type myType; private final @NotNull String myMapping; + private final @NotNull VcsKey myVcsKey; public enum Type { EXTRA_MAPPING, UNREGISTERED_ROOT } - public VcsRootError(@NotNull Type type, @NotNull String mapping) { + public VcsRootError(@NotNull Type type, @NotNull String mapping, @NotNull String key) { myType = type; myMapping = mapping; + myVcsKey = new VcsKey(key); } @NotNull @@ -50,6 +50,11 @@ public class VcsRootError { return myMapping; } + @NotNull + public VcsKey getVcsKey() { + return myVcsKey; + } + @Override public String toString() { return String.format("VcsRootError{%s - %s}", myType, myMapping); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java index 2ed939836dcc..922768298355 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.configurable; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; @@ -25,6 +26,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy; import com.intellij.openapi.vcs.impl.VcsDescriptor; +import com.intellij.openapi.vcs.roots.VcsRootErrorsFinder; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.*; import com.intellij.ui.components.JBLabel; @@ -125,7 +127,8 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab private boolean mappingIsError(VcsDirectoryMapping mapping) { String vcs = mapping.getVcs(); VcsRootChecker checker = myCheckers.get(vcs); - return checker != null && checker.isInvalidMapping(mapping); + return checker != null && + (mapping.isDefaultMapping() ? !checker.isRoot(myProject.getBasePath()) : !checker.isRoot(mapping.getDirectory())); } } @@ -263,16 +266,14 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab private void updateRootCheckers() { myCheckers.clear(); - for (VcsDescriptor descriptor : myVcsManager.getAllVcss()) { - String name = descriptor.getName(); - AbstractVcs vcs = myVcsManager.findVcsByName(name); + VcsRootChecker[] checkers = Extensions.getExtensions(VcsRootChecker.EXTENSION_POINT_NAME); + for (VcsRootChecker checker : checkers) { + VcsKey key = checker.getSupportedVcs(); + AbstractVcs vcs = myVcsManager.findVcsByName(key.getName()); if (vcs == null) { continue; } - VcsRootChecker checker = vcs.getRootChecker(); - if (checker != null) { - myCheckers.put(name, checker); - } + myCheckers.put(key.getName(), checker); } } @@ -416,27 +417,28 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab final JComponent errorPanel = Box.createVerticalBox(); final JBScrollPane pane = new JBScrollPane(errorPanel); - for (Map.Entry entry : myCheckers.entrySet()) { - VcsRootChecker checker = entry.getValue(); - for (final String root : checker.getUnregisteredRoots()) { - final String vcs = entry.getKey(); - String title = "Unregistered " + vcs + " root: " + FileUtil.toSystemDependentName(root); - final VcsRootErrorLabel vcsRootErrorLabel = new VcsRootErrorLabel(title); - vcsRootErrorLabel.setAddRootLinkHandler(new Runnable() { - @Override - public void run() { - addMapping(new VcsDirectoryMapping(root, vcs)); - errorPanel.remove(vcsRootErrorLabel); - if (errorPanel.getComponentCount() == 0) { - pane.setVisible(false); - } - pane.setMinimumSize(new Dimension(-1, calcMinHeight(errorPanel, DEFAULT_HEIGHT))); - validate(); + Collection myErrors = VcsRootErrorsFinder.getInstance(myProject).find(); + for (final VcsRootError root : myErrors) { + final VcsKey vcsKey = root.getVcsKey(); + final VcsDescriptor vcsDescriptor = myAllVcss.get(vcsKey.getName()); + String displayVcsName = vcsDescriptor.getDisplayName(); + String title = "Unregistered " + displayVcsName + " root: " + FileUtil.toSystemDependentName(root.getMapping()); + final VcsRootErrorLabel vcsRootErrorLabel = new VcsRootErrorLabel(title); + vcsRootErrorLabel.setAddRootLinkHandler(new Runnable() { + @Override + public void run() { + addMapping(new VcsDirectoryMapping(root.getMapping(), vcsKey.getName())); + errorPanel.remove(vcsRootErrorLabel); + if (errorPanel.getComponentCount() == 0) { + pane.setVisible(false); } - }); - errorPanel.add(vcsRootErrorLabel); - } + pane.setMinimumSize(new Dimension(-1, calcMinHeight(errorPanel, DEFAULT_HEIGHT))); + validate(); + } + }); + errorPanel.add(vcsRootErrorLabel); } + if (errorPanel.getComponentCount() == 0) { pane.setVisible(false); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java index 08414150336f..87d7faab4523 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java @@ -26,6 +26,7 @@ import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorSettings; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; @@ -44,6 +45,7 @@ import com.intellij.openapi.vcs.checkout.CompositeCheckoutListener; import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx; import com.intellij.openapi.vcs.history.VcsHistoryCache; import com.intellij.openapi.vcs.impl.projectlevelman.*; +import com.intellij.openapi.vcs.roots.VcsRootScanner; import com.intellij.openapi.vcs.update.ActionInfo; import com.intellij.openapi.vcs.update.UpdateInfoTree; import com.intellij.openapi.vcs.update.UpdatedFiles; @@ -240,6 +242,12 @@ public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx impleme else { myContentManager = ContentFactory.SERVICE.getInstance().createContentManager(true, myProject); } + if (!ApplicationManager.getApplication().isUnitTestMode()) { + VcsRootChecker[] checkers = Extensions.getExtensions(VcsRootChecker.EXTENSION_POINT_NAME); + if (checkers.length != 0) { + VcsRootScanner.start(myProject, checkers); + } + } } }); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetectInfo.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetectInfo.java index 43a2bb74f21f..64e327781cc8 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetectInfo.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetectInfo.java @@ -1,54 +1,37 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.intellij.openapi.vcs.roots; -import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vcs.VcsRoot; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; /** - * The result of root detection. - * - * @author Kirill Likhodedov + * @author Nadya Zabrodina */ public class VcsRootDetectInfo { - private final @NotNull Collection myRoots; + private final @NotNull Collection myRoots; private final boolean myFull; private final boolean myBelow; /** - * @param roots Git roots important for the project. - * @param full Pass true to indicate that the project is fully under Git. - * @param below Pass true to indicate that the project dir is below Git dir, - * i.e. .git is above the project dir, and there is no .git directly under the project dir. + * @param roots Vcs roots important for the project. + * @param full Pass true to indicate that the project is fully under Vcs. + * @param below Pass true to indicate that the project dir is below Vcs dir, + * i.e. .git is above the project dir, and there is no DOT dir directly under the project dir. */ - public VcsRootDetectInfo(@NotNull Collection roots, boolean full, boolean below) { - myRoots = new ArrayList(roots); + public VcsRootDetectInfo(@NotNull Collection roots, boolean full, boolean below) { + myRoots = new ArrayList(roots); myFull = full; myBelow = below; } /** - * @return True if the project is fully under Git. - * It is true if .git is directly inside or above the project dir. + * @return True if the project is fully under Vcs. + * It is true if f.e. .git is directly inside or above the project dir. */ - public boolean totallyUnderGit() { + public boolean totallyUnderVcs() { return myFull; } @@ -57,17 +40,17 @@ public class VcsRootDetectInfo { } @NotNull - public Collection getRoots() { - return new ArrayList(myRoots); + public Collection getRoots() { + return new ArrayList(myRoots); } /** - * Below implies totally under Git. - * @return true if the uppermost interesting Git root is above the project dir, - * false if all .git directories are immediately under the project dir or deeper. + * Below implies totally under Vcs. + * + * @return true if the uppermost interesting Vcs root is above the project dir, + * false if all vcs internal directories are immediately under the project dir or deeper. */ - public boolean projectIsBelowGit() { + public boolean projectIsBelowVcs() { return myBelow; } - } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetector.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetector.java index c5aae821fac0..1fb36896275f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetector.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootDetector.java @@ -1,53 +1,32 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.intellij.openapi.vcs.roots; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.vcs.roots.VcsRootDetectInfo; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsRoot; +import com.intellij.openapi.vcs.VcsRootChecker; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** - *

- * Scans the file system looking for Git roots, which contain the Project or its parts, - * and returns the information enclosed to the {@link com.intellij.openapi.vcs.roots.VcsRootDetectInfo}. - * The main part of the information are Git roots which will be proposed to the user to be added as VCS roots. - *

- *

- * Linked sources are not scanned. User should add Git roots for them explicitly. - *

- * - * @author Kirill Likhodedov + * @author Nadya Zabrodina */ public class VcsRootDetector { - private static final int MAXIMUM_SCAN_DEPTH = 2; @NotNull private final Project myProject; @NotNull private final ProjectRootManager myProjectManager; + @NotNull private final ProjectLevelVcsManager myVcsManager; public VcsRootDetector(@NotNull Project project) { myProject = project; myProjectManager = ProjectRootManager.getInstance(project); + myVcsManager = ProjectLevelVcsManager.getInstance(project); } @NotNull @@ -58,43 +37,49 @@ public class VcsRootDetector { @NotNull public VcsRootDetectInfo detect(@Nullable VirtualFile startDir) { if (startDir == null) { - return new VcsRootDetectInfo(Collections.emptyList(), false, false); + return new VcsRootDetectInfo(Collections.emptyList(), false, false); } - final Set roots = scanForRootsInsideDir(startDir); + final Set roots = scanForRootsInsideDir(startDir); roots.addAll(scanForRootsInContentRoots()); - - if (roots.contains(startDir)) { - return new VcsRootDetectInfo(roots, true, false); + for (VcsRoot root : roots) { + if (startDir.equals(root.getPath())) { + return new VcsRootDetectInfo(roots, true, false); + } } - - VirtualFile rootAbove = scanForSingleRootAboveDir(startDir); - if (rootAbove != null) { - roots.add(rootAbove); + List rootsAbove = scanForSingleRootAboveDir(startDir); + if (!rootsAbove.isEmpty()) { + roots.addAll(rootsAbove); return new VcsRootDetectInfo(roots, true, true); } return new VcsRootDetectInfo(roots, false, false); } - private Set scanForRootsInContentRoots() { - Set gitRoots = new HashSet(); + @NotNull + private Set scanForRootsInContentRoots() { + Set gitRoots = new HashSet(); VirtualFile[] roots = myProjectManager.getContentRoots(); for (VirtualFile contentRoot : roots) { - Set rootsInsideRoot = scanForRootsInsideDir(contentRoot); - if (!rootsInsideRoot.contains(contentRoot)) { - VirtualFile rootAbove = scanForSingleRootAboveDir(contentRoot); - if (rootAbove != null) { - rootsInsideRoot.add(rootAbove); + + Set rootsInsideRoot = scanForRootsInsideDir(contentRoot); + boolean shouldScanAbove = true; + for (VcsRoot root : rootsInsideRoot) { + if (contentRoot.equals(root.getPath())) { + shouldScanAbove = false; } } + if (shouldScanAbove) { + List rootsAbove = scanForSingleRootAboveDir(contentRoot); + rootsInsideRoot.addAll(rootsAbove); + } gitRoots.addAll(rootsInsideRoot); } return gitRoots; } @NotNull - private Set scanForRootsInsideDir(@NotNull final VirtualFile dir, final int depth) { - final Set roots = new HashSet(); + private Set scanForRootsInsideDir(@NotNull final VirtualFile dir, final int depth) { + final Set roots = new HashSet(); if (depth > MAXIMUM_SCAN_DEPTH) { // performance optimization via limitation: don't scan deep though the whole VFS, 2 levels under a content root is enough return roots; @@ -103,8 +88,9 @@ public class VcsRootDetector { if (myProject.isDisposed() || !dir.isDirectory()) { return roots; } - if (hasGitDir(dir)) { - roots.add(dir); + List vcsList = getVcsListFor(dir); + for (AbstractVcs vcs : vcsList) { + roots.add(new VcsRoot(vcs, dir)); } for (VirtualFile child : dir.getChildren()) { roots.addAll(scanForRootsInsideDir(child, depth + 1)); @@ -113,28 +99,40 @@ public class VcsRootDetector { } @NotNull - private Set scanForRootsInsideDir(@NotNull VirtualFile dir) { + private Set scanForRootsInsideDir(@NotNull VirtualFile dir) { return scanForRootsInsideDir(dir, 0); } - @Nullable - private VirtualFile scanForSingleRootAboveDir(@NotNull final VirtualFile dir) { + @NotNull + private List scanForSingleRootAboveDir(@NotNull final VirtualFile dir) { + List roots = new ArrayList(); if (myProject.isDisposed()) { - return null; + return roots; } VirtualFile par = dir.getParent(); while (par != null) { - if (hasGitDir(par)) { - return par; + List vcsList = getVcsListFor(par); + for (AbstractVcs vcs : vcsList) { + roots.add(new VcsRoot(vcs, par)); + } + if (!roots.isEmpty()) { + return roots; } par = par.getParent(); } - return null; + return roots; } - private static boolean hasGitDir(@NotNull VirtualFile dir) { - VirtualFile gitDir = dir.findChild(".git"); - return gitDir != null && gitDir.exists(); + @NotNull + private List getVcsListFor(@NotNull VirtualFile dir) { + VcsRootChecker[] checkers = Extensions.getExtensions(VcsRootChecker.EXTENSION_POINT_NAME); + List vcsList = new ArrayList(); + for (VcsRootChecker checker : checkers) { + if (checker.isRoot(dir.getPath())) { + vcsList.add(myVcsManager.findVcsByName(checker.getSupportedVcs().getName())); + } + } + return vcsList; } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootErrorsFinder.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootErrorsFinder.java index 73c1b43f583e..9a5aa900bf40 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootErrorsFinder.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootErrorsFinder.java @@ -1,40 +1,24 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.intellij.openapi.vcs.roots; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.VcsDirectoryMapping; -import com.intellij.openapi.vcs.VcsRootError; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** - * Detects actual Git roots and compares them to the ones registered in Settings | Version Control. - * - * @author Kirill Likhodedov + * @author Nadya Zabrodina */ public class VcsRootErrorsFinder { - private final @NotNull Project myProject; private final @NotNull ProjectLevelVcsManager myVcsManager; @@ -46,61 +30,67 @@ public class VcsRootErrorsFinder { @NotNull public Collection find() { List mappings = myVcsManager.getDirectoryMappings(); - Collection gitRoots = new VcsRootDetector(myProject).detect().getRoots(); + Collection vcsRoots = new VcsRootDetector(myProject).detect().getRoots(); Collection errors = new ArrayList(); - Collection gitPaths = rootsToPaths(gitRoots); - errors.addAll(findExtraMappings(mappings, gitPaths)); - errors.addAll(findUnregisteredRoots(mappings, gitPaths)); + errors.addAll(findExtraMappings(mappings, vcsRoots.isEmpty())); + errors.addAll(findUnregisteredRoots(mappings, vcsRoots)); return errors; } - private Collection findUnregisteredRoots(List mappings, Collection gitPaths) { + @NotNull + private Collection findUnregisteredRoots(@NotNull List mappings, + @NotNull Collection vcsRoots) { Collection errors = new ArrayList(); - List mappedPaths = mappingsToPaths(mappings); - for (String gitPath : gitPaths) { - if (!mappedPaths.contains(gitPath)) { - errors.add(new VcsRootError(VcsRootError.Type.UNREGISTERED_ROOT, gitPath)); + List mappedPaths = mappingsToPathsWithSelectedVcs(mappings); + for (VcsRoot root : vcsRoots) { + VirtualFile virtualFileFromRoot = root.getPath(); + if (virtualFileFromRoot == null) { + continue; + } + String vcsPath = virtualFileFromRoot.getPath(); + if (!mappedPaths.contains(vcsPath) && root.getVcs() != null) { + errors.add(new VcsRootError(VcsRootError.Type.UNREGISTERED_ROOT, vcsPath, root.getVcs().getName())); } } return errors; } - private static Collection findExtraMappings(List mappings, Collection gitPaths) { + @NotNull + private Collection findExtraMappings(@NotNull List mappings, boolean isEmptyVcsRoots) { Collection errors = new ArrayList(); for (VcsDirectoryMapping mapping : mappings) { if (mapping.isDefaultMapping()) { - if (gitPaths.isEmpty()) { - errors.add(new VcsRootError(VcsRootError.Type.EXTRA_MAPPING, VcsDirectoryMapping.PROJECT_CONSTANT)); + if (isEmptyVcsRoots && !StringUtil.isEmptyOrSpaces(mapping.getVcs())) { + errors.add(new VcsRootError(VcsRootError.Type.EXTRA_MAPPING, VcsDirectoryMapping.PROJECT_CONSTANT, mapping.getVcs())); } } else { String mappedPath = mapping.systemIndependentPath(); - if (!gitPaths.contains(mappedPath) && !hasGitDir(mappedPath)) { - errors.add(new VcsRootError(VcsRootError.Type.EXTRA_MAPPING, mappedPath)); + if (!isRoot(mapping)) { + errors.add(new VcsRootError(VcsRootError.Type.EXTRA_MAPPING, mappedPath, mapping.getVcs())); } } } return errors; } - private static boolean hasGitDir(String path) { - File file = new File(path, ".git"); - return file.exists(); - } - @NotNull - private static Collection rootsToPaths(@NotNull Collection gitRoots) { - Collection gitPaths = new ArrayList(gitRoots.size()); - for (VirtualFile root : gitRoots) { - gitPaths.add(root.getPath()); - } - return gitPaths; + public static Collection vcsRootsToVirtualFiles(@NotNull Collection vcsRoots) { + return ContainerUtil.map(vcsRoots, new Function() { + @Override + public VirtualFile fun(VcsRoot root) { + return root.getPath(); + } + }); } - private List mappingsToPaths(List mappings) { + private List mappingsToPathsWithSelectedVcs(@NotNull List mappings) { List paths = new ArrayList(); for (VcsDirectoryMapping mapping : mappings) { + if (StringUtil.isEmptyOrSpaces(mapping.getVcs())) { + continue; + } if (!mapping.isDefaultMapping()) { paths.add(mapping.systemIndependentPath()); } @@ -113,4 +103,19 @@ public class VcsRootErrorsFinder { } return paths; } + + public static VcsRootErrorsFinder getInstance(Project project) { + return new VcsRootErrorsFinder(project); + } + + private boolean isRoot(@NotNull final VcsDirectoryMapping mapping) { + VcsRootChecker[] checkers = Extensions.getExtensions(VcsRootChecker.EXTENSION_POINT_NAME); + final String pathToCheck = mapping.isDefaultMapping() ? myProject.getBasePath() : mapping.getDirectory(); + return ContainerUtil.find(checkers, new Condition() { + @Override + public boolean value(VcsRootChecker checker) { + return checker.getSupportedVcs().getName().equals(mapping.getVcs()) && checker.isRoot(pathToCheck); + } + }) != null; + } } diff --git a/plugins/git4idea/src/git4idea/roots/GitRootProblemNotifier.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootProblemNotifier.java similarity index 61% rename from plugins/git4idea/src/git4idea/roots/GitRootProblemNotifier.java rename to platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootProblemNotifier.java index 93cf2fa8cbb0..fac08321a25a 100644 --- a/plugins/git4idea/src/git4idea/roots/GitRootProblemNotifier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootProblemNotifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package git4idea.roots; +package com.intellij.openapi.vcs.roots; import com.intellij.idea.ActionsBundle; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationGroup; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; +import com.intellij.notification.*; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; @@ -29,9 +25,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.VcsDirectoryMapping; import com.intellij.openapi.vcs.VcsRootError; -import com.intellij.openapi.vcs.roots.VcsRootErrorsFinder; import com.intellij.util.Function; -import git4idea.GitPlatformFacade; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,33 +36,31 @@ import java.util.Collection; import static com.intellij.notification.NotificationType.ERROR; import static com.intellij.notification.NotificationType.INFORMATION; import static com.intellij.openapi.util.text.StringUtil.pluralize; -import static git4idea.GitVcs.IMPORTANT_ERROR_NOTIFICATION; -import static git4idea.GitVcs.MINOR_NOTIFICATION; -import static git4idea.Notificator.createNotification; /** - * Searches for Git roots problems via {@link com.intellij.openapi.vcs.roots.VcsRootErrorsFinder} and notifies about them. + * Searches for Vcs roots problems via {@link VcsRootErrorsFinder} and notifies about them. * - * @author Kirill Likhodedov + * @author Nadya Zabrodina */ -public class GitRootProblemNotifier { +public class VcsRootProblemNotifier { private final @NotNull Project myProject; - private final @NotNull GitPlatformFacade myPlatformFacade; private final @NotNull VcsConfiguration mySettings; private @Nullable Notification myNotification; private final @NotNull Object NOTIFICATION_LOCK = new Object(); - public static GitRootProblemNotifier getInstance(@NotNull Project project) { - return ServiceManager.getService(project, GitRootProblemNotifier.class); + public static final NotificationGroup IMPORTANT_ERROR_NOTIFICATION = new NotificationGroup( + "Vcs Important Messages", NotificationDisplayType.STICKY_BALLOON, true); + public static final NotificationGroup MINOR_NOTIFICATION = new NotificationGroup( + "Vcs Minor Notifications", NotificationDisplayType.BALLOON, true); + + public static VcsRootProblemNotifier getInstance(@NotNull Project project) { + return new VcsRootProblemNotifier(project); } - // registered as a project service - @SuppressWarnings("UnusedDeclaration") - private GitRootProblemNotifier(@NotNull Project project, @NotNull GitPlatformFacade platformFacade) { + private VcsRootProblemNotifier(@NotNull Project project) { myProject = project; - myPlatformFacade = platformFacade; mySettings = VcsConfiguration.getInstance(myProject); } @@ -85,8 +77,8 @@ public class GitRootProblemNotifier { return; } - Collection unregisteredRoots = getUnregisteredRoots(errors); - Collection invalidRoots = getInvalidRoots(errors); + Collection unregisteredRoots = getUnregisteredRoots(errors); + Collection invalidRoots = getInvalidRoots(errors); String title = makeTitle(unregisteredRoots, invalidRoots); String description = makeDescription(unregisteredRoots, invalidRoots); @@ -95,9 +87,9 @@ public class GitRootProblemNotifier { expireNotification(); NotificationGroup notificationGroup = invalidRoots.isEmpty() ? MINOR_NOTIFICATION : IMPORTANT_ERROR_NOTIFICATION; NotificationType notificationType = invalidRoots.isEmpty() ? INFORMATION : ERROR; - myNotification = createNotification(notificationGroup, title, description, notificationType, - new MyNotificationListener(myProject, mySettings)); - myPlatformFacade.getNotificator(myProject).notify(myNotification); + myNotification = notificationGroup.createNotification(title, description, notificationType, + new MyNotificationListener(myProject, mySettings)); + myNotification.notify(myProject); } } @@ -121,25 +113,29 @@ public class GitRootProblemNotifier { } @SuppressWarnings("StringConcatenationInsideStringBufferAppend") - private static String makeDescription(@NotNull Collection unregisteredRoots, @NotNull Collection invalidRoots) { - Function rootToDisplayableString = new Function() { + @NotNull + private static String makeDescription(@NotNull Collection unregisteredRoots, + @NotNull Collection invalidRoots) { + Function rootToDisplayableString = new Function() { @Override - public String fun(String path) { - if (path.equals(VcsDirectoryMapping.PROJECT_CONSTANT)) { - return StringUtil.escapeXml(path); + public String fun(VcsRootError rootError) { + if (rootError.getMapping().equals(VcsDirectoryMapping.PROJECT_CONSTANT)) { + return StringUtil.escapeXml(rootError.getMapping()); } - return FileUtil.toSystemDependentName(path); + return FileUtil.toSystemDependentName(rootError.getMapping()); } }; StringBuilder description = new StringBuilder(); if (!invalidRoots.isEmpty()) { if (invalidRoots.size() == 1) { - description.append("The directory " + rootToDisplayableString.fun(invalidRoots.iterator().next()) + " is registered as a Git root, " + - "but no Git repositories were found there."); + VcsRootError rootError = invalidRoots.iterator().next(); + description + .append("The directory " + rootToDisplayableString.fun(rootError) + " is registered as a " + rootError.getVcsKey() + " root, " + + "but no " + rootError.getVcsKey() + " repositories were found there."); } else { - description.append("The following directories are registered as Git roots, but they are not:
" + + description.append("The following directories are registered as Vcs roots, but they are not:
" + StringUtil.join(invalidRoots, rootToDisplayableString, ", ")); } description.append("
"); @@ -147,11 +143,13 @@ public class GitRootProblemNotifier { if (!unregisteredRoots.isEmpty()) { if (unregisteredRoots.size() == 1) { - description.append("The directory " + rootToDisplayableString.fun(unregisteredRoots.iterator().next()) + " is under Git, " + - "but is not registered in the Settings."); + VcsRootError unregisteredRoot = unregisteredRoots.iterator().next(); + description + .append("The directory " + rootToDisplayableString.fun(unregisteredRoot) + " is under " + unregisteredRoot.getVcsKey() + ", " + + "but is not registered in the Settings."); } else { - description.append("The following directories are roots of Git repositories, but they are not registered in the Settings:
" + + description.append("The following directories are roots of Vcs repositories, but they are not registered in the Settings:
" + StringUtil.join(unregisteredRoots, rootToDisplayableString, ", ")); } description.append("
"); @@ -163,36 +161,36 @@ public class GitRootProblemNotifier { } @NotNull - private static String makeTitle(@NotNull Collection unregisteredRoots, @NotNull Collection invalidRoots) { + private static String makeTitle(@NotNull Collection unregisteredRoots, @NotNull Collection invalidRoots) { String title; if (unregisteredRoots.isEmpty()) { - title = "Invalid Git root " + pluralize("mapping", invalidRoots.size()); + title = "Invalid Vcs root " + pluralize("mapping", invalidRoots.size()); } else if (invalidRoots.isEmpty()) { - title = "Unregistered Git " + pluralize("root", unregisteredRoots.size()) + " detected"; + title = "Unregistered Vcs " + pluralize("root", unregisteredRoots.size()) + " detected"; } else { - title = "Git root configuration problems"; + title = "Vcs root configuration problems"; } return title; } @NotNull - private static Collection getUnregisteredRoots(@NotNull Collection errors) { + private static Collection getUnregisteredRoots(@NotNull Collection errors) { return filterErrorsByType(errors, VcsRootError.Type.UNREGISTERED_ROOT); } @NotNull - private static Collection getInvalidRoots(@NotNull Collection errors) { + private static Collection getInvalidRoots(@NotNull Collection errors) { return filterErrorsByType(errors, VcsRootError.Type.EXTRA_MAPPING); } @NotNull - private static Collection filterErrorsByType(@NotNull Collection errors, @NotNull VcsRootError.Type type) { - Collection roots = new ArrayList(); + private static Collection filterErrorsByType(@NotNull Collection errors, @NotNull VcsRootError.Type type) { + Collection roots = new ArrayList(); for (VcsRootError error : errors) { if (error.getType() == type) { - roots.add(error.getMapping()); + roots.add(error); } } return roots; @@ -213,7 +211,7 @@ public class GitRootProblemNotifier { if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { if (event.getDescription().equals("configure") && !myProject.isDisposed()) { ShowSettingsUtil.getInstance().showSettingsDialog(myProject, ActionsBundle.message("group.VcsGroup.text")); - Collection errorsAfterPossibleFix = GitRootProblemNotifier.getInstance(myProject).scan(); + Collection errorsAfterPossibleFix = getInstance(myProject).scan(); if (errorsAfterPossibleFix.isEmpty() && !notification.isExpired()) { notification.expire(); } diff --git a/plugins/git4idea/src/git4idea/roots/GitRootScanner.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootScanner.java similarity index 80% rename from plugins/git4idea/src/git4idea/roots/GitRootScanner.java rename to platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootScanner.java index fb1612771653..8ee2f551fd80 100644 --- a/plugins/git4idea/src/git4idea/roots/GitRootScanner.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/roots/VcsRootScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package git4idea.roots; +package com.intellij.openapi.vcs.roots; import com.intellij.ProjectTopics; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootEvent; @@ -24,23 +23,24 @@ import com.intellij.openapi.roots.ModuleRootListener; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsListener; +import com.intellij.openapi.vcs.VcsRootChecker; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.util.Alarm; import com.intellij.util.messages.MessageBus; -import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.TimeUnit; /** - * @author Kirill Likhodedov + * @author Nadya Zabrodina */ -public class GitRootScanner implements BulkFileListener, ModuleRootListener, VcsListener { +public class VcsRootScanner implements BulkFileListener, ModuleRootListener, VcsListener { - @NotNull private final GitRootProblemNotifier myRootProblemNotifier; + @NotNull private final VcsRootProblemNotifier myRootProblemNotifier; + @NotNull private final VcsRootChecker[] myCheckers; private volatile boolean myProjectIsInitialized; private volatile boolean myMappingsAreReady; @@ -48,12 +48,13 @@ public class GitRootScanner implements BulkFileListener, ModuleRootListener, Vcs @NotNull private final Alarm myAlarm; private static final long WAIT_BEFORE_SCAN = TimeUnit.SECONDS.toMillis(1); - public static void start(@NotNull Project project) { - new GitRootScanner(project); + public static void start(@NotNull Project project, @NotNull VcsRootChecker[] checkers) { + new VcsRootScanner(project, checkers); } - private GitRootScanner(@NotNull Project project) { - myRootProblemNotifier = GitRootProblemNotifier.getInstance(project); + private VcsRootScanner(@NotNull Project project, @NotNull VcsRootChecker[] checkers) { + myRootProblemNotifier = VcsRootProblemNotifier.getInstance(project); + myCheckers = checkers; StartupManager.getInstance(project).runWhenProjectIsInitialized(new DumbAwareRunnable() { @Override @@ -79,8 +80,11 @@ public class GitRootScanner implements BulkFileListener, ModuleRootListener, Vcs public void after(@NotNull List events) { for (VFileEvent event : events) { String filePath = event.getPath(); - if (filePath != null && filePath.toLowerCase().endsWith(GitUtil.DOT_GIT)) { - scanIfReady(); + for (VcsRootChecker checker : myCheckers) { + if (checker.isVcsDir(filePath)) { + scanIfReady(); + break; + } } } } @@ -123,5 +127,4 @@ public class GitRootScanner implements BulkFileListener, ModuleRootListener, Vcs } }, WAIT_BEFORE_SCAN); } - } diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index da7d14a9117a..91e306d96e0d 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -109,6 +109,7 @@ + @@ -160,8 +161,6 @@ serviceImplementation="git4idea.Notificator"/> - diff --git a/plugins/git4idea/src/git4idea/GitVcs.java b/plugins/git4idea/src/git4idea/GitVcs.java index 12996da38d89..6d62a07bf422 100644 --- a/plugins/git4idea/src/git4idea/GitVcs.java +++ b/plugins/git4idea/src/git4idea/GitVcs.java @@ -46,6 +46,8 @@ import com.intellij.openapi.vcs.history.VcsHistoryProvider; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.merge.MergeProvider; import com.intellij.openapi.vcs.rollback.RollbackEnvironment; +import com.intellij.openapi.vcs.roots.VcsRootDetectInfo; +import com.intellij.openapi.vcs.roots.VcsRootDetector; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vcs.update.UpdateEnvironment; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; @@ -77,9 +79,6 @@ import git4idea.i18n.GitBundle; import git4idea.merge.GitMergeProvider; import git4idea.rollback.GitRollbackEnvironment; import git4idea.roots.GitIntegrationEnabler; -import git4idea.roots.GitRootChecker; -import com.intellij.openapi.vcs.roots.VcsRootDetectInfo; -import com.intellij.openapi.vcs.roots.VcsRootDetector; import git4idea.status.GitChangeProvider; import git4idea.ui.branch.GitBranchWidget; import git4idea.update.GitUpdateEnvironment; @@ -320,11 +319,6 @@ public class GitVcs extends AbstractVcs { return dir.isDirectory() && GitUtil.gitRootOrNull(dir) != null; } - @Override - public VcsRootChecker getRootChecker() { - return new GitRootChecker(myProject, myPlatformFacade); - } - @Override protected void start() throws VcsException { } diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java index d779dab856f3..3439149c38b6 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java @@ -16,13 +16,11 @@ package git4idea.repo; import com.intellij.dvcs.repo.AbstractRepositoryManager; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitPlatformFacade; import git4idea.GitUtil; -import git4idea.roots.GitRootScanner; import org.jetbrains.annotations.NotNull; /** @@ -32,25 +30,15 @@ public class GitRepositoryManager extends AbstractRepositoryManager roots = detectInfo.getRoots(); + Collection gitRoots = ContainerUtil.filter(detectInfo.getRoots(), new Condition() { + @Override + public boolean value(VcsRoot root) { + AbstractVcs gitVcs = root.getVcs(); + return gitVcs != null && gitVcs.getKeyInstanceMethod().equals(GitVcs.getKey()); + } + }); + Collection roots = VcsRootErrorsFinder.vcsRootsToVirtualFiles(gitRoots); VirtualFile projectDir = myProject.getBaseDir(); assert projectDir != null : "Base dir is unexpectedly null for project: " + myProject; - if (detectInfo.empty()) { + if (gitRoots.isEmpty()) { boolean succeeded = gitInitOrNotifyError(notificator, projectDir); if (succeeded) { addVcsRoots(Collections.singleton(projectDir)); @@ -66,7 +77,7 @@ public class GitIntegrationEnabler { } else { assert !roots.isEmpty(); - if (roots.size() > 1 || detectInfo.projectIsBelowGit()) { + if (roots.size() > 1 || detectInfo.projectIsBelowVcs()) { notifyAddedRoots(notificator, roots); } addVcsRoots(roots); @@ -95,9 +106,11 @@ public class GitIntegrationEnabler { private void refreshGitDir(final VirtualFile projectDir) { UIUtil.invokeAndWaitIfNeeded(new Runnable() { - @Override public void run() { + @Override + public void run() { myPlatformFacade.runReadAction(new Runnable() { - @Override public void run() { + @Override + public void run() { myPlatformFacade.getLocalFileSystem().refreshAndFindFileByPath(projectDir.getPath() + "/.git"); } }); diff --git a/plugins/git4idea/src/git4idea/roots/GitRootChecker.java b/plugins/git4idea/src/git4idea/roots/GitRootChecker.java index 47a221bbe1d8..c0e12e458c6c 100644 --- a/plugins/git4idea/src/git4idea/roots/GitRootChecker.java +++ b/plugins/git4idea/src/git4idea/roots/GitRootChecker.java @@ -15,62 +15,33 @@ */ package git4idea.roots; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.VcsDirectoryMapping; +import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.VcsRootChecker; -import com.intellij.openapi.vcs.VcsRootError; -import com.intellij.openapi.vcs.roots.VcsRootErrorsFinder; -import git4idea.GitPlatformFacade; import git4idea.GitUtil; +import git4idea.GitVcs; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.ArrayList; -import java.util.Collection; /** * @author Kirill Likhodedov */ -public class GitRootChecker implements VcsRootChecker { +public class GitRootChecker extends VcsRootChecker { - @NotNull private final Collection myErrors; - private final boolean myProjectMappingIsInvalid; - - public GitRootChecker(@NotNull Project project, @NotNull GitPlatformFacade platformFacade) { - myErrors = new VcsRootErrorsFinder(project).find(); - myProjectMappingIsInvalid = isProjectMappingInvalid(); - } - - private boolean isProjectMappingInvalid() { - for (VcsRootError error : myErrors) { - if (error.getType() == VcsRootError.Type.EXTRA_MAPPING && error.getMapping().equals(VcsDirectoryMapping.PROJECT_CONSTANT)) { - return true; - } - } - return false; + @Override + public boolean isRoot(@NotNull String path) { + return new File(path, GitUtil.DOT_GIT).exists(); } + @Override @NotNull - @Override - public Collection getUnregisteredRoots() { - Collection roots = new ArrayList(); - for (VcsRootError error : myErrors) { - if (error.getType() == VcsRootError.Type.UNREGISTERED_ROOT) { - roots.add(error.getMapping()); - } - } - return roots; + public VcsKey getSupportedVcs() { + return GitVcs.getKey(); } @Override - public boolean isInvalidMapping(@NotNull VcsDirectoryMapping mapping) { - // this information is available in myErrors, - // but the method may be called in VcsDirectoryConfigurationPanel after adding a mapping (to highlight errors right away) - // in which case ProjectLevelVcsManager#getAllVcsRoots() is not aware of new roots yet, - // while VcsRootErrorsFinder relies on the set of roots returned from ProjectLevelVcsManager. - if (mapping.isDefaultMapping()) { - return myProjectMappingIsInvalid; - } - return !new File(mapping.getDirectory(), GitUtil.DOT_GIT).exists(); + public boolean isVcsDir(@Nullable String path) { + return path != null && path.toLowerCase().endsWith(GitUtil.DOT_GIT); } } diff --git a/plugins/git4idea/src/git4idea/roots/GitRootFinder.java b/plugins/git4idea/src/git4idea/roots/GitRootFinder.java index 99f7ae277faf..01e20992d0f9 100644 --- a/plugins/git4idea/src/git4idea/roots/GitRootFinder.java +++ b/plugins/git4idea/src/git4idea/roots/GitRootFinder.java @@ -2,12 +2,12 @@ package git4idea.roots; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsDirectoryMapping; +import com.intellij.openapi.vcs.VcsRoot; import com.intellij.openapi.vcs.VcsRootFinder; import com.intellij.openapi.vcs.roots.VcsRootDetectInfo; import com.intellij.openapi.vcs.roots.VcsRootDetector; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtilRt; -import git4idea.GitPlatformFacade; import git4idea.GitVcs; import org.jetbrains.annotations.NotNull; @@ -20,25 +20,26 @@ import java.util.Collections; */ public class GitRootFinder implements VcsRootFinder { - @NotNull private final Project myProject; - @NotNull private final GitPlatformFacade myPlatformFacade; + @NotNull private final Project myProject; - public GitRootFinder(@NotNull Project project, @NotNull GitPlatformFacade platformFacade) { + public GitRootFinder(@NotNull Project project) { myProject = project; - myPlatformFacade = platformFacade; } @NotNull @Override public Collection findRoots(@NotNull VirtualFile root) { VcsRootDetectInfo info = new VcsRootDetector(myProject).detect(root); - Collection roots = info.getRoots(); + Collection roots = info.getRoots(); if (roots.isEmpty()) { return Collections.emptyList(); } Collection result = ContainerUtilRt.newArrayList(); - for (VirtualFile file : roots) { - result.add(new VcsDirectoryMapping(file.getPath(), GitVcs.getKey().getName())); + for (VcsRoot vcsRoot : roots) { + VirtualFile vFile = vcsRoot.getPath(); + if (vFile != null) { + result.add(new VcsDirectoryMapping(vFile.getPath(), GitVcs.getKey().getName())); + } } return result; } diff --git a/plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy b/plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy deleted file mode 100644 index 0016e30207d3..000000000000 --- a/plugins/git4idea/tests/git4idea/roots/AbstractGitRootTest.groovy +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package git4idea.roots -import com.intellij.dvcs.test.MockProject -import com.intellij.dvcs.test.MockProjectRootManager -import com.intellij.dvcs.test.MockVirtualFile -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.io.FileUtil -import git4idea.test.GitLightTest -import org.junit.After -import org.junit.Before -/** - * - * @author Kirill Likhodedov - */ -class AbstractGitRootTest extends GitLightTest { - - static File baseDir - - @Before - void setUp() { - super.setUp(); - } - - @After - void tearDown() { - super.tearDown(); - } - - /** - * Creates the necessary temporary directories in the filesystem with empty ".git" directories for given roots. - * And creates an instance of the project. - * @param gitRoots path to actual .git roots, relative to the project dir. - */ - Project initProject(Collection gitRoots, Collection projectStructure, Collection contentRoots = []) { - String projectDir = createDirs(gitRoots) - Project project = new MockProject(projectDir) - createProjectStructure(project, projectStructure); - createProjectStructure(project, contentRoots); - - contentRoots.each { - ((MockProjectRootManager)myPlatformFacade.getProjectRootManager(project)).myContentRoots << MockVirtualFile.fromPath(it, project) - } - - project - } - - static void createProjectStructure(Project project, Collection paths) { - paths.each { String path -> - File file = new File(project.baseDir.path + "/" + path) - file.mkdir() - } - } - - /** - * @return path to the project - */ - private static String createDirs(Collection gitRoots) { - if (gitRoots.empty) { - return baseDir = FileUtil.createTempDirectory("grdt", null); - } - - baseDir = createBaseTempDir() - int maxDepth = findMaxDepthAboveProject(gitRoots) - File projectDir = createChild(baseDir, maxDepth) - gitRoots.each { String path -> - File file = new File(projectDir.path + "/" + path) - file.mkdirs() - file.deleteOnExit() - - File gitDir = new File(file, ".git") - gitDir.mkdir() - gitDir.deleteOnExit() - } - return projectDir.path - } - - private static File createBaseTempDir() { - FileUtil.createTempDirectory("pref", null) - } - - private static File createChild(File base, int depth) { - File dir = base - depth.times { dir = FileUtil.createTempDirectory(dir, "grdt", null)} - dir - } - - // Assuming that there are no ".." inside the path - only in the beginning - static int findMaxDepthAboveProject(Collection paths) { - def len = { String path -> path.split("/").count("..") } - len(paths.max(len)) - } - -} diff --git a/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy b/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy index 54d063b291fe..41b5ecc5c612 100644 --- a/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy +++ b/plugins/git4idea/tests/git4idea/roots/GitIntegrationEnablerTest.groovy @@ -20,7 +20,7 @@ import com.intellij.notification.Notification import com.intellij.notification.NotificationType import com.intellij.openapi.vcs.roots.VcsRootDetectInfo import com.intellij.openapi.vfs.VirtualFile -import git4idea.test.GitLightTest +import com.intellij.testFramework.UsefulTestCase import git4idea.test.TestNotificator import org.junit.After import org.junit.Before @@ -28,18 +28,20 @@ import org.junit.Test import static git4idea.test.GitGTestUtil.toAbsolute import static junit.framework.Assert.* +import static org.junit.Assume.assumeTrue /** - * + * * @author Kirill Likhodedov */ -class GitIntegrationEnablerTest extends GitLightTest { +class GitIntegrationEnablerTest extends UsefulTestCase { public static final String TEST_NOTIFICATION_GROUP = "Test" @Override @Before public void setUp() { + assumeTrue(false); super.setUp(); } @@ -157,5 +159,4 @@ class GitIntegrationEnablerTest extends GitLightTest { void assertNotificationShown(String title, String message, NotificationType type) { assertNotificationShown(new Notification(TEST_NOTIFICATION_GROUP, title, message, type)) } - } diff --git a/plugins/git4idea/tests/git4idea/roots/GitPlatformTest.java b/plugins/git4idea/tests/git4idea/roots/GitPlatformTest.java new file mode 100644 index 000000000000..c435e76a0b27 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/roots/GitPlatformTest.java @@ -0,0 +1,209 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.roots; + +import com.intellij.ide.highlighter.ModuleFileType; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.module.EmptyModuleType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; +import com.intellij.openapi.roots.impl.RootModelImpl; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.UsefulTestCase; +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; + +import static com.intellij.dvcs.test.Executor.*; + +/** + * @author Nadya Zabrodina + */ +public abstract class GitPlatformTest extends UsefulTestCase { + + protected Project myProject; + protected VirtualFile myProjectRoot; + protected VirtualFile myRepository; + public static final String myRepositoryFolderName = "repository"; + private RootModelImpl myRootModel; + protected static final Collection myFilesToDelete = new HashSet(); + + protected IdeaProjectTestFixture myProjectFixture; + + @SuppressWarnings("JUnitTestCaseWithNonTrivialConstructors") + protected GitPlatformTest() { + PlatformTestCase.initPlatformLangPrefix(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + myProjectFixture = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getTestName(true)).getFixture(); + myProjectFixture.setUp(); + + myProject = myProjectFixture.getProject(); + myProjectRoot = myProject.getBaseDir(); + cd(myProjectRoot); + Module module = doCreateRealModuleIn("foo", myProject, EmptyModuleType + .getInstance()); + myRootModel = ((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).getRootModel(); + mkdir(myRepositoryFolderName); + myRepository = myProjectRoot.findChild(myRepositoryFolderName); + } + + @Override + protected void tearDown() throws Exception { + for (File file : myFilesToDelete) { + delete(file); + } + myProjectFixture.tearDown(); + super.tearDown(); + } + + private static void delete(File file) { + boolean b = FileUtil.delete(file); + if (!b && file.exists()) { + fail("Can't delete " + file.getAbsolutePath()); + } + } + + /** + * Creates the necessary temporary directories in the filesystem with empty ".git" directories for given roots. + * And creates an instance of the project. + * + * @param gitRoots path to actual .git roots, relative to the project dir. + */ + public void initProject(@NotNull Collection gitRoots, + @NotNull Collection projectStructure, + @NotNull Collection contentRoots) + throws IOException { + createDirs(gitRoots); + createProjectStructure(myProject, projectStructure); + createProjectStructure(myProject, contentRoots); + if (!contentRoots.isEmpty()) { + for (String root : contentRoots) { + VirtualFile f = myProjectRoot.findFileByRelativePath(root); + if (f != null) { + myRootModel.addContentEntry(f); + } + } + } + } + + static void createProjectStructure(@NotNull Project project, @NotNull Collection paths) { + for (String path : paths) { + cd(project.getBaseDir().getPath()); + File f = new File(project.getBaseDir().getPath(), path); + f.mkdirs(); + } + } + + @NotNull + public static Module doCreateRealModuleIn(@NotNull String moduleName, + @NotNull final Project project, + @NotNull final ModuleType moduleType) { + final VirtualFile baseDir = project.getBaseDir(); + assertNotNull(baseDir); + final File moduleFile = new File(baseDir.getPath().replace('/', File.separatorChar), + moduleName + ModuleFileType.DOT_DEFAULT_EXTENSION); + FileUtil.createIfDoesntExist(moduleFile); + myFilesToDelete.add(moduleFile); + return new WriteAction() { + @Override + protected void run(Result result) throws Throwable { + final VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(moduleFile); + assert virtualFile != null; + Module module = ModuleManager.getInstance(project).newModule(virtualFile.getPath(), moduleType.getId()); + module.getModuleFile(); + result.setResult(module); + } + }.execute().getResultObject(); + } + + /** + * @return path to the project + */ + private void createDirs(@NotNull Collection gitRoots) throws IOException { + File baseDir; + if (gitRoots.isEmpty()) { + return; + } + + baseDir = VfsUtilCore.virtualToIoFile(myProject.getBaseDir()); + int maxDepth = findMaxDepthAboveProject(gitRoots); + File projectDir = createChild(baseDir, maxDepth - 1); + cd(projectDir.getPath()); + for (String path : gitRoots) { + File file = new File(projectDir, path); + file.mkdirs(); + File gitDir = new File(file, ".git"); + gitDir.mkdirs(); + myFilesToDelete.add(gitDir); + gitDir.deleteOnExit(); + cd(gitDir.getPath()); + touch("HEAD", "ref: refs/heads/master"); + File head = new File(gitDir, "HEAD"); + myFilesToDelete.add(head); + touch("config", ""); + File config = new File(gitDir, "config"); + myFilesToDelete.add(config); + } + } + + @NotNull + private static File createChild(@NotNull File base, int depth) throws IOException { + File dir = base; + if (depth < 0) { + return dir; + } + for (int i = 0; i < depth; ++i) { + dir = FileUtil.createTempDirectory(dir, "grdt", null); + } + return dir; + } + + // Assuming that there are no ".." inside the path - only in the beginning + static int findMaxDepthAboveProject(@NotNull Collection paths) { + int max = 0; + for (String path : paths) { + String[] splits = path.split("/"); + int count = 0; + for (String split : splits) { + if (split.equals("..")) { + count++; + } + } + if (count > max) { + max = count; + } + } + return max; + } +} diff --git a/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy b/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy deleted file mode 100644 index 500c8fda7db7..000000000000 --- a/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.groovy +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package git4idea.roots - -import com.intellij.openapi.project.Project -import com.intellij.openapi.vcs.roots.VcsRootDetectInfo -import com.intellij.openapi.vcs.roots.VcsRootDetector -import com.intellij.openapi.vfs.VirtualFile -import git4idea.test.GitGTestUtil -import org.junit.After -import org.junit.Before -import org.junit.Test - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.fail - -/** - * @author Kirill Likhodedov - */ -class GitRootDetectorTest extends AbstractGitRootTest { - - Project myProject - - @Before - void setUp() { - super.setUp(); - } - - @After - void tearDown() { - super.tearDown(); - } - - @Test - void "no roots in project"() { - doTest gits: [], - expected: [], - full: false, - below: false - } - - @Test - void "project under single Git"() { - doTest "." - } - - @Test - void "project with Git under it"() { - doTest project: [".idea", "bin", "src", "community"], - gits: ["community"], - expected: ["community"], - full: false, - below: false - } - - @Test - public void "project with all subdirs under Git should still be not fully controlled"() { - doTest gits: [".idea", "src", "community"], - expected: [".idea", "src", "community"], - full: false, - below: false - } - - @Test - void "project under Git above it"() { - doTest gits: [".."], - expected: [".."], - full: true, - below: true - } - - @Test - void "IDEA project"() { - doTest ".", "./community", "./contrib" - } - - @Test - void "one above and one under"() { - doTest gits: ["..", "./community"], - expected: ["..", "./community"], - full: true, - below: true - } - - @Test - void "one above and one for project should show only project root"() { - doTest gits: ["..", "."], - expected: ["."], - full: true, - below: false - } - - @Test - void "one above and several under project"() { - doTest gits: ["..", "./community", "./contrib"], - expected: ["..", "./community", "./contrib"], - full: true, - below: true - } - - @Test - void "multiple above should be detected as one above"() { - doTest gits: ["..", "../.."], - expected: [".."], - full: true, - below: true - } - - @Test - void "unrelated root should not be detected"() { - doTest gits: ["../neighbour"], - expected: [], - full: false, - below: false - } - - @Test - void "linked source root alone should be detected"() { - doTest content_roots: ["../linked_root"], - gits: ["../linked_root"], - expected: ["../linked_root"], - full: false, - below: false - } - - @Test - void "linked source root and project root should be detected"() { - doTest content_roots: ["../linked_root"], - gits: [".", "../linked_root"], - expected: [".", "../linked_root"], - full: true, - below: false - } - - @Test - void "linked source below Git"() { - doTest content_roots: ["../linked_root/src"], - gits: [".", "../linked_root"], - expected: [".", "../linked_root"], - full: true, - below: false - } - - @Test - // This is a test of performance optimization via limitation: don't scan deep though the whole VFS, i.e. don't detect deep roots - void "don't scan deeper than 2 levels below a content root"() { - doTest content_roots: ["content_root"], - gits: ["community", "content_root/lev1/lev2", "content_root2/lev1/lev2/lev3"], - expected: ["community", "content_root/lev1/lev2"], - full: false, - below: false - } - - /** - * Perform test. Map contains actual Git repositories to be created on disk, - * and Git repositories expected to be detected by the VcsRootDetector. - * @param map - */ - private void doTest(Map map) { - myProject = initProject(map.gits, map.project, map.content_roots) - testInfo empty: map.expected.empty, - full : map.full, - roots: map.expected, - below: map.below - } - - /** - * Shorthand, when all Git roots are expected to be detected, and project is fully under Git. - * @param roots paths relative to the project dir. "..", ".", "./community" are accepted. - */ - private void doTest(String... roots = []) { - doTest gits: roots.toList(), - expected: roots.toList(), - full: true, - below: false - } - - void testInfo(Map expected) { - assertInfo(expected, detect()) - } - - void assertInfo(Map expected, VcsRootDetectInfo actual) { - assertEquals(expected.empty, actual.empty()) - if (expected.full ^ actual.totallyUnderGit()) { - fail("The project is unexpectedly ${actual.totallyUnderGit() ? "" : "not "}under Git${roots(actual.roots)}") - } - if (expected.below ^ actual.projectIsBelowGit()) { - fail("The project is unexpectedly ${actual.projectIsBelowGit() ? "below" : "not below"} Git${roots(actual.roots)}") - } - assertRoots(expected.roots, actual.roots) - } - - static String roots(Collection roots) { - "\nRoots:${roots.collect {"\n * $it"}}\n" - } - - void assertRoots(Collection expectedRelativePaths, Collection actual) { - assertEquals(GitGTestUtil.toAbsolute(expectedRelativePaths, myProject).toSet(), getPaths(actual).toSet()) - } - - static Collection getPaths(Collection files) { - files.collect { it.path } - } - - private VcsRootDetectInfo detect() { - new VcsRootDetector(myProject).detect() - } - -} \ No newline at end of file diff --git a/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.java b/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.java new file mode 100644 index 000000000000..75e5a15edc5d --- /dev/null +++ b/plugins/git4idea/tests/git4idea/roots/GitRootDetectorTest.java @@ -0,0 +1,222 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.roots; + +import com.intellij.dvcs.test.Executor; +import com.intellij.dvcs.test.TestRepositoryUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.VcsRoot; +import com.intellij.openapi.vcs.roots.VcsRootDetectInfo; +import com.intellij.openapi.vcs.roots.VcsRootDetector; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static com.intellij.dvcs.test.Executor.cd; + + +/** + * @author Nadya Zabrodina + */ +public class GitRootDetectorTest extends GitPlatformTest { + + public void testNoRootsInProject() throws IOException { + Map> map = new HashMap>(); + map.put("git", Collections.emptyList()); + map.put("content_roots", Collections.emptyList()); + doTest(map, null, Collections.emptyList(), false, false); + } + + public void testProjectUnderSingleGit() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".")); + map.put("content_roots", Collections.emptyList()); + doTest(map, myProjectRoot, Arrays.asList("."), true, false); + } + + public void testProjectWithGitUnderIt() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList("community")); + map.put("content_roots", Collections.emptyList()); + cd(myProjectRoot); + Executor.mkdir("src"); + Executor.mkdir(".idea"); + doTest(map, myProjectRoot, Arrays.asList("community"), false, false); + } + + public void testProjectWithAllSubdirsUnderGitShouldStillBeNotFullyControlled() throws IOException { + String[] dirNames = {".idea", "src", "community"}; + Map> map = new HashMap>(); + map.put("git", Arrays.asList(dirNames)); + map.put("content_roots", Collections.emptyList()); + doTest(map, myProjectRoot, Arrays.asList(dirNames), false, false); + } + + + public void testProjectUnderVcsAboveIt() throws IOException { + String subdir = "insideRepo"; + cd(myRepository); + Executor.mkdir(subdir); + Map> map = new HashMap>(); + map.put("git", Arrays.asList(myRepository.getName())); + map.put("content_roots", Collections.emptyList()); + VirtualFile vfile = myRepository.findChild(subdir); + doTest(map, vfile, Arrays.asList(myRepository.getName()), + true, true); + } + + + public void testIDEAProject() throws IOException { + String[] names = {"community", "contrib", "."}; + Map> map = new HashMap>(); + map.put("git", Arrays.asList(names)); + map.put("content_roots", Collections.emptyList()); + doTest(map, myProjectRoot, Arrays.asList(names), true, false); + } + + + public void testOneAboveAndOneUnder() throws IOException { + String[] names = {myRepository.getName() + "/community", "."}; + Map> map = new HashMap>(); + map.put("git", Arrays.asList(names)); + map.put("content_roots", Collections.emptyList()); + doTest(map, myRepository, Arrays.asList(names), true, true); + } + + public void testOneAboveAndOneForProjectShouldShowOnlyProjectRoot() throws IOException { + String[] names = {myRepository.getName(), "."}; + Map> map = new HashMap>(); + map.put("git", Arrays.asList(names)); + map.put("content_roots", Collections.emptyList()); + doTest(map, myRepository, Arrays.asList(myRepository.getName()), true, false); + } + + public void testOneAboveAndSeveralUnderProject() throws IOException { + String[] names = {".", myRepository.getName() + "/community", myRepository.getName() + "/contrib"}; + Map> map = new HashMap>(); + map.put("git", Arrays.asList(names)); + map.put("content_roots", Collections.emptyList()); + doTest(map, myRepository, Arrays.asList(names), true, true); + } + + + public void testMultipleAboveShouldBeDetectedAsOneAbove() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", myRepository.getName())); + map.put("content_roots", Collections.emptyList()); + String subdir = "insideRepo"; + cd(myRepository); + Executor.mkdir(subdir); + VirtualFile vfile = myRepository.findChild(subdir); + doTest(map, vfile, Arrays.asList(myRepository.getName()), true, true); + } + + public void testUnrelatedRootShouldNotBeDetected() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList("another")); + map.put("content_roots", Collections.emptyList()); + doTest(map, myRepository, Collections.emptyList(), false, false); + } + + + public void testLinkedSourceRootAloneShouldBeDetected() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList("linked_root")); + map.put("content_roots", Arrays.asList("linked_root")); + doTest(map, myRepository, Arrays.asList("linked_root"), false, false); + } + + public void testLinkedSourceRootAndProjectRootShouldBeDetected() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "linked_root")); + map.put("content_roots", Arrays.asList("linked_root")); + doTest(map, myProjectRoot, Arrays.asList(".", "linked_root"), true, false); + } + + public void testLinkedSourceBelowGit() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "linked_root")); + map.put("content_roots", Arrays.asList("linked_root/src")); + doTest(map, myProjectRoot, Arrays.asList(".", "linked_root"), true, false); + } + + // This is a test of performance optimization via limitation: don't scan deep though the whole VFS, i.e. don't detect deep roots + public void testDontScanDeeperThan2LevelsBelowAContentRoot() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList("community", "content_root/lev1/lev2", "content_root2/lev1/lev2/lev3")); + map.put("content_roots", Arrays.asList("content_root")); + doTest(map, myProjectRoot, Arrays.asList("community", "content_root/lev1/lev2"), false, false); + } + + void assertRoots(Collection expectedRelativePaths, Collection actual) { + TestRepositoryUtil.assertEqualCollections(actual, toAbsolute(expectedRelativePaths, myProject)); + } + + @NotNull + public static Collection toAbsolute(Collection relPaths, final Project project) { + return ContainerUtil.map(relPaths, new Function() { + @Override + public String fun(String s) { + try { + return FileUtil.toSystemIndependentName(new File(project.getBaseDir().getPath(), s).getCanonicalPath()); + } + catch (IOException e) { + fail(); + e.printStackTrace(); + return null; + } + } + }); + } + + @NotNull + static Collection getPaths(@NotNull Collection files) { + return ContainerUtil.map(files, new Function() { + @Override + public String fun(VcsRoot root) { + VirtualFile file = root.getPath(); + assert file != null; + return FileUtil.toSystemIndependentName(file.getPath()); + } + }); + } + + @NotNull + private VcsRootDetectInfo detect(@Nullable VirtualFile startDir) { + return new VcsRootDetector(myProject).detect(startDir); + } + + public void doTest(@NotNull Map> map, + @Nullable VirtualFile startDir, + @NotNull Collection expectedPaths, + boolean expectedFull, + boolean expectedBelow) + throws IOException { + initProject(map.get("git"), Collections.emptyList(), map.get("content_roots")); + + VcsRootDetectInfo info = detect(startDir); + assertRoots(expectedPaths, getPaths(info.getRoots())); + assertEquals(expectedFull, info.totallyUnderVcs()); + assertEquals(expectedBelow, info.projectIsBelowVcs()); + } +} diff --git a/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy b/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy deleted file mode 100644 index 8fbd2a4a8691..000000000000 --- a/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.groovy +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package git4idea.roots - -import com.intellij.openapi.vcs.VcsDirectoryMapping -import com.intellij.openapi.vcs.VcsRootError -import com.intellij.openapi.vcs.roots.VcsRootErrorsFinder -import git4idea.test.GitMockVcsManager -import org.junit.After -import org.junit.Before -import org.junit.Test - -import static git4idea.test.GitGTestUtil.toAbsolute -import static junit.framework.Assert.assertEquals -/** - * - * @author Kirill Likhodedov - */ -class GitRootErrorsFinderTest extends AbstractGitRootTest { - - static final String PROJECT = VcsDirectoryMapping.PROJECT_CONSTANT - - GitMockVcsManager myVcsManager - - @Before - void setUp() { - super.setUp() - } - - @After - void tearDown() { - super.tearDown(); - } - - @Test - void "No roots, then no errors"() { - doTest git: [], - roots: [], - errors: [] - } - - @Test - void "Same 1 root in both, then no errors"() { - doTest git: ["."], - roots: ["."], - errors: [] - } - - @Test - void "Same 2 roots in both, then no errors"() { - doTest git: ["..", "community"], - roots: ["..", "community"], - errors: [] - } - - @Test - void "One git, no VCS roots, then error"() { - doTest git: ["."], - roots: [], - errors: [unreg : ["."]] - } - - @Test - void "One VCS root, no gits, then error"() { - doTest git: [], - roots: ["."], - errors: [extra: ["."]] - } - - @Test - void "One root, but different, then 2 errors"() { - doTest git: ["."], - roots: ["community"], - errors: [unreg: ["."], extra: ["community"]] - } - - @Test - void "Two roots, one matching, one different, then 2 errors"() { - doTest git: [".", "community"], - roots: [".", "contrib"], - errors: [unreg: ["community"], extra: ["contrib"]] - } - - @Test - void "Two roots in git, one matching in VCS, then error"() { - doTest git: [".", "community"], - roots: ["."], - errors: [unreg: ["community"]] - } - - @Test - void "Two roots, both not matching, then 4 errors"() { - doTest git: ["..", "community"], - roots: [".", "contrib"], - errors: [unreg: ["..", "community"], extra: [".", "contrib"]] - } - - @Test - void "Project root, no gits, then error about extra root"() { - doTest content_roots: ["."], - git: [], - roots: [PROJECT], - errors: [extra: [PROJECT]] - } - - @Test - void "Project root, full under git, then correct"() { - doTest content_roots: ["."], - git: ["."], - roots: [PROJECT], - errors: [] - } - - @Test - void "Project root, git for a content root below project, then error"() { - doTest content_roots: [".", "content_root"], - git: ["content_root"], - roots: [PROJECT], - errors: [unreg: ["content_root"]] - } - - @Test - void "Project root, git below project folder not in a content root, then unregistered root error"() { - // this is to be fixed: auto-detection of Git repositories in subfolders for the mapping - doTest content_roots: ["."], - git: ["community"], - roots: [PROJECT], - errors: [unreg: ["community"]] - } - - @Test - void "Project root, git for full project, content root, linked source, folder below project, then errors"() { - doTest content_roots: [".", "content_root", "../linked_source_root"], - git: [".", "content_root", "../linked_source_root", "folder"], - roots: [PROJECT], - errors: [unreg: ["content_root", "../linked_source_root", "folder"]] - } - - @Test - void "Project root, root for folder, git for full project, content root, linked source, folder below project, then errors"() { - doTest content_roots: [".", "content_root", "../linked_source_root"], - git: [".", "content_root", "../linked_source_root", "folder"], - roots: [PROJECT, "folder"], - errors: [unreg: ["content_root", "../linked_source_root"]] - } - - @Test - void "Project root, git like in IDEA project, then correct"() { - doTest content_roots: [".", "community", "contrib"], - git: [".", "community", "contrib"], - roots: [PROJECT], - errors: [unreg: ["community", "contrib"]] - } - - @Test - void "Real Git root deeper than 3 levels should be detected"() { - doTest content_roots: [".", "community", "contrib"], - git: [".", "community", "contrib", "community/level1/level2/level3"], - roots: [PROJECT, "community/level1/level2/level3"], - errors: [unreg: ["community", "contrib"]] - } - - private void doTest(Map map) { - myProject = initProject(map.git, [], map.content_roots) - myVcsManager = (GitMockVcsManager) myPlatformFacade.getVcsManager(myProject) - - addVcsRoots(map.roots) - - Collection expected = new ArrayList(); - expected.addAll map.errors.unreg.collect { unreg(it) } - expected.addAll map.errors.extra.collect { extra(it) } - - Collection actual = new VcsRootErrorsFinder(myProject).find() - assertEquals expected.toSet(), actual.toSet() - } - - void addVcsRoots(Collection relativeRoots) { - relativeRoots.each { - if (it.equals(PROJECT)) { - myVcsManager.setProjectRootMapping() - } - else { - String root = toAbsolute(it, myProject) - myVcsManager.addRoots(root) - } - } - } - - VcsRootError unreg(String path) { - return new VcsRootError(VcsRootError.Type.UNREGISTERED_ROOT, toAbsolute(path, myProject)) - } - - VcsRootError extra(String path) { - return new VcsRootError(VcsRootError.Type.EXTRA_MAPPING, path.equals(PROJECT) ? PROJECT : toAbsolute(path, myProject)) - } - - -} diff --git a/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.java b/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.java new file mode 100644 index 000000000000..b1bf00feabc1 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/roots/GitRootErrorsFinderTest.java @@ -0,0 +1,270 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.roots; + +import com.intellij.dvcs.test.TestRepositoryUtil; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsDirectoryMapping; +import com.intellij.openapi.vcs.VcsRootError; +import com.intellij.openapi.vcs.roots.VcsRootErrorsFinder; +import git4idea.GitVcs; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.*; + +import static git4idea.test.GitGTestUtil.toAbsolute; + +/** + * @author Nadya Zabrodina + */ +public class GitRootErrorsFinderTest extends GitPlatformTest { + + static final String PROJECT = VcsDirectoryMapping.PROJECT_CONSTANT; + @NotNull private ProjectLevelVcsManager myVcsManager; + + @Override + public void setUp() throws Exception { + super.setUp(); + myVcsManager = ProjectLevelVcsManager.getInstance(myProject); + } + + public void testNoRootsThenNoErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Collections.emptyList()); + map.put("roots", Collections.emptyList()); + map.put("content_roots", Collections.emptyList()); + doTest(map, Collections.>emptyMap()); + } + + public void testSameOneRootInBothThenNoErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".")); + map.put("roots", Arrays.asList(".")); + map.put("content_roots", Collections.emptyList()); + doTest(map, Collections.>emptyMap()); + } + + public void testSameTwoRootsInBothThenNoErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "community")); + map.put("roots", Arrays.asList(".", "community")); + map.put("content_roots", Collections.emptyList()); + doTest(map, Collections.>emptyMap()); + } + + public void testOneGitNoVCSRootsThenError() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".")); + map.put("roots", Collections.emptyList()); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList(".")); + doTest(map, errorsMap); + } + + public void testOneVCSRootNoGitsThenError() throws IOException { + Map> map = new HashMap>(); + map.put("roots", Arrays.asList(".")); + map.put("git", Collections.emptyList()); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("extra", Arrays.asList(".")); + doTest(map, errorsMap); + } + + public void testOneRootButDifferentThenTwoErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".")); + map.put("roots", Arrays.asList("community")); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("extra", Arrays.asList("community")); + errorsMap.put("unreg", Arrays.asList(".")); + doTest(map, errorsMap); + } + + public void testTwoRootsOneMatchingOneDifferentThenTwoErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "community")); + map.put("roots", Arrays.asList(".", "contrib")); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("extra", Arrays.asList("contrib")); + errorsMap.put("unreg", Arrays.asList("community")); + doTest(map, errorsMap); + } + + public void testTwoRootsInGitOneMatchingInVCSThenError() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "community")); + map.put("roots", Arrays.asList(".")); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("community")); + doTest(map, errorsMap); + } + + public void testTwoRootsBothNotMatchingThenFourErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "community")); + map.put("roots", Arrays.asList("another", "contrib")); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("extra", Arrays.asList("contrib", "another")); + errorsMap.put("unreg", Arrays.asList("community", ".")); + doTest(map, errorsMap); + } + + public void testProjectRootNoGitsThenErrorAboutExtraRoot() throws IOException { + Map> map = new HashMap>(); + map.put("git", Collections.emptyList()); + map.put("roots", Arrays.asList(PROJECT)); + map.put("content_roots", Collections.emptyList()); + Map> errorsMap = new HashMap>(); + errorsMap.put("extra", Arrays.asList(PROJECT)); + doTest(map, errorsMap); + } + + public void testProjectRootFullUnderGitThenCorrect() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".")); + map.put("roots", Arrays.asList(".", PROJECT)); + map.put("content_roots", Collections.emptyList()); + doTest(map, Collections.>emptyMap()); + } + + public void testProjectRootGitForAContentRootBelowProjectThenError() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList("content_root")); + map.put("roots", Arrays.asList(PROJECT)); + map.put("content_roots", Arrays.asList("content_root")); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("content_root")); + doTest(map, errorsMap); + } + + public void testProjectRootGitBelowProjectFolderNotInAContentRootThenUnregisteredRootError() throws IOException { + // this is to be fixed: auto-detection of Git repositories in subfolders for the mapping + Map> map = new HashMap>(); + map.put("git", Arrays.asList("community")); + map.put("roots", Arrays.asList(PROJECT)); + map.put("content_roots", Arrays.asList(".")); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("community")); + doTest(map, errorsMap); + } + + public void testProjectRootGitForFullProjectContentRootLinkedSourceFolderBelowProjectThenErrors() throws IOException + + { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "content_root", "../linked_source_root", "folder")); + map.put("roots", Arrays.asList(PROJECT)); + map.put("content_roots", Arrays.asList(".", "content_root", "../linked_source_root")); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("content_root", "../linked_source_root", "folder")); + doTest(map, errorsMap); + } + + public void testProjectRootRootForFolderGitForFullProjectContentRootLinkedSourceFolderBelowProjectThenErrors() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "content_root", "../linked_source_root", "folder")); + map.put("roots", Arrays.asList(PROJECT, "folder")); + map.put("content_roots", Arrays.asList(".", "content_root", "../linked_source_root")); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("content_root", "../linked_source_root")); + doTest(map, errorsMap); + } + + public void testProjectRootGitLikeInIDEAProjectThenError() throws IOException { + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "community", "contrib")); + map.put("roots", Arrays.asList(PROJECT)); + map.put("content_roots", Arrays.asList(".", "community", "contrib")); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("community", "contrib")); + doTest(map, errorsMap); + } + + public void testRealGitRootDeeperThanThreeLevelsShouldBeDetected() throws IOException { + + Map> map = new HashMap>(); + map.put("git", Arrays.asList(".", "community", "contrib", "community/level1/level2/level3")); + map.put("roots", Arrays.asList(PROJECT, "community/level1/level2/level3")); + map.put("content_roots", Arrays.asList(".", "community", "contrib")); + Map> errorsMap = new HashMap>(); + errorsMap.put("unreg", Arrays.asList("community", "contrib")); + doTest(map, errorsMap); + } + + private void doTest(@NotNull Map> map, @NotNull Map> errors) throws IOException { + initProject(map.get("git"), Collections.emptyList(), map.get("content_roots")); + addVcsRoots(map.get("roots")); + + Collection expected = new ArrayList(); + Collection unregPaths = errors.get("unreg"); + Collection extraPaths = errors.get("extra"); + if (unregPaths != null) { + expected.addAll(unregAll(unregPaths)); + } + if (extraPaths != null) { + expected.addAll(extraAll(extraPaths)); + } + Collection actual = new VcsRootErrorsFinder(myProject).find(); + TestRepositoryUtil.assertEqualCollections(actual, expected); + } + + void addVcsRoots(@NotNull Collection relativeRoots) { + for (String root : relativeRoots) { + if (root.equals(PROJECT)) { + myVcsManager.setDirectoryMapping("", GitVcs.NAME); + } + else { + String absoluteRoot = toAbsolute(root, myProject); + myVcsManager.setDirectoryMapping(absoluteRoot, GitVcs.NAME); + } + } + } + + @NotNull + Collection unregAll(@NotNull Collection paths) { + Collection unregRoots = new ArrayList(); + for (String path : paths) { + unregRoots.add(unreg(path)); + } + return unregRoots; + } + + @NotNull + Collection extraAll(@NotNull Collection paths) { + Collection extraRoots = new ArrayList(); + for (String path : paths) { + extraRoots.add(extra(path)); + } + return extraRoots; + } + + @NotNull + VcsRootError unreg(@NotNull String path) { + return new VcsRootError(VcsRootError.Type.UNREGISTERED_ROOT, toAbsolute(path, myProject), GitVcs.NAME); + } + + @NotNull + VcsRootError extra(@NotNull String path) { + return new VcsRootError(VcsRootError.Type.EXTRA_MAPPING, PROJECT.equals(path) ? PROJECT : toAbsolute(path, myProject), GitVcs.NAME); + } +} diff --git a/plugins/hg4idea/src/META-INF/plugin.xml b/plugins/hg4idea/src/META-INF/plugin.xml index 890c9f8b3cd2..8eb193bd5fd6 100644 --- a/plugins/hg4idea/src/META-INF/plugin.xml +++ b/plugins/hg4idea/src/META-INF/plugin.xml @@ -10,6 +10,7 @@ + diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/roots/HgRootChecker.java b/plugins/hg4idea/src/org/zmlx/hg4idea/roots/HgRootChecker.java new file mode 100644 index 000000000000..93a108c32bc0 --- /dev/null +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/roots/HgRootChecker.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zmlx.hg4idea.roots; + +import com.intellij.openapi.vcs.VcsKey; +import com.intellij.openapi.vcs.VcsRootChecker; +import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.HgVcs; +import org.zmlx.hg4idea.util.HgUtil; + +import java.io.File; + +/** + * @author Nadya Zabrodina + */ +public class HgRootChecker extends VcsRootChecker { + + @Override + public boolean isRoot(@NotNull String path) { + return new File(path, HgUtil.DOT_HG).exists(); + } + + @Override + public VcsKey getSupportedVcs() { + return HgVcs.getKey(); + } + + @Override + public boolean isVcsDir(String path) { + return path != null && path.toLowerCase().endsWith(HgUtil.DOT_HG); + } +}