From c7941ef5e21e427fb6ead021cf91d394c8d88479 Mon Sep 17 00:00:00 2001 From: Guodong Zhu Date: Sun, 22 Mar 2026 23:57:53 -0400 Subject: [PATCH 1/3] feat(plugin): add branch deletion option after worktree remove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delegate to IntelliJ's built-in GitBrancher.deleteBranches() for branch deletion — provides proper error dialogs, undo support, and automatic git state refresh. Add BranchDeletionMode setting (Ask/Always/Never) in Settings > Tools > Worktree Manager. Default is Ask, which shows a confirmation dialog after worktree removal. Protected branches (main, master) are never offered for deletion. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../actions/worktree/RemoveWorktreeAction.kt | 45 ++++++++++++++++++- .../com/block/wt/settings/WtPluginSettings.kt | 3 ++ .../block/wt/settings/WtSettingsComponent.kt | 9 ++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt index ccd961e..23964a7 100644 --- a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt +++ b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt @@ -7,6 +7,7 @@ import com.block.wt.progress.asScope import com.block.wt.services.ContextService import com.block.wt.services.SymlinkSwitchService import com.block.wt.services.WorktreeService +import com.block.wt.settings.BranchDeletionMode import com.block.wt.settings.WtPluginSettings import com.block.wt.ui.Notifications import com.block.wt.ui.WorktreePanel @@ -18,6 +19,8 @@ import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.PopupStep import com.intellij.openapi.ui.popup.util.BaseListPopupStep +import git4idea.branch.GitBrancher +import git4idea.repo.GitRepositoryManager class RemoveWorktreeAction : WtConfigAction() { @@ -113,11 +116,13 @@ class RemoveWorktreeAction : WtConfigAction() { result.fold( onSuccess = { - scope.fraction(0.95) + scope.fraction(0.90) scope.text("Refreshing worktree list...") worktreeService.refreshWorktreeList() scope.fraction(1.0) Notifications.info(project, "Worktree Removed", "Removed ${wt.displayName}") + + offerBranchDeletion(project, wt.branch) }, onFailure = { ex -> Notifications.error(project, "Remove Failed", ex.message ?: "Unknown error") @@ -125,4 +130,42 @@ class RemoveWorktreeAction : WtConfigAction() { ) } } + + private fun offerBranchDeletion(project: Project, branch: String?) { + if (branch.isNullOrBlank() || branch in PROTECTED_BRANCHES) return + + val mode = try { + BranchDeletionMode.valueOf(WtPluginSettings.getInstance().state.branchDeletionAfterRemove) + } catch (_: IllegalArgumentException) { + BranchDeletionMode.ASK + } + if (mode == BranchDeletionMode.NEVER) return + + val shouldDelete = when (mode) { + BranchDeletionMode.ALWAYS -> true + BranchDeletionMode.ASK -> { + var answer = Messages.NO + ApplicationManager.getApplication().invokeAndWait { + answer = Messages.showYesNoDialog( + project, + "Delete branch '$branch'?", + "Delete Branch", + Messages.getQuestionIcon(), + ) + } + answer == Messages.YES + } + BranchDeletionMode.NEVER -> false + } + if (!shouldDelete) return + + ApplicationManager.getApplication().invokeLater { + val repos = GitRepositoryManager.getInstance(project).repositories + GitBrancher.getInstance(project).deleteBranches(mapOf(branch to repos), null) + } + } + + companion object { + private val PROTECTED_BRANCHES = setOf("main", "master") + } } diff --git a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtPluginSettings.kt b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtPluginSettings.kt index b245e7f..ee4e366 100644 --- a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtPluginSettings.kt +++ b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtPluginSettings.kt @@ -7,6 +7,8 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service +enum class BranchDeletionMode { ASK, ALWAYS, NEVER } + @Service(Service.Level.APP) @State( name = "com.block.wt.settings.WtPluginSettings", @@ -27,6 +29,7 @@ class WtPluginSettings : PersistentStateComponent { var lastWelcomeVersion: String = "", var enhancedSessionDetection: Boolean = true, var agentTerminalNavigation: Boolean = true, + var branchDeletionAfterRemove: String = "ASK", ) @Volatile diff --git a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtSettingsComponent.kt b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtSettingsComponent.kt index 9efaa3b..d169bef 100644 --- a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtSettingsComponent.kt +++ b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/settings/WtSettingsComponent.kt @@ -2,6 +2,7 @@ package com.block.wt.settings import com.intellij.openapi.ui.DialogPanel import com.intellij.ui.dsl.builder.bindIntValue +import com.intellij.ui.dsl.builder.bindItem import com.intellij.ui.dsl.builder.bindSelected import com.intellij.ui.dsl.builder.panel import javax.swing.JComponent @@ -55,6 +56,14 @@ class WtSettingsComponent { checkBox("Confirm before removing worktrees") .bindSelected(settings.state::confirmBeforeRemove) } + row("After removing worktree, delete branch:") { + comboBox(BranchDeletionMode.entries.toList()) + .bindItem( + { BranchDeletionMode.valueOf(settings.state.branchDeletionAfterRemove) }, + { settings.state.branchDeletionAfterRemove = (it ?: BranchDeletionMode.ASK).name }, + ) + .comment("Ask = prompt each time, Always = delete without asking, Never = keep branch") + } } } From e478378a690d6fb6ae47dcefb6cb7502a223a087 Mon Sep 17 00:00:00 2001 From: Guodong Zhu Date: Mon, 23 Mar 2026 00:07:52 -0400 Subject: [PATCH 2/3] fix(plugin): use isMain flag instead of path comparison for remove guard The normalizeSafe() path comparison resolved symlink paths to physical paths, causing the active worktree (accessed via symlink) to incorrectly match mainRepoRoot. Use the isMain flag from GitParser instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../com/block/wt/actions/worktree/RemoveWorktreeAction.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt index 23964a7..d42fd48 100644 --- a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt +++ b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/worktree/RemoveWorktreeAction.kt @@ -11,7 +11,6 @@ import com.block.wt.settings.BranchDeletionMode import com.block.wt.settings.WtPluginSettings import com.block.wt.ui.Notifications import com.block.wt.ui.WorktreePanel -import com.block.wt.util.normalizeSafe import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project @@ -71,11 +70,11 @@ class RemoveWorktreeAction : WtConfigAction() { } private fun confirmAndRemove(project: Project, wt: WorktreeInfo, worktreeService: WorktreeService) { - val config = ContextService.getInstance(project).getCurrentConfig() - if (config != null && wt.path.normalizeSafe() == config.mainRepoRoot.normalizeSafe()) { + if (wt.isMain) { Notifications.error(project, "Cannot Remove", "Cannot remove the main repository worktree") return } + val config = ContextService.getInstance(project).getCurrentConfig() val needsConfirmation = WtPluginSettings.getInstance().state.confirmBeforeRemove || wt.isDirty == true From eb1902c2c345ab1604761c8c3526bac671829cc1 Mon Sep 17 00:00:00 2001 From: Guodong Zhu Date: Mon, 23 Mar 2026 00:27:03 -0400 Subject: [PATCH 3/3] fix(plugin): reload config before worktree operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ContextService.reload() is now called in requireConfig() — the single entry point for all config-dependent actions. This ensures config is fresh before create/remove/switch operations, not just after. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/main/kotlin/com/block/wt/actions/WtAction.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/WtAction.kt b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/WtAction.kt index 98b6818..f5ce722 100644 --- a/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/WtAction.kt +++ b/wt-jetbrains-plugin/src/main/kotlin/com/block/wt/actions/WtAction.kt @@ -53,7 +53,9 @@ abstract class WtConfigAction : WtAction() { protected fun requireConfig(e: AnActionEvent): ContextConfig? { val project = e.project ?: return null - return ContextService.getInstance(project).getCurrentConfig() + val contextService = ContextService.getInstance(project) + contextService.reload() + return contextService.getCurrentConfig() } }