mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-162494 git: do not load git content from EditorHistoryManager
Potemking progress during service initialization may deadlock during event pumping. GitOrigin-RevId: b405124f5fb7644d5dd9c5e10a2caaa935fc5665
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c015b73c0d
commit
4d8680e81c
@@ -179,7 +179,7 @@ private fun headContentBytes(project: Project, root: VirtualFile, status: GitFil
|
||||
@Throws(VcsException::class)
|
||||
private fun stagedContentFile(project: Project, root: VirtualFile, status: GitFileStatus): VirtualFile {
|
||||
val filePath = status.path(ContentVersion.STAGED)
|
||||
return GitIndexFileSystemRefresher.getInstance(project).getFile(root, filePath)
|
||||
return GitIndexFileSystemRefresher.getInstance(project).createFile(root, filePath)
|
||||
?: throw VcsException(GitBundle.message("stage.diff.staged.content.exception.message", status.path))
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class GitStageLineStatusTrackerProvider : LineStatusTrackerContentLoader {
|
||||
val repository = GitRepositoryManager.getInstance(project).getRepositoryForFile(file) ?: return null
|
||||
|
||||
val indexFileRefresher = GitIndexFileSystemRefresher.getInstance(project)
|
||||
val indexFile = indexFileRefresher.getFile(repository.root, status.path(ContentVersion.STAGED)) ?: return null
|
||||
val indexFile = indexFileRefresher.createFile(repository.root, status.path(ContentVersion.STAGED)) ?: return null
|
||||
val indexDocument = runReadAction { FileDocumentManager.getInstance().getDocument(indexFile) } ?: return null
|
||||
indexDocument.putUserData(LineStatusTrackerBase.SEPARATE_UNDO_STACK, Registry.`is`("git.stage.separate.undo.stack"))
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package git4idea.index.actions
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor
|
||||
@@ -11,11 +12,18 @@ import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vcs.impl.LineStatusTrackerManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import com.intellij.util.OpenSourceUtil
|
||||
import git4idea.GitDisposable
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.index.*
|
||||
import git4idea.index.vfs.GitIndexFileSystemRefresher
|
||||
import git4idea.index.vfs.GitIndexVirtualFile
|
||||
import git4idea.index.vfs.filePath
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
abstract class GitStageShowVersionAction(private val showStaged: Boolean) : DumbAwareAction() {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
@@ -39,21 +47,33 @@ abstract class GitStageShowVersionAction(private val showStaged: Boolean) : Dumb
|
||||
val project = e.project ?: return
|
||||
val sourceFile = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return
|
||||
val root = getRoot(project, sourceFile) ?: return
|
||||
val targetFile = if (showStaged) {
|
||||
GitIndexFileSystemRefresher.getInstance(project).getFile(root, sourceFile.filePath())
|
||||
val caret = e.getData(CommonDataKeys.CARET)
|
||||
|
||||
if (showStaged) {
|
||||
GitDisposable.getInstance(project).coroutineScope.childScope("Show staged file").async {
|
||||
val filePath = sourceFile.filePath()
|
||||
withBackgroundProgress(project, GitBundle.message("stage.vfs.read.process", filePath.name), true) {
|
||||
val targetFile = GitIndexFileSystemRefresher.getInstance(project).createFile(root, filePath) ?: return@withBackgroundProgress
|
||||
withContext(Dispatchers.EDT) {
|
||||
navigateToFile(project, targetFile, sourceFile, caret)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sourceFile.filePath().virtualFile
|
||||
} ?: return
|
||||
val targetFile = sourceFile.filePath().virtualFile ?: return
|
||||
navigateToFile(project, targetFile, sourceFile, caret)
|
||||
}
|
||||
}
|
||||
|
||||
val caret = e.getData(CommonDataKeys.CARET)
|
||||
private fun navigateToFile(project: Project, targetFile: VirtualFile, sourceFile: VirtualFile, caret: Caret?) {
|
||||
if (caret == null) {
|
||||
OpenSourceUtil.navigate(OpenFileDescriptor(project, targetFile))
|
||||
return
|
||||
}
|
||||
|
||||
val targetPosition = getTargetPosition(project, sourceFile, targetFile, caret)
|
||||
OpenSourceUtil.navigate(OpenFileDescriptor(project, targetFile, targetPosition.line, targetPosition.column))
|
||||
else {
|
||||
val targetPosition = getTargetPosition(project, sourceFile, targetFile, caret)
|
||||
OpenSourceUtil.navigate(OpenFileDescriptor(project, targetFile, targetPosition.line, targetPosition.column))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTargetPosition(project: Project, sourceFile: VirtualFile, targetFile: VirtualFile, caret: Caret): LogicalPosition {
|
||||
|
||||
@@ -15,9 +15,12 @@ class GitIndexFileSystem : VirtualFileSystem(), NonPhysicalFileSystem {
|
||||
override fun isReadOnly(): Boolean = false
|
||||
|
||||
override fun findFileByPath(path: String): VirtualFile? {
|
||||
val (project, virtualFile, filePath) = GitIndexVirtualFile.decode(path) ?: return null
|
||||
val (project, root, filePath) = GitIndexVirtualFile.decode(path) ?: return null
|
||||
|
||||
return GitIndexFileSystemRefresher.getInstance(project).getFile(virtualFile, filePath)
|
||||
// we do not check actual content here
|
||||
// the returned file is valid, but may stop being such after the first read access
|
||||
// this is a tradeoff with making EditorHistoryManager load every file that was ever loaded
|
||||
return GitIndexFileSystemRefresher.getInstance(project).findFile(root, filePath)
|
||||
}
|
||||
|
||||
override fun refreshAndFindFileByPath(path: String): VirtualFile? = findFileByPath(path)
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
||||
import com.intellij.openapi.progress.util.PotemkinProgress
|
||||
@@ -24,7 +23,6 @@ import com.intellij.openapi.project.ProjectCloseListener
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.util.ThrowableComputable
|
||||
import com.intellij.openapi.vcs.FilePath
|
||||
import com.intellij.openapi.vcs.VcsException
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
@@ -36,11 +34,11 @@ import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent.REFRESH_REQUESTOR
|
||||
import com.intellij.util.LocalTimeCounter
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.messages.MessageBusConnection
|
||||
import com.intellij.vcs.log.Hash
|
||||
import com.intellij.vcs.log.impl.HashImpl
|
||||
import com.intellij.vcsUtil.VcsFileUtil
|
||||
import com.intellij.vcsUtil.VcsUtil
|
||||
import git4idea.commands.Git
|
||||
import git4idea.commands.GitCommand
|
||||
import git4idea.commands.GitLineHandler
|
||||
@@ -53,7 +51,6 @@ import org.jetbrains.annotations.NonNls
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.Throws
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
class GitIndexFileSystemRefresher(private val project: Project) : Disposable {
|
||||
@@ -90,44 +87,53 @@ class GitIndexFileSystemRefresher(private val project: Project) : Disposable {
|
||||
connection.subscribe(EncodingManagerListener.ENCODING_MANAGER_CHANGES, MyEncodingManagerListener())
|
||||
}
|
||||
|
||||
fun getFile(root: VirtualFile, filePath: FilePath): GitIndexVirtualFile? {
|
||||
try {
|
||||
return cache.get(Key(root, filePath))
|
||||
}
|
||||
catch (e: Exception) {
|
||||
val cause = e.cause
|
||||
if (cause is ProcessCanceledException) {
|
||||
throw cause
|
||||
}
|
||||
throw e
|
||||
}
|
||||
fun findFile(
|
||||
root: VirtualFile, filePath: FilePath,
|
||||
): GitIndexVirtualFile? {
|
||||
val indexFile = cache.get(Key(root, filePath))
|
||||
|
||||
if (indexFile.isValid()) return indexFile
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createIndexVirtualFile(root: VirtualFile, filePath: FilePath): GitIndexVirtualFile? {
|
||||
if (isShutDown) return null
|
||||
@RequiresBackgroundThread
|
||||
fun createFile(
|
||||
root: VirtualFile, filePath: FilePath,
|
||||
): GitIndexVirtualFile? {
|
||||
val indexFile = cache.get(Key(root, filePath))
|
||||
|
||||
if (indexFile.data != null) return indexFile
|
||||
|
||||
val stagedFile = readMetadataFromGit(root, filePath) ?: return null
|
||||
val length = readLengthFromGit(root, stagedFile.blobHash)
|
||||
val indexFile = GitIndexVirtualFile(project, root, filePath, stagedFile.hash(), length, stagedFile.isExecutable)
|
||||
OutsidersPsiFileSupport.markFile(indexFile, filePath)
|
||||
|
||||
indexFile.setInitialData(stagedFile.hash(), length, stagedFile.isExecutable)
|
||||
|
||||
return indexFile
|
||||
}
|
||||
|
||||
private fun createIndexVirtualFile(key: Key): GitIndexVirtualFile? {
|
||||
if (!ApplicationManager.getApplication().isDispatchThread) return createIndexVirtualFile(key.root, key.filePath)
|
||||
if (isShutDown) return null
|
||||
|
||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously(ThrowableComputable {
|
||||
createIndexVirtualFile(key.root, key.filePath)
|
||||
}, GitBundle.message("stage.vfs.read.process", key.filePath.name), false, project)
|
||||
val indexFile = GitIndexVirtualFile(project, key.root, key.filePath)
|
||||
OutsidersPsiFileSupport.markFile(indexFile, key.filePath)
|
||||
return indexFile
|
||||
}
|
||||
|
||||
fun refresh(condition: (GitIndexVirtualFile) -> Boolean) {
|
||||
val filesToRefresh = cache.asMap().values.filter(condition)
|
||||
val filesToRefresh = cache.asMap().values
|
||||
.filter { it.data != null }
|
||||
.filter(condition)
|
||||
if (filesToRefresh.isEmpty()) return
|
||||
refresh(filesToRefresh)
|
||||
refreshImpl(filesToRefresh)
|
||||
}
|
||||
|
||||
private fun refresh(filesToRefresh: List<GitIndexVirtualFile>) {
|
||||
fun initialRefresh(filesToRefresh: List<GitIndexVirtualFile>) {
|
||||
refreshImpl(filesToRefresh)
|
||||
}
|
||||
|
||||
private fun refreshImpl(filesToRefresh: List<GitIndexVirtualFile>) {
|
||||
if (isShutDown) return
|
||||
|
||||
LOG.debug("Starting async refresh for ${filesToRefresh.joinToString { it.path }}")
|
||||
@@ -151,19 +157,21 @@ class GitIndexFileSystemRefresher(private val project: Project) : Disposable {
|
||||
}
|
||||
|
||||
private fun readFromGit(file: GitIndexVirtualFile): IndexFileData? {
|
||||
val (oldHash, oldModificationStamp) = runReadAction { Pair(file.hash, file.modificationStamp) }
|
||||
return readFromGit(file, oldHash, oldModificationStamp)
|
||||
val (oldData, oldModificationStamp) = runReadAction { Pair(file.data, file.modificationStamp) }
|
||||
return readFromGit(file, oldData, oldModificationStamp)
|
||||
}
|
||||
|
||||
private fun readFromGit(file: GitIndexVirtualFile,
|
||||
oldHash: Hash?,
|
||||
oldModificationStamp: Long): IndexFileData? {
|
||||
private fun readFromGit(
|
||||
file: GitIndexVirtualFile,
|
||||
oldData: GitIndexVirtualFile.CachedData?,
|
||||
oldModificationStamp: Long,
|
||||
): IndexFileData? {
|
||||
val stagedFile = readMetadataFromGit(file.root, file.filePath)
|
||||
val newHash = stagedFile?.hash()
|
||||
if (oldHash != newHash) {
|
||||
if (oldData == null || oldData.hash != newHash) {
|
||||
val newLength = if (stagedFile != null) readLengthFromGit(file.root, stagedFile.blobHash) else 0
|
||||
LOG.debug("Preparing refresh for $file")
|
||||
return IndexFileData(file, oldHash, newHash, file.length, newLength, stagedFile?.isExecutable ?: false, oldModificationStamp)
|
||||
return IndexFileData(file, oldData, newHash, file.length, newLength, stagedFile?.isExecutable ?: false, oldModificationStamp)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -181,11 +189,11 @@ class GitIndexFileSystemRefresher(private val project: Project) : Disposable {
|
||||
val event = VFileContentChangeEvent(requestor, file, file.modificationStamp, newModStamp)
|
||||
ApplicationManager.getApplication().messageBus.syncPublisher(VirtualFileManager.VFS_CHANGES).before(listOf(event))
|
||||
|
||||
val oldHash = file.hash
|
||||
val oldData = file.data
|
||||
val oldModificationStamp = file.modificationStamp
|
||||
|
||||
val applyChanges = computeUnderPotemkinProgress(project, GitBundle.message("stage.vfs.write.process", file.name)) {
|
||||
val indexFileData = readFromGit(file, oldHash, oldModificationStamp)
|
||||
val indexFileData = readFromGit(file, oldData, oldModificationStamp)
|
||||
if (indexFileData != null) {
|
||||
LOG.info("Detected memory-disk conflict in $file")
|
||||
return@computeUnderPotemkinProgress {
|
||||
@@ -263,7 +271,7 @@ class GitIndexFileSystemRefresher(private val project: Project) : Disposable {
|
||||
|
||||
@JvmStatic
|
||||
fun refreshVirtualFiles(project: Project, paths: Collection<VirtualFile>) {
|
||||
refreshFilePaths(project, paths.map(VcsUtil::getFilePath))
|
||||
refreshFilePaths(project, paths.map { it.filePath() })
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -292,16 +300,19 @@ class GitIndexFileSystemRefresher(private val project: Project) : Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private inner class IndexFileData(private val file: GitIndexVirtualFile,
|
||||
private val oldHash: Hash?,
|
||||
private val newHash: Hash?,
|
||||
oldLength: Long,
|
||||
private val newLength: Long,
|
||||
private val newExecutable: Boolean,
|
||||
oldModificationStamp: Long) {
|
||||
private inner class IndexFileData(
|
||||
private val file: GitIndexVirtualFile,
|
||||
private val oldData: GitIndexVirtualFile.CachedData?,
|
||||
private val newHash: Hash?,
|
||||
oldLength: Long,
|
||||
private val newLength: Long,
|
||||
private val newExecutable: Boolean,
|
||||
oldModificationStamp: Long,
|
||||
) {
|
||||
val event = VFileContentChangeEvent(REFRESH_REQUESTOR, file, oldModificationStamp, -1, 0, 0, oldLength, newLength)
|
||||
|
||||
fun isOutdated() = file.hash != oldHash
|
||||
fun isOutdated() = file.data != null &&
|
||||
file.data?.hash != oldData?.hash
|
||||
|
||||
fun apply() {
|
||||
LOG.debug("Refreshing $file")
|
||||
|
||||
@@ -24,18 +24,19 @@ import git4idea.i18n.GitBundle
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import kotlin.Throws
|
||||
|
||||
class GitIndexVirtualFile(private val project: Project,
|
||||
val root: VirtualFile,
|
||||
val filePath: FilePath,
|
||||
@Volatile internal var hash: Hash?,
|
||||
@Volatile internal var length: Long,
|
||||
@Volatile internal var isExecutable: Boolean) : VirtualFile(), VirtualFilePathWrapper {
|
||||
class GitIndexVirtualFile(
|
||||
private val project: Project,
|
||||
val root: VirtualFile,
|
||||
val filePath: FilePath,
|
||||
) : VirtualFile(), VirtualFilePathWrapper {
|
||||
init {
|
||||
putUserData(FileDocumentManagerBase.TRACK_NON_PHYSICAL, true)
|
||||
}
|
||||
|
||||
@Volatile
|
||||
internal var data: CachedData? = null // null: the file was not loaded yet
|
||||
|
||||
@Volatile
|
||||
private var modificationStamp = LocalTimeCounter.currentTime()
|
||||
|
||||
@@ -44,13 +45,14 @@ class GitIndexVirtualFile(private val project: Project,
|
||||
override fun getChildren(): Array<VirtualFile> = EMPTY_ARRAY
|
||||
override fun isWritable(): Boolean = true
|
||||
override fun isDirectory(): Boolean = false
|
||||
override fun isValid(): Boolean = hash != null && !project.isDisposed
|
||||
override fun isValid(): Boolean = !project.isDisposed && (data == null || data?.hash != null)
|
||||
override fun getName(): String = filePath.name
|
||||
override fun getPresentableName(): String = GitBundle.message("stage.vfs.presentable.file.name", filePath.name)
|
||||
override fun getPath(): String = encode(project, root, filePath)
|
||||
override fun getPresentablePath(): String = filePath.path
|
||||
override fun enforcePresentableName(): Boolean = true
|
||||
override fun getLength(): Long = length
|
||||
internal val isExecutable: Boolean get() = data?.isExecutable ?: false
|
||||
override fun getLength(): Long = data?.length ?: 0
|
||||
override fun getTimeStamp(): Long = 0
|
||||
override fun getModificationStamp(): Long = modificationStamp
|
||||
override fun getFileType(): FileType = filePath.virtualFile?.fileType ?: super.getFileType()
|
||||
@@ -58,17 +60,18 @@ class GitIndexVirtualFile(private val project: Project,
|
||||
LOG.error("Refreshing index files is not supported (called for $this). Use GitIndexFileSystemRefresher to refresh.")
|
||||
}
|
||||
|
||||
internal fun setInitialData(newHash: Hash?, newLength: Long, newExecutable: Boolean) {
|
||||
data = CachedData(newHash, newLength, newExecutable)
|
||||
}
|
||||
|
||||
@RequiresWriteLock
|
||||
internal fun setDataFromRefresh(newHash: Hash?, newLength: Long, newExecutable: Boolean) {
|
||||
hash = newHash
|
||||
length = newLength
|
||||
isExecutable = newExecutable
|
||||
data = CachedData(newHash, newLength, newExecutable)
|
||||
}
|
||||
|
||||
@RequiresWriteLock
|
||||
internal fun setDataFromWrite(newHash: Hash, newLength: Long, newModificationStamp: Long) {
|
||||
hash = newHash
|
||||
length = newLength
|
||||
data = CachedData(newHash, newLength, isExecutable)
|
||||
modificationStamp = newModificationStamp
|
||||
}
|
||||
|
||||
@@ -91,6 +94,10 @@ class GitIndexVirtualFile(private val project: Project,
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun contentsToByteArray(): ByteArray {
|
||||
if (data == null) {
|
||||
GitIndexFileSystemRefresher.getInstance(project).initialRefresh(listOf(this))
|
||||
}
|
||||
|
||||
try {
|
||||
if (ApplicationManager.getApplication().isDispatchThread) {
|
||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously(ThrowableComputable<ByteArray, IOException> {
|
||||
@@ -153,6 +160,8 @@ class GitIndexVirtualFile(private val project: Project,
|
||||
return path.substringAfterLast(SEPARATOR).replace('/', File.separatorChar)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class CachedData(val hash: Hash?, val length: Long, val isExecutable: Boolean)
|
||||
}
|
||||
|
||||
internal fun VirtualFile.filePath(): FilePath {
|
||||
|
||||
@@ -102,7 +102,7 @@ class GitStageTrackerTest : GitSingleRepoTest() {
|
||||
assertTrue(trackerState().isEmpty())
|
||||
|
||||
val file = projectRoot.findChild(fileName)!!
|
||||
val indexFile = project.service<GitIndexFileSystemRefresher>().getFile(projectRoot, VcsUtil.getFilePath(file))!!
|
||||
val indexFile = project.service<GitIndexFileSystemRefresher>().createFile(projectRoot, VcsUtil.getFilePath(file))!!
|
||||
val document = runReadAction { FileDocumentManager.getInstance().getDocument(indexFile)!! }
|
||||
|
||||
runWithTrackerUpdate("setText") {
|
||||
|
||||
Reference in New Issue
Block a user