602 lines
22 KiB
Python
602 lines
22 KiB
Python
# --- FILE: gitsync_tool/gui/tabs/remote_tab.py ---
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
from typing import Callable, Optional, List, Any
|
|
|
|
from gitutility.gui.tooltip import Tooltip
|
|
from gitutility.logging_setup import log_handler
|
|
|
|
|
|
class RemoteTab(ttk.Frame):
|
|
"""
|
|
The 'Remote Repository' tab in the main application notebook.
|
|
This tab provides widgets for configuring the remote, performing sync
|
|
actions (fetch, pull, push), and managing remote/local branches.
|
|
"""
|
|
|
|
def __init__(self, master: tk.Misc, **kwargs):
|
|
"""Initializes the Remote Repository tab."""
|
|
super().__init__(master, padding=(10, 10))
|
|
|
|
# Store callbacks from kwargs
|
|
self.apply_remote_config_callback = kwargs.get("apply_remote_config_cb")
|
|
self.check_connection_auth_callback = kwargs.get("check_connection_auth_cb")
|
|
self.refresh_remote_status_callback = kwargs.get("refresh_remote_status_cb")
|
|
self.fetch_remote_callback = kwargs.get("fetch_remote_cb")
|
|
self.pull_remote_callback = kwargs.get("pull_remote_cb")
|
|
self.push_remote_callback = kwargs.get("push_remote_cb")
|
|
self.push_tags_remote_callback = kwargs.get("push_tags_remote_cb")
|
|
self.refresh_remote_branches_callback = kwargs.get("refresh_remote_branches_cb")
|
|
self.refresh_local_branches_callback = kwargs.get("refresh_branches_cb")
|
|
self.checkout_remote_branch_callback = kwargs.get("checkout_remote_branch_cb")
|
|
self.compare_branch_with_current_callback = kwargs.get(
|
|
"compare_branch_with_current_cb"
|
|
)
|
|
self.delete_local_branch_callback = kwargs.get("delete_local_branch_cb")
|
|
self.checkout_local_branch_callback = kwargs.get("checkout_branch_cb")
|
|
self.merge_local_branch_callback = kwargs.get("merge_local_branch_cb")
|
|
|
|
# Get a reference to the main frame for shared components like menus
|
|
self.main_frame = self.master.master
|
|
|
|
# --- Tkinter Variables specific to this tab ---
|
|
self.remote_url_var = tk.StringVar()
|
|
self.remote_name_var = tk.StringVar()
|
|
self.remote_auth_status_var = tk.StringVar(value="Status: Unknown")
|
|
self.remote_ahead_behind_var = tk.StringVar(value="Sync Status: Unknown")
|
|
|
|
# Configure layout
|
|
self.columnconfigure(0, weight=1)
|
|
self.columnconfigure(1, weight=1)
|
|
self.rowconfigure(3, weight=1)
|
|
|
|
# Create widgets
|
|
self._create_widgets()
|
|
|
|
def _create_widgets(self) -> None:
|
|
"""Creates and arranges all widgets for this tab."""
|
|
# --- Top Frame: Remote Config & Sync Status ---
|
|
top_frame = ttk.LabelFrame(
|
|
self, text="Remote Configuration & Sync Status", padding=(10, 5)
|
|
)
|
|
top_frame.grid(row=0, column=0, columnspan=2, sticky="ew", padx=5, pady=(5, 0))
|
|
top_frame.columnconfigure(1, weight=1)
|
|
|
|
ttk.Label(top_frame, text="Remote URL:").grid(
|
|
row=0, column=0, sticky=tk.W, padx=5, pady=3
|
|
)
|
|
self.remote_url_entry = ttk.Entry(
|
|
top_frame, textvariable=self.remote_url_var, width=60
|
|
)
|
|
self.remote_url_entry.grid(row=0, column=1, sticky=tk.EW, padx=5, pady=3)
|
|
Tooltip(
|
|
self.remote_url_entry,
|
|
"URL of the remote repository (e.g., https://... or ssh://...).",
|
|
)
|
|
|
|
ttk.Label(top_frame, text="Local Name:").grid(
|
|
row=1, column=0, sticky=tk.W, padx=5, pady=3
|
|
)
|
|
self.remote_name_entry = ttk.Entry(
|
|
top_frame, textvariable=self.remote_name_var, width=20
|
|
)
|
|
self.remote_name_entry.grid(row=1, column=1, sticky=tk.W, padx=5, pady=3)
|
|
Tooltip(
|
|
self.remote_name_entry, "Local alias for the remote (Default: 'origin')."
|
|
)
|
|
|
|
self.sync_status_label = ttk.Label(
|
|
top_frame,
|
|
textvariable=self.remote_ahead_behind_var,
|
|
anchor=tk.W,
|
|
padding=(5, 2),
|
|
)
|
|
self.sync_status_label.grid(row=2, column=1, sticky="ew", padx=5, pady=(2, 5))
|
|
Tooltip(
|
|
self.sync_status_label,
|
|
"Shows the current local branch and its sync status (ahead/behind) relative to its upstream.",
|
|
)
|
|
|
|
config_action_frame = ttk.Frame(top_frame)
|
|
config_action_frame.grid(
|
|
row=0, column=2, rowspan=3, sticky="ne", padx=(15, 5), pady=3
|
|
)
|
|
|
|
self.apply_remote_config_button = ttk.Button(
|
|
config_action_frame,
|
|
text="Apply Config",
|
|
command=self.apply_remote_config_callback,
|
|
state=tk.DISABLED,
|
|
width=18,
|
|
)
|
|
self.apply_remote_config_button.pack(side=tk.TOP, fill=tk.X, pady=1)
|
|
Tooltip(
|
|
self.apply_remote_config_button,
|
|
"Add or update this remote configuration in the local .git/config file.",
|
|
)
|
|
|
|
self.check_auth_button = ttk.Button(
|
|
config_action_frame,
|
|
text="Check Connection",
|
|
command=self.check_connection_auth_callback,
|
|
state=tk.DISABLED,
|
|
width=18,
|
|
)
|
|
self.check_auth_button.pack(side=tk.TOP, fill=tk.X, pady=1)
|
|
Tooltip(
|
|
self.check_auth_button,
|
|
"Verify connection and authentication status for the configured remote.",
|
|
)
|
|
|
|
self.auth_status_indicator_label = ttk.Label(
|
|
config_action_frame,
|
|
textvariable=self.remote_auth_status_var,
|
|
anchor=tk.CENTER,
|
|
relief=tk.SUNKEN,
|
|
padding=(5, 1),
|
|
width=18,
|
|
)
|
|
self.auth_status_indicator_label.pack(side=tk.TOP, fill=tk.X, pady=(1, 3))
|
|
self.update_auth_status_indicator("unknown")
|
|
Tooltip(
|
|
self.auth_status_indicator_label,
|
|
"Connection and authentication status (Unknown, Checking, Connected, Auth Required, Failed, Error).",
|
|
)
|
|
|
|
self.refresh_sync_status_button = ttk.Button(
|
|
config_action_frame,
|
|
text="Refresh Sync Status",
|
|
command=self.refresh_remote_status_callback,
|
|
state=tk.DISABLED,
|
|
width=18,
|
|
)
|
|
self.refresh_sync_status_button.pack(side=tk.TOP, fill=tk.X, pady=(3, 1))
|
|
Tooltip(
|
|
self.refresh_sync_status_button,
|
|
"Check how many commits the current local branch is ahead or behind its upstream remote branch.",
|
|
)
|
|
|
|
# --- Middle Frame: Common Remote Actions ---
|
|
actions_frame = ttk.LabelFrame(
|
|
self, text="Common Remote Actions", padding=(10, 5)
|
|
)
|
|
actions_frame.grid(row=1, column=0, columnspan=2, sticky="ew", padx=5, pady=5)
|
|
action_buttons_inner_frame = ttk.Frame(actions_frame)
|
|
action_buttons_inner_frame.pack()
|
|
|
|
self.fetch_button = ttk.Button(
|
|
action_buttons_inner_frame,
|
|
text="Fetch",
|
|
command=self.fetch_remote_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.fetch_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
Tooltip(
|
|
self.fetch_button,
|
|
"Download objects and references from the remote repository (does not modify local branches).",
|
|
)
|
|
|
|
self.pull_button = ttk.Button(
|
|
action_buttons_inner_frame,
|
|
text="Pull",
|
|
command=self.pull_remote_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.pull_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
Tooltip(
|
|
self.pull_button,
|
|
"Fetch changes from the remote and merge them into the current local branch.",
|
|
)
|
|
|
|
self.push_button = ttk.Button(
|
|
action_buttons_inner_frame,
|
|
text="Push",
|
|
command=self.push_remote_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.push_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
Tooltip(
|
|
self.push_button,
|
|
"Upload local commits from the current branch to the corresponding branch on the remote repository.",
|
|
)
|
|
|
|
self.push_tags_button = ttk.Button(
|
|
action_buttons_inner_frame,
|
|
text="Push Tags",
|
|
command=self.push_tags_remote_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.push_tags_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
Tooltip(
|
|
self.push_tags_button, "Upload all local tags to the remote repository."
|
|
)
|
|
|
|
# --- Bottom Frame: Branch Lists ---
|
|
branch_view_frame = ttk.Frame(self)
|
|
branch_view_frame.grid(
|
|
row=3, column=0, columnspan=2, sticky="nsew", padx=0, pady=(5, 5)
|
|
)
|
|
branch_view_frame.rowconfigure(0, weight=1)
|
|
branch_view_frame.columnconfigure(0, weight=1)
|
|
branch_view_frame.columnconfigure(1, weight=1)
|
|
|
|
# Remote Branches List
|
|
remote_list_frame = ttk.Frame(branch_view_frame, padding=(5, 0, 10, 0))
|
|
remote_list_frame.grid(row=0, column=0, sticky="nsew")
|
|
remote_list_frame.rowconfigure(1, weight=1)
|
|
remote_list_frame.columnconfigure(0, weight=1)
|
|
ttk.Label(remote_list_frame, text="Remote Branches (from last Fetch):").grid(
|
|
row=0, column=0, columnspan=2, sticky="w", padx=5, pady=(0, 2)
|
|
)
|
|
|
|
self.remote_branches_listbox = tk.Listbox(
|
|
remote_list_frame,
|
|
height=8,
|
|
exportselection=False,
|
|
selectmode=tk.SINGLE,
|
|
font=("Consolas", 9),
|
|
borderwidth=1,
|
|
relief=tk.SUNKEN,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.remote_branches_listbox.grid(row=1, column=0, sticky="nsew")
|
|
self.remote_branches_listbox.bind(
|
|
"<Button-3>", self._show_remote_branches_context_menu
|
|
)
|
|
|
|
rb_scrollbar = ttk.Scrollbar(
|
|
remote_list_frame,
|
|
orient=tk.VERTICAL,
|
|
command=self.remote_branches_listbox.yview,
|
|
)
|
|
rb_scrollbar.grid(row=1, column=1, sticky="ns")
|
|
self.remote_branches_listbox.config(yscrollcommand=rb_scrollbar.set)
|
|
Tooltip(
|
|
self.remote_branches_listbox,
|
|
"Branches existing on the remote repository. Right-click for actions (Compare, Checkout as local).",
|
|
)
|
|
|
|
self.refresh_remote_branches_button = ttk.Button(
|
|
remote_list_frame,
|
|
text="Refresh Remote List",
|
|
command=self.refresh_remote_branches_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.refresh_remote_branches_button.grid(
|
|
row=2, column=0, columnspan=2, sticky="w", padx=5, pady=(5, 0)
|
|
)
|
|
Tooltip(
|
|
self.refresh_remote_branches_button,
|
|
"Update the list of remote branches (requires fetching from the remote).",
|
|
)
|
|
|
|
# Local Branches List
|
|
local_list_frame = ttk.Frame(branch_view_frame, padding=(10, 0, 5, 0))
|
|
local_list_frame.grid(row=0, column=1, sticky="nsew")
|
|
local_list_frame.rowconfigure(1, weight=1)
|
|
local_list_frame.columnconfigure(0, weight=1)
|
|
ttk.Label(local_list_frame, text="Local Branches (* = Current):").grid(
|
|
row=0, column=0, columnspan=2, sticky="w", padx=5, pady=(0, 2)
|
|
)
|
|
|
|
self.local_branches_listbox_remote_tab = tk.Listbox(
|
|
local_list_frame,
|
|
height=8,
|
|
exportselection=False,
|
|
selectmode=tk.SINGLE,
|
|
font=("Consolas", 9),
|
|
borderwidth=1,
|
|
relief=tk.SUNKEN,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.local_branches_listbox_remote_tab.grid(row=1, column=0, sticky="nsew")
|
|
self.local_branches_listbox_remote_tab.bind(
|
|
"<Button-3>", self._show_local_branches_context_menu
|
|
)
|
|
|
|
lb_scrollbar_remote_tab = ttk.Scrollbar(
|
|
local_list_frame,
|
|
orient=tk.VERTICAL,
|
|
command=self.local_branches_listbox_remote_tab.yview,
|
|
)
|
|
lb_scrollbar_remote_tab.grid(row=1, column=1, sticky="ns")
|
|
self.local_branches_listbox_remote_tab.config(
|
|
yscrollcommand=lb_scrollbar_remote_tab.set
|
|
)
|
|
Tooltip(
|
|
self.local_branches_listbox_remote_tab,
|
|
"Your local branches. Right-click for actions (Checkout, Merge, Delete, Compare).",
|
|
)
|
|
|
|
self.refresh_local_branches_button_remote_tab = ttk.Button(
|
|
local_list_frame,
|
|
text="Refresh Local List",
|
|
command=self.refresh_local_branches_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.refresh_local_branches_button_remote_tab.grid(
|
|
row=2, column=0, columnspan=2, sticky="w", padx=5, pady=(5, 0)
|
|
)
|
|
Tooltip(
|
|
self.refresh_local_branches_button_remote_tab,
|
|
"Update the list of local branches.",
|
|
)
|
|
|
|
def set_action_widgets_state(self, state: str) -> None:
|
|
"""Sets the state of all action widgets in this tab."""
|
|
widgets = [
|
|
self.apply_remote_config_button,
|
|
self.check_auth_button,
|
|
self.refresh_sync_status_button,
|
|
self.fetch_button,
|
|
self.pull_button,
|
|
self.push_button,
|
|
self.push_tags_button,
|
|
self.refresh_remote_branches_button,
|
|
self.refresh_local_branches_button_remote_tab,
|
|
]
|
|
list_state = tk.NORMAL if state == tk.NORMAL else tk.DISABLED
|
|
listboxes = [
|
|
self.remote_branches_listbox,
|
|
self.local_branches_listbox_remote_tab,
|
|
]
|
|
|
|
for widget in widgets:
|
|
if widget and widget.winfo_exists():
|
|
widget.config(state=state)
|
|
|
|
for lb in listboxes:
|
|
if lb and lb.winfo_exists():
|
|
lb.config(state=list_state)
|
|
|
|
def update_auth_status_indicator(self, status: str):
|
|
label = self.auth_status_indicator_label
|
|
if not label or not label.winfo_exists():
|
|
return
|
|
|
|
text, color, tooltip_text = (
|
|
"Status: Unknown",
|
|
self.main_frame.STATUS_DEFAULT_BG,
|
|
"Connection status.",
|
|
)
|
|
if status == "ok":
|
|
text, color, tooltip_text = (
|
|
"Status: Connected",
|
|
self.main_frame.STATUS_GREEN,
|
|
"Successfully connected to the remote.",
|
|
)
|
|
elif status == "required":
|
|
text, color, tooltip_text = (
|
|
"Status: Auth Required",
|
|
self.main_frame.STATUS_YELLOW,
|
|
"Authentication needed. Use 'Check Connection'.",
|
|
)
|
|
elif status == "failed":
|
|
text, color, tooltip_text = (
|
|
"Status: Auth Failed",
|
|
self.main_frame.STATUS_RED,
|
|
"Authentication failed. Check credentials.",
|
|
)
|
|
elif status == "connection_failed":
|
|
text, color, tooltip_text = (
|
|
"Status: Connection Failed",
|
|
self.main_frame.STATUS_RED,
|
|
"Could not connect to the remote.",
|
|
)
|
|
elif status == "checking":
|
|
text, color, tooltip_text = (
|
|
"Status: Checking...",
|
|
self.main_frame.STATUS_YELLOW,
|
|
"Attempting to contact the remote...",
|
|
)
|
|
elif status == "unknown_error":
|
|
text, color, tooltip_text = (
|
|
"Status: Error",
|
|
self.main_frame.STATUS_RED,
|
|
"An unknown error occurred.",
|
|
)
|
|
|
|
self.remote_auth_status_var.set(text)
|
|
label.config(background=color)
|
|
Tooltip(label, tooltip_text)
|
|
|
|
def update_ahead_behind_status(
|
|
self,
|
|
current_branch: Optional[str] = None,
|
|
status_text: Optional[str] = None,
|
|
ahead: Optional[int] = None,
|
|
behind: Optional[int] = None,
|
|
):
|
|
if not hasattr(self, "remote_ahead_behind_var"):
|
|
return
|
|
|
|
branch_part = (
|
|
f"Branch '{current_branch}': " if current_branch else "Current Branch: "
|
|
)
|
|
|
|
if status_text is not None:
|
|
text_to_display = status_text
|
|
elif ahead is not None and behind is not None:
|
|
if ahead == 0 and behind == 0:
|
|
status_part = "Up to date"
|
|
else:
|
|
parts = []
|
|
if ahead > 0:
|
|
parts.append(f"{ahead} ahead (Push needed)")
|
|
if behind > 0:
|
|
parts.append(f"{behind} behind (Pull needed)")
|
|
status_part = ", ".join(parts)
|
|
text_to_display = branch_part + status_part
|
|
else:
|
|
text_to_display = branch_part + "Unknown Status"
|
|
|
|
self.remote_ahead_behind_var.set(text_to_display)
|
|
|
|
def update_remote_branches_list(self, remote_branch_list: List[str]):
|
|
listbox = self.remote_branches_listbox
|
|
if not listbox or not listbox.winfo_exists():
|
|
return
|
|
|
|
listbox.config(state=tk.NORMAL)
|
|
listbox.delete(0, tk.END)
|
|
if remote_branch_list:
|
|
if remote_branch_list == ["(Error)"]:
|
|
listbox.insert(tk.END, "(Error retrieving list)")
|
|
listbox.config(fg="red")
|
|
else:
|
|
listbox.config(fg="black")
|
|
for branch in remote_branch_list:
|
|
listbox.insert(tk.END, f" {branch}")
|
|
else:
|
|
listbox.insert(tk.END, "(No remote branches found)")
|
|
listbox.config(fg="grey")
|
|
listbox.yview_moveto(0.0)
|
|
|
|
def update_local_branch_list(
|
|
self, branches: List[str], current_branch: Optional[str]
|
|
):
|
|
listbox = self.local_branches_listbox_remote_tab
|
|
if not listbox or not listbox.winfo_exists():
|
|
return
|
|
|
|
listbox.config(state=tk.NORMAL)
|
|
listbox.delete(0, tk.END)
|
|
selected_index = -1
|
|
if branches:
|
|
listbox.config(fg="black")
|
|
for i, branch in enumerate(branches):
|
|
prefix = "* " if branch == current_branch else " "
|
|
listbox.insert(tk.END, f"{prefix}{branch}")
|
|
if branch == current_branch:
|
|
selected_index = i
|
|
else:
|
|
listbox.insert(tk.END, "(No local branches)")
|
|
listbox.config(fg="grey")
|
|
|
|
if selected_index != -1:
|
|
listbox.selection_set(selected_index)
|
|
listbox.see(selected_index)
|
|
listbox.yview_moveto(0.0)
|
|
|
|
def get_selected_local_branch(self) -> Optional[str]:
|
|
listbox = self.local_branches_listbox_remote_tab
|
|
selection = listbox.curselection()
|
|
if not selection:
|
|
return None
|
|
|
|
item_text = listbox.get(selection[0]).lstrip("* ").strip()
|
|
return item_text if not item_text.startswith("(") else None
|
|
|
|
def _show_remote_branches_context_menu(self, event: tk.Event) -> None:
|
|
func_name = "_show_remote_branches_context_menu"
|
|
listbox = event.widget
|
|
|
|
try:
|
|
idx = listbox.nearest(event.y)
|
|
if idx < 0:
|
|
return
|
|
listbox.selection_clear(0, tk.END)
|
|
listbox.selection_set(idx)
|
|
listbox.activate(idx)
|
|
selected_item_text = listbox.get(idx).strip()
|
|
except tk.TclError:
|
|
return
|
|
|
|
menu = self.main_frame.remote_branch_context_menu
|
|
menu.delete(0, tk.END)
|
|
|
|
is_valid_branch = (
|
|
"/" in selected_item_text and not selected_item_text.startswith("(")
|
|
)
|
|
if is_valid_branch:
|
|
remote_branch_full_name = selected_item_text
|
|
local_branch_suggestion = remote_branch_full_name.split("/", 1)[1]
|
|
|
|
current_local_branch = self.main_frame.current_local_branch
|
|
compare_label = (
|
|
f"Compare with current '{current_local_branch}'"
|
|
if current_local_branch
|
|
else "Compare..."
|
|
)
|
|
menu.add_command(
|
|
label=compare_label,
|
|
state=tk.NORMAL if current_local_branch else tk.DISABLED,
|
|
command=lambda b=remote_branch_full_name: self.compare_branch_with_current_callback(
|
|
b
|
|
),
|
|
)
|
|
|
|
menu.add_command(
|
|
label=f"Checkout as new local branch '{local_branch_suggestion}'",
|
|
command=lambda rb=remote_branch_full_name, lb=local_branch_suggestion: self.checkout_remote_branch_callback(
|
|
rb, lb
|
|
),
|
|
)
|
|
|
|
menu.add_separator()
|
|
|
|
menu.add_command(label="Cancel")
|
|
menu.tk_popup(event.x_root, event.y_root)
|
|
|
|
def _show_local_branches_context_menu(self, event: tk.Event) -> None:
|
|
func_name = "_show_local_branches_context_menu"
|
|
listbox = event.widget
|
|
|
|
try:
|
|
idx = listbox.nearest(event.y)
|
|
if idx < 0:
|
|
return
|
|
listbox.selection_clear(0, tk.END)
|
|
listbox.selection_set(idx)
|
|
listbox.activate(idx)
|
|
selected_item_text = listbox.get(idx).strip()
|
|
except tk.TclError:
|
|
return
|
|
|
|
menu = self.main_frame.local_branch_context_menu
|
|
menu.delete(0, tk.END)
|
|
|
|
is_current = selected_item_text.startswith("*")
|
|
branch_name = selected_item_text.lstrip("* ").strip()
|
|
current_branch = self.main_frame.current_local_branch
|
|
is_valid = not branch_name.startswith("(")
|
|
|
|
if is_valid:
|
|
menu.add_command(
|
|
label=f"Checkout Branch '{branch_name}'",
|
|
state=tk.DISABLED if is_current else tk.NORMAL,
|
|
command=lambda b=branch_name: self.checkout_local_branch_callback(b),
|
|
)
|
|
menu.add_command(
|
|
label=f"Merge '{branch_name}' into '{current_branch}'...",
|
|
state=tk.DISABLED if is_current or not current_branch else tk.NORMAL,
|
|
command=lambda b=branch_name: self.merge_local_branch_callback(b),
|
|
)
|
|
menu.add_command(
|
|
label=f"Compare with current '{current_branch}'",
|
|
state=tk.DISABLED if is_current or not current_branch else tk.NORMAL,
|
|
command=lambda b=branch_name: self.compare_branch_with_current_callback(
|
|
b
|
|
),
|
|
)
|
|
menu.add_separator()
|
|
menu.add_command(
|
|
label=f"Delete Branch '{branch_name}'...",
|
|
state=tk.DISABLED if is_current else tk.NORMAL,
|
|
command=lambda b=branch_name: self.delete_local_branch_callback(
|
|
b, False
|
|
),
|
|
)
|
|
menu.add_command(
|
|
label=f"Force Delete Branch '{branch_name}'...",
|
|
state=tk.DISABLED if is_current else tk.NORMAL,
|
|
command=lambda b=branch_name: self.delete_local_branch_callback(
|
|
b, True
|
|
),
|
|
)
|
|
menu.add_separator()
|
|
|
|
menu.add_command(label="Cancel")
|
|
menu.tk_popup(event.x_root, event.y_root)
|