diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index a0e811f66..718c2d2be 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -148,7 +148,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ <c-y>: Copy pull request URL to clipboard c: Checkout by name F: Force checkout - d: Delete branch + d: View delete options r: Rebase checked-out branch onto this branch M: Merge into currently checked out branch f: Fast-forward this branch from its upstream @@ -255,7 +255,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ n: New branch M: Merge into currently checked out branch r: Rebase checked-out branch onto this branch - d: Delete branch + d: Delete remote tag u: Set as upstream of checked-out branch g: View reset options w: View worktree options @@ -332,7 +332,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <space>: Checkout
-  d: Delete tag
+  d: View delete options
   P: Push tag
   n: Create tag
   g: View reset options
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index d34463b81..02e6af23f 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -172,7 +172,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
 
   <space>: チェックアウト
-  d: タグを削除
+  d: View delete options
   P: タグをpush
   n: タグを作成
   g: View reset options
@@ -220,7 +220,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <c-y>: Pull RequestのURLをクリップボードにコピー
   c: Checkout by name
   F: Force checkout
-  d: ブランチを削除
+  d: View delete options
   r: Rebase checked-out branch onto this branch
   M: 現在のブランチにマージ
   f: Fast-forward this branch from its upstream
@@ -320,7 +320,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   n: 新しいブランチを作成
   M: 現在のブランチにマージ
   r: Rebase checked-out branch onto this branch
-  d: ブランチを削除
+  d: Delete remote tag
   u: Set as upstream of checked-out branch
   g: View reset options
   w: View worktree options
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index 5446c24aa..4af5fb1a3 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -183,7 +183,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <c-y>: 풀 리퀘스트 URL을 클립보드에 복사
   c: 이름으로 체크아웃
   F: 강제 체크아웃
-  d: 브랜치 삭제
+  d: View delete options
   r: 체크아웃된 브랜치를 이 브랜치에 리베이스
   M: 현재 브랜치에 병합
   f: Fast-forward this branch from its upstream
@@ -239,7 +239,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   n: 새 브랜치 생성
   M: 현재 브랜치에 병합
   r: 체크아웃된 브랜치를 이 브랜치에 리베이스
-  d: 브랜치 삭제
+  d: Delete remote tag
   u: Set as upstream of checked-out branch
   g: View reset options
   w: View worktree options
@@ -309,7 +309,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
 
   <space>: 체크아웃
-  d: 태그 삭제
+  d: View delete options
   P: 태그를 push
   n: 태그를 생성
   g: View reset options
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index 1593fc56d..7d366fcda 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -90,7 +90,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <c-y>: Kopieer de URL van het pull-verzoek naar het klembord
   c: Uitchecken bij naam
   F: Forceer checkout
-  d: Verwijder branch
+  d: View delete options
   r: Rebase branch
   M: Merge in met huidige checked out branch
   f: Fast-forward deze branch vanaf zijn upstream
@@ -233,7 +233,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   n: Nieuwe branch
   M: Merge in met huidige checked out branch
   r: Rebase branch
-  d: Verwijder branch
+  d: Delete remote tag
   u: Stel in als upstream van uitgecheckte branch
   g: Bekijk reset opties
   w: View worktree options
@@ -332,7 +332,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
 
   <space>: Uitchecken
-  d: Verwijder tag
+  d: View delete options
   P: Push tag
   n: Creëer tag
   g: Bekijk reset opties
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 01a7a621c..f4f59228e 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -106,7 +106,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <c-y>: Skopiuj adres URL żądania pobrania do schowka
   c: Przełącz używając nazwy
   F: Wymuś przełączenie
-  d: Usuń gałąź
+  d: View delete options
   r: Zmiana bazy gałęzi
   M: Scal do obecnej gałęzi
   f: Fast-forward this branch from its upstream
@@ -232,7 +232,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   n: Nowa gałąź
   M: Scal do obecnej gałęzi
   r: Zmiana bazy gałęzi
-  d: Usuń gałąź
+  d: Delete remote tag
   u: Set as upstream of checked-out branch
   g: Wyświetl opcje resetu
   w: View worktree options
@@ -325,7 +325,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
 
   <space>: Przełącz
-  d: Delete tag
+  d: View delete options
   P: Push tag
   n: Create tag
   g: Wyświetl opcje resetu
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index 6ae627bc9..9a03d2935 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -181,7 +181,7 @@ _Связки клавиш_
   <c-y>: Скопировать URL запроса на принятие изменений в буфер обмена
   c: Переключить по названию
   F: Принудительное переключение
-  d: Удалить ветку
+  d: View delete options
   r: Перебазировать переключённую ветку на эту ветку
   M: Слияние с текущей переключённой веткой
   f: Перемотать эту ветку вперёд из её upstream-ветки
@@ -277,7 +277,7 @@ _Связки клавиш_
 
 
   <space>: Переключить
-  d: Удалить тег
+  d: View delete options
   P: Отправить тег
   n: Создать тег
   g: Просмотреть параметры сброса
@@ -294,7 +294,7 @@ _Связки клавиш_
   n: Новая ветка
   M: Слияние с текущей переключённой веткой
   r: Перебазировать переключённую ветку на эту ветку
-  d: Удалить ветку
+  d: Delete remote tag
   u: Установить как upstream-ветку переключённую ветку
   g: Просмотреть параметры сброса
   w: View worktree options
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index 924607639..6f60cb644 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -84,7 +84,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   <c-y>: 将抓取请求 URL 复制到剪贴板
   c: 按名称检出
   F: 强制检出
-  d: 删除分支
+  d: View delete options
   r: 将已检出的分支变基到该分支
   M: 合并到当前检出的分支
   f: 从上游快进此分支
@@ -234,7 +234,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
 
 
   <space>: 检出
-  d: 删除标签
+  d: View delete options
   P: 推送标签
   n: 创建标签
   g: 查看重置选项
@@ -334,7 +334,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
   n: 新分支
   M: 合并到当前检出的分支
   r: 将已检出的分支变基到该分支
-  d: 删除分支
+  d: Delete remote tag
   u: 设置为检出分支的上游
   g: 查看重置选项
   w: View worktree options
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 4103a88ec..eb82367b5 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -256,7 +256,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
   <c-y>: 複製拉取請求的 URL 到剪貼板
   c: 根據名稱檢出
   F: 強制檢出
-  d: 刪除分支
+  d: View delete options
   r: 將已檢出的分支變基至此分支
   M: 合併到當前檢出的分支
   f: 從上游快進此分支
@@ -273,7 +273,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
 
 
   <space>: 檢出
-  d: 刪除標籤
+  d: View delete options
   P: 推送標籤
   n: 建立標籤
   g: 檢視重設選項
@@ -344,7 +344,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
   n: 新分支
   M: 合併到當前檢出的分支
   r: 將已檢出的分支變基至此分支
-  d: 刪除分支
+  d: Delete remote tag
   u: 將此分支設為當前分支之上游
   g: 檢視重設選項
   w: View worktree options
diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go
index 24244080b..b8d562dae 100644
--- a/pkg/commands/git_commands/branch.go
+++ b/pkg/commands/git_commands/branch.go
@@ -85,8 +85,8 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
 	return "", err
 }
 
-// Delete delete branch
-func (self *BranchCommands) Delete(branch string, force bool) error {
+// LocalDelete delete branch locally
+func (self *BranchCommands) LocalDelete(branch string, force bool) error {
 	cmdArgs := NewGitCmd("branch").
 		ArgIfElse(force, "-D", "-d").
 		Arg(branch).
diff --git a/pkg/commands/git_commands/branch_test.go b/pkg/commands/git_commands/branch_test.go
index dee2b03c8..b94f700cc 100644
--- a/pkg/commands/git_commands/branch_test.go
+++ b/pkg/commands/git_commands/branch_test.go
@@ -93,7 +93,7 @@ func TestBranchDeleteBranch(t *testing.T) {
 		t.Run(s.testName, func(t *testing.T) {
 			instance := buildBranchCommands(commonDeps{runner: s.runner})
 
-			s.test(instance.Delete("test", s.force))
+			s.test(instance.LocalDelete("test", s.force))
 			s.runner.CheckForMissingCalls()
 		})
 	}
diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go
index b9f20fb3a..ce8f79442 100644
--- a/pkg/commands/git_commands/remote.go
+++ b/pkg/commands/git_commands/remote.go
@@ -56,6 +56,14 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
 	return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
 }
 
+func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
+	cmdArgs := NewGitCmd("push").
+		Arg(remoteName, "--delete", tagName).
+		ToArgv()
+
+	return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
+}
+
 // CheckRemoteBranchExists Returns remote branch
 func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
 	cmdArgs := NewGitCmd("show-ref").
diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go
index 2517ce380..0656e1e19 100644
--- a/pkg/commands/git_commands/tag.go
+++ b/pkg/commands/git_commands/tag.go
@@ -41,7 +41,7 @@ func (self *TagCommands) HasTag(tagName string) bool {
 	return self.cmd.New(cmdArgs).Run() == nil
 }
 
-func (self *TagCommands) Delete(tagName string) error {
+func (self *TagCommands) LocalDelete(tagName string) error {
 	cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
 		ToArgv()
 
diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go
index cc45d833a..1430ad239 100644
--- a/pkg/gui/controllers.go
+++ b/pkg/gui/controllers.go
@@ -86,6 +86,7 @@ func (gui *Gui) resetHelpersAndControllers() {
 		Files:           helpers.NewFilesHelper(helperCommon),
 		WorkingTree:     helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
 		Tags:            helpers.NewTagsHelper(helperCommon, commitsHelper),
+		BranchesHelper:  helpers.NewBranchesHelper(helperCommon),
 		GPG:             helpers.NewGpgHelper(helperCommon),
 		MergeAndRebase:  rebaseHelper,
 		MergeConflicts:  mergeConflictsHelper,
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index e7023959f..84d9e838e 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -70,7 +70,8 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
 		{
 			Key:         opts.GetKey(opts.Config.Universal.Remove),
 			Handler:     self.checkSelectedAndReal(self.delete),
-			Description: self.c.Tr.DeleteBranch,
+			Description: self.c.Tr.ViewDeleteOptions,
+			OpensMenu:   true,
 		},
 		{
 			Key:         opts.GetKey(opts.Config.Branches.RebaseBranch),
@@ -316,19 +317,6 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
 	return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
 }
 
-func (self *BranchesController) delete(branch *models.Branch) error {
-	checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
-	if checkedOutBranch.Name == branch.Name {
-		return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
-	}
-
-	if self.checkedOutByOtherWorktree(branch) {
-		return self.promptWorktreeBranchDelete(branch)
-	}
-
-	return self.deleteWithForce(branch, false)
-}
-
 func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
 	return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
 }
@@ -371,18 +359,34 @@ func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *model
 	})
 }
 
-func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, force bool) error {
-	title := self.c.Tr.DeleteBranch
-	var templateStr string
-	if force {
-		templateStr = self.c.Tr.ForceDeleteBranchMessage
-	} else {
-		templateStr = self.c.Tr.DeleteBranchMessage
+func (self *BranchesController) localDelete(branch *models.Branch) error {
+	if self.checkedOutByOtherWorktree(branch) {
+		return self.promptWorktreeBranchDelete(branch)
 	}
+
+	return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
+		self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
+		err := self.c.Git().Branch.LocalDelete(branch.Name, false)
+		if err != nil && strings.Contains(err.Error(), "git branch -D ") {
+			return self.forceDelete(branch)
+		}
+		if err != nil {
+			return self.c.Error(err)
+		}
+		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
+	})
+}
+
+func (self *BranchesController) remoteDelete(branch *models.Branch) error {
+	return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.Name)
+}
+
+func (self *BranchesController) forceDelete(branch *models.Branch) error {
+	title := self.c.Tr.ForceDeleteBranchTitle
 	message := utils.ResolvePlaceholderString(
-		templateStr,
+		self.c.Tr.ForceDeleteBranchMessage,
 		map[string]string{
-			"selectedBranchName": selectedBranch.Name,
+			"selectedBranchName": branch.Name,
 		},
 	)
 
@@ -390,19 +394,60 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
 		Title:  title,
 		Prompt: message,
 		HandleConfirm: func() error {
-			self.c.LogAction(self.c.Tr.Actions.DeleteBranch)
-			if err := self.c.Git().Branch.Delete(selectedBranch.Name, force); err != nil {
-				errMessage := err.Error()
-				if !force && strings.Contains(errMessage, "git branch -D ") {
-					return self.deleteWithForce(selectedBranch, true)
-				}
-				return self.c.ErrorMsg(errMessage)
+			if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
+				return self.c.ErrorMsg(err.Error())
 			}
 			return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
 		},
 	})
 }
 
+func (self *BranchesController) delete(branch *models.Branch) error {
+	menuItems := []*types.MenuItem{}
+	checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
+
+	localDeleteItem := &types.MenuItem{
+		Label: self.c.Tr.DeleteLocalBranch,
+		Key:   'c',
+		OnPress: func() error {
+			return self.localDelete(branch)
+		},
+	}
+	if checkedOutBranch.Name == branch.Name {
+		localDeleteItem = &types.MenuItem{
+			Label:   self.c.Tr.DeleteLocalBranch,
+			Key:     'c',
+			Tooltip: self.c.Tr.CantDeleteCheckOutBranch,
+			OnPress: func() error {
+				return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
+			},
+		}
+	}
+	menuItems = append(menuItems, localDeleteItem)
+
+	if branch.IsTrackingRemote() && !branch.UpstreamGone {
+		menuItems = append(menuItems, &types.MenuItem{
+			Label: self.c.Tr.DeleteRemoteBranch,
+			Key:   'r',
+			OnPress: func() error {
+				return self.remoteDelete(branch)
+			},
+		})
+	}
+
+	menuTitle := utils.ResolvePlaceholderString(
+		self.c.Tr.DeleteBranchTitle,
+		map[string]string{
+			"selectedBranchName": branch.Name,
+		},
+	)
+
+	return self.c.Menu(types.CreateMenuOptions{
+		Title: menuTitle,
+		Items: menuItems,
+	})
+}
+
 func (self *BranchesController) merge() error {
 	selectedBranchName := self.context().GetSelected().Name
 	return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go
new file mode 100644
index 000000000..6bc336e8e
--- /dev/null
+++ b/pkg/gui/controllers/helpers/branches_helper.go
@@ -0,0 +1,46 @@
+package helpers
+
+import (
+	"github.com/jesseduffield/gocui"
+	"github.com/jesseduffield/lazygit/pkg/gui/types"
+	"github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+type BranchesHelper struct {
+	c *HelperCommon
+}
+
+func NewBranchesHelper(c *HelperCommon) *BranchesHelper {
+	return &BranchesHelper{
+		c: c,
+	}
+}
+
+func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName string) error {
+	title := utils.ResolvePlaceholderString(
+		self.c.Tr.DeleteBranchTitle,
+		map[string]string{
+			"selectedBranchName": branchName,
+		},
+	)
+	prompt := utils.ResolvePlaceholderString(
+		self.c.Tr.DeleteRemoteBranchPrompt,
+		map[string]string{
+			"selectedBranchName": branchName,
+			"upstream":           remoteName,
+		},
+	)
+	return self.c.Confirm(types.ConfirmOpts{
+		Title:  title,
+		Prompt: prompt,
+		HandleConfirm: func() error {
+			return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
+				self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
+				if err := self.c.Git().Remote.DeleteRemoteBranch(task, remoteName, branchName); err != nil {
+					return self.c.Error(err)
+				}
+				return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
+			})
+		},
+	})
+}
diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go
index e87b57eb0..22a7ea91b 100644
--- a/pkg/gui/controllers/helpers/helpers.go
+++ b/pkg/gui/controllers/helpers/helpers.go
@@ -22,6 +22,7 @@ type Helpers struct {
 	Suggestions    *SuggestionsHelper
 	Files          *FilesHelper
 	WorkingTree    *WorkingTreeHelper
+	BranchesHelper *BranchesHelper
 	Tags           *TagsHelper
 	MergeAndRebase *MergeAndRebaseHelper
 	MergeConflicts *MergeConflictsHelper
diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go
index 529b00a90..ffb55d5ca 100644
--- a/pkg/gui/controllers/remote_branches_controller.go
+++ b/pkg/gui/controllers/remote_branches_controller.go
@@ -1,10 +1,8 @@
 package controllers
 
 import (
-	"fmt"
 	"strings"
 
-	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -53,7 +51,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
 		{
 			Key:         opts.GetKey(opts.Config.Universal.Remove),
 			Handler:     self.checkSelected(self.delete),
-			Description: self.c.Tr.DeleteBranch,
+			Description: self.c.Tr.DeleteRemoteTag,
 		},
 		{
 			Key:         opts.GetKey(opts.Config.Branches.SetUpstream),
@@ -112,23 +110,7 @@ func (self *RemoteBranchesController) checkSelected(callback func(*models.Remote
 }
 
 func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch) error {
-	message := fmt.Sprintf("%s '%s'?", self.c.Tr.DeleteRemoteBranchMessage, selectedBranch.FullName())
-
-	return self.c.Confirm(types.ConfirmOpts{
-		Title:  self.c.Tr.DeleteRemoteBranch,
-		Prompt: message,
-		HandleConfirm: func() error {
-			return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
-				self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
-				err := self.c.Git().Remote.DeleteRemoteBranch(task, selectedBranch.RemoteName, selectedBranch.Name)
-				if err != nil {
-					_ = self.c.Error(err)
-				}
-
-				return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
-			})
-		},
-	})
+	return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranch.RemoteName, selectedBranch.Name)
 }
 
 func (self *RemoteBranchesController) merge(selectedBranch *models.RemoteBranch) error {
diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go
index 80248391e..91d590c32 100644
--- a/pkg/gui/controllers/tags_controller.go
+++ b/pkg/gui/controllers/tags_controller.go
@@ -34,7 +34,8 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
 		{
 			Key:         opts.GetKey(opts.Config.Universal.Remove),
 			Handler:     self.withSelectedTag(self.delete),
-			Description: self.c.Tr.DeleteTag,
+			Description: self.c.Tr.ViewDeleteOptions,
+			OpensMenu:   true,
 		},
 		{
 			Key:         opts.GetKey(opts.Config.Branches.PushTag),
@@ -88,27 +89,93 @@ func (self *TagsController) checkout(tag *models.Tag) error {
 	return self.c.PushContext(self.c.Contexts().Branches)
 }
 
-func (self *TagsController) delete(tag *models.Tag) error {
-	prompt := utils.ResolvePlaceholderString(
-		self.c.Tr.DeleteTagPrompt,
+func (self *TagsController) localDelete(tag *models.Tag) error {
+	return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
+		self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag)
+		if err := self.c.Git().Tag.LocalDelete(tag.Name); err != nil {
+			return self.c.Error(err)
+		}
+		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
+	})
+}
+
+func (self *TagsController) remoteDelete(tag *models.Tag) error {
+	title := utils.ResolvePlaceholderString(
+		self.c.Tr.SelectRemoteTagUpstream,
 		map[string]string{
 			"tagName": tag.Name,
 		},
 	)
 
-	return self.c.Confirm(types.ConfirmOpts{
-		Title:  self.c.Tr.DeleteTagTitle,
-		Prompt: prompt,
-		HandleConfirm: func() error {
-			self.c.LogAction(self.c.Tr.Actions.DeleteTag)
-			if err := self.c.Git().Tag.Delete(tag.Name); err != nil {
-				return self.c.Error(err)
-			}
-			return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
+	return self.c.Prompt(types.PromptOpts{
+		Title:               title,
+		InitialContent:      "origin",
+		FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
+		HandleConfirm: func(upstream string) error {
+			confirmTitle := utils.ResolvePlaceholderString(
+				self.c.Tr.DeleteTagTitle,
+				map[string]string{
+					"tagName": tag.Name,
+				},
+			)
+			confirmPrompt := utils.ResolvePlaceholderString(
+				self.c.Tr.DeleteRemoteTagPrompt,
+				map[string]string{
+					"tagName":  tag.Name,
+					"upstream": upstream,
+				},
+			)
+
+			return self.c.Confirm(types.ConfirmOpts{
+				Title:  confirmTitle,
+				Prompt: confirmPrompt,
+				HandleConfirm: func() error {
+					return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(t gocui.Task) error {
+						self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag)
+						if err := self.c.Git().Remote.DeleteRemoteTag(t, upstream, tag.Name); err != nil {
+							return self.c.Error(err)
+						}
+						self.c.Toast(self.c.Tr.RemoteTagDeletedMessage)
+						return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
+					})
+				},
+			})
 		},
 	})
 }
 
+func (self *TagsController) delete(tag *models.Tag) error {
+	menuTitle := utils.ResolvePlaceholderString(
+		self.c.Tr.DeleteTagTitle,
+		map[string]string{
+			"tagName": tag.Name,
+		},
+	)
+
+	menuItems := []*types.MenuItem{
+		{
+			Label: self.c.Tr.DeleteLocalTag,
+			Key:   'c',
+			OnPress: func() error {
+				return self.localDelete(tag)
+			},
+		},
+		{
+			Label:     self.c.Tr.DeleteRemoteTag,
+			Key:       'r',
+			OpensMenu: true,
+			OnPress: func() error {
+				return self.remoteDelete(tag)
+			},
+		},
+	}
+
+	return self.c.Menu(types.CreateMenuOptions{
+		Title: menuTitle,
+		Items: menuItems,
+	})
+}
+
 func (self *TagsController) push(tag *models.Tag) error {
 	title := utils.ResolvePlaceholderString(
 		self.c.Tr.PushTagTitle,
diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go
index 2a494c97e..b7ec74811 100644
--- a/pkg/i18n/chinese.go
+++ b/pkg/i18n/chinese.go
@@ -82,8 +82,6 @@ func chineseTranslationSet() TranslationSet {
 		BranchName:                          "分支名称",
 		NewBranchNameBranchOff:              "新分支名称(基于 {{.branchName}})",
 		CantDeleteCheckOutBranch:            "您不能删除已检出的分支!",
-		DeleteBranch:                        "删除分支",
-		DeleteBranchMessage:                 "您确定要删除分支 {{.selectedBranchName}} 吗?",
 		ForceDeleteBranchMessage:            "{{.selectedBranchName}} 还没有被完全合并。您确定要删除它吗?",
 		RebaseBranch:                        "将已检出的分支变基到该分支",
 		CantRebaseOntoSelf:                  "您不能将分支变基到其自身",
@@ -309,9 +307,6 @@ func chineseTranslationSet() TranslationSet {
 		TagMessageTitle:                     "标签消息",
 		AnnotatedTag:                        "附注标签",
 		LightweightTag:                      "轻量标签",
-		DeleteTag:                           "删除标签",
-		DeleteTagTitle:                      "删除标签",
-		DeleteTagPrompt:                     "您确定要删除标签 {{.tagName}} 吗?",
 		PushTagTitle:                        "将 {{.tagName}} 推送到远程仓库:",
 		PushTag:                             "推送标签",
 		CreateTag:                           "创建标签",
@@ -457,7 +452,6 @@ func chineseTranslationSet() TranslationSet {
 			CheckoutTag:                       "检出标签",
 			CheckoutBranch:                    "检出分支",
 			ForceCheckoutBranch:               "强制检出分支",
-			DeleteBranch:                      "删除分支",
 			Merge:                             "合并",
 			RebaseBranch:                      "变基分支",
 			RenameBranch:                      "重命名分支",
@@ -521,7 +515,6 @@ func chineseTranslationSet() TranslationSet {
 			BulkUpdateSubmodules:              "批量更新子模块",
 			BulkDeinitialiseSubmodules:        "批量取消初始化子模块",
 			UpdateSubmodule:                   "更新子模块",
-			DeleteTag:                         "删除标签",
 			PushTag:                           "推送标签",
 			NukeWorkingTree:                   "Nuke 工作树",
 			DiscardUnstagedFileChanges:        "放弃未暂存的文件更改",
diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go
index 79e1f89ec..284a81b60 100644
--- a/pkg/i18n/dutch.go
+++ b/pkg/i18n/dutch.go
@@ -48,8 +48,6 @@ func dutchTranslationSet() TranslationSet {
 		BranchName:                          "Branch naam",
 		NewBranchNameBranchOff:              "Nieuw branch naam (Branch is afgeleid van '{{.branchName}}')",
 		CantDeleteCheckOutBranch:            "Je kan een uitgecheckte branch niet verwijderen!",
-		DeleteBranch:                        "Verwijder branch",
-		DeleteBranchMessage:                 "Weet je zeker dat je branch '{{.selectedBranchName}}' wilt verwijderen?",
 		ForceDeleteBranchMessage:            "Weet je zeker dat je branch '{{.selectedBranchName}}' geforceerd wil verwijderen?",
 		RebaseBranch:                        "Rebase branch",
 		CantRebaseOntoSelf:                  "Je kan niet een branch rebasen op zichzelf",
@@ -263,10 +261,7 @@ func dutchTranslationSet() TranslationSet {
 		SetUpstreamMessage:                  "Weet je zeker dat je de upstream branch van '{{.checkedOut}}' naar '{{.selected}}' wilt zetten",
 		EditRemote:                          "Wijzig remote",
 		TagCommit:                           "Tag commit",
-		TagNameTitle:                        "Tag naam",
-		DeleteTag:                           "Verwijder tag",
-		DeleteTagTitle:                      "Verwijder tag",
-		DeleteTagPrompt:                     "Weet je zeker dat je '{{.tagName}}' wil verwijderen?",
+		TagNameTitle:                        "Tag naam:",
 		PushTagTitle:                        "Remote om tag '{{.tagName}}' te pushen naar:",
 		PushTag:                             "Push tag",
 		CreateTag:                           "Creëer tag",
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 4fb86573f..109f13e42 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -68,8 +68,11 @@ type TranslationSet struct {
 	BranchName                          string
 	NewBranchNameBranchOff              string
 	CantDeleteCheckOutBranch            string
-	DeleteBranch                        string
-	DeleteBranchMessage                 string
+	DeleteBranchTitle                   string
+	DeleteLocalBranch                   string
+	DeleteRemoteBranchOption            string
+	DeleteRemoteBranchPrompt            string
+	ForceDeleteBranchTitle              string
 	ForceDeleteBranchMessage            string
 	RebaseBranch                        string
 	CantRebaseOntoSelf                  string
@@ -292,6 +295,7 @@ type TranslationSet struct {
 	DiscardUntrackedFiles               string
 	DiscardStagedChanges                string
 	HardReset                           string
+	ViewDeleteOptions                   string
 	ViewResetOptions                    string
 	CreateFixupCommit                   string
 	CreateFixupCommitDescription        string
@@ -352,9 +356,12 @@ type TranslationSet struct {
 	TagMessageTitle                     string
 	LightweightTag                      string
 	AnnotatedTag                        string
-	DeleteTag                           string
 	DeleteTagTitle                      string
-	DeleteTagPrompt                     string
+	DeleteLocalTag                      string
+	DeleteRemoteTag                     string
+	SelectRemoteTagUpstream             string
+	DeleteRemoteTagPrompt               string
+	RemoteTagDeletedMessage             string
 	PushTagTitle                        string
 	PushTag                             string
 	CreateTag                           string
@@ -633,6 +640,7 @@ type Actions struct {
 	CheckoutTag                       string
 	CheckoutBranch                    string
 	ForceCheckoutBranch               string
+	DeleteLocalBranch                 string
 	DeleteBranch                      string
 	Merge                             string
 	RebaseBranch                      string
@@ -715,7 +723,8 @@ type Actions struct {
 	UpdateSubmodule                   string
 	CreateLightweightTag              string
 	CreateAnnotatedTag                string
-	DeleteTag                         string
+	DeleteLocalTag                    string
+	DeleteRemoteTag                   string
 	PushTag                           string
 	NukeWorkingTree                   string
 	DiscardUnstagedFileChanges        string
@@ -829,8 +838,11 @@ func EnglishTranslationSet() TranslationSet {
 		BranchName:                          "Branch name",
 		NewBranchNameBranchOff:              "New branch name (branch is off of '{{.branchName}}')",
 		CantDeleteCheckOutBranch:            "You cannot delete the checked out branch!",
-		DeleteBranch:                        "Delete branch",
-		DeleteBranchMessage:                 "Are you sure you want to delete the branch '{{.selectedBranchName}}'?",
+		DeleteBranchTitle:                   "Delete branch '{{.selectedBranchName}}'?",
+		DeleteLocalBranch:                   "Delete local branch",
+		DeleteRemoteBranchOption:            "Delete remote branch",
+		DeleteRemoteBranchPrompt:            "Are you sure you want to delete the remote branch '{{.selectedBranchName}}' from '{{.upstream}}'?",
+		ForceDeleteBranchTitle:              "Force delete branch",
 		ForceDeleteBranchMessage:            "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?",
 		RebaseBranch:                        "Rebase checked-out branch onto this branch",
 		CantRebaseOntoSelf:                  "You cannot rebase a branch onto itself",
@@ -1056,6 +1068,7 @@ func EnglishTranslationSet() TranslationSet {
 		DiscardUntrackedFiles:               "Discard untracked files",
 		DiscardStagedChanges:                "Discard staged changes",
 		HardReset:                           "Hard reset",
+		ViewDeleteOptions:                   "View delete options",
 		ViewResetOptions:                    `View reset options`,
 		CreateFixupCommitDescription:        `Create fixup commit for this commit`,
 		SquashAboveCommits:                  `Squash all 'fixup!' commits above selected commit (autosquash)`,
@@ -1102,7 +1115,7 @@ func EnglishTranslationSet() TranslationSet {
 		EditRemoteUrl:                       `Enter updated remote url for {{.remoteName}}:`,
 		RemoveRemote:                        `Remove remote`,
 		RemoveRemotePrompt:                  "Are you sure you want to remove remote",
-		DeleteRemoteBranch:                  "Delete Remote Branch",
+		DeleteRemoteBranch:                  "Delete remote branch",
 		DeleteRemoteBranchMessage:           "Are you sure you want to delete remote branch",
 		SetAsUpstream:                       "Set as upstream of checked-out branch",
 		SetUpstream:                         "Set upstream of selected branch",
@@ -1116,9 +1129,12 @@ func EnglishTranslationSet() TranslationSet {
 		TagMessageTitle:                     "Tag description",
 		AnnotatedTag:                        "Annotated tag",
 		LightweightTag:                      "Lightweight tag",
-		DeleteTag:                           "Delete tag",
-		DeleteTagTitle:                      "Delete tag",
-		DeleteTagPrompt:                     "Are you sure you want to delete tag '{{.tagName}}'?",
+		DeleteTagTitle:                      "Delete tag '{{.tagName}}'?",
+		DeleteLocalTag:                      "Delete local tag",
+		DeleteRemoteTag:                     "Delete remote tag",
+		RemoteTagDeletedMessage:             "Remote tag deleted",
+		SelectRemoteTagUpstream:             "Remote from which to remove tag '{{.tagName}}':",
+		DeleteRemoteTagPrompt:               "Are you sure you want to delete the remote tag '{{.tagName}}' from '{{.upstream}}'?",
 		PushTagTitle:                        "Remote to push tag '{{.tagName}}' to:",
 		PushTag:                             "Push tag",
 		CreateTag:                           "Create tag",
@@ -1355,6 +1371,7 @@ func EnglishTranslationSet() TranslationSet {
 			CheckoutTag:                       "Checkout tag",
 			CheckoutBranch:                    "Checkout branch",
 			ForceCheckoutBranch:               "Force checkout branch",
+			DeleteLocalBranch:                 "Delete local branch",
 			DeleteBranch:                      "Delete branch",
 			Merge:                             "Merge",
 			RebaseBranch:                      "Rebase branch",
@@ -1436,7 +1453,8 @@ func EnglishTranslationSet() TranslationSet {
 			BulkUpdateSubmodules:              "Bulk update submodules",
 			BulkDeinitialiseSubmodules:        "Bulk deinitialise submodules",
 			UpdateSubmodule:                   "Update submodule",
-			DeleteTag:                         "Delete tag",
+			DeleteLocalTag:                    "Delete local tag",
+			DeleteRemoteTag:                   "Delete remote tag",
 			PushTag:                           "Push tag",
 			NukeWorkingTree:                   "Nuke working tree",
 			DiscardUnstagedFileChanges:        "Discard unstaged file changes",
diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go
index 3cbc6e225..1b8c53065 100644
--- a/pkg/i18n/japanese.go
+++ b/pkg/i18n/japanese.go
@@ -73,9 +73,6 @@ func japaneseTranslationSet() TranslationSet {
 		BranchName:               "ブランチ名",
 		NewBranchNameBranchOff:   "新規ブランチ名 ('{{.branchName}}' に作成)",
 		CantDeleteCheckOutBranch: "チェックアウト中のブランチは削除できません!",
-		DeleteBranch:             "ブランチを削除",
-		DeleteBranchMessage:      "ブランチ '{{.selectedBranchName}}' を削除します。よろしいですか?",
-		ForceDeleteBranchMessage: "'{{.selectedBranchName}}' はマージされていません。本当に削除しますか?",
 		// LcRebaseBranch:                      "Rebase checked-out branch onto this branch",
 		CantRebaseOntoSelf:        "ブランチを自分自身にリベースすることはできません。",
 		CantMergeBranchIntoItself: "ブランチを自分自身にマージすることはできません。",
@@ -319,9 +316,6 @@ func japaneseTranslationSet() TranslationSet {
 		TagMessageTitle:        "タグメッセージ",
 		AnnotatedTag:           "注釈付きタグ",
 		LightweightTag:         "軽量タグ",
-		DeleteTag:              "タグを削除",
-		DeleteTagTitle:         "タグを削除",
-		DeleteTagPrompt:        "タグ '{{.tagName}}' を削除します。よろしいですか?",
 		PushTagTitle:           "リモートにタグ '{{.tagName}}' をpush",
 		PushTag:                "タグをpush",
 		CreateTag:              "タグを作成",
@@ -554,7 +548,6 @@ func japaneseTranslationSet() TranslationSet {
 			BulkUpdateSubmodules:              "サブモジュールを一括更新",
 			// BulkDeinitialiseSubmodules:        "Bulk deinitialise submodules",
 			UpdateSubmodule: "サブモジュールを更新",
-			DeleteTag:       "タグを削除",
 			PushTag:         "タグをpush",
 			// NukeWorkingTree:                   "Nuke working tree",
 			// DiscardUnstagedFileChanges:        "Discard unstaged file changes",
diff --git a/pkg/i18n/korean.go b/pkg/i18n/korean.go
index f22e6cc47..32c29db14 100644
--- a/pkg/i18n/korean.go
+++ b/pkg/i18n/korean.go
@@ -72,8 +72,6 @@ func koreanTranslationSet() TranslationSet {
 		BranchName:                          "브랜치 이름",
 		NewBranchNameBranchOff:              "새 브랜치 이름 (branch is off of '{{.branchName}}')",
 		CantDeleteCheckOutBranch:            "체크아웃하는 브랜치는 삭제할 수 없습니다!",
-		DeleteBranch:                        "브랜치 삭제",
-		DeleteBranchMessage:                 "정말로 브랜치 '{{.selectedBranchName}}' 를 삭제하시겠습니까?",
 		ForceDeleteBranchMessage:            "'{{.selectedBranchName}}'는 완전히 병합되지 않았습니다. 정말 삭제하시겠습니까?",
 		RebaseBranch:                        "체크아웃된 브랜치를 이 브랜치에 리베이스",
 		CantRebaseOntoSelf:                  "브랜치를 자기 자신에게 리베이스할 수는 없습니다.",
@@ -314,9 +312,6 @@ func koreanTranslationSet() TranslationSet {
 		TagMessageTitle:            "태그 메시지",
 		AnnotatedTag:               "Annotated tag",
 		LightweightTag:             "Lightweight tag",
-		DeleteTag:                  "태그 삭제",
-		DeleteTagTitle:             "태그 삭제",
-		DeleteTagPrompt:            "정말로 태그 '{{.tagName}}' 를 삭제하시겠습니까?",
 		PushTagTitle:               "원격에 태그 '{{.tagName}}' 를 푸시",
 		PushTag:                    "태그를 push",
 		CreateTag:                  "태그를 생성",
@@ -478,7 +473,6 @@ func koreanTranslationSet() TranslationSet {
 			CheckoutTag:                       "태그 체크아웃",
 			CheckoutBranch:                    "브랜치 체크아웃",
 			ForceCheckoutBranch:               "브랜치 Force 체크아웃",
-			DeleteBranch:                      "브랜치 삭제",
 			Merge:                             "병합",
 			RebaseBranch:                      "브랜치 리베이스",
 			RenameBranch:                      "브랜치 이름 변경",
@@ -552,7 +546,6 @@ func koreanTranslationSet() TranslationSet {
 			BulkUpdateSubmodules:              "Bulk update submodules",
 			BulkDeinitialiseSubmodules:        "Bulk deinitialise submodules",
 			UpdateSubmodule:                   "서브모듈 업데이트",
-			DeleteTag:                         "태그 삭제",
 			PushTag:                           "태그 푸시g",
 			NukeWorkingTree:                   "Nuke working tree",
 			DiscardUnstagedFileChanges:        "Unstaged 파일 변경사항 버리기",
diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go
index a2ff1bb12..5ec7b9548 100644
--- a/pkg/i18n/polish.go
+++ b/pkg/i18n/polish.go
@@ -43,8 +43,6 @@ func polishTranslationSet() TranslationSet {
 		BranchName:                          "Nazwa gałęzi",
 		NewBranchNameBranchOff:              "Nazwa nowej gałęzi (gałąź na bazie '{{.branchName}}')",
 		CantDeleteCheckOutBranch:            "Nie możesz usunąć obecnie przełączonej gałęzi!",
-		DeleteBranch:                        "Usuń gałąź",
-		DeleteBranchMessage:                 "Jesteś pewien, że chcesz usunąć gałąź '{{.selectedBranchName}}' ?",
 		ForceDeleteBranchMessage:            "Na pewno wymusić usunięcie gałęzi '{{.selectedBranchName}}'?",
 		RebaseBranch:                        "Zmiana bazy gałęzi",
 		CantRebaseOntoSelf:                  "Nie możesz zmienić bazy gałęzi na nią samą",
diff --git a/pkg/i18n/russian.go b/pkg/i18n/russian.go
index ae21430e6..fc8806e73 100644
--- a/pkg/i18n/russian.go
+++ b/pkg/i18n/russian.go
@@ -91,8 +91,6 @@ func RussianTranslationSet() TranslationSet {
 		BranchName:                          "Название ветки",
 		NewBranchNameBranchOff:              "Название новой ветки (Ветка с '{{.branchName}}')",
 		CantDeleteCheckOutBranch:            "Невозможно удалить переключённую ветку!",
-		DeleteBranch:                        "Удалить ветку",
-		DeleteBranchMessage:                 "Вы уверены, что хотите удалить ветку '{{.selectedBranchName}}'?",
 		ForceDeleteBranchMessage:            "'{{.selectedBranchName}}' не полностью слилась. Вы уверены, что хотите удалить его?",
 		RebaseBranch:                        "Перебазировать переключённую ветку на эту ветку",
 		CantRebaseOntoSelf:                  "Невозможно перебазировать ветку на себя",
@@ -375,9 +373,7 @@ func RussianTranslationSet() TranslationSet {
 		TagMessageTitle:                     "Сообщения тега",
 		AnnotatedTag:                        "Аннотированный тег",
 		LightweightTag:                      "Легковесный тег",
-		DeleteTag:                           "Удалить тег",
 		DeleteTagTitle:                      "Удалить тег",
-		DeleteTagPrompt:                     "Вы уверены, что хотите удалить тег '{{.tagName}}'?",
 		PushTagTitle:                        "Удалённый репозитории для отправки тега '{{.tagName}}' в:",
 		PushTag:                             "Отправить тег",
 		CreateTag:                           "Создать тег",
@@ -647,7 +643,6 @@ func RussianTranslationSet() TranslationSet {
 			BulkUpdateSubmodules:              "Массовое обновление подмодулей",
 			BulkDeinitialiseSubmodules:        "Массовая деинициализация подмодулей",
 			UpdateSubmodule:                   "Обновить подмодуль",
-			DeleteTag:                         "Удалить тег",
 			PushTag:                           "Отправить тег",
 			NukeWorkingTree:                   "Уничтожить рабочее дерево",
 			DiscardUnstagedFileChanges:        "Отменить непроиндексированные изменения файла",
diff --git a/pkg/i18n/traditional_chinese.go b/pkg/i18n/traditional_chinese.go
index 3ccac945a..8c5dc9599 100644
--- a/pkg/i18n/traditional_chinese.go
+++ b/pkg/i18n/traditional_chinese.go
@@ -124,8 +124,6 @@ func traditionalChineseTranslationSet() TranslationSet {
 		BranchName:                          "分支名稱",
 		NewBranchNameBranchOff:              "新的分支名稱 (根據 '{{.branchName}}' 分支創建)",
 		CantDeleteCheckOutBranch:            "你不能刪除已檢出的分支!",
-		DeleteBranch:                        "刪除分支",
-		DeleteBranchMessage:                 "你確定要刪除 '{{.selectedBranchName}}' 分支嗎?",
 		ForceDeleteBranchMessage:            "'{{.selectedBranchName}}' 分支尚未完全合併。你確定要刪除嗎?",
 		RebaseBranch:                        "將已檢出的分支變基至此分支",
 		CantRebaseOntoSelf:                  "你不能將分支變基至自己",
@@ -402,9 +400,6 @@ func traditionalChineseTranslationSet() TranslationSet {
 		TagMessageTitle:                     "標籤訊息",
 		AnnotatedTag:                        "附註標籤",
 		LightweightTag:                      "輕量標籤",
-		DeleteTag:                           "刪除標籤",
-		DeleteTagTitle:                      "刪除標籤",
-		DeleteTagPrompt:                     "你確定要刪除 '{{.tagName}}' 標籤嗎?",
 		PushTagTitle:                        "推送標籤 '{{.tagName}}' 至遠端:",
 		PushTag:                             "推送標籤",
 		CreateTag:                           "建立標籤",
@@ -674,7 +669,6 @@ func traditionalChineseTranslationSet() TranslationSet {
 			BulkUpdateSubmodules:              "批量更新子模塊",
 			BulkDeinitialiseSubmodules:        "批量取消初始化子模塊",
 			UpdateSubmodule:                   "更新子模塊",
-			DeleteTag:                         "刪除標籤",
 			PushTag:                           "推送標籤",
 			NukeWorkingTree:                   "清空工作樹",
 			DiscardUnstagedFileChanges:        "放棄未預存的檔案更改",
diff --git a/pkg/integration/components/git.go b/pkg/integration/components/git.go
index f11c3342c..ed327b3ed 100644
--- a/pkg/integration/components/git.go
+++ b/pkg/integration/components/git.go
@@ -18,14 +18,28 @@ func (self *Git) TagNamesAt(ref string, expectedNames []string) *Git {
 	return self.assert([]string{"git", "tag", "--sort=v:refname", "--points-at", ref}, strings.Join(expectedNames, "\n"))
 }
 
+func (self *Git) RemoteTagDeleted(ref string, tagName string) *Git {
+	return self.expect([]string{"git", "ls-remote", ref, fmt.Sprintf("refs/tags/%s", tagName)}, func(s string) (bool, string) {
+		return len(s) == 0, fmt.Sprintf("Expected tag %s to have been removed from %s", tagName, ref)
+	})
+}
+
 func (self *Git) assert(cmdArgs []string, expected string) *Git {
+	self.expect(cmdArgs, func(output string) (bool, string) {
+		return output == expected, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expected, output)
+	})
+
+	return self
+}
+
+func (self *Git) expect(cmdArgs []string, condition func(string) (bool, string)) *Git {
 	self.assertWithRetries(func() (bool, string) {
 		output, err := self.shell.runCommandWithOutput(cmdArgs)
 		if err != nil {
 			return false, fmt.Sprintf("Unexpected error running command: `%v`. Error: %s", cmdArgs, err.Error())
 		}
 		actual := strings.TrimSpace(output)
-		return actual == expected, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expected, actual)
+		return condition(actual)
 	})
 
 	return self
diff --git a/pkg/integration/components/menu_driver.go b/pkg/integration/components/menu_driver.go
index 34d081dfa..7ab42a3e1 100644
--- a/pkg/integration/components/menu_driver.go
+++ b/pkg/integration/components/menu_driver.go
@@ -66,6 +66,12 @@ func (self *MenuDriver) Wait(milliseconds int) *MenuDriver {
 	return self
 }
 
+func (self *MenuDriver) Tooltip(option *TextMatcher) *MenuDriver {
+	self.t.Views().Tooltip().Content(option)
+
+	return self
+}
+
 func (self *MenuDriver) checkNecessaryChecksCompleted() {
 	if !self.hasCheckedTitle {
 		self.t.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().")
diff --git a/pkg/integration/components/shell.go b/pkg/integration/components/shell.go
index 7b0e9ddbf..4ba1bff67 100644
--- a/pkg/integration/components/shell.go
+++ b/pkg/integration/components/shell.go
@@ -162,6 +162,10 @@ func (self *Shell) CreateAnnotatedTag(name string, message string, ref string) *
 	return self.RunCommand([]string{"git", "tag", "-a", name, "-m", message, ref})
 }
 
+func (self *Shell) PushBranch(upstream, branch string) *Shell {
+	return self.RunCommand([]string{"git", "push", "--set-upstream", upstream, branch})
+}
+
 // convenience method for creating a file and adding it
 func (self *Shell) CreateFileAndAdd(fileName string, fileContents string) *Shell {
 	return self.
diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go
index eb4f585dc..c7655da74 100644
--- a/pkg/integration/components/views.go
+++ b/pkg/integration/components/views.go
@@ -222,3 +222,7 @@ func (self *Views) Suggestions() *ViewDriver {
 func (self *Views) Search() *ViewDriver {
 	return self.regularView("search")
 }
+
+func (self *Views) Tooltip() *ViewDriver {
+	return self.regularView("tooltip")
+}
diff --git a/pkg/integration/tests/branch/delete.go b/pkg/integration/tests/branch/delete.go
index 78c21f7a7..cdda78ec6 100644
--- a/pkg/integration/tests/branch/delete.go
+++ b/pkg/integration/tests/branch/delete.go
@@ -6,38 +6,110 @@ import (
 )
 
 var Delete = NewIntegrationTest(NewIntegrationTestArgs{
-	Description:  "Try to delete the checked out branch first (to no avail), and then delete another branch.",
+	Description:  "Try all combination of local and remote branch deletions",
 	ExtraCmdArgs: []string{},
 	Skip:         false,
 	SetupConfig:  func(config *config.AppConfig) {},
 	SetupRepo: func(shell *Shell) {
 		shell.
+			CloneIntoRemote("origin").
 			EmptyCommit("blah").
 			NewBranch("branch-one").
-			NewBranch("branch-two")
+			PushBranch("origin", "branch-one").
+			NewBranch("branch-two").
+			PushBranch("origin", "branch-two").
+			EmptyCommit("deletion blocker").
+			NewBranch("branch-three")
 	},
 	Run: func(t *TestDriver, keys config.KeybindingConfig) {
 		t.Views().Branches().
 			Focus().
 			Lines(
-				MatchesRegexp(`\*.*branch-two`).IsSelected(),
+				MatchesRegexp(`\*.*branch-three`).IsSelected(),
+				MatchesRegexp(`branch-two`),
 				MatchesRegexp(`branch-one`),
 				MatchesRegexp(`master`),
 			).
 			Press(keys.Universal.Remove).
 			Tap(func() {
-				t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("You cannot delete the checked out branch!")).Confirm()
+				t.ExpectPopup().
+					Menu().
+					Tooltip(Contains("You cannot delete the checked out branch!")).
+					Title(Equals("Delete branch 'branch-three'?")).
+					Select(Contains("Delete local branch")).
+					Confirm()
+				t.ExpectPopup().
+					Alert().
+					Title(Equals("Error")).
+					Content(Contains("You cannot delete the checked out branch!")).
+					Confirm()
 			}).
 			SelectNextItem().
 			Press(keys.Universal.Remove).
 			Tap(func() {
-				t.ExpectPopup().Confirmation().
-					Title(Equals("Delete branch")).
-					Content(Contains("Are you sure you want to delete the branch 'branch-one'?")).
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete branch 'branch-two'?")).
+					Select(Contains("Delete local branch")).
+					Confirm()
+			}).
+			Tap(func() {
+				t.ExpectPopup().
+					Confirmation().
+					Title(Equals("Force delete branch")).
+					Content(Equals("'branch-two' is not fully merged. Are you sure you want to delete it?")).
 					Confirm()
 			}).
 			Lines(
-				MatchesRegexp(`\*.*branch-two`),
+				MatchesRegexp(`\*.*branch-three`),
+				MatchesRegexp(`branch-one`).IsSelected(),
+				MatchesRegexp(`master`),
+			).
+			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete branch 'branch-one'?")).
+					Select(Contains("Delete remote branch")).
+					Confirm()
+			}).
+			Tap(func() {
+				t.ExpectPopup().
+					Confirmation().
+					Title(Equals("Delete branch 'branch-one'?")).
+					Content(Equals("Are you sure you want to delete the remote branch 'branch-one' from 'origin'?")).
+					Confirm()
+			}).
+			Tap(func() {
+				t.Views().Remotes().
+					Focus().
+					Lines(Contains("origin")).
+					PressEnter()
+
+				t.Views().
+					RemoteBranches().
+					Lines(Equals("branch-two")).
+					Press(keys.Universal.Return)
+
+				t.Views().
+					Branches().
+					Focus()
+			}).
+			Lines(
+				MatchesRegexp(`\*.*branch-three`),
+				MatchesRegexp(`branch-one \(upstream gone\)`).IsSelected(),
+				MatchesRegexp(`master`),
+			).
+			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete branch 'branch-one'?")).
+					Select(Contains("Delete local branch")).
+					Confirm()
+			}).
+			Lines(
+				MatchesRegexp(`\*.*branch-three`),
 				MatchesRegexp(`master`).IsSelected(),
 			)
 	},
diff --git a/pkg/integration/tests/tag/crud_annotated.go b/pkg/integration/tests/tag/crud_annotated.go
index d32c7f265..930859c90 100644
--- a/pkg/integration/tests/tag/crud_annotated.go
+++ b/pkg/integration/tests/tag/crud_annotated.go
@@ -12,6 +12,7 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{
 	SetupConfig:  func(config *config.AppConfig) {},
 	SetupRepo: func(shell *Shell) {
 		shell.EmptyCommit("initial commit")
+		shell.CloneIntoRemote("origin")
 	},
 	Run: func(t *TestDriver, keys config.KeybindingConfig) {
 		t.Views().Tags().
@@ -31,11 +32,53 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{
 			Lines(
 				MatchesRegexp(`new-tag.*message`).IsSelected(),
 			).
+			Press(keys.Universal.Push).
+			Tap(func() {
+				t.ExpectPopup().Prompt().
+					Title(Equals("Remote to push tag 'new-tag' to:")).
+					InitialText(Equals("origin")).
+					SuggestionLines(
+						Contains("origin"),
+					).
+					Confirm()
+			}).
 			Press(keys.Universal.Remove).
 			Tap(func() {
-				t.ExpectPopup().Confirmation().
-					Title(Equals("Delete tag")).
-					Content(Equals("Are you sure you want to delete tag 'new-tag'?")).
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete tag 'new-tag'?")).
+					Select(Contains("Delete remote tag")).
+					Confirm()
+			}).
+			Tap(func() {
+				t.ExpectPopup().Prompt().
+					Title(Equals("Remote from which to remove tag 'new-tag':")).
+					InitialText(Equals("origin")).
+					SuggestionLines(
+						Contains("origin"),
+					).
+					Confirm()
+			}).
+			Tap(func() {
+				t.ExpectPopup().
+					Confirmation().
+					Title(Equals("Delete tag 'new-tag'?")).
+					Content(Equals("Are you sure you want to delete the remote tag 'new-tag' from 'origin'?")).
+					Confirm()
+			}).
+			Lines(
+				MatchesRegexp(`new-tag.*message`).IsSelected(),
+			).
+			Tap(func() {
+				t.Git().
+					RemoteTagDeleted("origin", "new-tag")
+			}).
+			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete tag 'new-tag'?")).
+					Select(Contains("Delete local tag")).
 					Confirm()
 			}).
 			IsEmpty().
diff --git a/pkg/integration/tests/tag/crud_lightweight.go b/pkg/integration/tests/tag/crud_lightweight.go
index ce5ff9e42..6aab10dd1 100644
--- a/pkg/integration/tests/tag/crud_lightweight.go
+++ b/pkg/integration/tests/tag/crud_lightweight.go
@@ -12,6 +12,7 @@ var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{
 	SetupConfig:  func(config *config.AppConfig) {},
 	SetupRepo: func(shell *Shell) {
 		shell.EmptyCommit("initial commit")
+		shell.CloneIntoRemote("origin")
 	},
 	Run: func(t *TestDriver, keys config.KeybindingConfig) {
 		t.Views().Tags().
@@ -36,11 +37,53 @@ var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{
 					).
 					PressEscape()
 			}).
+			Press(keys.Universal.Push).
+			Tap(func() {
+				t.ExpectPopup().Prompt().
+					Title(Equals("Remote to push tag 'new-tag' to:")).
+					InitialText(Equals("origin")).
+					SuggestionLines(
+						Contains("origin"),
+					).
+					Confirm()
+			}).
 			Press(keys.Universal.Remove).
 			Tap(func() {
-				t.ExpectPopup().Confirmation().
-					Title(Equals("Delete tag")).
-					Content(Equals("Are you sure you want to delete tag 'new-tag'?")).
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete tag 'new-tag'?")).
+					Select(Contains("Delete remote tag")).
+					Confirm()
+			}).
+			Tap(func() {
+				t.ExpectPopup().Prompt().
+					Title(Equals("Remote from which to remove tag 'new-tag':")).
+					InitialText(Equals("origin")).
+					SuggestionLines(
+						Contains("origin"),
+					).
+					Confirm()
+			}).
+			Tap(func() {
+				t.ExpectPopup().
+					Confirmation().
+					Title(Equals("Delete tag 'new-tag'?")).
+					Content(Equals("Are you sure you want to delete the remote tag 'new-tag' from 'origin'?")).
+					Confirm()
+			}).
+			Lines(
+				MatchesRegexp(`new-tag.*initial commit`).IsSelected(),
+			).
+			Tap(func() {
+				t.Git().
+					RemoteTagDeleted("origin", "new-tag")
+			}).
+			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete tag 'new-tag'?")).
+					Select(Contains("Delete local tag")).
 					Confirm()
 			}).
 			IsEmpty()
diff --git a/pkg/integration/tests/worktree/detach_worktree_from_branch.go b/pkg/integration/tests/worktree/detach_worktree_from_branch.go
index 09b3146e5..f6724ef3a 100644
--- a/pkg/integration/tests/worktree/detach_worktree_from_branch.go
+++ b/pkg/integration/tests/worktree/detach_worktree_from_branch.go
@@ -27,6 +27,13 @@ var DetachWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
 			).
 			NavigateToLine(Contains("newbranch")).
 			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete branch 'newbranch'?")).
+					Select(Contains("Delete local branch")).
+					Confirm()
+			}).
 			Tap(func() {
 				t.ExpectPopup().Menu().
 					Title(Equals("Branch newbranch is checked out by worktree linked-worktree")).
diff --git a/pkg/integration/tests/worktree/remove_worktree_from_branch.go b/pkg/integration/tests/worktree/remove_worktree_from_branch.go
index 2885f5669..7ef9d0ae9 100644
--- a/pkg/integration/tests/worktree/remove_worktree_from_branch.go
+++ b/pkg/integration/tests/worktree/remove_worktree_from_branch.go
@@ -28,6 +28,13 @@ var RemoveWorktreeFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
 			).
 			NavigateToLine(Contains("newbranch")).
 			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete branch 'newbranch'?")).
+					Select(Contains("Delete local branch")).
+					Confirm()
+			}).
 			Tap(func() {
 				t.ExpectPopup().Menu().
 					Title(Equals("Branch newbranch is checked out by worktree linked-worktree")).