[lvcs] support different modes when computing differences between directories

When a single revision in the directory history is selected, differences are computed with the local directory version. This will return all the changes made in the revision, plus all the changes made afterward. This commit adds support for comparison with the next revision, to see only changes made in the revision.

IJPL-85116

GitOrigin-RevId: 8c09bfdda925c859cbb226467ebfe4b63854eaf1
This commit is contained in:
Julia Beliaeva
2024-05-22 21:33:05 +02:00
committed by intellij-monorepo-bot
parent 279fe2b4ed
commit 75ed06f746
13 changed files with 101 additions and 23 deletions

View File

@@ -827,7 +827,7 @@ f:com.intellij.openapi.command.impl.FileUndoProvider
- a:getPresentation(com.intellij.platform.lvcs.impl.ActivityItem):com.intellij.platform.lvcs.impl.ActivityPresentation
- a:getSupportedFilterKindFor(com.intellij.platform.lvcs.impl.ActivityScope):com.intellij.platform.lvcs.impl.FilterKind
- a:loadActivityList(com.intellij.platform.lvcs.impl.ActivityScope,java.lang.String):com.intellij.platform.lvcs.impl.ActivityData
- a:loadDiffData(com.intellij.platform.lvcs.impl.ActivityScope,com.intellij.platform.lvcs.impl.ActivitySelection):com.intellij.platform.lvcs.impl.ActivityDiffData
- a:loadDiffData(com.intellij.platform.lvcs.impl.ActivityScope,com.intellij.platform.lvcs.impl.ActivitySelection,com.intellij.platform.lvcs.impl.DirectoryDiffMode):com.intellij.platform.lvcs.impl.ActivityDiffData
- a:loadSingleDiff(com.intellij.platform.lvcs.impl.ActivityScope,com.intellij.platform.lvcs.impl.ActivitySelection):com.intellij.diff.chains.DiffRequestProducer
*:com.intellij.platform.lvcs.impl.ActivityScope
- *sf:Companion:com.intellij.platform.lvcs.impl.ActivityScope$Companion
@@ -953,6 +953,13 @@ f:com.intellij.platform.lvcs.impl.ChangeSetSelectionKt
- sf:getLeftRevision(com.intellij.platform.lvcs.impl.ChangeSetSelection):com.intellij.platform.lvcs.impl.RevisionId
- sf:getRightRevision(com.intellij.platform.lvcs.impl.ChangeSetSelection):com.intellij.platform.lvcs.impl.RevisionId
- sf:toChangeSetSelection(com.intellij.platform.lvcs.impl.ActivitySelection):com.intellij.platform.lvcs.impl.ChangeSetSelection
*e:com.intellij.platform.lvcs.impl.DirectoryDiffMode
- java.lang.Enum
- sf:WithLocal:com.intellij.platform.lvcs.impl.DirectoryDiffMode
- sf:WithNext:com.intellij.platform.lvcs.impl.DirectoryDiffMode
- s:getEntries():kotlin.enums.EnumEntries
- s:valueOf(java.lang.String):com.intellij.platform.lvcs.impl.DirectoryDiffMode
- s:values():com.intellij.platform.lvcs.impl.DirectoryDiffMode[]
*e:com.intellij.platform.lvcs.impl.FilterKind
- java.lang.Enum
- sf:CONTENT:com.intellij.platform.lvcs.impl.FilterKind
@@ -1066,6 +1073,7 @@ f:com.intellij.platform.lvcs.impl.ui.ActivityView$Companion
- f:showInToolWindow(com.intellij.openapi.project.Project,com.intellij.history.integration.IdeaGateway,com.intellij.platform.lvcs.impl.ActivityScope):V
*f:com.intellij.platform.lvcs.impl.ui.ActivityViewDataKeys
- sf:INSTANCE:com.intellij.platform.lvcs.impl.ui.ActivityViewDataKeys
- f:getDIRECTORY_DIFF_MODE():com.intellij.openapi.actionSystem.DataKey
- f:getSCOPE():com.intellij.openapi.actionSystem.DataKey
- f:getSELECTED_DIFFERENCES():com.intellij.openapi.actionSystem.DataKey
- f:getSELECTION():com.intellij.openapi.actionSystem.DataKey

View File

@@ -12,7 +12,7 @@ interface ActivityProvider {
fun loadActivityList(scope: ActivityScope, fileFilter: String?): ActivityData
fun filterActivityList(scope: ActivityScope, data: ActivityData, contentFilter: String?): Set<ActivityItem>?
fun loadDiffData(scope: ActivityScope, selection: ActivitySelection): ActivityDiffData?
fun loadDiffData(scope: ActivityScope, selection: ActivitySelection, diffMode: DirectoryDiffMode): ActivityDiffData?
fun loadSingleDiff(scope: ActivityScope, selection: ActivitySelection): DiffRequestProducer?
fun getSupportedFilterKindFor(scope: ActivityScope): FilterKind
@@ -23,4 +23,9 @@ interface ActivityProvider {
@ApiStatus.Experimental
enum class FilterKind {
FILE, CONTENT
}
@ApiStatus.Experimental
enum class DirectoryDiffMode {
WithLocal, WithNext
}

View File

@@ -131,9 +131,9 @@ internal class LocalHistoryActivityProvider(val project: Project, private val ga
return data.items.filterTo(mutableSetOf()) { (it is ChangeSetActivityItem) && filteredIds.contains(it.id) }
}
override fun loadDiffData(scope: ActivityScope, selection: ActivitySelection): ActivityDiffData? {
override fun loadDiffData(scope: ActivityScope, selection: ActivitySelection, diffMode: DirectoryDiffMode): ActivityDiffData? {
val changeSetSelection = selection.toChangeSetSelection() ?: return null
return facade.createDiffData(gateway, scope, changeSetSelection, USE_OLD_CONTENT)
return facade.createDiffData(gateway, scope, changeSetSelection, diffMode, USE_OLD_CONTENT)
}
override fun loadSingleDiff(scope: ActivityScope, selection: ActivitySelection): DiffRequestProducer? {

View File

@@ -45,9 +45,9 @@ abstract class ChangeSetSelectionAction : DumbAwareAction() {
val selection = activitySelection.toChangeSetSelection() ?: return
actionPerformed(project, facade, gateway, activityScope, selection)
actionPerformed(project, facade, gateway, activityScope, selection, e)
}
abstract fun actionPerformed(project: Project, facade: LocalHistoryFacade, gateway: IdeaGateway,
activityScope: ActivityScope, selection: ChangeSetSelection)
activityScope: ActivityScope, selection: ChangeSetSelection, e: AnActionEvent)
}

View File

@@ -7,6 +7,7 @@ import com.intellij.history.core.revisions.Difference
import com.intellij.history.integration.IdeaGateway
import com.intellij.history.integration.LocalHistoryBundle
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
@@ -15,9 +16,11 @@ import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction
import com.intellij.platform.lvcs.impl.ActivityScope
import com.intellij.platform.lvcs.impl.ChangeSetSelection
import com.intellij.platform.lvcs.impl.DirectoryDiffMode
import com.intellij.platform.lvcs.impl.USE_OLD_CONTENT
import com.intellij.platform.lvcs.impl.diff.getDiff
import com.intellij.platform.lvcs.impl.statistics.LocalHistoryCounter
import com.intellij.platform.lvcs.impl.ui.ActivityViewDataKeys
internal class CreatePatchAction : ChangeSetSelectionAction() {
@@ -25,11 +28,13 @@ internal class CreatePatchAction : ChangeSetSelectionAction() {
facade: LocalHistoryFacade,
gateway: IdeaGateway,
activityScope: ActivityScope,
selection: ChangeSetSelection) {
selection: ChangeSetSelection,
e: AnActionEvent) {
LocalHistoryCounter.logActionInvoked(LocalHistoryCounter.ActionKind.CreatePatch, activityScope)
val diffMode = e.getData(ActivityViewDataKeys.DIRECTORY_DIFF_MODE) ?: DirectoryDiffMode.WithLocal
val changes = ProgressManager.getInstance().runProcessWithProgressSynchronously(ThrowableComputable {
val diff = facade.getDiff(gateway, activityScope, selection, USE_OLD_CONTENT)
val diff = facade.getDiff(gateway, activityScope, selection, diffMode, USE_OLD_CONTENT)
if (diff.any { it.left?.hasUnavailableContent() == true || it.right?.hasUnavailableContent() == true }) {
return@ThrowableComputable null
}

View File

@@ -4,9 +4,11 @@ package com.intellij.platform.lvcs.impl.actions
import com.intellij.history.core.LocalHistoryFacade
import com.intellij.history.integration.IdeaGateway
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.Project
import com.intellij.platform.lvcs.impl.ActivityScope
import com.intellij.platform.lvcs.impl.ChangeSetSelection
import com.intellij.platform.lvcs.impl.DirectoryDiffMode
import com.intellij.platform.lvcs.impl.USE_OLD_CONTENT
import com.intellij.platform.lvcs.impl.operations.createReverter
import com.intellij.platform.lvcs.impl.statistics.LocalHistoryCounter
@@ -21,10 +23,12 @@ internal class RevertAction : ChangeSetSelectionAction() {
facade: LocalHistoryFacade,
gateway: IdeaGateway,
activityScope: ActivityScope,
selection: ChangeSetSelection) {
selection: ChangeSetSelection,
e: AnActionEvent) {
LocalHistoryCounter.logActionInvoked(LocalHistoryCounter.ActionKind.RevertRevisions, activityScope)
val reverter = facade.createReverter(project, gateway, activityScope, selection, USE_OLD_CONTENT)
val diffMode = DirectoryDiffMode.WithLocal // revert everything after the selected changeset
val reverter = facade.createReverter(project, gateway, activityScope, selection, diffMode, USE_OLD_CONTENT)
if (reverter == null || reverter.checkCanRevert().isNotEmpty()) return
reverter.revert()
}

View File

@@ -15,8 +15,9 @@ private class ActivityDiffDataImpl(override val presentableChanges: Iterable<Pre
internal fun LocalHistoryFacade.createDiffData(gateway: IdeaGateway,
scope: ActivityScope,
selection: ChangeSetSelection,
diffMode: DirectoryDiffMode,
isOldContentUsed: Boolean): ActivityDiffData {
val differences = getDiff(gateway, scope, selection, isOldContentUsed)
val differences = getDiff(gateway, scope, selection, diffMode, isOldContentUsed)
val presentableChanges = JBIterable.from(differences)
.mapNotNull { it.toPresentableChange(gateway, scope, selection, isOldContentUsed) }
return ActivityDiffDataImpl(presentableChanges)

View File

@@ -31,21 +31,43 @@ internal fun LocalHistoryFacade.getSingleFileDiff(rootEntry: RootEntry,
internal fun LocalHistoryFacade.getDiff(rootEntry: RootEntry,
selection: ChangeSetSelection,
entryPaths: Collection<String>,
diffMode: DirectoryDiffMode,
isOldContentUsed: Boolean): List<Difference> {
return entryPaths.flatMap {
val leftEntry = findEntry(rootEntry, selection.leftRevision, it, isOldContentUsed)
val rightEntry = findEntry(rootEntry, selection.rightRevision, it, isOldContentUsed)
Entry.getDifferencesBetween(leftEntry, rightEntry, selection.rightRevision is RevisionId.Current)
val leftRevision = selection.leftRevision
val rightRevision = when (diffMode) {
DirectoryDiffMode.WithLocal -> selection.rightRevision
DirectoryDiffMode.WithNext -> {
val rightItem = selection.rightItem
if (rightItem != null) RevisionId.ChangeSet(rightItem.id) else nextRevision(leftRevision)
}
}
return entryPaths.flatMap {
val leftEntry = findEntry(rootEntry, leftRevision, it, isOldContentUsed)
val rightEntry = findEntry(rootEntry, rightRevision, it, isOldContentUsed)
Entry.getDifferencesBetween(leftEntry, rightEntry, rightRevision is RevisionId.Current)
}
}
private fun LocalHistoryFacade.nextRevision(revisionId: RevisionId): RevisionId {
if (revisionId is RevisionId.Current) return RevisionId.Current
revisionId as RevisionId.ChangeSet
var nextChange: Long? = null
for (change in changes) {
if (change.id == revisionId.id) break
nextChange = change.id
}
return nextChange?.let { RevisionId.ChangeSet(it) } ?: RevisionId.Current
}
internal fun LocalHistoryFacade.getDiff(gateway: IdeaGateway,
scope: ActivityScope,
selection: ChangeSetSelection,
diffMode: DirectoryDiffMode,
isOldContentUsed: Boolean): List<Difference> {
val rootEntry = selection.data.getRootEntry(gateway)
val entryPaths = getEntryPaths(gateway, scope)
return getDiff(rootEntry, selection, entryPaths, isOldContentUsed).toList()
return getDiff(rootEntry, selection, entryPaths, diffMode, isOldContentUsed).toList()
}
internal fun getEntryPaths(gateway: IdeaGateway, scope: ActivityScope): Collection<String> {

View File

@@ -18,7 +18,7 @@ import org.jetbrains.annotations.Nls
import java.util.function.Supplier
internal fun LocalHistoryFacade.createReverter(project: Project, gateway: IdeaGateway, scope: ActivityScope, selection: ChangeSetSelection,
isOldContentUsed: Boolean): Reverter? {
diffMode: DirectoryDiffMode, isOldContentUsed: Boolean): Reverter? {
val targetRevisionId = selection.leftRevision
if (targetRevisionId == RevisionId.Current) return null
@@ -32,7 +32,7 @@ internal fun LocalHistoryFacade.createReverter(project: Project, gateway: IdeaGa
commandNameSupplier)
}
val diff = getDiff(gateway, scope, selection, isOldContentUsed)
val diff = getDiff(gateway, scope, selection, diffMode, isOldContentUsed)
return DifferenceReverter(project, this, gateway, diff, commandNameSupplier)
}

View File

@@ -151,6 +151,7 @@ class ActivityView(private val project: Project, gateway: IdeaGateway, val activ
if (ActivityViewDataKeys.SELECTION.`is`(dataId)) return activityList.selection
if (ActivityViewDataKeys.SCOPE.`is`(dataId)) return activityScope
if (EditorTabDiffPreviewManager.EDITOR_TAB_DIFF_PREVIEW.`is`(dataId)) return editorDiffPreview
if (ActivityViewDataKeys.DIRECTORY_DIFF_MODE.`is`(dataId)) return model.diffMode
return null
}

View File

@@ -5,6 +5,7 @@ import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.vcs.changes.ui.PresentableChange
import com.intellij.platform.lvcs.impl.ActivityScope
import com.intellij.platform.lvcs.impl.ActivitySelection
import com.intellij.platform.lvcs.impl.DirectoryDiffMode
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
@@ -12,4 +13,5 @@ object ActivityViewDataKeys {
val SELECTION: DataKey<ActivitySelection> = DataKey.create("ActivityView.Selection")
val SCOPE: DataKey<ActivityScope> = DataKey.create("ActivityView.Scope")
val SELECTED_DIFFERENCES: DataKey<Iterable<PresentableChange>> = DataKey.create("ActivityView.SelectedDifferences")
val DIRECTORY_DIFF_MODE: DataKey<DirectoryDiffMode> = DataKey.create("ActivityView.DirectoryDiffMode")
}

View File

@@ -22,6 +22,7 @@ internal class ActivityViewModel(private val project: Project, gateway: IdeaGate
private val activityItemsFlow = MutableStateFlow(ActivityData.EMPTY)
private val selectionFlow = MutableStateFlow<ActivitySelection?>(null)
private val diffModeFlow = MutableStateFlow(DirectoryDiffMode.WithLocal)
private val filterFlow = MutableStateFlow<String?>(null)
@@ -50,15 +51,15 @@ internal class ActivityViewModel(private val project: Project, gateway: IdeaGate
}
if (!isSingleDiffSupported) {
coroutineScope.launch {
selectionFlow.collectLatest { selection ->
thisLogger<ActivityViewModel>().debug("Loading diff data for $activityScope")
combine(selectionFlow, diffModeFlow) { s, d -> s to d }.collectLatest { (selection, diffMode) ->
thisLogger<ActivityViewModel>().debug("Loading diff data for $activityScope diff mode $diffMode")
withContext(Dispatchers.EDT) {
eventDispatcher.multicaster.onDiffDataLoadingStarted()
}
val diffData = selection?.let {
withContext(Dispatchers.Default) {
LocalHistoryCounter.logLoadDiff(project, activityScope) {
activityProvider.loadDiffData(activityScope, selection)
activityProvider.loadDiffData(activityScope, selection, diffMode)
}
}
}
@@ -89,6 +90,7 @@ internal class ActivityViewModel(private val project: Project, gateway: IdeaGate
}
internal val selection get() = selectionFlow.value
internal val diffMode get() = diffModeFlow.value
internal val isSingleDiffSupported get() = !activityScope.hasMultipleFiles

View File

@@ -234,12 +234,40 @@ class LocalHistoryActivityProviderTest : IntegrationTestCase() {
TestCase.assertEquals(listOf("ADDED:${file.name}"), getDiffDataForSelection(provider, scope, activityList, 3, 4))
TestCase.assertEquals(listOf("ADDED:${file.name}", "ADDED:${directory.name}", "ADDED:${innerDirectory.name}").sorted(),
getDiffDataForSelection(provider, scope, activityList, 0, 4))
TestCase.assertEquals(listOf("MODIFIED:${file.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithNext, 0))
TestCase.assertEquals(listOf("MODIFIED:${file.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithLocal, 0))
TestCase.assertEquals(listOf("MODIFIED:${file.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithNext, 1))
TestCase.assertEquals(listOf("MODIFIED:${file.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithLocal, 1))
TestCase.assertEquals(listOf("ADDED:${innerDirectory.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithNext, 2))
TestCase.assertEquals(listOf("MODIFIED:${file.name}", "ADDED:${innerDirectory.name}").sorted(),
getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithLocal, 2))
TestCase.assertEquals(listOf("ADDED:${directory.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithNext, 3))
TestCase.assertEquals(listOf("MODIFIED:${file.name}", "ADDED:${directory.name}", "ADDED:${innerDirectory.name}").sorted(),
getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithLocal, 3))
TestCase.assertEquals(listOf("ADDED:${file.name}"), getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithNext, 4))
TestCase.assertEquals(listOf("ADDED:${file.name}", "ADDED:${directory.name}", "ADDED:${innerDirectory.name}").sorted(),
getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithLocal, 4))
}
private fun getDiffDataForSelection(provider: LocalHistoryActivityProvider, scope: ActivityScope, activityList: ActivityData,
diffMode: DirectoryDiffMode, index: Int): List<String> {
return getDiffDataForSelection(provider, scope, activityList, diffMode, index, index)
}
private fun getDiffDataForSelection(provider: LocalHistoryActivityProvider, scope: ActivityScope, activityList: ActivityData,
from: Int, to: Int): List<String> {
val selection = ActivitySelection(listOf(activityList.items[from], activityList.items[to]), activityList)
return provider.loadDiffData(scope, selection)!!.presentableChanges.map { "${it.fileStatus}:${it.filePath.name}" }.sorted()
return getDiffDataForSelection(provider, scope, activityList, DirectoryDiffMode.WithLocal, from, to)
}
private fun getDiffDataForSelection(provider: LocalHistoryActivityProvider, scope: ActivityScope, activityList: ActivityData,
diffMode: DirectoryDiffMode, from: Int, to: Int): List<String> {
val selection = ActivitySelection(listOf(from, to).distinct().map { activityList.items[it] }, activityList)
return provider.loadDiffData(scope, selection, diffMode)!!.presentableChanges.map { "${it.fileStatus}:${it.filePath.name}" }.sorted()
}
private fun ActivityData.getLabelNameSet(): Set<String> {