Skip to content
Merged
Show file tree
Hide file tree
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
@@ -1,9 +1,11 @@
package com.github.monosoul.git.updateindex.extended

import com.github.monosoul.git.updateindex.extended.changes.view.SkippedWorktreeFilesCache
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.ChangesViewManager
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.vcsUtil.VcsUtil
Expand All @@ -29,6 +31,11 @@ class ExtendedUpdateIndexTask(
files.forEach(vcsDirtyScopeManager::fileDirty)
}
}

logger.debug("Git update index command executed, refreshing Changes view")

SkippedWorktreeFilesCache.getInstance(project).clear()
ChangesViewManager.getInstanceEx(project).scheduleRefresh()
}

private fun GitLineHandler.runAndLog() = run(Git.getInstance()::runCommand)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.monosoul.git.updateindex.extended.changes.view

import com.github.monosoul.git.updateindex.extended.changes.view.Constants.SKIPPED_FILE
import com.intellij.externalProcessAuthHelper.AuthenticationMode.NONE
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.ProjectLevelVcsManager
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.VcsRoot
import com.intellij.vcsUtil.VcsUtil
import git4idea.GitUtil
import git4idea.commands.Git
import git4idea.commands.GitCommand.LS_FILES
import git4idea.commands.GitCommandResult
import git4idea.commands.GitLineHandler
import org.jetbrains.annotations.Blocking

@Blocking
fun getSkippedWorktreeFiles(project: Project): List<FilePath> {
val vcsManager = ProjectLevelVcsManager.getInstance(project) ?: return emptyList()

return vcsManager.allVcsRoots.map(VcsRoot::getPath).map { vcsRoot ->
GitLineHandler(project, vcsRoot, LS_FILES).apply {
addParameters("-v")
ignoreAuthenticationMode = NONE
}.let(Git.getInstance()::runCommand).mapOrThrow { result ->
result.filter {
it.startsWith(SKIPPED_FILE)
}.map {
it.removePrefix("$SKIPPED_FILE ")
}.map {
VcsUtil.getFilePath(vcsRoot, GitUtil.unescapePath(it))
}
}
}.flatten()
}

@Throws(VcsException::class)
private fun <T> GitCommandResult.mapOrThrow(mapper: (List<String>) -> T): T {
throwOnError()

return mapper(output)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@ import com.github.monosoul.git.updateindex.extended.changes.view.Constants.PROPE
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.ChangesViewModifier
import com.intellij.openapi.vcs.changes.ui.ChangesViewModelBuilder

class SkippedWorktreeChangesViewModifier(private val project: Project) : ChangesViewModifier {

private val logger = logger<SkippedWorktreeChangesViewModifier>()

override fun modifyTreeModelBuilder(modelBuilder: ChangesViewModelBuilder) {
val showSkippedTree = PropertiesComponent.getInstance().getBoolean(PROPERTY, false)
if (!showSkippedTree) {
logger.debug { "Show skipped files is turned off. Doing nothing." }
return
}

val skippedFiles = ProgressManager.getInstance().run(GetSkippedWorktreeFilesTask(project))
val cache = SkippedWorktreeFilesCache.getInstance(project)
val skippedFiles = cache.getOrLoad()

if (skippedFiles != null && skippedFiles.isNotEmpty()) {
logger.debug { "Skipped files: $skippedFiles" }

logger.debug { "Skipped files: $skippedFiles" }
val rootNode = ChangesBrowserSkippedWorktreeNode(project, skippedFiles)
modelBuilder.insertSubtreeRoot(rootNode)
modelBuilder.insertFilesIntoNode(skippedFiles.mapNotNull { it.virtualFile }, rootNode)
}
}

val rootNode = ChangesBrowserSkippedWorktreeNode(project, skippedFiles)
modelBuilder.insertSubtreeRoot(rootNode)
modelBuilder.insertFilesIntoNode(skippedFiles.mapNotNull { it.virtualFile }, rootNode)
companion object {
private val logger = logger<SkippedWorktreeChangesViewModifier>()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.github.monosoul.git.updateindex.extended.changes.view

import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.ChangesViewManager
import com.intellij.platform.ide.progress.withBackgroundProgress
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicReference

@Service(Service.Level.PROJECT)
class SkippedWorktreeFilesCache(private val project: Project) {

@OptIn(ExperimentalCoroutinesApi::class)
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO.limitedParallelism(1))
private val cachedFiles = AtomicReference<List<FilePath>?>(null)

init {
Disposer.register(project) { scope.cancel() }
}

fun getOrLoad(): List<FilePath>? {
val cached = cachedFiles.get()
if (cached != null) {
return cached
}

scope.launch {
try {
val files = withBackgroundProgress(project, "Getting Skipped Files", cancellable = false) {
getSkippedWorktreeFiles(project)
}
cachedFiles.set(files)
ChangesViewManager.getInstanceEx(project).scheduleRefresh()
} catch (e: Exception) {
logger.warn("Failed to load skipped worktree files", e)
}
}

return null
}

fun clear() {
cachedFiles.set(null)
}

/**
* For testing purposes: set cached files directly without async loading
*/
internal fun setCachedFiles(files: List<FilePath>) {
cachedFiles.set(files)
}

companion object {
private val logger = logger<SkippedWorktreeFilesCache>()

fun getInstance(project: Project): SkippedWorktreeFilesCache = project.service()
}
}
3 changes: 3 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<projectService
serviceImplementation="com.github.monosoul.git.updateindex.extended.support.PresentationUpdater"/>

<projectService
serviceImplementation="com.github.monosoul.git.updateindex.extended.changes.view.SkippedWorktreeFilesCache"/>

<vcs.changes.changesViewModifier
implementation="com.github.monosoul.git.updateindex.extended.changes.view.SkippedWorktreeChangesViewModifier"/>
</extensions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package com.github.monosoul.git.updateindex.extended

import com.github.monosoul.git.updateindex.extended.ExtendedUpdateIndexTaskTest.FilesAndCommandArgumentsSource.NoVcsRoot
import com.github.monosoul.git.updateindex.extended.ExtendedUpdateIndexTaskTest.FilesAndCommandArgumentsSource.WithVcsRoot
import com.github.monosoul.git.updateindex.extended.changes.view.SkippedWorktreeFilesCache
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.application.ApplicationManager.setApplication
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.util.Disposer.dispose
import com.intellij.openapi.vcs.ProjectLevelVcsManager
import com.intellij.openapi.vcs.changes.ChangesViewEx
import com.intellij.openapi.vcs.changes.ChangesViewI
import com.intellij.openapi.vcs.changes.ChangesViewManager
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager
import com.intellij.openapi.vfs.VirtualFile
import git4idea.commands.Git
Expand All @@ -18,6 +22,7 @@ import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyAll
import io.mockk.verifyOrder
Expand Down Expand Up @@ -56,6 +61,11 @@ internal class ExtendedUpdateIndexTaskTest {
@MockK
private lateinit var gitCommandResult: GitCommandResult

@MockK(relaxUnitFun = true)
private lateinit var changesViewManager: ChangesViewManager

private lateinit var cache: SkippedWorktreeFilesCache

@BeforeEach
fun setUp() {
parent = TestDisposable()
Expand All @@ -68,6 +78,12 @@ internal class ExtendedUpdateIndexTaskTest {
project.registerService(vcsManager, parent)
project.registerService(dirtyScopeManager, parent)
project.registerService(updateIndexLineHandlerFactory, parent)
project.registerService<ChangesViewI>(changesViewManager, parent)
project.registerService<ChangesViewEx>(changesViewManager, parent)

// Register the cache service with a spy to verify clear() calls
cache = spyk(SkippedWorktreeFilesCache(project))
project.registerService(SkippedWorktreeFilesCache::class.java, cache, parent)

every { updateIndexLineHandlerFactory.invoke(any(), any(), any()) } returns gitLineHandler
every { git.runCommand(any<GitLineHandler>()) } returns gitCommandResult
Expand Down Expand Up @@ -98,6 +114,10 @@ internal class ExtendedUpdateIndexTaskTest {
gitLineHandler wasNot Called
dirtyScopeManager wasNot Called
}
verify {
cache.clear()
changesViewManager.scheduleRefresh()
}
}

@ParameterizedTest
Expand Down Expand Up @@ -127,6 +147,10 @@ internal class ExtendedUpdateIndexTaskTest {
verify(exactly = files.size) {
dirtyScopeManager.fileDirty(any<VirtualFile>())
}
verify {
cache.clear()
changesViewManager.scheduleRefresh()
}
}

@ParameterizedTest
Expand Down Expand Up @@ -159,6 +183,10 @@ internal class ExtendedUpdateIndexTaskTest {
verify(exactly = files.size) {
dirtyScopeManager.fileDirty(any<VirtualFile>())
}
verify {
cache.clear()
changesViewManager.scheduleRefresh()
}
}

private sealed class FilesAndCommandArgumentsSource(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import git4idea.config.GitExecutableManager
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verifyAll
import kotlinx.coroutines.runBlocking
import org.apache.commons.lang3.RandomStringUtils
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
Expand All @@ -46,7 +46,7 @@ import kotlin.random.Random
import kotlin.random.nextInt

@ExtendWith(MockKExtension::class)
internal class GetSkippedWorktreeFilesTaskTest {
internal class GetSkippedWorktreeFilesKtTest {

private lateinit var parent: TestDisposable
private lateinit var application: MockApplication
Expand All @@ -70,8 +70,6 @@ internal class GetSkippedWorktreeFilesTaskTest {
@MockK
private lateinit var git: Git

private lateinit var task: GetSkippedWorktreeFilesTask

@BeforeEach
fun setUp() {
parent = TestDisposable()
Expand All @@ -95,8 +93,6 @@ internal class GetSkippedWorktreeFilesTaskTest {
vcsRoot = VcsRoot(vcs, MockVirtualFile(true, "vcsRoot"))

every { vcsManager.allVcsRoots } returns arrayOf(vcsRoot)

task = GetSkippedWorktreeFilesTask(project)
}

@AfterEach
Expand All @@ -112,8 +108,9 @@ internal class GetSkippedWorktreeFilesTaskTest {
fun `should do nothing if the result doesn't contain skipped files`(result: GitCommandResult) {
every { git.runCommand(any<GitLineHandler>()) } returns result

task.run(mockk())
val actual = task.result
val actual = runBlocking {
getSkippedWorktreeFiles(project)
}

expectThat(actual).isEmpty()

Expand All @@ -125,8 +122,9 @@ internal class GetSkippedWorktreeFilesTaskTest {
fun `should return a list of skipped files`(result: GitCommandResult) {
every { git.runCommand(any<GitLineHandler>()) } returns result

task.run(mockk())
val actual = task.result
val actual = runBlocking {
getSkippedWorktreeFiles(project)
}

expectThat(actual)
.hasSize(result.output.size)
Expand All @@ -141,10 +139,10 @@ internal class GetSkippedWorktreeFilesTaskTest {
fun `should throw an exception in case of an error`(result: GitCommandResult) {
every { git.runCommand(any<GitLineHandler>()) } returns result

task.run(mockk())

expectThrows<VcsException> {
task.result
runBlocking {
getSkippedWorktreeFiles(project)
}
}.message isEqualTo result.errorOutputAsJoinedString

verifyGitCall()
Expand Down
Loading