440 lines
16 KiB
Python
440 lines
16 KiB
Python
# --- FILE: gitsync_tool/gui/window_handler.py ---
|
|
|
|
import os
|
|
import re
|
|
from typing import TYPE_CHECKING, Optional, List, Dict, Any
|
|
|
|
# Importazioni dal pacchetto
|
|
from gitutility.logging_setup import log_handler
|
|
from gitutility.gui.dialogs import (
|
|
CreateTagDialog,
|
|
CreateBranchDialog,
|
|
CloneFromRemoteDialog,
|
|
)
|
|
from gitutility.gui.purge_dialog import PurgeConfirmationDialog
|
|
from gitutility.gui.editors import GitignoreEditorWindow
|
|
from gitutility.gui.diff_viewer import DiffViewerWindow
|
|
from gitutility.gui.commit_detail_window import CommitDetailWindow
|
|
from gitutility.gui.diff_summary_viewer import DiffSummaryWindow
|
|
from gitutility.async_tasks import async_workers
|
|
|
|
# Forward reference per evitare importazioni circolari
|
|
if TYPE_CHECKING:
|
|
from gitutility.app import GitSvnSyncApp
|
|
from gitutility.commands.git_commands import GitCommands
|
|
|
|
|
|
class WindowHandler:
|
|
"""
|
|
Handles the creation and logic for all secondary windows, dialogs,
|
|
and editors within the application.
|
|
"""
|
|
|
|
def __init__(self, app_instance: "GitSvnSyncApp"):
|
|
"""
|
|
Initializes the WindowHandler.
|
|
|
|
Args:
|
|
app_instance: The main GitSvnSyncApp instance.
|
|
"""
|
|
self.app = app_instance
|
|
self.master = app_instance.master
|
|
self.main_frame = app_instance.main_frame
|
|
self.git_commands: "GitCommands" = app_instance.git_commands
|
|
log_handler.log_debug("WindowHandler initialized.", func_name="__init__")
|
|
|
|
# --- Tag and Branch Dialog Handlers ---
|
|
|
|
def handle_create_tag(self) -> None:
|
|
"""Handles tag creation: shows dialog, suggests name, starts async op."""
|
|
func_name = "handle_create_tag"
|
|
log_handler.log_info(
|
|
f"--- Action Triggered: Create Tag ---", func_name=func_name
|
|
)
|
|
|
|
svn_path = self.app._get_and_validate_svn_path("Create Tag")
|
|
if not svn_path or not self.app._is_repo_ready(svn_path):
|
|
log_handler.log_warning(
|
|
"Create Tag failed: Repo not ready.", func_name=func_name
|
|
)
|
|
self.main_frame.show_error("Action Failed", "Repository is not ready.")
|
|
return
|
|
|
|
self.main_frame.update_status_bar("Processing: Generating tag suggestion...")
|
|
suggested_name = self.app._generate_next_tag_suggestion(svn_path)
|
|
self.main_frame.update_status_bar("Ready for tag input.")
|
|
|
|
dialog = CreateTagDialog(self.master, suggested_tag_name=suggested_name)
|
|
tag_info = dialog.result
|
|
|
|
if tag_info:
|
|
tag_name, tag_message = tag_info
|
|
log_handler.log_info(
|
|
f"User provided tag: '{tag_name}'", func_name=func_name
|
|
)
|
|
args = (self.app.action_handler, svn_path, tag_name, tag_message)
|
|
self.app._start_async_operation(
|
|
worker_func=async_workers.run_create_tag_async,
|
|
args_tuple=args,
|
|
context_dict={
|
|
"context": "create_tag",
|
|
"status_msg": f"Creating tag '{tag_name}'",
|
|
"committed_flag_possible": True,
|
|
},
|
|
)
|
|
else:
|
|
log_handler.log_info("Tag creation cancelled.", func_name=func_name)
|
|
self.main_frame.update_status_bar("Cancelled.")
|
|
|
|
def handle_create_branch(self) -> None:
|
|
"""Handles branch creation: shows dialog, starts async operation."""
|
|
func_name = "handle_create_branch"
|
|
log_handler.log_info(
|
|
f"--- Action Triggered: Create Branch ---", func_name=func_name
|
|
)
|
|
|
|
svn_path = self.app._get_and_validate_svn_path("Create Branch")
|
|
if not svn_path or not self.app._is_repo_ready(svn_path):
|
|
log_handler.log_warning(
|
|
"Create Branch failed: Repo not ready.", func_name=func_name
|
|
)
|
|
self.main_frame.show_error("Action Failed", "Repository is not ready.")
|
|
return
|
|
|
|
self.main_frame.update_status_bar("Ready for branch name input.")
|
|
dialog = CreateBranchDialog(self.master)
|
|
branch_name = dialog.result
|
|
|
|
if branch_name:
|
|
log_handler.log_info(
|
|
f"User provided branch name: '{branch_name}'", func_name=func_name
|
|
)
|
|
args = (self.app.action_handler, svn_path, branch_name)
|
|
self.app._start_async_operation(
|
|
worker_func=async_workers.run_create_branch_async,
|
|
args_tuple=args,
|
|
context_dict={
|
|
"context": "create_branch",
|
|
"status_msg": f"Creating branch '{branch_name}'",
|
|
"new_branch_name": branch_name,
|
|
},
|
|
)
|
|
else:
|
|
log_handler.log_info("Branch creation cancelled.", func_name=func_name)
|
|
self.main_frame.update_status_bar("Cancelled.")
|
|
|
|
# --- Clone and Purge Dialog Handlers ---
|
|
|
|
def handle_clone_remote_repo(self) -> None:
|
|
"""Handles the 'Clone from Remote...' action."""
|
|
func_name = "handle_clone_remote_repo"
|
|
log_handler.log_info(
|
|
f"--- Action Triggered: Clone Remote Repository ---", func_name=func_name
|
|
)
|
|
|
|
dialog = CloneFromRemoteDialog(self.master)
|
|
dialog_result = dialog.result
|
|
|
|
if not dialog_result:
|
|
log_handler.log_info(
|
|
"Clone operation cancelled by user.", func_name=func_name
|
|
)
|
|
self.main_frame.update_status_bar("Clone cancelled.")
|
|
return
|
|
|
|
remote_url, local_parent_dir, profile_name_input = dialog_result
|
|
|
|
try:
|
|
repo_name_from_url = os.path.basename(remote_url)
|
|
if repo_name_from_url.lower().endswith(".git"):
|
|
repo_name_from_url = repo_name_from_url[:-4]
|
|
if not repo_name_from_url:
|
|
raise ValueError("Could not derive repository name from URL.")
|
|
|
|
target_clone_dir = os.path.join(local_parent_dir, repo_name_from_url)
|
|
target_clone_dir = os.path.abspath(target_clone_dir)
|
|
|
|
if profile_name_input:
|
|
final_profile_name = profile_name_input
|
|
if final_profile_name in self.app.config_manager.get_profile_sections():
|
|
raise ValueError(
|
|
f"Profile name '{final_profile_name}' already exists."
|
|
)
|
|
else:
|
|
final_profile_name = repo_name_from_url
|
|
counter = 1
|
|
while (
|
|
final_profile_name in self.app.config_manager.get_profile_sections()
|
|
):
|
|
final_profile_name = f"{repo_name_from_url}_{counter}"
|
|
counter += 1
|
|
|
|
if os.path.exists(target_clone_dir):
|
|
raise ValueError(
|
|
f"Clone failed: Target directory already exists:\n{target_clone_dir}"
|
|
)
|
|
|
|
except ValueError as ve:
|
|
log_handler.log_error(
|
|
f"Clone configuration error: {ve}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error("Configuration Error", str(ve))
|
|
return
|
|
|
|
args = (self.git_commands, remote_url, target_clone_dir, final_profile_name)
|
|
self.app._start_async_operation(
|
|
worker_func=async_workers.run_clone_remote_async,
|
|
args_tuple=args,
|
|
context_dict={
|
|
"context": "clone_remote",
|
|
"status_msg": f"Cloning '{repo_name_from_url}'...",
|
|
"clone_success_data": {
|
|
"profile_name": final_profile_name,
|
|
"cloned_path": target_clone_dir,
|
|
"remote_url": remote_url,
|
|
},
|
|
},
|
|
)
|
|
|
|
def handle_purge_confirmation(
|
|
self, repo_path: str, purgeable_files: List[Dict[str, Any]]
|
|
):
|
|
"""Shows the purge confirmation dialog and starts the purge if confirmed."""
|
|
func_name = "handle_purge_confirmation"
|
|
|
|
dialog = PurgeConfirmationDialog(self.master, purgeable_files, repo_path)
|
|
|
|
if dialog.result:
|
|
log_handler.log_warning(
|
|
"User confirmed DESTRUCTIVE history purge.", func_name=func_name
|
|
)
|
|
|
|
remote_name = self.main_frame.remote_name_var.get().strip() or "origin"
|
|
remote_url = self.main_frame.remote_url_var.get().strip()
|
|
|
|
if not remote_url:
|
|
self.main_frame.show_error(
|
|
"Action Failed", "Remote URL must be configured."
|
|
)
|
|
self.app._reenable_widgets_after_modal()
|
|
return
|
|
|
|
file_paths = [item["path"] for item in purgeable_files]
|
|
|
|
args = (
|
|
self.app.history_cleaner,
|
|
repo_path,
|
|
file_paths,
|
|
remote_name,
|
|
remote_url,
|
|
)
|
|
|
|
self.app._start_async_operation(
|
|
worker_func=async_workers.run_purge_files_from_history_async,
|
|
args_tuple=args,
|
|
context_dict={
|
|
"context": "purge_history",
|
|
"status_msg": "Purging files from history (this may take a while)...",
|
|
},
|
|
)
|
|
else:
|
|
log_handler.log_info(
|
|
"User cancelled history purge operation.", func_name=func_name
|
|
)
|
|
self.main_frame.update_status_bar("History clean-up cancelled.")
|
|
self.app._reenable_widgets_after_modal()
|
|
|
|
# --- Editor and Viewer Handlers ---
|
|
|
|
def handle_open_gitignore_editor(self) -> None:
|
|
"""Opens the .gitignore editor window."""
|
|
func_name = "handle_open_gitignore_editor"
|
|
log_handler.log_info(
|
|
f"--- Action Triggered: Edit .gitignore ---", func_name=func_name
|
|
)
|
|
|
|
svn_path = self.app._get_and_validate_svn_path("Edit .gitignore")
|
|
if not svn_path or not self.app._is_repo_ready(svn_path):
|
|
log_handler.log_warning(
|
|
"Cannot edit .gitignore: Repo not ready.", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Action Failed", "Select a valid and prepared repository."
|
|
)
|
|
return
|
|
|
|
gitignore_path = os.path.join(svn_path, ".gitignore")
|
|
try:
|
|
GitignoreEditorWindow(
|
|
master=self.master,
|
|
gitignore_path=gitignore_path,
|
|
on_save_success_callback=self.app._handle_gitignore_save,
|
|
)
|
|
except Exception as e:
|
|
log_handler.log_exception(
|
|
f"Error opening .gitignore editor: {e}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error("Editor Error", f"Could not open editor:\n{e}")
|
|
|
|
def handle_open_diff_viewer(self, file_status_line: str):
|
|
"""Opens the Diff Viewer for a file from the 'changed files' list."""
|
|
func_name = "handle_open_diff_viewer"
|
|
log_handler.log_info(
|
|
f"--- Action Triggered: Open Diff Viewer ---", func_name=func_name
|
|
)
|
|
|
|
svn_path = self.app._get_and_validate_svn_path("Open Diff Viewer")
|
|
if not svn_path:
|
|
return
|
|
|
|
relative_path = self.app._extract_path_from_status_line(file_status_line)
|
|
if not relative_path:
|
|
log_handler.log_error(
|
|
f"Could not extract path from: {file_status_line}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Path Error",
|
|
f"Could not parse file path from line:\n{file_status_line}",
|
|
)
|
|
return
|
|
|
|
status_code = file_status_line.strip("\x00").strip()[:2].strip()
|
|
if status_code == "D":
|
|
self.main_frame.show_info(
|
|
"Diff Not Applicable",
|
|
"Cannot diff a deleted file against the working directory.",
|
|
)
|
|
return
|
|
if status_code in ["??", "!!"]:
|
|
self.main_frame.show_info(
|
|
"Diff Not Applicable", "Cannot diff an untracked or ignored file."
|
|
)
|
|
return
|
|
|
|
try:
|
|
DiffViewerWindow(
|
|
master=self.master,
|
|
git_commands=self.git_commands,
|
|
repo_path=svn_path,
|
|
relative_file_path=relative_path,
|
|
ref1="WORKING_DIR",
|
|
ref2="HEAD",
|
|
)
|
|
except Exception as e:
|
|
log_handler.log_exception(
|
|
f"Error opening diff viewer: {e}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Diff Viewer Error", f"Could not display diff:\n{e}"
|
|
)
|
|
|
|
def handle_show_commit_details(self, commit_details: Dict[str, Any]):
|
|
"""Opens the CommitDetailWindow to display commit details."""
|
|
func_name = "handle_show_commit_details"
|
|
if not isinstance(commit_details, dict) or not commit_details.get("hash_full"):
|
|
log_handler.log_error(
|
|
"Invalid commit details received.", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Display Error", "Internal error: Invalid commit data."
|
|
)
|
|
self.app._reenable_widgets_after_modal()
|
|
return
|
|
|
|
try:
|
|
log_handler.log_debug(
|
|
f"Opening CommitDetailWindow for {commit_details.get('hash_full')[:7]}...",
|
|
func_name=func_name,
|
|
)
|
|
CommitDetailWindow(
|
|
master=self.master,
|
|
commit_data=commit_details,
|
|
open_diff_callback=self._handle_open_commit_file_diff,
|
|
)
|
|
log_handler.log_info("Commit Detail window closed.", func_name=func_name)
|
|
self.main_frame.update_status_bar("Ready.")
|
|
except Exception as e_detail:
|
|
log_handler.log_exception(
|
|
f"Error opening commit detail window: {e_detail}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Display Error", f"Could not display commit details:\n{e_detail}"
|
|
)
|
|
finally:
|
|
self.app._reenable_widgets_after_modal()
|
|
|
|
def _handle_open_commit_file_diff(
|
|
self,
|
|
commit_hash: str,
|
|
file_status: str,
|
|
file_path: str,
|
|
old_file_path: Optional[str] = None,
|
|
):
|
|
"""Callback to open diff for a file from the commit detail view."""
|
|
func_name = "_handle_open_commit_file_diff"
|
|
log_handler.log_info(
|
|
f"Requesting diff for file '{file_path}' in commit '{commit_hash[:7]}'",
|
|
func_name=func_name,
|
|
)
|
|
|
|
svn_path = self.app._get_and_validate_svn_path("Open Commit File Diff")
|
|
if not svn_path:
|
|
return
|
|
|
|
ref1 = f"{commit_hash}^"
|
|
ref2 = commit_hash
|
|
|
|
if file_status == "D":
|
|
self.main_frame.show_info(
|
|
"Diff Not Applicable",
|
|
"Diff for deleted files in this view is not supported.",
|
|
)
|
|
return
|
|
|
|
try:
|
|
DiffViewerWindow(
|
|
master=self.master,
|
|
git_commands=self.git_commands,
|
|
repo_path=svn_path,
|
|
relative_file_path=file_path,
|
|
ref1=ref1,
|
|
ref2=ref2,
|
|
)
|
|
except Exception as e_diff:
|
|
log_handler.log_exception(
|
|
f"Error opening commit file diff viewer: {e_diff}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Diff Viewer Error", f"Could not display file changes:\n{e_diff}"
|
|
)
|
|
|
|
def handle_show_comparison_summary(
|
|
self, ref1: str, ref2: str, repo_path: str, changed_files: List[str]
|
|
):
|
|
"""Opens the comparison summary window."""
|
|
func_name = "handle_show_comparison_summary"
|
|
log_handler.log_debug(
|
|
f"Attempting to show comparison summary: {ref1} vs {ref2}",
|
|
func_name=func_name,
|
|
)
|
|
|
|
try:
|
|
DiffSummaryWindow(
|
|
master=self.master,
|
|
git_commands=self.git_commands,
|
|
repo_path=repo_path,
|
|
ref1=ref1,
|
|
ref2=ref2,
|
|
changed_files_status=changed_files,
|
|
)
|
|
self.main_frame.update_status_bar("Ready.")
|
|
except Exception as e_summary:
|
|
log_handler.log_exception(
|
|
f"Error opening diff summary window: {e_summary}", func_name=func_name
|
|
)
|
|
self.main_frame.show_error(
|
|
"Display Error", f"Could not display comparison results:\n{e_summary}"
|
|
)
|
|
finally:
|
|
self.app._reenable_widgets_after_modal()
|