diff --git a/src/Dialogs/BranchActions/BranchActionDialog.vala b/src/Dialogs/BranchActions/BranchActionDialog.vala index ce95d7ad7e..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,13 +68,24 @@ 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); + 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..e7eff5e226 --- /dev/null +++ b/src/Dialogs/BranchActions/BranchDeletePage.vala @@ -0,0 +1,77 @@ +/* + * 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 new_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); + dialog.is_destructive = 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..dc864287e0 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,14 @@ 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, 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; + } + // 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',