mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[vcs] run ignored to excluded check under a coroutine
GitOrigin-RevId: 497999ad700156d64a9145025a3d65c9bc0eaba1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
76ba594010
commit
bda5729f05
@@ -9,6 +9,7 @@ import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.idea.ActionsBundle
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.components.Service
|
||||
@@ -16,10 +17,8 @@ import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleUtil
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.runModalTask
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -41,17 +40,17 @@ import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog
|
||||
import com.intellij.openapi.vcs.ignore.IgnoredToExcludedSynchronizerConstants.ASKED_MARK_IGNORED_FILES_AS_EXCLUDED_PROPERTY
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModel
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.platform.workspace.jps.entities.ContentRootEntity
|
||||
import com.intellij.platform.workspace.jps.entities.SourceRootEntity
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.EditorNotificationProvider
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.application
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue
|
||||
import com.intellij.util.ui.update.Update
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
@@ -59,14 +58,6 @@ import javax.swing.JComponent
|
||||
|
||||
private val LOG = logger<IgnoredToExcludedSynchronizer>()
|
||||
|
||||
private fun exclude(module: Module, dirs: Collection<VirtualFile>) {
|
||||
runInEdt {
|
||||
MarkRootsManager.modifyRoots(module, dirs.toTypedArray()) { vFile, entry ->
|
||||
entry.addExcludeFolder(vFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows [EditorNotifications] in .ignore files with a suggestion to exclude ignored directories.
|
||||
* Silently excludes them if [VcsConfiguration.MARK_IGNORED_AS_EXCLUDED] is enabled.
|
||||
@@ -74,7 +65,7 @@ private fun exclude(module: Module, dirs: Collection<VirtualFile>) {
|
||||
* Not internal service. Can be used directly in related modules.
|
||||
*/
|
||||
@Service(Service.Level.PROJECT)
|
||||
class IgnoredToExcludedSynchronizer(project: Project, cs: CoroutineScope) : FilesProcessorImpl(project, project) {
|
||||
class IgnoredToExcludedSynchronizer(project: Project, private val cs: CoroutineScope) : FilesProcessorImpl(project, project) {
|
||||
private val queue = MergingUpdateQueue("IgnoredToExcludedSynchronizer", 1000, true, null, this, null, Alarm.ThreadToUse.POOLED_THREAD)
|
||||
|
||||
init {
|
||||
@@ -173,6 +164,43 @@ class IgnoredToExcludedSynchronizer(project: Project, cs: CoroutineScope) : File
|
||||
doActionOnChosenFiles(doFilterFiles(ignoredDirs))
|
||||
}
|
||||
}
|
||||
|
||||
private val ignoredToExcludeCheckMutex = Mutex()
|
||||
|
||||
internal fun checkIgnoredToExclude() {
|
||||
cs.launch(Dispatchers.Default, CoroutineStart.UNDISPATCHED) {
|
||||
// shouldn't be run in parallel
|
||||
val canRun = ignoredToExcludeCheckMutex.tryLock()
|
||||
if (!canRun) {
|
||||
return@launch
|
||||
}
|
||||
yield()
|
||||
|
||||
try {
|
||||
val dirsToExclude = withModalProgress(project, ActionsBundle.message("action.CheckIgnoredAndNotExcludedDirectories.progress")) {
|
||||
VcsIgnoreManagerImpl.getInstanceImpl(project).awaitRefreshQueue()
|
||||
val ignoredFilePaths = ChangeListManager.getInstance(project).ignoredFilePaths
|
||||
determineIgnoredDirsToExclude(project, ignoredFilePaths)
|
||||
}
|
||||
|
||||
if (dirsToExclude.isEmpty()) {
|
||||
VcsNotifier.getInstance(project)
|
||||
.notifyMinorInfo(IGNORED_TO_EXCLUDE_NOT_FOUND, "", message("ignore.to.exclude.no.directories.found"))
|
||||
}
|
||||
else {
|
||||
val userSelectedFiles = withContext(Dispatchers.EDT) {
|
||||
selectFilesToExclude(project, dirsToExclude)
|
||||
}
|
||||
if (userSelectedFiles.isNotEmpty()) {
|
||||
markIgnoredAsExcluded(project, userSelectedFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
ignoredToExcludeCheckMutex.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun markIgnoredAsExcluded(project: Project, files: Collection<VirtualFile>) {
|
||||
@@ -184,7 +212,11 @@ private fun markIgnoredAsExcluded(project: Project, files: Collection<VirtualFil
|
||||
}
|
||||
|
||||
for ((module, ignoredDirs) in ignoredDirsByModule) {
|
||||
exclude(module!!, ignoredDirs)
|
||||
runInEdt {
|
||||
MarkRootsManager.modifyRoots(module!!, ignoredDirs.toTypedArray<VirtualFile>()) { vFile, entry ->
|
||||
entry.addExcludeFolder(vFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,25 +312,8 @@ internal class CheckIgnoredToExcludeAction : DumbAwareAction() {
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project!!
|
||||
|
||||
runModalTask(ActionsBundle.message("action.CheckIgnoredAndNotExcludedDirectories.progress"), project, true) {
|
||||
VcsIgnoreManagerImpl.getInstanceImpl(project).awaitRefreshQueue()
|
||||
val ignoredFilePaths = ChangeListManager.getInstance(project).ignoredFilePaths
|
||||
val dirsToExclude = determineIgnoredDirsToExclude(project, ignoredFilePaths)
|
||||
if (dirsToExclude.isEmpty()) {
|
||||
VcsNotifier.getInstance(project)
|
||||
.notifyMinorInfo(IGNORED_TO_EXCLUDE_NOT_FOUND, "", message("ignore.to.exclude.no.directories.found"))
|
||||
}
|
||||
else {
|
||||
application.invokeAndWait {
|
||||
val userSelectedFiles = selectFilesToExclude(project, dirsToExclude)
|
||||
if (userSelectedFiles.isNotEmpty()) {
|
||||
markIgnoredAsExcluded(project, userSelectedFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val project = e.project ?: return
|
||||
project.service<IgnoredToExcludedSynchronizer>().checkIgnoredToExclude()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class GitUntrackedFilesHolder internal constructor(
|
||||
private val repoRoot: VirtualFile = repository.getRoot()
|
||||
|
||||
private val dirtyFiles = HashSet<FilePath>()
|
||||
@Volatile
|
||||
private var isEverythingDirty = true
|
||||
|
||||
@Volatile
|
||||
@@ -205,27 +206,21 @@ class GitUntrackedFilesHolder internal constructor(
|
||||
* Queries Git to check the status of [dirtyFiles] and moves them to [untrackedFiles].
|
||||
*/
|
||||
private suspend fun doUpdate() {
|
||||
val (nothingToDo, dirt) = acquireDirt()
|
||||
if (nothingToDo) {
|
||||
val dirtyScope = acquireDirt()
|
||||
if (dirtyScope == null) {
|
||||
BackgroundTaskUtil.syncPublisher(project, VcsManagedFilesHolder.TOPIC).updatingModeChanged()
|
||||
return
|
||||
}
|
||||
|
||||
BackgroundTaskUtil.syncPublisher(project, GitRefreshListener.TOPIC).progressStarted()
|
||||
try {
|
||||
val everythingDirty = dirt == null || dirt.contains(VcsUtil.getFilePath(repoRoot))
|
||||
val activity = logUntrackedRefresh(project, everythingDirty)
|
||||
val (untracked, ignored) = refreshFiles(dirt)
|
||||
val activity = logUntrackedRefresh(project, dirtyScope == DirtyScope.Everything)
|
||||
val (untracked, ignored) = refreshFiles(dirtyScope)
|
||||
activity.finished()
|
||||
|
||||
val filteredUntracked = removePathsUnderOtherRoots(untracked, "unversioned")
|
||||
val filteredIgnored = removePathsUnderOtherRoots(ignored, "ignored")
|
||||
|
||||
val dirtyScope = dirt?.let {
|
||||
RecursiveFilePathSet(repoRoot.isCaseSensitive).apply {
|
||||
addAll(it)
|
||||
}
|
||||
}
|
||||
val (oldIgnored, newIgnored) = applyRefreshResult(filteredUntracked, filteredIgnored, dirtyScope)
|
||||
|
||||
BackgroundTaskUtil.syncPublisher(project, GitRefreshListener.TOPIC).repositoryUpdated(repository)
|
||||
@@ -239,55 +234,72 @@ class GitUntrackedFilesHolder internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun acquireDirt(): Pair<Boolean, List<FilePath>?> {
|
||||
synchronized(LOCK) {
|
||||
val nothingToDo = !isDirty
|
||||
if (nothingToDo) isInUpdateMode = false
|
||||
|
||||
val dirt = if (isEverythingDirty) null else dirtyFiles.toList()
|
||||
dirtyFiles.clear()
|
||||
isEverythingDirty = false
|
||||
return nothingToDo to dirt
|
||||
private fun acquireDirt(): DirtyScope? {
|
||||
return synchronized(LOCK) {
|
||||
try {
|
||||
when {
|
||||
isEverythingDirty || dirtyFiles.contains(VcsUtil.getFilePath(repoRoot)) -> {
|
||||
DirtyScope.Everything
|
||||
}
|
||||
dirtyFiles.isNotEmpty() -> {
|
||||
DirtyScope.Files(dirtyFiles.toList())
|
||||
}
|
||||
else -> {
|
||||
isInUpdateMode = false
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
dirtyFiles.clear()
|
||||
isEverythingDirty = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyRefreshResult(
|
||||
untracked: Set<FilePath>,
|
||||
ignored: Set<FilePath>,
|
||||
dirtyScope: RecursiveFilePathSet?,
|
||||
): Pair<Set<FilePath>, Set<FilePath>> {
|
||||
dirtyScope: DirtyScope,
|
||||
): UpdatedValue<Set<FilePath>> {
|
||||
synchronized(LOCK) {
|
||||
val oldIgnored = _ignoredFilesHolder.ignoredFilePaths
|
||||
|
||||
val newIgnored = RecursiveFilePathSet(repoRoot.isCaseSensitive)
|
||||
val newUntracked = RecursiveFilePathSet(repoRoot.isCaseSensitive)
|
||||
val caseSensitive = repoRoot.isCaseSensitive
|
||||
val newIgnored = RecursiveFilePathSet(caseSensitive)
|
||||
val newUntracked = RecursiveFilePathSet(caseSensitive)
|
||||
|
||||
if (dirtyScope != null) {
|
||||
val untrackedSet = untrackedFiles.toSet()
|
||||
untrackedSet.removeIf { dirtyScope.hasAncestor(it) }
|
||||
untrackedSet.addAll(untracked)
|
||||
newUntracked.addAll(untrackedSet)
|
||||
when (dirtyScope) {
|
||||
DirtyScope.Everything -> {
|
||||
newUntracked.addAll(untracked)
|
||||
newIgnored.addAll(ignored)
|
||||
}
|
||||
is DirtyScope.Files -> {
|
||||
val dirtyFiles = RecursiveFilePathSet(caseSensitive).apply {
|
||||
addAll(dirtyScope.files)
|
||||
}
|
||||
val untrackedSet = untrackedFiles.toSet()
|
||||
untrackedSet.removeIf { dirtyFiles.hasAncestor(it) }
|
||||
untrackedSet.addAll(untracked)
|
||||
newUntracked.addAll(untrackedSet)
|
||||
|
||||
for (filePath in oldIgnored) {
|
||||
if (!dirtyScope.hasAncestor(filePath)) {
|
||||
newIgnored.add(filePath)
|
||||
for (filePath in oldIgnored) {
|
||||
if (!dirtyFiles.hasAncestor(filePath)) {
|
||||
newIgnored.add(filePath)
|
||||
}
|
||||
}
|
||||
for (filePath in ignored) {
|
||||
if (!newIgnored.hasAncestor(filePath)) { // prevent storing both parent and child directories
|
||||
newIgnored.add(filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (filePath in ignored) {
|
||||
if (!newIgnored.hasAncestor(filePath)) { // prevent storing both parent and child directories
|
||||
newIgnored.add(filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
newUntracked.addAll(untracked)
|
||||
newIgnored.addAll(ignored)
|
||||
}
|
||||
|
||||
_ignoredFilesHolder.ignoredFiles.set(newIgnored)
|
||||
untrackedFiles.set(newUntracked)
|
||||
isInUpdateMode = isDirty
|
||||
return oldIgnored to _ignoredFilesHolder.ignoredFilePaths
|
||||
return UpdatedValue(oldIgnored, _ignoredFilesHolder.ignoredFilePaths)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,12 +334,16 @@ class GitUntrackedFilesHolder internal constructor(
|
||||
}
|
||||
|
||||
|
||||
private suspend fun refreshFiles(dirty: List<FilePath>?): RefreshResult {
|
||||
private suspend fun refreshFiles(dirtyScope: DirtyScope): RefreshResult {
|
||||
try {
|
||||
val withIgnored = AdvancedSettings.getBoolean("vcs.process.ignored")
|
||||
val fileStatuses = withContext(Dispatchers.IO) {
|
||||
val dirtyFiles = when (dirtyScope) {
|
||||
DirtyScope.Everything -> emptyList()
|
||||
is DirtyScope.Files -> dirtyScope.files
|
||||
}
|
||||
coroutineToIndicator {
|
||||
getFileStatus(project, repoRoot, dirty.orEmpty(), false, true, withIgnored)
|
||||
getFileStatus(project, repoRoot, dirtyFiles, false, true, withIgnored)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +425,14 @@ class GitUntrackedFilesHolder internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface DirtyScope {
|
||||
class Files(val files: List<FilePath>) : DirtyScope
|
||||
object Everything : DirtyScope
|
||||
}
|
||||
|
||||
private data class RefreshResult(
|
||||
val untracked: Set<FilePath> = emptySet(),
|
||||
val ignored: Set<FilePath> = emptySet(),
|
||||
)
|
||||
|
||||
private data class UpdatedValue<T>(val old: T, val new: T)
|
||||
|
||||
Reference in New Issue
Block a user