[git-stash] show a warning when index contains files other than selected

When the "git stash push" command is called for the list of files, the whole index state is saved to the stash regardless of which files were specified to the command. Because of this, stash created in this situation would contain unexpected changes. The warning is meant to notify the user about such behavior.

GitOrigin-RevId: 92ae7335703096a62aa06d89f09ccb63e84e8af7
This commit is contained in:
Julia Beliaeva
2023-09-13 11:38:19 +00:00
committed by intellij-monorepo-bot
parent 5090b8b301
commit 92290b3c81
7 changed files with 59 additions and 6 deletions
@@ -220,7 +220,7 @@ public class VcsNotifier {
public Notification notifyMinorWarning(@NonNls @Nullable String displayId,
@NotificationTitle @NotNull String title,
@NotificationContent @NotNull String message) {
return notifyMinorWarning(displayId, title, message, null);
return notifyMinorWarning(displayId, title, message, (NotificationListener)null);
}
@NotNull
@@ -229,6 +229,14 @@ public class VcsNotifier {
return notify(STANDARD_NOTIFICATION, displayId, "", message, NotificationType.WARNING, (NotificationListener)null);
}
@NotNull
public Notification notifyMinorWarning(@NonNls @Nullable String displayId,
@NotificationTitle @NotNull String title,
@NotificationContent @NotNull String message,
NotificationAction... actions) {
return notify(STANDARD_NOTIFICATION, displayId, title, message, NotificationType.WARNING, actions);
}
@NotNull
public Notification notifyMinorWarning(@NonNls @Nullable String displayId,
@NotificationTitle @NotNull String title,
@@ -421,6 +421,9 @@ stash.files.in.roots.success=Files in {0} stashed successfully
stash.view.stashes.link=View Git stashes
stash.enable.stashes.link=Enable and view "{0}" tab
stash.changes.non.empty.index.for.roots.notification.text=Staging area in {0} contains modifications to other files which were saved to the stash
stash.changes.non.empty.index.notification.action=Do not show again
tag.action.name=Tag
tag.button=Create Tag
tag.commit.label=&Commit:
@@ -87,6 +87,7 @@ class GitNotificationIdsHolder : NotificationIdsHolder {
STASH_SUCCESSFUL,
STASH_FAILED,
STASH_LOCAL_CHANGES_DETECTED,
STASH_NON_EMPTY_INDEX_DETECTED,
TAG_CREATED,
TAG_NOT_CREATED,
TAG_DELETED,
@@ -196,6 +197,7 @@ class GitNotificationIdsHolder : NotificationIdsHolder {
const val STASH_SUCCESSFUL = "git.stash.successful"
const val STASH_FAILED = "git.stash.failed"
const val STASH_LOCAL_CHANGES_DETECTED = "git.stash.local.changes.detected"
const val STASH_NON_EMPTY_INDEX_DETECTED = "git.stash.non.empty.index.detected"
const val TAG_CREATED = "git.tag.created"
const val TAG_NOT_CREATED = "git.tag.not.created"
const val TAG_DELETED = "git.tag.deleted"
@@ -1,16 +1,22 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package git4idea.index.actions
import com.intellij.ide.util.PropertiesComponent
import com.intellij.notification.NotificationAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vcs.VcsNotifier
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.MultiMap
import git4idea.GitNotificationIdsHolder
import git4idea.GitStashUsageCollector
import git4idea.commands.Git
import git4idea.config.GitExecutableManager
import git4idea.config.GitVersionSpecialty
import git4idea.i18n.GitBundle
import git4idea.index.GitStageTracker
import git4idea.index.ui.GitFileStatusNode
import git4idea.index.ui.NodeKind
import git4idea.stash.GitStashOperations
@@ -43,12 +49,46 @@ object GitStashOperation : StagingAreaOperation {
refreshStash(project, root)
}
override fun reportResult(project: Project, successfulRoots: Collection<VirtualFile>, exceptionsByRoot: MultiMap<VirtualFile, VcsException>) {
super.reportResult(project, successfulRoots, exceptionsByRoot)
override fun reportResult(project: Project,
nodes: List<GitFileStatusNode>,
successfulRoots: Collection<VirtualFile>,
exceptionsByRoot: MultiMap<VirtualFile, VcsException>) {
super.reportResult(project, nodes, successfulRoots, exceptionsByRoot)
if (successfulRoots.isNotEmpty()) {
GitStashOperations.showSuccessNotification(project, successfulRoots, !exceptionsByRoot.isEmpty)
showNonEmptyStagingAreaNotification(project, nodes, successfulRoots)
}
}
private const val SHOW_NOTIFICATION_PROPERTY = "git.stash.notify.non.empty.index"
private fun showNonEmptyStagingAreaNotification(project: Project, nodes: List<GitFileStatusNode>, successfulRoots: Collection<VirtualFile>) {
if (PropertiesComponent.getInstance(project).getBoolean(SHOW_NOTIFICATION_PROPERTY, true)) {
val tracker = GitStageTracker.getInstance(project)
val stagedRoots = tracker.state.stagedRoots
val stagedRootsToStash = mutableSetOf<VirtualFile>()
for ((root, nodesInRoot) in nodes.filter { stagedRoots.contains(it.root) && successfulRoots.contains(it.root) }.groupBy { it.root }) {
val paths = nodesInRoot.mapTo(mutableSetOf()) { it.filePath }
if (tracker.stagedFiles(root).any { !paths.contains(it) }) {
stagedRootsToStash.add(root)
}
}
if (stagedRootsToStash.isEmpty()) return
val message = GitBundle.message("stash.changes.non.empty.index.for.roots.notification.text",
GitStashOperations.getRootsText(project, stagedRootsToStash))
val action = NotificationAction.createSimpleExpiring(GitBundle.message("stash.changes.non.empty.index.notification.action")) {
PropertiesComponent.getInstance(project).setValue(SHOW_NOTIFICATION_PROPERTY, false)
}
VcsNotifier.getInstance(project).notifyMinorWarning(GitNotificationIdsHolder.STASH_NON_EMPTY_INDEX_DETECTED, "", message, action)
}
}
private fun GitStageTracker.stagedFiles(root: VirtualFile): Collection<FilePath> {
val rootState = state.rootStates[root] ?: return emptyList()
return rootState.statuses.filterValues { it.getStagedStatus() != null }.map { it.key }
}
}
class GitStageStashFilesAction : StagingAreaOperationAction(GitStashOperation) {
@@ -36,7 +36,7 @@ interface StagingAreaOperation {
@Throws(VcsException::class)
fun processPaths(project: Project, root: VirtualFile, nodes: List<GitFileStatusNode>)
fun reportResult(project: Project, successfulRoots: Collection<VirtualFile>, exceptionsByRoot: MultiMap<VirtualFile, VcsException>) {
fun reportResult(project: Project, nodes: List<GitFileStatusNode>, successfulRoots: Collection<VirtualFile>, exceptionsByRoot: MultiMap<VirtualFile, VcsException>) {
if (!exceptionsByRoot.isEmpty) {
showErrorMessage(project, errorMessage, exceptionsByRoot.values())
}
@@ -78,7 +78,7 @@ fun performStageOperation(project: Project, nodes: List<GitFileStatusNode>, oper
}
}
operation.reportResult(project, successfulRoots, exceptions)
operation.reportResult(project, nodes, successfulRoots, exceptions)
stagingAreaActionInvoked()
}
}
@@ -311,7 +311,7 @@ object GitStashOperations {
return GitBundle.message("stash.files.in.roots.success", rootsText)
}
private fun getRootsText(project: Project, roots: Collection<VirtualFile>): String {
internal fun getRootsText(project: Project, roots: Collection<VirtualFile>): String {
val rootsText = roots.joinToString(", ") { "'${VcsImplUtil.getShortVcsRootName(project, it)}'" }
return StringUtil.shortenTextWithEllipsis(rootsText, 100, 0)
}