From a503cb744afd2d9a8c0e042326fdc80ccc12cfcc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 16 Aug 2026 13:43:22 +0100 Subject: [PATCH 1/4] Simple implementation of delete branch action --- .../BranchActions/BranchActionDialog.vala | 2 + .../BranchActions/BranchDeletePage.vala | 76 +++++++++++++++++++ src/FolderManager/FileView.vala | 1 + src/FolderManager/ProjectFolderItem.vala | 8 ++ src/Services/MonitoredRepository.vala | 11 +++ src/meson.build | 1 + 6 files changed, 99 insertions(+) create mode 100644 src/Dialogs/BranchActions/BranchDeletePage.vala diff --git a/src/Dialogs/BranchActions/BranchActionDialog.vala b/src/Dialogs/BranchActions/BranchActionDialog.vala index ce95d7ad7e..cb7e2e7820 100644 --- a/src/Dialogs/BranchActions/BranchActionDialog.vala +++ b/src/Dialogs/BranchActions/BranchActionDialog.vala @@ -70,10 +70,12 @@ public class Scratch.Dialogs.BranchActionDialog : Granite.MessageDialog { var checkout_page = new BranchCheckoutPage (this); var create_page = new BranchCreatePage (this); + var delete_page = new BranchDeletePage (this); stack = new Gtk.Stack (); stack.add_titled (checkout_page, BranchAction.CHECKOUT.to_string (), _("Checkout")); stack.add_titled (create_page, BranchAction.CREATE.to_string (), _("New")); + stack.add_titled (delete_page, BranchAction.DELETE.to_string (), _("Delete")); var sidebar = new Gtk.StackSidebar () { stack = stack diff --git a/src/Dialogs/BranchActions/BranchDeletePage.vala b/src/Dialogs/BranchActions/BranchDeletePage.vala new file mode 100644 index 0000000000..eeb2ee63e5 --- /dev/null +++ b/src/Dialogs/BranchActions/BranchDeletePage.vala @@ -0,0 +1,76 @@ +/* + * Copyright 2025-2026 elementary, Inc. + * SPDX-License-Identifier: GPL-3.0-or-later +* +* Authored by: Jeremy Wootten +*/ + +public class Scratch.Dialogs.BranchDeletePage : Gtk.Box, BranchActionPage { + public BranchAction action { + get { + return BranchAction.DELETE; + } + } + + public Ggit.Ref? branch_ref { + get { + return list_box.get_selected_row ().bref; + } + } + + public string target_branch_name { + get { + return ""; + } + } + + public BranchActionDialog dialog { get; construct; } + + private BranchListBox list_box; + private Gtk.CheckButton delete_unmerged_check; + + public BranchDeletePage (BranchActionDialog dialog) { + Object ( + dialog: dialog + ); + } + + construct { + orientation = VERTICAL; + list_box = new BranchListBox (dialog, false) { + margin_bottom = 12 + }; // No remotes + add (list_box); + delete_unmerged_check = new Gtk.CheckButton.with_label (_("Delete even if unmerged into default branch")) { + active = false, + valign = CENTER + }; + + + var action_bar = new Gtk.ActionBar (); + action_bar.pack_start (delete_unmerged_check); + add (action_bar); + + list_box.branch_changed.connect (() => { + delete_unmerged_check.active = false; + }); + + delete_unmerged_check.toggled.connect (() => { + update_can_apply (); + }); + + + } + + public override void focus_start_widget () { + list_box.grab_focus (); + } + + private void update_can_apply () { + var text = list_box.text; + var exists = dialog.project.has_branch_name (text, null); + var is_current = dialog.project.get_current_branch_name () == text; + var is_merged = dialog.project.branch_name_is_merged (text); + dialog.can_apply = exists && !is_current && (is_merged || delete_unmerged_check.active); + } +} diff --git a/src/FolderManager/FileView.vala b/src/FolderManager/FileView.vala index 2841e2715e..46067de37f 100644 --- a/src/FolderManager/FileView.vala +++ b/src/FolderManager/FileView.vala @@ -265,6 +265,7 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane case MERGE: break; case DELETE: + dialog.project.delete_branch (dialog.branch_ref); break; case CREATE: dialog.project.new_branch (dialog.new_branch_name); diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 4f44497cd3..7ef7e3dabe 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -408,6 +408,10 @@ namespace Scratch.FolderManager { } } + public bool delete_branch (Ggit.Ref bref) { + return monitored_repo.delete_branch (bref); + } + public bool is_recent_ref (Ggit.Ref bref) { return monitored_repo.is_recent_ref (bref); } @@ -446,6 +450,10 @@ namespace Scratch.FolderManager { return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; } + public bool branch_name_is_merged (string branch_name) { + return false; + } + // The parameter "is_explicit" indicates whether a global search was requested // via a context menu on an explicitly chosen folder, in which case everything in that // folder will be searched, or whether the hot-key was used in which case the search will diff --git a/src/Services/MonitoredRepository.vala b/src/Services/MonitoredRepository.vala index 00317fc53a..24c3e3786d 100644 --- a/src/Services/MonitoredRepository.vala +++ b/src/Services/MonitoredRepository.vala @@ -368,6 +368,17 @@ namespace Scratch.Services { return true; } + public bool delete_branch (Ggit.Ref bref) { + try { + bref.@delete (); + } catch (Error e) { + warning ("Error deleting branch %s. %s", ((Ggit.Branch)bref).get_name (), e.message); + return false; + } + + return true; + } + private bool do_update = false; public void update_status_map () { if (update_timer_id == 0) { diff --git a/src/meson.build b/src/meson.build index e58f5eb722..3581b4616f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -27,6 +27,7 @@ code_files = files( 'Dialogs/BranchActions/BranchActionDialog.vala', 'Dialogs/BranchActions/BranchCheckoutPage.vala', 'Dialogs/BranchActions/BranchCreatePage.vala', + 'Dialogs/BranchActions/BranchDeletePage.vala', 'Dialogs/BranchActions/BranchListBox.vala', 'Dialogs/BranchActions/BranchNameRow.vala', 'FolderManager/File.vala', From a4219b007c4123f7eee1692b5880e3c6e276f181 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 16 Aug 2026 14:30:24 +0100 Subject: [PATCH 2/4] Fix property name --- src/Dialogs/BranchActions/BranchDeletePage.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dialogs/BranchActions/BranchDeletePage.vala b/src/Dialogs/BranchActions/BranchDeletePage.vala index eeb2ee63e5..c2d48595d1 100644 --- a/src/Dialogs/BranchActions/BranchDeletePage.vala +++ b/src/Dialogs/BranchActions/BranchDeletePage.vala @@ -18,7 +18,7 @@ public class Scratch.Dialogs.BranchDeletePage : Gtk.Box, BranchActionPage { } } - public string target_branch_name { + public string new_branch_name { get { return ""; } From 021e58d6517ff1157e81115e605bb6d44d73a032 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 16 Aug 2026 14:31:50 +0100 Subject: [PATCH 3/4] Show destructive action style --- src/Dialogs/BranchActions/BranchActionDialog.vala | 10 ++++++++++ src/Dialogs/BranchActions/BranchDeletePage.vala | 1 + 2 files changed, 11 insertions(+) diff --git a/src/Dialogs/BranchActions/BranchActionDialog.vala b/src/Dialogs/BranchActions/BranchActionDialog.vala index cb7e2e7820..2a4c726349 100644 --- a/src/Dialogs/BranchActions/BranchActionDialog.vala +++ b/src/Dialogs/BranchActions/BranchActionDialog.vala @@ -43,6 +43,7 @@ public class Scratch.Dialogs.BranchActionDialog : Granite.MessageDialog { } public bool can_apply { get; set; default = false; } + public bool is_destructive { get; set; default = false; } public FolderManager.ProjectFolderItem project { get; construct; } private Gtk.Stack stack; @@ -67,6 +68,15 @@ public class Scratch.Dialogs.BranchActionDialog : Granite.MessageDialog { var apply_button = add_button (_("Apply"), Gtk.ResponseType.APPLY); bind_property ("can-apply", apply_button, "sensitive", SYNC_CREATE); + notify["is-destructive"].connect (() => { + var ctx = apply_button.get_style_context (); + var scda = Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION; + if (is_destructive && !ctx.has_class (scda)) { + ctx.add_class (scda); + } else if (!is_destructive && ctx.has_class (scda)) { + ctx.remove_class (scda); + } + }); var checkout_page = new BranchCheckoutPage (this); var create_page = new BranchCreatePage (this); diff --git a/src/Dialogs/BranchActions/BranchDeletePage.vala b/src/Dialogs/BranchActions/BranchDeletePage.vala index c2d48595d1..e7eff5e226 100644 --- a/src/Dialogs/BranchActions/BranchDeletePage.vala +++ b/src/Dialogs/BranchActions/BranchDeletePage.vala @@ -72,5 +72,6 @@ public class Scratch.Dialogs.BranchDeletePage : Gtk.Box, BranchActionPage { var is_current = dialog.project.get_current_branch_name () == text; var is_merged = dialog.project.branch_name_is_merged (text); dialog.can_apply = exists && !is_current && (is_merged || delete_unmerged_check.active); + dialog.is_destructive = delete_unmerged_check.active; } } From 0850e458b35caf1156476d1a0b338981d71bd365 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 16 Aug 2026 14:39:30 +0100 Subject: [PATCH 4/4] Comment on branch_name_is_merged --- src/FolderManager/ProjectFolderItem.vala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 7ef7e3dabe..dc864287e0 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -450,7 +450,11 @@ namespace Scratch.FolderManager { return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; } - public bool branch_name_is_merged (string branch_name) { + public bool branch_name_is_merged (string branch_name, Ggit.Ref? target = null) { + //TODO Implement checking whether one branch merged into another (target) branch + // A null target is to be treated as the default branch. + // It is not obvious how to do this with Ggit; moreover merging of a remote branch + // into a remote target will not be detectable anyway. So return false for now return false; }