From 0f66a79419ac67de8fa0ff839b4adc52d6b3ae3d Mon Sep 17 00:00:00 2001 From: Kirill Bochkarev Date: Thu, 12 Sep 2024 16:04:27 +0200 Subject: [PATCH] IJPL-641 namesake .iml modules renaming (cherry picked from commit be49546d7c3e424faf764bf6632ced4c8af902b2) IJ-MR-146834 GitOrigin-RevId: 142221700b1b2346e7ed12b18fbd6292759f489d --- .../testSrc/ChangeModuleStorePathTest.kt | 2 +- .../testSrc/TwoSimultaneousProjectsTest.kt | 97 +++++++++++++++++++ .../FileReferenceInWorkspaceEntityUpdater.kt | 12 ++- 3 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 platform/configuration-store-impl/testSrc/TwoSimultaneousProjectsTest.kt diff --git a/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt b/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt index 2f5c83914853..763eab44a2ff 100644 --- a/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt +++ b/platform/configuration-store-impl/testSrc/ChangeModuleStorePathTest.kt @@ -35,7 +35,7 @@ import kotlin.io.path.isRegularFile import kotlin.io.path.readText import kotlin.properties.Delegates -private val Module.storage: FileBasedStorage +internal val Module.storage: FileBasedStorage get() = (stateStore.storageManager as StateStorageManagerImpl).getCachedFileStorages(listOf(StoragePathMacros.MODULE_FILE)).first() @RunsInActiveStoreMode diff --git a/platform/configuration-store-impl/testSrc/TwoSimultaneousProjectsTest.kt b/platform/configuration-store-impl/testSrc/TwoSimultaneousProjectsTest.kt new file mode 100644 index 000000000000..ff2127967797 --- /dev/null +++ b/platform/configuration-store-impl/testSrc/TwoSimultaneousProjectsTest.kt @@ -0,0 +1,97 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.configurationStore + +import com.intellij.ide.highlighter.ModuleFileType +import com.intellij.openapi.application.writeAction +import com.intellij.openapi.components.StoragePathMacros +import com.intellij.openapi.components.impl.stores.stateStore +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.project.stateStore +import com.intellij.testFramework.assertions.Assertions.assertThat +import com.intellij.testFramework.junit5.TestApplication +import com.intellij.testFramework.junit5.fixture.TestFixture +import com.intellij.testFramework.junit5.fixture.projectFixture +import com.intellij.testFramework.junit5.fixture.testFixture +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assumptions.assumeThat +import org.jetbrains.annotations.TestOnly +import org.junit.jupiter.api.Test +import java.nio.file.Path + +@TestApplication +class TwoSimultaneousProjectsTest { + private companion object { + const val SHARED_NAME = "shared.name" + + val projectA = projectFixture(openAfterCreation = true) + val moduleFromProjectAFixture = projectA.persistentModuleFixture(SHARED_NAME) + + val projectB = projectFixture(openAfterCreation = true) + val moduleFromProjectBFixture = projectB.persistentModuleFixture(SHARED_NAME) + + /** + * Base TestFixture.moduleFixture uses NonPersistentStateStorageManager as storageManager + */ + @TestOnly + fun TestFixture.persistentModuleFixture(name: String): TestFixture = + testFixture(name) { + val project = this@persistentModuleFixture.init() + val manager = ModuleManager.getInstance(project) + val module = writeAction { + val projectDir = project.guessProjectDir()?.toNioPath() ?: throw RuntimeException("Cannot guess project dir for $project") + assumeThat(projectDir).isNotNull().exists() + manager.newModule(projectDir.resolve("$name${ModuleFileType.DOT_DEFAULT_EXTENSION}"), "") + } + initialized(module) { + writeAction { + manager.disposeModule(module) + } + } + } + } + + @Test + fun `rename module using rename iml in one project does not change similarly named module in another project`() = runBlocking { + val moduleA = moduleFromProjectAFixture.get() + val moduleB = moduleFromProjectBFixture.get() + val originalModuleAFile = checkModuleAndGetFile(moduleA) + val originalModuleBFile = checkModuleAndGetFile(moduleB) + val newName = "$SHARED_NAME.v2" + writeAction { + LocalFileSystem.getInstance().refreshAndFindFileByNioFile(originalModuleAFile)!!.rename(null, "${newName}${ModuleFileType.DOT_DEFAULT_EXTENSION}") + } + assertModuleFileRenamed(moduleA, newName, originalModuleAFile) + assertModuleFileNotRenamed(moduleB, originalModuleBFile) + } + + private suspend fun checkModuleAndGetFile(module: Module): Path { + module.project.stateStore.save() + + val moduleFile = module.storage.file + assertThat(moduleFile).isRegularFile.hasFileName("$SHARED_NAME${ModuleFileType.DOT_DEFAULT_EXTENSION}") + return moduleFile + } + + private fun assertModuleFileRenamed(renamedModule: Module, newName: String, originalModuleFile: Path) { + val newFile = renamedModule.storage.file + assertThat(newFile).isRegularFile.hasFileName("${newName}${ModuleFileType.DOT_DEFAULT_EXTENSION}") + assertThat(originalModuleFile).doesNotExist().isNotEqualTo(newFile) + + // ensure that macro value is updated + assertThat(renamedModule.stateStore.storageManager.expandMacro(StoragePathMacros.MODULE_FILE)).isEqualTo(newFile) + assertThat(renamedModule.moduleNioFile).isEqualTo(newFile) + } + + private fun assertModuleFileNotRenamed(module: Module, originalModuleFile: Path) { + val currentModuleFile = module.storage.file + assertThat(currentModuleFile).isRegularFile.hasFileName("$SHARED_NAME${ModuleFileType.DOT_DEFAULT_EXTENSION}").isEqualTo(originalModuleFile) + + // ensure that macro value is NOT updated + assertThat(module.stateStore.storageManager.expandMacro(StoragePathMacros.MODULE_FILE)).isEqualTo(originalModuleFile) + assertThat(module.moduleNioFile).isEqualTo(originalModuleFile) + } +} diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt index f8b64264f4b2..f402dc3a2684 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/FileReferenceInWorkspaceEntityUpdater.kt @@ -21,6 +21,8 @@ import com.intellij.platform.backend.workspace.impl.WorkspaceModelInternal import com.intellij.platform.workspace.jps.entities.ModuleId import com.intellij.platform.workspace.jps.entities.modifyModuleEntity import com.intellij.platform.workspace.jps.serialization.impl.ModulePath +import com.intellij.platform.workspace.storage.impl.url.VirtualFileUrlManagerImpl +import com.intellij.util.PathUtil import com.intellij.workspaceModel.core.fileIndex.impl.getOldAndNewUrls import com.intellij.workspaceModel.ide.impl.legacyBridge.watcher.VirtualFileUrlWatcher import java.nio.file.Path @@ -55,7 +57,7 @@ internal class FileReferenceInWorkspaceEntityUpdater(private val project: Projec if (changedUrlsList.isEmpty() && changedModuleStorePaths.isEmpty()) { return null } - + return object : AsyncFileListener.ChangeApplier { override fun beforeVfsChange() { val virtualFileUrlWatcher = VirtualFileUrlWatcher.getInstance(project) @@ -81,9 +83,13 @@ internal class FileReferenceInWorkspaceEntityUpdater(private val project: Projec val oldModuleId = ModuleId(oldModuleName) val workspaceModel = WorkspaceModel.getInstance(project) + val moduleEntity = workspaceModel.currentSnapshot.resolve(oldModuleId) val description = "Update module name when iml file is renamed" if (moduleEntity != null) { + val oldModuleDir = PathUtil.getParentPath(oldUrl) + if (moduleEntity.entitySource.virtualFileUrl?.url != oldModuleDir) + return workspaceModel.updateProjectModel(description) { diff -> diff.modifyModuleEntity(moduleEntity) { this.name = newModuleName } } @@ -95,7 +101,7 @@ internal class FileReferenceInWorkspaceEntityUpdater(private val project: Projec } } } - + private fun collectChangedModuleStorePathsAfterDirectoryRename(event: VFilePropertyChangeEvent, changedModuleStorePaths: ArrayList>) { if (!event.file.isDirectory || event.requestor is StateStorage || event.propertyName != VirtualFile.PROP_NAME) return @@ -123,4 +129,4 @@ internal class FileReferenceInWorkspaceEntityUpdater(private val project: Projec } private fun String.isImlFile() = Files.getFileExtension(this) == ModuleFileType.DEFAULT_EXTENSION -} \ No newline at end of file +}