Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.block.wt.progress.ProgressScope
import com.block.wt.progress.asScope
import com.block.wt.ui.Notifications
import com.block.wt.util.PathHelper
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.ide.projectView.ProjectView
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.WriteAction
Expand All @@ -15,9 +17,14 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.vcs.ProjectLevelVcsManager
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.ui.EditorNotifications
import git4idea.repo.GitRepositoryManager
import com.intellij.openapi.progress.runBlockingCancellable
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -78,44 +85,78 @@ class SymlinkSwitchService(
}

// Phase 3: VFS refresh (20%–45%)
// Re-resolve projectRoot AFTER symlink swap so VFS picks up the new target
// Poke VFS at all relevant paths so it re-reads the symlink entry and rescans the new target tree
scope?.fraction(0.20)
scope?.text("Refreshing file system...")
indicator?.text = "Refreshing file system..."
val projectRoot = project.basePath?.let { VfsUtil.findFileByIoFile(java.io.File(it), true) }
val localFileSystem = LocalFileSystem.getInstance()
val refreshPaths = buildRefreshPaths(symlinkPath, newTarget, config.mainRepoRoot)
withContext(Dispatchers.IO) {
localFileSystem.refreshNioFiles(refreshPaths, false, true, null)
}
val projectRoot = localFileSystem.refreshAndFindFileByNioFile(symlinkPath)
?: project.basePath?.let { localFileSystem.refreshAndFindFileByNioFile(Path.of(it)) }
if (projectRoot != null) {
projectRoot.refresh(false, true)
VfsUtil.markDirtyAndRefresh(false, true, true, projectRoot)
// Refresh .git so git4idea reads the new gitdir pointer in Phase 5
projectRoot.findChild(".git")?.let { VfsUtil.markDirtyAndRefresh(false, true, true, it) }
}

// Phase 4: Reload editors (45%–65%)
// Must happen AFTER VFS refresh so documents reload from new worktree content
// Phase 4: Reload editors and project state (45%–65%)
// Propagate VFS changes into documents, PSI, Project View, and daemon
scope?.fraction(0.45)
scope?.text("Reloading editors...")
indicator?.text = "Reloading editors..."
scope?.text("Reloading editors and project state...")
indicator?.text = "Reloading editors and project state..."
app.invokeAndWait({
val fdm = FileDocumentManager.getInstance()
val psiDocManager = PsiDocumentManager.getInstance(project)
val psiManager = PsiManager.getInstance(project)
val openFiles = FileEditorManager.getInstance(project).openFiles

WriteAction.run<Nothing> {
val fdm = FileDocumentManager.getInstance()
for (openFile in FileEditorManager.getInstance(project).openFiles) {
// Force VFS to re-read this file's content from disk (not from cache)
for (openFile in openFiles) {
openFile.refresh(false, false)
}
for (openFile in openFiles) {
val doc = fdm.getCachedDocument(openFile) ?: continue
fdm.reloadFromDisk(doc)
psiDocManager.getCachedPsiFile(doc)?.let(psiManager::reloadFromDisk)
}
}

psiDocManager.commitAllDocuments()
if (openFiles.isNotEmpty()) {
psiDocManager.reparseFiles(openFiles.toList(), true)
}
psiManager.dropPsiCaches()

// Fire synthetic roots-changed event so indexer and module system see the new tree
WriteAction.run<Nothing> {
ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true)
}

ProjectView.getInstance(project).refresh()
EditorNotifications.getInstance(project).updateAllNotifications()
DaemonCodeAnalyzer.getInstance(project).restart("wt symlink switch")
}, ModalityState.defaultModalityState())

// Phase 5: Git state (65%–85%)
// Re-set VCS mappings to force git4idea to re-read the .git file's new gitdir pointer
scope?.fraction(0.65)
scope?.text("Updating git state...")
indicator?.text = "Updating git state..."
app.invokeAndWait({
val vcsManager = ProjectLevelVcsManager.getInstance(project)
vcsManager.setDirectoryMappings(vcsManager.directoryMappings.toList())
}, ModalityState.defaultModalityState())
withContext(Dispatchers.IO) {
val repos = GitRepositoryManager.getInstance(project).repositories
for (repo in repos) {
repo.update()
}
}
VcsDirtyScopeManager.getInstance(project).markEverythingDirty()
projectRoot?.let { VcsDirtyScopeManager.getInstance(project).rootDirty(it) }

// Phase 6: Refresh list (85%–100%)
scope?.fraction(0.85)
Expand All @@ -138,6 +179,25 @@ class SymlinkSwitchService(
}
}

private fun buildRefreshPaths(
symlinkPath: Path,
newTarget: Path,
mainRepoRoot: Path?,
): Set<Path> = buildSet {
add(symlinkPath)
add(symlinkPath.resolve(".git"))
add(newTarget)
add(newTarget.resolve(".git"))
project.basePath?.let { basePath ->
add(Path.of(basePath))
add(Path.of(basePath).resolve(".git"))
}
mainRepoRoot?.let {
add(it)
add(it.resolve(".git"))
}
}

companion object {
fun getInstance(project: Project): SymlinkSwitchService = project.service()
}
Expand Down
Loading