mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
[vcs/mappings] IJPL-181017 Deduplicate symlink roots
Skip root if it is located in a symbolic link hierarchy and the target path is already registered (cherry picked from commit d0b280bfb2014dbaaf66ead771e0b8b157097e05) (cherry picked from commit 96aaec2e174f62b5c2d89ed23836190ee05bd424) IJ-MR-163036 GitOrigin-RevId: fa9a68a264cc33a0dbb485e41dec81723e64c3ef
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d7a23e3832
commit
8b47332de2
@@ -77,7 +77,7 @@ final class VcsRootDetectorImpl implements VcsRootDetector {
|
||||
|
||||
detectedRoots.addAll(scanForRootsInsideDir(myProject, dirToScan, null, scannedDirs));
|
||||
detectedRoots.addAll(scanForRootsAboveDirs(Collections.singletonList(dirToScan), scannedDirs, detectedRoots));
|
||||
return detectedRoots;
|
||||
return deduplicate(detectedRoots);
|
||||
}
|
||||
|
||||
private @NotNull Collection<VcsRoot> scanForRootsInContentRoots() {
|
||||
@@ -106,7 +106,43 @@ final class VcsRootDetectorImpl implements VcsRootDetector {
|
||||
detectedAndKnownRoots.addAll(Arrays.asList(ProjectLevelVcsManager.getInstance(myProject).getAllVcsRoots()));
|
||||
detectedRoots.addAll(scanDependentRoots(scannedDirs, detectedAndKnownRoots));
|
||||
|
||||
return detectedRoots;
|
||||
return deduplicate(detectedRoots);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a deduplicated set of {@code detectedRoots} with removed links pointing to the same canonical file.
|
||||
*/
|
||||
private static @NotNull Set<VcsRoot> deduplicate(@NotNull Set<VcsRoot> detectedRoots) {
|
||||
if (detectedRoots.size() <= 1) return detectedRoots;
|
||||
|
||||
Set<VcsRoot> result = new HashSet<>();
|
||||
|
||||
Set<VirtualFile> processedCanonicalRoots = new HashSet<>();
|
||||
Set<VcsRoot> rootsUnderSymlink = new HashSet<>();
|
||||
for (VcsRoot root : detectedRoots) {
|
||||
VirtualFile path = root.getPath();
|
||||
VirtualFile canonicalPath = path.getCanonicalFile();
|
||||
if (canonicalPath != null && !path.equals(canonicalPath)) {
|
||||
rootsUnderSymlink.add(root);
|
||||
} else {
|
||||
processedCanonicalRoots.add(path);
|
||||
result.add(root);
|
||||
}
|
||||
}
|
||||
|
||||
if (rootsUnderSymlink.isEmpty()) return detectedRoots;
|
||||
|
||||
for (VcsRoot root : ContainerUtil.sorted(rootsUnderSymlink, Comparator.comparing(root -> root.getPath().toNioPath()))) {
|
||||
VirtualFile canonicalFile = root.getPath().getCanonicalFile();
|
||||
if (processedCanonicalRoots.contains(canonicalFile)) {
|
||||
LOG.debug("Skipping duplicate VCS root %s: root for canonical file '%s' is already detected".formatted(root, canonicalFile));
|
||||
} else {
|
||||
processedCanonicalRoots.add(canonicalFile);
|
||||
result.add(root);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private @NotNull Set<VcsRoot> scanForRootsInsideDir(@NotNull Project project,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.vcs.roots
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
@@ -8,8 +8,11 @@ import com.intellij.openapi.vcs.VcsTestUtil.assertEqualCollections
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.testFramework.PsiTestUtil
|
||||
import com.intellij.testFramework.VfsTestUtil
|
||||
import com.intellij.testFramework.utils.io.createDirectory
|
||||
import com.intellij.testFramework.utils.io.createFile
|
||||
import org.jetbrains.jps.model.serialization.PathMacroUtil
|
||||
import java.util.Collections.emptyList
|
||||
import kotlin.io.path.createSymbolicLinkPointingTo
|
||||
|
||||
class VcsRootDetectorTest : VcsRootBaseTest() {
|
||||
fun `test no roots`() {
|
||||
@@ -164,6 +167,95 @@ class VcsRootDetectorTest : VcsRootBaseTest() {
|
||||
expect(projectRoot)
|
||||
}
|
||||
|
||||
// IJPL-172487
|
||||
fun `test root referenced via child symlink should not be duplicated`() {
|
||||
initRepository(projectRoot)
|
||||
|
||||
// Created layout:
|
||||
// project/
|
||||
// -child/
|
||||
// --test
|
||||
// --root-symlink -> project
|
||||
val targetDir = projectNioRoot.createDirectory("child")
|
||||
targetDir.createFile("test")
|
||||
targetDir.resolve("root-symlink").createSymbolicLinkPointingTo(projectNioRoot)
|
||||
|
||||
VfsTestUtil.syncRefresh()
|
||||
|
||||
PsiTestUtil.addContentRoot(rootModule, projectRoot)
|
||||
|
||||
expect(projectRoot)
|
||||
}
|
||||
|
||||
// IJPL-181017
|
||||
fun `test root has symlink parent`() {
|
||||
val symlink = projectNioRoot.resolve("symlink")
|
||||
val projectSibling = testNioRoot.createDirectory("project-sibling")
|
||||
symlink.createSymbolicLinkPointingTo(projectSibling)
|
||||
VfsTestUtil.syncRefresh()
|
||||
|
||||
val vcsRoot = createVcsRoots("symlink/repo-root")
|
||||
// Expected `project/symlink/repo-root` which is not a canonical path `project-sibling/repo-root`
|
||||
expect(vcsRoot)
|
||||
}
|
||||
|
||||
|
||||
fun `test multiple roots under symlink`() {
|
||||
val symlink = projectNioRoot.resolve("symlink")
|
||||
val projectSibling = testNioRoot.createDirectory("project-sibling")
|
||||
symlink.createSymbolicLinkPointingTo(projectSibling)
|
||||
VfsTestUtil.syncRefresh()
|
||||
|
||||
// Created layout:
|
||||
// project/
|
||||
// -symlink -> project-sibling
|
||||
// project-sibling/
|
||||
// -root1
|
||||
// -root2
|
||||
// -root3
|
||||
val roots = createVcsRoots("symlink/root1", "symlink/root2", "symlink/root3")
|
||||
expect(roots)
|
||||
}
|
||||
|
||||
fun `test multiple roots with symlink target outside project`() {
|
||||
initRepository(projectRoot)
|
||||
|
||||
val symlink = projectNioRoot.resolve("symlink")
|
||||
val projectSibling = testNioRoot.createDirectory("project-sibling")
|
||||
symlink.createSymbolicLinkPointingTo(projectSibling)
|
||||
VfsTestUtil.syncRefresh()
|
||||
|
||||
val vcsRoot = createVcsRoots("symlink/repo-root")
|
||||
expect(vcsRoot + projectRoot)
|
||||
}
|
||||
|
||||
// Combined scenario for IJPL-172487 and IJPL-181017
|
||||
fun `test symlinks under symlinks`() {
|
||||
val symlink = projectNioRoot.resolve("symlink")
|
||||
val projectSibling = testNioRoot.createDirectory("project-sibling")
|
||||
symlink.createSymbolicLinkPointingTo(projectSibling)
|
||||
VfsTestUtil.syncRefresh()
|
||||
|
||||
val vcsRoot = createVcsRoots("symlink/repo-root")
|
||||
|
||||
// Created layout:
|
||||
// project/
|
||||
// -symlink -> project-sibling/
|
||||
// project-sibling/
|
||||
// -repo-root
|
||||
// --test
|
||||
// --sublink -> repo-root
|
||||
val repoRoot = symlink.resolve("repo-root")
|
||||
repoRoot.createFile("test")
|
||||
val linkToRepo = repoRoot.resolve("sublink")
|
||||
linkToRepo.createSymbolicLinkPointingTo(repoRoot)
|
||||
|
||||
VfsTestUtil.syncRefresh()
|
||||
|
||||
// Expected only `project/symlink/repo-root`
|
||||
expect(vcsRoot)
|
||||
}
|
||||
|
||||
private fun createVcsRoots(vararg relativePaths: String, registerContentRoot: Boolean = true): List<VirtualFile> {
|
||||
return createVcsRoots(listOf(*relativePaths), registerContentRoot)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user