SXXXXXXX_GitUtility/gitutility/gui/purge_dialog.py
2025-07-07 15:05:51 +02:00

201 lines
7.3 KiB
Python

# --- FILE: gitsync_tool/gui/purge_dialog.py ---
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from typing import List, Dict, Any, Optional
# Importa usando il percorso assoluto
from gitutility.logging_setup import log_handler
from gitutility.gui.tooltip import Tooltip
def _format_size(size_bytes: int) -> str:
"""Formats a size in bytes to a human-readable string (KB, MB, GB)."""
if size_bytes < 1024:
return f"{size_bytes} B"
kb = size_bytes / 1024
if kb < 1024:
return f"{kb:.1f} KB"
mb = kb / 1024
if mb < 1024:
return f"{mb:.2f} MB"
gb = mb / 1024
return f"{gb:.2f} GB"
class PurgeConfirmationDialog(simpledialog.Dialog):
"""
A modal dialog to show files that can be purged from Git history and
to get explicit user confirmation for this destructive action.
"""
def __init__(
self,
master: tk.Misc,
files_to_purge: List[Dict[str, Any]],
repo_path: str,
title: str = "Confirm History Purge",
):
"""
Initialize the dialog.
Args:
master: The parent window.
files_to_purge: A list of dicts, each with 'path' and 'size' keys.
repo_path: The path of the repository being cleaned.
title (str): The title for the dialog window.
"""
self.files_to_purge = files_to_purge
self.repo_path = repo_path
self.confirmation_var = tk.BooleanVar(value=False)
self.result: bool = False # The final result will be a boolean
super().__init__(master, title=title)
def body(self, master: tk.Frame) -> Optional[tk.Widget]:
"""Creates the dialog body."""
# Main container frame
container = ttk.Frame(master, padding="10")
container.pack(fill=tk.BOTH, expand=True)
# --- 1. Warning Section ---
warning_frame = ttk.Frame(container, style="Card.TFrame", padding=10)
warning_frame.pack(fill=tk.X, pady=(0, 10))
# Use a custom style for the warning frame if available
try:
s = ttk.Style()
s.configure("Card.TFrame", background="#FFF5F5", relief="solid", borderwidth=1)
except tk.TclError:
pass # Fallback to default frame style
warning_icon = ttk.Label(warning_frame, text="⚠️", font=("Segoe UI", 16))
warning_icon.pack(side=tk.LEFT, padx=(0, 10), anchor="n")
warning_text_frame = ttk.Frame(warning_frame, style="Card.TFrame")
warning_text_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
ttk.Label(
warning_text_frame,
text="DESTRUCTIVE ACTION",
font=("Segoe UI", 11, "bold"),
foreground="#D9534F",
style="Card.TLabel"
).pack(anchor="w")
warning_message = (
"This will permanently remove the files listed below from your entire Git history. "
"This operation rewrites history and requires a force push, which can disrupt collaboration.\n"
"Ensure all team members have pushed their changes before proceeding."
)
ttk.Label(
warning_text_frame,
text=warning_message,
wraplength=500,
justify=tk.LEFT,
style="Card.TLabel"
).pack(anchor="w", pady=(5, 0))
try:
s.configure("Card.TLabel", background="#FFF5F5")
except tk.TclError:
pass
# --- 2. File List Section ---
files_frame = ttk.LabelFrame(
container, text=f"Files to Purge ({len(self.files_to_purge)} found)", padding=10
)
files_frame.pack(fill=tk.BOTH, expand=True, pady=10)
files_frame.rowconfigure(0, weight=1)
files_frame.columnconfigure(0, weight=1)
columns = ("path", "size")
self.tree = ttk.Treeview(files_frame, columns=columns, show="headings")
self.tree.heading("path", text="File Path", anchor="w")
self.tree.heading("size", text="Size", anchor="e")
self.tree.column("path", width=400, stretch=tk.YES, anchor="w")
self.tree.column("size", width=100, stretch=tk.NO, anchor="e")
scrollbar = ttk.Scrollbar(files_frame, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
self.tree.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
# Populate the treeview
for file_info in self.files_to_purge:
path = file_info.get("path", "N/A")
size_bytes = file_info.get("size", 0)
formatted_size = _format_size(size_bytes)
self.tree.insert("", tk.END, values=(path, formatted_size))
# --- 3. Confirmation Checkbox Section ---
confirm_frame = ttk.Frame(container, padding=(0, 10, 0, 0))
confirm_frame.pack(fill=tk.X)
self.confirm_check = ttk.Checkbutton(
confirm_frame,
text="I understand the risks and want to permanently delete these files from history.",
variable=self.confirmation_var,
command=self._on_confirm_toggle,
)
self.confirm_check.pack(anchor="w")
return self.tree # Initial focus on the list
def _on_confirm_toggle(self):
"""Enables/disables the 'Confirm' button based on the checkbox state."""
# Find the OK button in the buttonbox
ok_button = self.ok_button
if ok_button:
if self.confirmation_var.get():
ok_button.config(state=tk.NORMAL)
else:
ok_button.config(state=tk.DISABLED)
def buttonbox(self):
"""Creates the OK and Cancel buttons."""
box = ttk.Frame(self)
self.ok_button = ttk.Button(
box,
text="Confirm and Purge",
width=20,
command=self.ok,
state=tk.DISABLED, # Initially disabled
style="Danger.TButton"
)
self.ok_button.pack(side=tk.LEFT, padx=5, pady=5)
try:
s = ttk.Style()
s.configure("Danger.TButton", foreground="white", background="#D9534F")
except tk.TclError:
pass # Fallback to default button style
Tooltip(self.ok_button, "This button is enabled only after you check the confirmation box.")
cancel_button = ttk.Button(
box, text="Cancel", width=10, command=self.cancel
)
cancel_button.pack(side=tk.LEFT, padx=5, pady=5)
self.bind("<Return>", lambda e: self.ok() if self.confirmation_var.get() else None)
self.bind("<Escape>", lambda e: self.cancel())
box.pack()
def validate(self) -> bool:
"""Validation is handled by the confirmation checkbox state."""
# The OK button is only enabled if the checkbox is checked, so if `ok` is
# called, we can assume the user has confirmed.
if not self.confirmation_var.get():
messagebox.showwarning(
"Confirmation Required",
"You must check the box to confirm you understand the risks before proceeding.",
parent=self,
)
return False
return True
def apply(self):
"""Sets the result to True as the action has been confirmed."""
self.result = True