git-branches-dashboard: add support for removing multiple Git remotes
authorDmitry Zhuravlev <dmitry.zhuravlev@jetbrains.com>
Wed, 5 Aug 2020 12:06:06 +0000 (15:06 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Tue, 11 Aug 2020 14:36:33 +0000 (14:36 +0000)
GitOrigin-RevId: dbf3e1b386c451085df433c645b09dad38a9de53

plugins/git4idea/resources/messages/GitBundle.properties
plugins/git4idea/src/git4idea/remote/GitConfigureRemotesDialog.kt
plugins/git4idea/src/git4idea/ui/branch/dashboard/BranchesDashboardActions.kt

index 27aec5e72f3e3a04d1e200f0ea70672cbd534aa9..a87adf7df3b111ce85bdc4273b2ac5d59fe90ef2 100644 (file)
@@ -516,7 +516,7 @@ action.Git.Log.Branches.Change.Branch.Filter.On.Selection.description=When a bra
 action.Git.Log.Branches.Change.Branch.Filter.text=Update Branch Filter in Log
 action.Git.Log.Branches.Change.Branch.Filter.description=Update branch filter in log with selected branches
 action.Git.Log.Edit.Remote.text=Edit Remote 
-action.Git.Log.Remove.Remote.text=Remove Remote
+action.Git.Log.Remove.Remote.text=Remove {0,choice,1#Remote|2#Remotes}
 group.Git.HEAD.Branch.Filter.title=HEAD (Current Branch)
 group.Git.Local.Branch.title=Local
 group.Git.Remote.Branch.title=Remote
@@ -990,11 +990,11 @@ remotes.dialog.title=Git Remotes
 remotes.dialog.adding.remote=Adding Remote...
 remote.dialog.add.remote=Add Remote
 remotes.dialog.cannot.add.remote.error.message=Couldn''t add remote {0} ''{1}''
-remotes.dialog.remove.remote.message=Remove remote {0} ''{1}''?
-remotes.dialog.remove.remote.title=Remove Remote
-remotes.dialog.removing.remote.progress=Removing Remote...
-remotes.dialog.removing.remote.error.title=Remove Remote
-remotes.dialog.removing.remote.error.message=Couldn''t remove remote {0}
+remotes.dialog.remove.remote.message=Remove {0,choice,1#remote|2#remotes} {1}?
+remotes.dialog.remove.remote.title=Remove {0,choice,1#Remote|2#Remotes}
+remotes.dialog.removing.remote.progress=Removing {0,choice,1#Remote|2#Remotes}...
+remotes.dialog.removing.remote.error.title=Remove {0,choice,1#Remote|2#Remotes}
+remotes.dialog.removing.remote.error.message=Couldn''t remove {0,choice,1#remote|2#remotes} {1}
 remotes.changing.remote.progress=Changing Remote...
 remotes.changing.remote.error.title=Change Remote
 remotes.changing.remote.error.message=Couldn''t change remote {0} to {1} ''{2}''
index 3a17123215b51f8b44235e0b5f69d1268e4f13ac..5b633c7441a847a84075e8f20bd386269f34133a 100644 (file)
@@ -85,7 +85,9 @@ class GitConfigureRemotesDialog(val project: Project, val repositories: Collecti
                      message("remote.dialog.add.remote"),
                      message("remotes.dialog.cannot.add.remote.error.message", dialog.remoteName, dialog.remoteUrl),
                      repository, rebuildTreeOnSuccess) {
-        git.addRemote(repository, dialog.remoteName, dialog.remoteUrl)
+        arrayListOf<GitCommandResult>().apply {
+          add(git.addRemote(repository, dialog.remoteName, dialog.remoteUrl))
+        }
       }
     }
   }
@@ -95,7 +97,7 @@ class GitConfigureRemotesDialog(val project: Project, val repositories: Collecti
     val remote = remoteNode.remote
     val repository = remoteNode.repository
 
-    removeRemote(git, repository, remote, rootPane, rebuildTreeOnSuccess)
+    removeRemotes(git, repository, setOf(remote), rootPane, rebuildTreeOnSuccess)
   }
 
   private fun editRemote() {
@@ -113,7 +115,7 @@ class GitConfigureRemotesDialog(val project: Project, val repositories: Collecti
       val fontMetrics = table.getFontMetrics(UIManager.getFont("Table.font").deriveFont(Font.BOLD))
       val nameWidth = fontMetrics.stringWidth(node.getPresentableString())
       val remote = (node as? RemoteNode)?.remote
-      val urlWidth = if (remote == null) 0 else fontMetrics.stringWidth(getUrl(remote))
+      val urlWidth = if (remote == null) 0 else fontMetrics.stringWidth(remote.url)
       if (maxNameWidth < nameWidth) maxNameWidth = nameWidth
       if (maxUrlWidth < urlWidth) maxUrlWidth = urlWidth
     }
@@ -196,7 +198,7 @@ class GitConfigureRemotesDialog(val project: Project, val repositories: Collecti
       when {
         columnIndex == NAME_COLUMN -> return node
         node is RepoNode -> return ""
-        node is RemoteNode -> return getUrl(node.remote)
+        node is RemoteNode -> return node.remote.url
         else -> {
           LOG.error("Unexpected position at row $rowIndex and column $columnIndex")
           return ""
@@ -222,22 +224,26 @@ class GitConfigureRemotesDialog(val project: Project, val repositories: Collecti
   }
 }
 
-fun removeRemote(git: Git, repository: GitRepository, remote: GitRemote, parent: Component? = null, onSuccess: () -> Unit = {}) {
+fun removeRemotes(git: Git, repository: GitRepository, remotes: Set<GitRemote>, parent: Component? = null, onSuccess: () -> Unit = {}) {
   if (YES == showYesNoDialog(if (parent == null) parent else repository.project,
-                             message("remotes.dialog.remove.remote.message", remote.name, getUrl(remote)),
-                             message("remotes.dialog.remove.remote.title"), getQuestionIcon())) {
-    runInModalTask(message("remotes.dialog.removing.remote.progress"),
-                   message("remotes.dialog.removing.remote.error.title"),
-                   message("remotes.dialog.removing.remote.error.message", remote),
+                             message("remotes.dialog.remove.remote.message", remotes.size, remotes.toStringRepresentation()),
+                             message("remotes.dialog.remove.remote.title", remotes.size), getQuestionIcon())) {
+    runInModalTask(message("remotes.dialog.removing.remote.progress", remotes.size),
+                   message("remotes.dialog.removing.remote.error.title", remotes.size),
+                   message("remotes.dialog.removing.remote.error.message", remotes.size, remotes.toStringRepresentation()), //FIXME
                    repository, onSuccess) {
-      git.removeRemote(repository, remote)
+      arrayListOf<GitCommandResult>().apply {
+        for (remote in remotes) {
+          add(git.removeRemote(repository, remote))
+        }
+      }
     }
   }
 }
 
 fun editRemote(git: Git, repository: GitRepository, remote: GitRemote, onSuccess: () -> Unit = {}) {
   val oldName = remote.name
-  val oldUrl = getUrl(remote)
+  val oldUrl = remote.url
   val dialog = GitDefineRemoteDialog(repository, git, oldName, oldUrl)
   if (dialog.showAndGet()) {
     val newRemoteName = dialog.remoteName
@@ -247,12 +253,17 @@ fun editRemote(git: Git, repository: GitRepository, remote: GitRemote, onSuccess
                    message("remotes.changing.remote.error.title"),
                    message("remotes.changing.remote.error.message", oldName, newRemoteName, newRemoteUrl),
                    repository, onSuccess) {
-      changeRemote(git, repository, oldName, oldUrl, newRemoteName, newRemoteUrl)
+      arrayListOf<GitCommandResult>().apply {
+        add(changeRemote(git, repository, oldName, oldUrl, newRemoteName, newRemoteUrl))
+      }
     }
   }
 }
 
-private fun getUrl(remote: GitRemote) = remote.urls.firstOrNull() ?: ""
+private val GitRemote.url: String get() = urls.firstOrNull() ?: ""
+
+private fun Set<GitRemote>.toStringRepresentation() =
+  if (size == 1) with(first()){"$name '$url'"} else "\n${joinToString(separator = "\n") {"${it.name} '${it.url}'" }}"
 
 private fun changeRemote(git: Git, repo: GitRepository, oldName: String, oldUrl: String, newName: String, newUrl: String): GitCommandResult {
   var result : GitCommandResult? = null
@@ -271,19 +282,21 @@ private fun runInModalTask(@Nls(capitalization = Nls.Capitalization.Title) title
                            @Nls(capitalization = Nls.Capitalization.Sentence) errorMessage: String,
                            repository: GitRepository,
                            onSuccess: () -> Unit,
-                           operation: () -> GitCommandResult) {
+                           operation: () -> List<GitCommandResult>?) {
   ProgressManager.getInstance().run(object : Task.Modal(repository.project, title, true) {
-    private var result: GitCommandResult? = null
+    private var results: List<GitCommandResult>? = null
 
     override fun run(indicator: ProgressIndicator) {
-      result = operation()
+      results = operation()
       repository.update()
     }
 
     override fun onSuccess() {
       onSuccess()
-      if (result == null || !result!!.success()) {
-        val errorDetails = if (result == null) message("remotes.operation.not.executed.message") else result!!.errorOutputAsJoinedString
+      if (results == null || results!!.any { !it.success() }) {
+        val errorDetails =
+          if (results == null) message("remotes.operation.not.executed.message")
+          else results!!.joinToString(separator = "\n") { it.errorOutputAsJoinedString }
         val message = message("remotes.operation.error.message", errorMessage, repository, errorDetails)
         LOG.warn(message)
         showErrorDialog(myProject, message, errorTitle)
index 0175ff15143ab7f89b45fb49b8dcc16d86b74a29..c664e99c61b7f949d49d13ba217191362129ab0c 100644 (file)
@@ -28,7 +28,7 @@ import git4idea.i18n.GitBundle.message
 import git4idea.i18n.GitBundleExtensions.messagePointer
 import git4idea.isRemoteBranchProtected
 import git4idea.remote.editRemote
-import git4idea.remote.removeRemote
+import git4idea.remote.removeRemotes
 import git4idea.repo.GitRemote
 import git4idea.repo.GitRepository
 import git4idea.repo.GitRepositoryManager
@@ -51,9 +51,11 @@ internal object BranchesDashboardActions {
       BranchActionsBuilder(project, tree).build()?.getChildren(e) ?: AnAction.EMPTY_ARRAY
   }
 
-  class MultipleLocalBranchActions : ActionGroup(), DumbAware {
-    override fun getChildren(e: AnActionEvent?): Array<AnAction> =
-      arrayOf(ShowArbitraryBranchesDiffAction(), UpdateSelectedBranchAction(), DeleteBranchAction())
+  class MultipleLocalBranchActions(private val containsRemoteBranches: Boolean, private val repository: GitRepository) : ActionGroup(), DumbAware {
+    override fun getChildren(e: AnActionEvent?): Array<AnAction> {
+      val commonActions: Array<AnAction> = arrayOf(ShowArbitraryBranchesDiffAction(), UpdateSelectedBranchAction(), DeleteBranchAction())
+      return if (containsRemoteBranches) commonActions + arrayOf<AnAction>(Separator(), EditRemoteAction(repository), RemoveRemoteAction(repository)) else commonActions
+    }
   }
 
   class CurrentBranchActions(project: Project,
@@ -98,6 +100,12 @@ internal object BranchesDashboardActions {
       arrayListOf<AnAction>(EditRemoteAction(currentRepository), RemoveRemoteAction(currentRepository)).toTypedArray()
   }
 
+  class MultipleGroupActions(private val currentRepository: GitRepository) : ActionGroup(), DumbAware {
+
+    override fun getChildren(e: AnActionEvent?): Array<AnAction> =
+      arrayListOf<AnAction>(RemoveRemoteAction(currentRepository)).toTypedArray()
+  }
+
   class RemoteGlobalActions : ActionGroup(), DumbAware {
 
     override fun getChildren(e: AnActionEvent?): Array<AnAction> =
@@ -111,7 +119,7 @@ internal object BranchesDashboardActions {
       val guessRepo = DvcsUtil.guessCurrentRepositoryQuick(project, GitUtil.getRepositoryManager(project),
                                                            GitVcsSettings.getInstance(project).recentRootPath) ?: return null
       if (multipleBranchSelection) {
-        return MultipleLocalBranchActions()
+        return MultipleLocalBranchActions(selectedBranches.any { !it.isLocal }, guessRepo)
       }
 
       val branchInfo = selectedBranches.singleOrNull()
@@ -127,6 +135,9 @@ internal object BranchesDashboardActions {
       if (selectedRemotes.size == 1) {
         return GroupActions(guessRepo)
       }
+      else if (selectedRemotes.isNotEmpty()) {
+        return MultipleGroupActions(guessRepo)
+      }
 
       val selectedBranchNodes = tree.getSelectedBranchNodes()
       if (selectedBranchNodes.size == 1 && selectedBranchNodes.first().type == NodeType.REMOTE_ROOT) {
@@ -394,10 +405,14 @@ internal object BranchesDashboardActions {
     }
   }
 
-  class RemoveRemoteAction(private val repository: GitRepository) : RemoteActionBase(repository, messagePointer("action.Git.Log.Remove.Remote.text")) {
+  class RemoveRemoteAction(private val repository: GitRepository) : RemoteActionBase(repository) {
+
+    override fun update(e: AnActionEvent, project: Project, remoteNames: Set<String>) {
+      e.presentation.text = message("action.Git.Log.Remove.Remote.text", remoteNames.size)
+    }
 
     override fun doAction(e: AnActionEvent, project: Project, remotes: Set<GitRemote>) {
-      removeRemote(service(), repository, remotes.first())
+      removeRemotes(service(), repository, remotes)
     }
   }