# --- FILE: gitsync_tool/async_tasks/async_workers.py --- import os import queue import logging # Usato solo per i livelli di logging (es. logging.INFO) import datetime from typing import List, Dict, Any, Tuple, Optional, Set # Aggiunto Set import subprocess # ---<<< MODIFICA IMPORT >>>--- # Importa usando il percorso assoluto dal pacchetto gitsync_tool from gitutility.logging_setup import log_handler # Usa import relativi per salire di livello e raggiungere altri moduli from ..commands.git_commands import GitCommands, GitCommandError from ..core.action_handler import ActionHandler from ..core.backup_handler import BackupHandler from ..core.remote_actions import RemoteActionHandler from ..core.wiki_updater import WikiUpdater # ---<<< FINE MODIFICA IMPORT >>>--- # Nota: Queste sono funzioni standalone, non metodi di una classe. # === Worker per Refresh GUI === def run_refresh_tags_async( git_commands: GitCommands, repo_path: str, results_queue: queue.Queue[Dict[str, Any]], # Type hint per la coda ) -> None: # Le funzioni worker non ritornano nulla direttamente """Worker to fetch tag list asynchronously.""" func_name: str = "run_refresh_tags_async" log_handler.log_debug( f"[Worker] Started: Refresh Tags for '{repo_path}'", func_name=func_name ) result_payload: Dict[str, Any] = { # Dizionario per il risultato "status": "error", # Default a errore "result": [("(Error)", "")], # Formato atteso dalla GUI in caso di errore "message": "Tag refresh failed.", "exception": None, } try: # Chiama il metodo corretto in GitCommands tags_data: List[Tuple[str, str]] = git_commands.list_tags(repo_path) count: int = len(tags_data) message: str = f"Tags refreshed ({count} found)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload per successo result_payload["status"] = "success" result_payload["result"] = tags_data result_payload["message"] = message except (GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing tags: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error refreshing tags: {type(e).__name__}" # result è già impostato al valore di errore finally: try: results_queue.put(result_payload) # Metti il risultato nella coda except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug(f"[Worker] Finished: Refresh Tags", func_name=func_name) def run_refresh_branches_async( git_commands: GitCommands, repo_path: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to fetch local branch list asynchronously.""" func_name: str = "run_refresh_branches_async" log_handler.log_debug( f"[Worker] Started: Refresh Local Branches for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": (["(Error)"], None), # Formato atteso tupla (lista, corrente) "message": "Branch refresh failed.", "exception": None, } try: # Chiama il metodo corretto in GitCommands branches: List[str] current: Optional[str] branches, current = git_commands.list_branches(repo_path) count: int = len(branches) curr_disp: str = current if current else "None (Detached?)" message: str = f"Local branches refreshed ({count} found). Current: {curr_disp}" log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = (branches, current) result_payload["message"] = message except (GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing local branches: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = ( f"Error refreshing local branches: {type(e).__name__}" ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Refresh Local Branches", func_name=func_name ) def run_refresh_history_async( git_commands: GitCommands, repo_path: str, branch_filter: Optional[str], # Può essere None log_scope: str, # Descrizione per i log (es. 'All History') results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to fetch commit history asynchronously.""" func_name: str = "run_refresh_history_async" log_handler.log_debug( f"[Worker] Started: Refresh History ({log_scope}) for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": ["(Error retrieving history)"], # Formato atteso lista stringhe "message": "History refresh failed.", "exception": None, } try: # Chiama il metodo corretto in GitCommands log_data: List[str] = git_commands.get_commit_log( repo_path, max_count=200, branch=branch_filter ) count: int = len(log_data) message: str = f"History refreshed ({count} entries for {log_scope})." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = log_data result_payload["message"] = message except (GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing history: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error refreshing history: {type(e).__name__}" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Refresh History ({log_scope})", func_name=func_name ) def run_refresh_changes_async( git_commands: GitCommands, repo_path: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to get status of changed files asynchronously.""" func_name: str = "run_refresh_changes_async" log_handler.log_debug( f"[Worker] Started: Refresh Changes for '{repo_path}'", func_name=func_name ) result_payload: Dict[str, Any] = { "status": "error", "result": ["(Error refreshing changes)"], "message": "Changes refresh failed.", "exception": None, } try: # Chiama il metodo corretto in GitCommands files_status_list: List[str] = git_commands.get_status_short(repo_path) count: int = len(files_status_list) log_handler.log_info(f"[Worker] Found {count} changes.", func_name=func_name) message: str = ( f"Ready ({count} changes detected)." if count > 0 else "Ready (No changes detected)." ) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = files_status_list result_payload["message"] = message except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing changes: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error refreshing changes: {type(e).__name__}" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Refresh Changes", func_name=func_name ) # === Worker per Azioni Principali === def run_prepare_async( action_handler: ActionHandler, repo_path: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to prepare repository asynchronously.""" func_name: str = "run_prepare_async" log_handler.log_debug( f"[Worker] Started: Prepare Repo for '{repo_path}'", func_name=func_name ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": "Prepare failed.", "exception": None, } try: # Chiama il metodo corretto in ActionHandler success: bool = action_handler.execute_prepare_repo(repo_path) message: str = "Repository prepared successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except ValueError as e: # Gestione specifica per "already prepared" if "already prepared" in str(e).lower(): log_handler.log_warning(f"[Worker] Warning: {e}", func_name=func_name) result_payload["status"] = "warning" # Segnala come warning result_payload["result"] = True # Considera successo funzionale result_payload["message"] = str(e) result_payload["exception"] = e # Allega comunque l'eccezione else: # Rilancia altri ValueError log_handler.log_exception( f"[Worker] VALUE ERROR preparing repo: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error preparing repository: {e}" except (GitCommandError, IOError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION preparing repo: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error preparing repository: {type(e).__name__}" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug(f"[Worker] Finished: Prepare Repo", func_name=func_name) def run_create_bundle_async( action_handler: ActionHandler, repo_path: str, bundle_full_path: str, profile_name: str, autobackup_enabled: bool, backup_base_dir: str, autocommit_enabled: bool, commit_message: str, excluded_extensions: Set[str], # Usa Set excluded_dirs: Set[str], # Usa Set results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to create Git bundle asynchronously.""" func_name: str = "run_create_bundle_async" log_handler.log_debug( f"[Worker] Started: Create Bundle '{os.path.basename(bundle_full_path)}' from '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": None, "message": "Bundle creation failed.", "exception": None, "committed": False, # Flag per indicare se è stato fatto un autocommit } try: # Chiama il metodo corretto in ActionHandler result_path: Optional[str] = action_handler.execute_create_bundle( repo_path=repo_path, bundle_full_path=bundle_full_path, profile_name=profile_name, autobackup_enabled=autobackup_enabled, backup_base_dir=backup_base_dir, autocommit_enabled=autocommit_enabled, commit_message=commit_message, excluded_extensions=excluded_extensions, excluded_dirs=excluded_dirs, ) # Determina messaggio successo e aggiorna payload result_payload["status"] = "success" result_payload["result"] = result_path result_payload["committed"] = ( autocommit_enabled # Indica se l'autocommit è stato TENTATO (il risultato effettivo è gestito da ActionHandler) ) if result_path: message: str = ( f"Bundle created successfully: {os.path.basename(result_path)}" ) result_payload["message"] = message log_handler.log_info(f"[Worker] {message}", func_name=func_name) else: message = "Bundle creation finished (no file generated - repo empty or no changes?)." result_payload["message"] = message log_handler.log_warning(f"[Worker] {message}", func_name=func_name) except (IOError, GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating bundle: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error creating bundle: {type(e).__name__}" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug(f"[Worker] Finished: Create Bundle", func_name=func_name) def run_fetch_bundle_async( action_handler: ActionHandler, target_repo_path_str: str, bundle_full_path: str, profile_name: str, autobackup_enabled: bool, backup_base_dir: str, excluded_extensions: Set[str], # Usa Set excluded_dirs: Set[str], # Usa Set results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to fetch/clone from Git bundle asynchronously.""" func_name: str = "run_fetch_bundle_async" log_handler.log_debug( f"[Worker] Started: Fetch Bundle '{os.path.basename(bundle_full_path)}' into '{target_repo_path_str}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": "Fetch from bundle failed.", "exception": None, "conflict": False, # Flag specifico per conflitti "repo_path": target_repo_path_str, # Passa path per messaggi errore } try: # Chiama il metodo corretto in ActionHandler success: bool = action_handler.execute_fetch_bundle( target_repo_path_str=target_repo_path_str, bundle_full_path=bundle_full_path, profile_name=profile_name, autobackup_enabled=autobackup_enabled, backup_base_dir=backup_base_dir, excluded_extensions=excluded_extensions, excluded_dirs=excluded_dirs, ) message: str = "Fetch/Clone from bundle completed successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except (FileNotFoundError, IOError, GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION fetching bundle: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error fetching from bundle: {type(e).__name__}" # Controlla se è un errore di conflitto merge if isinstance(e, GitCommandError) and "merge conflict" in str(e).lower(): result_payload["conflict"] = True result_payload["message"] = ( f"Merge conflict occurred during fetch/merge from bundle." ) log_handler.log_error( "[Worker] Merge conflict detected during fetch from bundle.", func_name=func_name, ) elif isinstance(e, FileNotFoundError): result_payload["message"] = ( f"Bundle file not found: {os.path.basename(bundle_full_path)}" ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug(f"[Worker] Finished: Fetch Bundle", func_name=func_name) def run_manual_backup_async( backup_handler: BackupHandler, repo_path: str, backup_base_dir: str, profile_name: str, excluded_extensions: Set[str], # Usa Set excluded_dirs: Set[str], # Usa Set results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to create manual backup asynchronously.""" func_name: str = "run_manual_backup_async" log_handler.log_debug( f"[Worker] Started: Manual Backup for '{repo_path}' (Profile: {profile_name})", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": None, "message": "Manual backup failed.", "exception": None, } try: # Chiama il metodo corretto in BackupHandler result_path: Optional[str] = backup_handler.create_zip_backup( source_repo_path=repo_path, backup_base_dir=backup_base_dir, profile_name=profile_name, excluded_extensions=excluded_extensions, excluded_dirs_base=excluded_dirs, ) # Messaggio successo ts: str = datetime.datetime.now().strftime("%H:%M:%S") message: str if result_path: message = f"Manual backup created: {os.path.basename(result_path)} ({ts})." else: message = ( f"Manual backup finished (no file generated - empty/excluded?) ({ts})." ) log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = result_path result_payload["message"] = message except (IOError, ValueError, PermissionError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating manual backup: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error creating backup: {type(e).__name__}" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug(f"[Worker] Finished: Manual Backup", func_name=func_name) def run_commit_async( action_handler: ActionHandler, repo_path: str, commit_message: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to perform manual commit asynchronously.""" func_name: str = "run_commit_async" log_handler.log_debug( f"[Worker] Started: Commit for '{repo_path}'", func_name=func_name ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": "Commit failed.", "exception": None, "committed": False, # Flag esplicito se commit è avvenuto } try: # Chiama il metodo corretto in ActionHandler committed: bool = action_handler.execute_manual_commit( repo_path, commit_message ) message: str if committed: message = "Commit successful." else: message = "Commit finished (no changes detected to commit)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = committed result_payload["message"] = message result_payload["committed"] = committed # Imposta flag corretto except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION committing: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error committing changes: {type(e).__name__}" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug(f"[Worker] Finished: Commit", func_name=func_name) def run_untrack_async( action_handler: ActionHandler, repo_path: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to untrack files based on .gitignore asynchronously.""" func_name: str = "run_untrack_async" log_handler.log_debug( f"[Worker] Started: Untrack Files Check for '{repo_path}'", func_name=func_name ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": "Untracking failed.", "exception": None, "committed": False, # Flag se commit di untrack è avvenuto } try: # Chiama il metodo corretto in ActionHandler committed: bool = action_handler.execute_untrack_files_from_gitignore(repo_path) message: str if committed: message = ( "Untracking complete: Files removed from index and commit created." ) else: message = ( "Untrack check complete (no tracked files matched .gitignore rules)." ) log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = committed # Indica se azione è stata fatta result_payload["message"] = message result_payload["committed"] = committed # Indica se commit è avvenuto except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION untracking files: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = ( f"Error during untracking operation: {type(e).__name__}" ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Untrack Files Check", func_name=func_name ) def run_add_file_async( git_commands: GitCommands, repo_path: str, relative_path: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to add a file to staging asynchronously.""" func_name: str = "run_add_file_async" base_filename: str = os.path.basename(relative_path) log_handler.log_debug( f"[Worker] Started: Add File '{relative_path}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": f"Add file '{base_filename}' failed.", "exception": None, } try: # Chiama il metodo corretto in GitCommands success: bool = git_commands.add_file(repo_path, relative_path) message: str = f"File '{base_filename}' added to staging area successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION adding file: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = ( f"Error adding file '{base_filename}': {type(e).__name__}" ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Add File '{relative_path}'", func_name=func_name ) def run_create_tag_async( action_handler: ActionHandler, repo_path: str, tag_name: str, tag_message: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to create an annotated tag asynchronously.""" func_name: str = "run_create_tag_async" log_handler.log_debug( f"[Worker] Started: Create Tag '{tag_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": f"Create tag '{tag_name}' failed.", "exception": None, "committed": False, # Indica se pre-tag commit è stato fatto } try: # Chiama il metodo corretto in ActionHandler (passa None per arg 'ignored') # execute_create_tag gestisce il pre-tag commit success: bool = action_handler.execute_create_tag( repo_path=repo_path, ignored=None, tag_name=tag_name, tag_message=tag_message, ) message: str = f"Tag '{tag_name}' created successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message result_payload["committed"] = True # Tag annotato implica un oggetto commit/tag except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating tag: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = ( f"Error creating tag '{tag_name}': {type(e).__name__}" ) # Controlla se l'errore è 'already exists' if isinstance(e, GitCommandError) and "already exists" in str(e).lower(): result_payload["message"] = f"Tag '{tag_name}' already exists." finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Create Tag '{tag_name}'", func_name=func_name ) def run_checkout_tag_async( action_handler: ActionHandler, repo_path: str, tag_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to checkout a tag asynchronously.""" func_name: str = "run_checkout_tag_async" log_handler.log_debug( f"[Worker] Started: Checkout Tag '{tag_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": f"Checkout tag '{tag_name}' failed.", "exception": None, } try: # Chiama il metodo corretto in ActionHandler success: bool = action_handler.execute_checkout_tag(repo_path, tag_name) message: str = f"Checked out tag '{tag_name}' (Detached HEAD state)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except (ValueError, GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION checking out tag: {e}", func_name=func_name ) # Gestisci errore specifico per uncommitted changes if isinstance(e, ValueError) and "Uncommitted changes" in str(e): msg: str = ( "Checkout failed: Uncommitted changes exist. Commit or stash first." ) elif isinstance(e, GitCommandError) and ( "not found" in str(e).lower() or "did not match" in str(e).lower() ): msg = f"Checkout failed: Tag '{tag_name}' not found or invalid." else: msg = f"Error checking out tag '{tag_name}': {type(e).__name__}" result_payload["exception"] = e result_payload["message"] = msg finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Checkout Tag '{tag_name}'", func_name=func_name ) def run_create_branch_async( action_handler: ActionHandler, repo_path: str, branch_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to create a branch asynchronously.""" func_name: str = "run_create_branch_async" log_handler.log_debug( f"[Worker] Started: Create Branch '{branch_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": f"Create branch '{branch_name}' failed.", "exception": None, } try: # Chiama il metodo corretto in ActionHandler success: bool = action_handler.execute_create_branch(repo_path, branch_name) message: str = f"Branch '{branch_name}' created successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating branch: {e}", func_name=func_name ) result_payload["exception"] = e # Gestisci errori specifici if isinstance(e, GitCommandError) and "already exists" in str(e).lower(): result_payload["message"] = f"Branch '{branch_name}' already exists." elif isinstance(e, ValueError): # Es. nome invalido result_payload["message"] = f"Invalid branch name: {e}" else: result_payload["message"] = ( f"Error creating branch '{branch_name}': {type(e).__name__}" ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Create Branch '{branch_name}'", func_name=func_name ) def run_checkout_branch_async( action_handler: ActionHandler, repo_path: str, branch_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to checkout an existing local branch asynchronously.""" func_name: str = "run_checkout_branch_async" log_handler.log_debug( f"[Worker] Started: Checkout Branch '{branch_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": f"Checkout branch '{branch_name}' failed.", "exception": None, } try: # Chiama il metodo corretto in ActionHandler success: bool = action_handler.execute_switch_branch(repo_path, branch_name) message: str = f"Switched successfully to branch '{branch_name}'." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except (ValueError, GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION checking out branch: {e}", func_name=func_name ) # Gestisci errore specifico per uncommitted changes if isinstance(e, ValueError) and "Uncommitted changes" in str(e): msg: str = ( "Checkout failed: Uncommitted changes exist. Commit or stash first." ) elif isinstance(e, GitCommandError) and ( "not found" in str(e).lower() or "did not match" in str(e).lower() ): msg = f"Checkout failed: Branch '{branch_name}' not found or invalid." else: msg = f"Error checking out branch '{branch_name}': {type(e).__name__}" result_payload["exception"] = e result_payload["message"] = msg finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Checkout Branch '{branch_name}'", func_name=func_name ) # === Worker per Azioni Remote === def run_apply_remote_config_async( remote_action_handler: RemoteActionHandler, repo_path: str, remote_name: str, remote_url: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to apply remote configuration asynchronously.""" func_name: str = "run_apply_remote_config_async" log_handler.log_debug( f"[Worker] Started: Apply Remote Config for '{remote_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": "Apply remote config failed.", "exception": None, } try: # Chiama il metodo corretto in RemoteActionHandler success: bool = remote_action_handler.apply_remote_config( repo_path, remote_name, remote_url ) message: str = f"Remote '{remote_name}' configuration applied successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Aggiorna payload successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = message except (GitCommandError, ValueError, Exception) as e: # Cattura eccezioni specifiche o generiche log_handler.log_exception( f"[Worker] EXCEPTION applying remote config: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = ( f"Error applying remote config: {e}" # Usa messaggio eccezione ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Apply Remote Config for '{remote_name}'", func_name=func_name, ) def run_check_connection_async( git_commands: GitCommands, repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to check remote connection/auth using 'git ls-remote'.""" func_name: str = "run_check_connection_async" log_handler.log_debug( f"[Worker] Started: Check Connection/Auth for '{remote_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", # Default a errore "result": "unknown_error", # Default a errore sconosciuto "message": f"Failed to check remote '{remote_name}'.", "exception": None, } try: # Esegui ls-remote catturando output, senza check=True result: subprocess.CompletedProcess = git_commands.git_ls_remote( repo_path, remote_name ) # Analizza il risultato if result.returncode == 0: message: str = f"Connection to remote '{remote_name}' successful." log_handler.log_info(f"[Worker] {message}", func_name=func_name) result_payload.update(status="success", result="connected", message=message) elif result.returncode == 2: # Remote vuoto/unborn message = f"Connected to remote '{remote_name}' (Note: Repository might be empty or unborn)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) result_payload.update( status="success", result="connected_empty", message=message ) else: # Errore (RC != 0 e != 2) stderr_lower: str = (result.stderr or "").lower() log_handler.log_warning( f"[Worker] ls-remote failed (RC={result.returncode}). Stderr: {stderr_lower}", func_name=func_name, ) auth_errors = [ "authentication failed", "permission denied", "could not read username", "could not read password", ] conn_errors = [ "repository not found", "could not resolve host", "name or service not known", "network is unreachable", "failed to connect", "unable to access", "could not connect", "connection timed out", ] if any(err in stderr_lower for err in auth_errors): message = ( f"Authentication required or failed for remote '{remote_name}'." ) log_handler.log_warning(f"[Worker] {message}", func_name=func_name) result_payload.update( status="auth_required", result="authentication needed", message=message, ) elif any(err in stderr_lower for err in conn_errors): message = f"Connection failed for remote '{remote_name}': Repository or host not found/reachable." log_handler.log_error(f"[Worker] {message}", func_name=func_name) result_payload.update( status="error", result="connection_failed", message=message ) else: message = f"Failed to check remote '{remote_name}'. Check logs. (RC={result.returncode})" log_handler.log_error( f"[Worker] Unknown error checking remote. Stderr: {result.stderr}", func_name=func_name, ) result_payload.update( status="error", result="unknown_error", message=message ) # Allega eccezione fittizia o dettaglio errore result_payload["exception"] = GitCommandError(message, stderr=result.stderr) except Exception as e: # Errore imprevisto nell'esecuzione del worker stesso log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION checking connection: {e}", func_name=func_name, ) result_payload.update( status="error", result="worker_exception", message=f"Unexpected error checking connection: {type(e).__name__}", ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Check Connection/Auth for '{remote_name}'", func_name=func_name, ) def run_interactive_auth_attempt_async( git_commands: GitCommands, repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to attempt interactive Git fetch to trigger credential prompts.""" func_name: str = "run_interactive_auth_attempt_async" log_handler.log_info( f"[Worker] Started: Interactive Auth Attempt for '{remote_name}' via Fetch in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": "auth_attempt_failed", "message": f"Interactive auth for '{remote_name}' failed.", "exception": None, } try: # Esegui git fetch in modalità interattiva (no capture, no hide) result: subprocess.CompletedProcess = git_commands.git_fetch_interactive( repo_path, remote_name ) # Controlla solo il codice di ritorno if result.returncode == 0: message: str = ( f"Interactive authentication attempt for '{remote_name}' seems successful." ) log_handler.log_info(f"[Worker] {message}", func_name=func_name) result_payload.update( status="success", result="auth_attempt_success", message=message ) else: message = f"Interactive authentication attempt for '{remote_name}' failed or was cancelled (RC={result.returncode})." log_handler.log_warning(f"[Worker] {message}", func_name=func_name) result_payload["message"] = message # Crea eccezione fittizia (non abbiamo stderr qui) result_payload["exception"] = GitCommandError(message, stderr=None) except Exception as e: # Errore imprevisto nell'esecuzione del worker log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during interactive auth attempt: {e}", func_name=func_name, ) result_payload.update( status="error", result="worker_exception", message=f"Unexpected error during interactive auth: {type(e).__name__}", ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Interactive Auth Attempt for '{remote_name}'", func_name=func_name, ) def run_fetch_remote_async( remote_action_handler: RemoteActionHandler, repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to execute 'git fetch' asynchronously via RemoteActionHandler.""" func_name: str = "run_fetch_remote_async" log_handler.log_debug( f"[Worker] Started: Fetch Remote '{remote_name}' for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": f"Fetch remote '{remote_name}' failed.", "exception": None, } try: # Chiama il metodo execute_remote_fetch che ritorna già un dizionario risultato result_info: Dict[str, Any] = remote_action_handler.execute_remote_fetch( repo_path, remote_name ) result_payload = result_info # Usa il risultato diretto dall'handler log_handler.log_info( f"[Worker] Fetch result status for '{remote_name}': {result_payload.get('status')}", func_name=func_name, ) except Exception as e: # Cattura eccezioni impreviste sollevate da execute_remote_fetch stesso log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during fetch execution: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error during fetch operation: {type(e).__name__}" ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Fetch Remote '{remote_name}'", func_name=func_name ) def run_pull_remote_async( remote_action_handler: RemoteActionHandler, git_commands: GitCommands, # Necessario per ottenere branch corrente repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to execute 'git pull' asynchronously via RemoteActionHandler.""" func_name: str = "run_pull_remote_async" log_handler.log_debug( f"[Worker] Started: Pull Remote '{remote_name}' for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": f"Pull remote '{remote_name}' failed.", "exception": None, } try: # --- Ottieni il branch corrente --- current_branch_name: Optional[str] = git_commands.get_current_branch_name( repo_path ) if not current_branch_name: raise ValueError( "Cannot perform pull: Unable to determine current branch (possibly detached HEAD)." ) log_handler.log_debug( f"[Worker] Current branch for pull: '{current_branch_name}'", func_name=func_name, ) # --- Chiama l'Action Handler --- result_info: Dict[str, Any] = remote_action_handler.execute_remote_pull( repo_path, remote_name, current_branch_name ) result_payload = result_info # Usa risultato dall'handler log_handler.log_info( f"[Worker] Pull result status for '{remote_name}': {result_payload.get('status')}", func_name=func_name, ) # Aggiungi info extra per gestione conflitti (assicura che repo_path sia presente) if result_payload.get("status") == "conflict": result_payload["repo_path"] = repo_path except (GitCommandError, ValueError) as e: # Cattura errori dalla determinazione del branch o validazione log_handler.log_error( f"[Worker] Handled EXCEPTION during pull setup/execution: {e}", func_name=func_name, ) result_payload["message"] = f"Pull failed: {e}" result_payload["exception"] = e except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during pull operation: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error during pull operation: {type(e).__name__}" ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Pull Remote '{remote_name}'", func_name=func_name ) def run_push_remote_async( remote_action_handler: RemoteActionHandler, git_commands: GitCommands, # Necessario per ottenere branch corrente repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to execute 'git push' asynchronously via RemoteActionHandler.""" func_name: str = "run_push_remote_async" log_handler.log_debug( f"[Worker] Started: Push Remote '{remote_name}' for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": f"Push remote '{remote_name}' failed.", "exception": None, } try: # --- Ottieni il branch corrente --- current_branch_name: Optional[str] = git_commands.get_current_branch_name( repo_path ) if not current_branch_name: raise ValueError( "Cannot perform push: Unable to determine current branch (possibly detached HEAD)." ) log_handler.log_debug( f"[Worker] Current branch for push: '{current_branch_name}'", func_name=func_name, ) # --- Chiama l'Action Handler --- # Nota: force=False per push standard da GUI result_info: Dict[str, Any] = remote_action_handler.execute_remote_push( repo_path=repo_path, remote_name=remote_name, current_branch_name=current_branch_name, force=False, ) result_payload = result_info # Usa risultato dall'handler log_handler.log_info( f"[Worker] Push result status for '{current_branch_name}' to '{remote_name}': {result_payload.get('status')}", func_name=func_name, ) # Aggiungi info extra per gestione GUI (nome branch se rifiutato) if result_payload.get("status") == "rejected": result_payload["branch_name"] = current_branch_name except (GitCommandError, ValueError) as e: # Cattura errori dalla determinazione del branch o validazione log_handler.log_error( f"[Worker] Handled EXCEPTION during push setup/execution: {e}", func_name=func_name, ) result_payload["message"] = f"Push failed: {e}" result_payload["exception"] = e except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during push operation: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error during push operation: {type(e).__name__}" ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Push Remote '{remote_name}'", func_name=func_name ) def run_push_tags_async( remote_action_handler: RemoteActionHandler, repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to execute 'git push --tags' asynchronously via RemoteActionHandler.""" func_name: str = "run_push_tags_async" log_handler.log_debug( f"[Worker] Started: Push Tags to '{remote_name}' for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": f"Push tags to '{remote_name}' failed.", "exception": None, } try: # Chiama l'Action Handler result_info: Dict[str, Any] = remote_action_handler.execute_push_tags( repo_path, remote_name ) result_payload = result_info # Usa risultato dall'handler log_handler.log_info( f"[Worker] Push tags result status for '{remote_name}': {result_payload.get('status')}", func_name=func_name, ) except (GitCommandError, ValueError) as e: # Cattura errori noti sollevati da execute_push_tags log_handler.log_error( f"[Worker] Handled EXCEPTION during push tags execution: {e}", func_name=func_name, ) result_payload["message"] = f"Push tags failed: {e}" result_payload["exception"] = e except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during push tags operation: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error during push tags operation: {type(e).__name__}" ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Push Tags to '{remote_name}'", func_name=func_name ) def run_get_ahead_behind_async( git_commands: GitCommands, repo_path: str, local_branch: str, upstream_branch: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker function to get ahead/behind commit counts asynchronously.""" func_name: str = "run_get_ahead_behind_async" log_handler.log_debug( f"[Worker] Started: Get Ahead/Behind for '{local_branch}' vs '{upstream_branch}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", # Default a errore "result": (None, None), # Default risultato tupla "message": f"Could not get ahead/behind status for '{local_branch}'.", "exception": None, } try: # Chiama il metodo in GitCommands ahead_count: Optional[int] behind_count: Optional[int] ahead_count, behind_count = git_commands.get_ahead_behind_count( working_directory=repo_path, local_branch=local_branch, upstream_branch=upstream_branch, ) # Verifica se il comando ha restituito valori validi if ahead_count is not None and behind_count is not None: result_payload["status"] = "success" result_payload["result"] = (ahead_count, behind_count) message: str if ahead_count == 0 and behind_count == 0: message = ( f"Branch '{local_branch}' is up to date with '{upstream_branch}'." ) else: parts: List[str] = [] if ahead_count > 0: plural_a = "s" if ahead_count > 1 else "" parts.append(f"{ahead_count} commit{plural_a} ahead") if behind_count > 0: plural_b = "s" if behind_count > 1 else "" parts.append(f"{behind_count} commit{plural_b} behind") message = ( f"Branch '{local_branch}' is " + " and ".join(parts) + f" of '{upstream_branch}'." ) result_payload["message"] = message log_handler.log_info(f"[Worker] {message}", func_name=func_name) else: # Se get_ahead_behind_count ha restituito None, c'è stato un errore log_handler.log_warning( f"[Worker] Failed to get valid ahead/behind counts for '{local_branch}'.", func_name=func_name, ) # Mantiene lo status di errore e il messaggio di default except Exception as e: # Cattura eccezioni impreviste nel worker stesso log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION getting ahead/behind: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error getting ahead/behind status: {type(e).__name__}" ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Get Ahead/Behind for '{local_branch}'", func_name=func_name, ) def run_clone_remote_async( git_commands: GitCommands, remote_url: str, local_clone_path: str, profile_name_to_create: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker function to execute 'git clone' asynchronously.""" func_name: str = "run_clone_remote_async" log_handler.log_debug( f"[Worker] Started: Clone from '{remote_url}' into '{local_clone_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": "Clone operation failed.", "exception": None, "result": None, } try: # Chiama il metodo git_clone (check=False) clone_result: subprocess.CompletedProcess = git_commands.git_clone( remote_url, local_clone_path ) # Analizza il risultato if clone_result.returncode == 0: result_payload["status"] = "success" result_payload["message"] = ( f"Repository cloned successfully into '{os.path.basename(local_clone_path)}'." ) # Passa dati necessari per creare profilo nel risultato result_payload["result"] = { "cloned_path": local_clone_path, "profile_name": profile_name_to_create, "remote_url": remote_url, } log_handler.log_info( f"[Worker] Clone successful: {result_payload['message']}", func_name=func_name, ) else: # Errore durante il clone stderr_full: str = clone_result.stderr if clone_result.stderr else "" stderr_lower: str = stderr_full.lower() log_handler.log_error( f"Clone command failed (RC={clone_result.returncode}). Stderr: {stderr_lower}", func_name=func_name, ) # Controlla errori specifici noti auth_errors = [ "authentication failed", "permission denied", "could not read username", "could not read password", ] conn_errors = [ "repository not found", "could not resolve host", "name or service not known", "network is unreachable", "failed to connect", "unable to access", ] path_errors = [ "already exists and is not an empty directory", "could not create work tree", ] if any(err in stderr_lower for err in auth_errors): result_payload["message"] = ( f"Authentication required or failed for cloning '{remote_url}'." ) elif any(err in stderr_lower for err in conn_errors): result_payload["message"] = ( f"Connection failed while cloning: '{remote_url}' not found/reachable." ) elif any(err in stderr_lower for err in path_errors): result_payload["message"] = ( f"Clone failed: Target directory '{local_clone_path}' invalid or not empty." ) else: result_payload["message"] = ( f"Clone from '{remote_url}' failed (RC={clone_result.returncode}). Check logs." ) result_payload["exception"] = GitCommandError( result_payload["message"], stderr=stderr_full ) except Exception as e: # Cattura eccezioni impreviste nel worker stesso log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during clone operation: {e}", func_name=func_name, ) result_payload.update( status="error", result="worker_exception", message=f"Unexpected error during clone: {type(e).__name__}", ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Clone Remote '{remote_url}'", func_name=func_name ) def run_refresh_remote_branches_async( git_commands: GitCommands, repo_path: str, remote_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker function to get the list of remote branches asynchronously.""" func_name: str = "run_refresh_remote_branches_async" log_handler.log_debug( f"[Worker] Started: Refresh Remote Branches for '{remote_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": ["(Error)"], "message": f"Could not get remote branches for '{remote_name}'.", "exception": None, } try: # Chiama il metodo in GitCommands remote_branches: List[str] = git_commands.git_list_remote_branches( repo_path, remote_name ) # Successo (anche se lista vuota) result_payload["status"] = "success" result_payload["result"] = remote_branches count: int = len(remote_branches) if count > 0: result_payload["message"] = ( f"Found {count} remote branches for '{remote_name}'." ) else: result_payload["message"] = ( f"No remote branches found for '{remote_name}' (or remote invalid)." ) log_handler.log_info( f"[Worker] {result_payload['message']}", func_name=func_name ) except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION refreshing remote branches: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error listing remote branches: {type(e).__name__}" ) result_payload["exception"] = e # result è già impostato a "(Error)" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Refresh Remote Branches for '{remote_name}'", func_name=func_name, ) def run_checkout_tracking_branch_async( action_handler: ActionHandler, repo_path: str, new_local_branch_name: str, remote_tracking_branch_full_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker function to checkout a remote branch as a new local tracking branch.""" func_name: str = "run_checkout_tracking_branch_async" log_handler.log_debug( f"[Worker] Started: Checkout Remote '{remote_tracking_branch_full_name}' as Local '{new_local_branch_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": "Checkout tracking branch failed.", "exception": None, } try: # Chiama il metodo execute_checkout_tracking_branch che ritorna già un dizionario result_info: Dict[str, Any] = action_handler.execute_checkout_tracking_branch( repo_path=repo_path, new_local_branch_name=new_local_branch_name, remote_tracking_branch_full_name=remote_tracking_branch_full_name, ) result_payload = result_info # Usa risultato diretto log_handler.log_info( f"[Worker] Checkout tracking branch result status: {result_payload.get('status')}", func_name=func_name, ) except Exception as e: # Cattura eccezioni impreviste sollevate da execute_... (es., validazione iniziale) log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during checkout tracking branch execution: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error during checkout operation: {type(e).__name__}" ) result_payload["exception"] = e result_payload["result"] = "worker_exception" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Checkout Remote Branch '{remote_tracking_branch_full_name}' as Local '{new_local_branch_name}'", func_name=func_name, ) def run_delete_local_branch_async( action_handler: ActionHandler, repo_path: str, branch_name: str, force: bool, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker function to delete a local branch asynchronously.""" func_name: str = "run_delete_local_branch_async" action_type: str = "Force delete" if force else "Delete" log_handler.log_debug( f"[Worker] Started: {action_type} Local Branch '{branch_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": f"Delete branch '{branch_name}' failed.", "exception": None, } try: # Chiama il metodo execute_delete_local_branch che ritorna dizionario result_info: Dict[str, Any] = action_handler.execute_delete_local_branch( repo_path=repo_path, branch_name=branch_name, force=force ) result_payload = result_info # Usa risultato diretto log_handler.log_info( f"[Worker] Delete local branch '{branch_name}' result status: {result_payload.get('status')}", func_name=func_name, ) except ValueError as ve: # Cattura ValueError da check branch corrente log_handler.log_error( f"[Worker] Handled VALIDATION EXCEPTION during delete branch setup: {ve}", func_name=func_name, ) result_payload.update(status="error", message=str(ve), exception=ve) except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during delete local branch: {e}", func_name=func_name, ) result_payload.update( status="error", message=f"Unexpected error deleting branch: {type(e).__name__}", exception=e, ) result_payload["result"] = ( "worker_exception" # Opzionale: per indicare errore worker ) finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: {action_type} Local Branch '{branch_name}'", func_name=func_name, ) def run_merge_local_branch_async( action_handler: ActionHandler, git_commands: GitCommands, # Necessario per ottenere branch corrente repo_path: str, branch_to_merge: str, no_ff: bool, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker function to merge a local branch into the current branch asynchronously.""" func_name: str = "run_merge_local_branch_async" log_handler.log_debug( f"[Worker] Started: Merge Local Branch '{branch_to_merge}' into current for '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { # Default error "status": "error", "message": f"Merge branch '{branch_to_merge}' failed.", "exception": None, } current_branch_name: Optional[str] = ( None # Per aggiungerlo al risultato in caso di conflitto ) try: # --- Ottieni il branch corrente --- current_branch_name = git_commands.get_current_branch_name(repo_path) if not current_branch_name: raise ValueError("Cannot perform merge: Currently in detached HEAD state.") log_handler.log_debug( f"[Worker] Current branch for merge validation: '{current_branch_name}'", func_name=func_name, ) # --- Chiama l'Action Handler --- result_info: Dict[str, Any] = action_handler.execute_merge_local_branch( repo_path=repo_path, branch_to_merge=branch_to_merge, current_branch=current_branch_name, no_ff=no_ff, ) result_payload = result_info # Usa risultato diretto log_handler.log_info( f"[Worker] Merge local branch '{branch_to_merge}' result status: {result_payload.get('status')}", func_name=func_name, ) # Aggiungi info extra per gestione GUI in caso di conflitto if result_payload.get("status") == "conflict": result_payload["repo_path"] = repo_path # Path per messaggio utente result_payload["branch_merged_into"] = ( current_branch_name # Branch dove risolvere ) except ValueError as ve: # Cattura ValueError da validazione (merge into self, branch non esiste, etc.) log_handler.log_error( f"[Worker] Handled VALIDATION EXCEPTION during merge setup: {ve}", func_name=func_name, ) result_payload.update( status="error", message=f"Merge failed: {ve}", exception=ve ) except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during merge operation: {e}", func_name=func_name, ) result_payload.update( status="error", message=f"Unexpected error during merge: {type(e).__name__}", exception=e, ) result_payload["result"] = "worker_exception" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Merge Local Branch '{branch_to_merge}'", func_name=func_name, ) def run_compare_branches_async( git_commands: GitCommands, repo_path: str, ref1: str, # Riferimento 1 (di solito il branch corrente) ref2: str, # Riferimento 2 (il branch selezionato dall'utente) results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to get the list of changed files between two references using diff-tree.""" func_name: str = "run_compare_branches_async" log_handler.log_debug( f"[Worker] Started: Compare Branches '{ref1}' vs '{ref2}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": ["(Error)"], "message": f"Could not compare '{ref1}' and '{ref2}'.", "exception": None, } try: # Chiama il metodo in GitCommands return_code: int changed_files_list: List[str] return_code, changed_files_list = git_commands.git_diff_tree( working_directory=repo_path, ref1=ref1, ref2=ref2 ) # Verifica l'esito if return_code == 0: result_payload["status"] = "success" result_payload["result"] = changed_files_list count: int = len(changed_files_list) if count > 0: result_payload["message"] = ( f"Comparison complete: Found {count} differences between '{ref1}' and '{ref2}'." ) else: result_payload["message"] = ( f"No differences found between '{ref1}' and '{ref2}'." ) log_handler.log_info( f"[Worker] {result_payload['message']}", func_name=func_name ) else: # Errore durante diff-tree (ref invalidi?) result_payload["status"] = "error" result_payload["message"] = ( f"Failed to compare '{ref1}' and '{ref2}'. Invalid reference(s)?" ) log_handler.log_error( f"[Worker] git diff-tree command failed (RC={return_code})", func_name=func_name, ) result_payload["exception"] = GitCommandError( result_payload["message"], stderr="See previous logs" ) except Exception as e: # Cattura eccezioni impreviste nel worker stesso log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during branch comparison: {e}", func_name=func_name, ) result_payload["status"] = "error" result_payload["message"] = ( f"Unexpected error comparing branches: {type(e).__name__}" ) result_payload["exception"] = e # result già impostato a "(Error)" finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Compare Branches '{ref1}' vs '{ref2}'", func_name=func_name, ) def run_get_commit_details_async( git_commands: GitCommands, repo_path: str, commit_hash: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to fetch detailed information about a specific commit.""" func_name: str = "run_get_commit_details_async" log_handler.log_debug( f"[Worker] Started: Get details for commit '{commit_hash}' in '{repo_path}'", func_name=func_name, ) # ---<<< MODIFICA: Inizializza con tipi corretti >>>--- commit_details: Dict[str, Any] = { "hash_full": None, "author_name": None, "author_email": None, "author_date": None, "subject": None, "body": "", "files_changed": [], # Lista di tuple (status, path1, Optional[path2]) } result_payload: Dict[str, Any] = { "status": "error", "result": commit_details, # Passa dict vuoto/default in caso di errore "message": f"Could not get details for commit '{commit_hash}'.", "exception": None, } # ---<<< FINE MODIFICA >>>--- try: # --- 1. Ottieni Metadati e Lista File con 'git show --name-status -z' --- separator: str = "|||---|||" pretty_format: str = ( f"%H{separator}%an{separator}%ae{separator}%ad{separator}%s" ) show_cmd: List[str] = [ "git", "show", f"--pretty=format:{pretty_format}", "--name-status", "-z", commit_hash, ] log_handler.log_debug( f"[Worker] Executing git show command: {' '.join(show_cmd)}", func_name=func_name, ) show_result = git_commands.log_and_execute( command=show_cmd, working_directory=repo_path, check=False, capture=True, hide_console=True, log_output_level=logging.DEBUG, ) if show_result.returncode == 0 and show_result.stdout: output_parts: List[str] = show_result.stdout.split("\n", 1) metadata_line: str = output_parts[0] files_part_raw: str = output_parts[1] if len(output_parts) > 1 else "" meta_parts: List[str] = metadata_line.split(separator) if len(meta_parts) == 5: commit_details["hash_full"] = meta_parts[0].strip() commit_details["author_name"] = meta_parts[1].strip() commit_details["author_email"] = meta_parts[2].strip() commit_details["author_date"] = meta_parts[3].strip() commit_details["subject"] = meta_parts[4].strip() log_handler.log_debug( f"[Worker] Parsed metadata: Hash={commit_details['hash_full']}, Author={commit_details['author_name']}", func_name=func_name, ) else: log_handler.log_warning( f"[Worker] Could not parse metadata line correctly: {metadata_line}", func_name=func_name, ) commit_details["subject"] = "(Could not parse subject)" if files_part_raw: file_entries: List[str] = files_part_raw.strip("\x00").split("\x00") parsed_files: List[Tuple[str, str, Optional[str]]] = [] i: int = 0 while i < len(file_entries): if not file_entries[i]: i += 1 continue status_char: str = file_entries[i].strip() if status_char.startswith(("R", "C")): if i + 2 < len(file_entries): status_code = status_char[0] old_path = file_entries[i + 1] new_path = file_entries[i + 2] if old_path and new_path: parsed_files.append((status_code, old_path, new_path)) else: log_handler.log_warning( f"[Worker] Incomplete R/C entry (empty path?): {file_entries[i:i+3]}", func_name=func_name, ) i += 3 else: log_handler.log_warning( f"[Worker] Incomplete R/C entry (not enough parts): {file_entries[i:]}", func_name=func_name, ) break elif status_char: if i + 1 < len(file_entries): file_path = file_entries[i + 1] if file_path: parsed_files.append((status_char[0], file_path, None)) else: log_handler.log_warning( f"[Worker] Incomplete A/M/D/T entry (empty path?): {file_entries[i:i+2]}", func_name=func_name, ) i += 2 else: log_handler.log_warning( f"[Worker] Incomplete A/M/D/T entry (not enough parts): {file_entries[i:]}", func_name=func_name, ) break else: log_handler.log_warning( f"[Worker] Unexpected empty status_char at index {i}", func_name=func_name, ) i += 1 commit_details["files_changed"] = parsed_files log_handler.log_debug( f"[Worker] Parsed {len(parsed_files)} changed files.", func_name=func_name, ) else: log_handler.log_debug( "[Worker] No file changes part found in output.", func_name=func_name, ) # --- 2. Ottieni Corpo Messaggio Commit --- body_cmd: List[str] = ["git", "show", "-s", "--format=%B", commit_hash] body_result = git_commands.log_and_execute( command=body_cmd, working_directory=repo_path, check=False, capture=True, hide_console=True, ) if body_result.returncode == 0: commit_details["body"] = body_result.stdout.strip() log_handler.log_debug( "[Worker] Fetched full commit message body.", func_name=func_name ) else: log_handler.log_warning( f"[Worker] Could not fetch commit body (RC={body_result.returncode}).", func_name=func_name, ) # Successo result_payload["status"] = "success" result_payload["message"] = ( f"Details retrieved successfully for commit '{commit_hash}'." ) result_payload["result"] = commit_details # Inserisce i dati popolati log_handler.log_info( f"[Worker] {result_payload['message']}", func_name=func_name ) elif ( "unknown revision or path not in the working tree" in (show_result.stderr or "").lower() ): result_payload["message"] = ( f"Commit hash '{commit_hash}' not found or invalid." ) log_handler.log_error( f"[Worker] {result_payload['message']}", func_name=func_name ) result_payload["exception"] = ValueError(result_payload["message"]) else: # Altro errore da 'git show' stderr_msg = (show_result.stderr or "Unknown git show error").strip() result_payload["message"] = ( f"Failed to get commit details (RC={show_result.returncode}): {stderr_msg}" ) log_handler.log_error( f"[Worker] {result_payload['message']}", func_name=func_name ) result_payload["exception"] = GitCommandError( result_payload["message"], stderr=show_result.stderr ) except GitCommandError as git_err: log_handler.log_exception( f"[Worker] GitCommandError getting commit details: {git_err}", func_name=func_name, ) result_payload["message"] = f"Git error retrieving commit details: {git_err}" result_payload["exception"] = git_err except Exception as e: log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION getting commit details: {e}", func_name=func_name, ) result_payload["message"] = ( f"Unexpected error retrieving commit details: {type(e).__name__}" ) result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Get details for commit '{commit_hash}'", func_name=func_name, ) def run_update_wiki_async( wiki_updater: WikiUpdater, main_repo_path: str, main_repo_remote_url: str, # Aggiungi altri parametri se necessari (es. nomi file wiki) results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to update the Gitea Wiki asynchronously.""" func_name = "run_update_wiki_async" log_handler.log_debug("[Worker] Started: Update Gitea Wiki", func_name=func_name) result_payload = { "status": "error", "message": "Wiki update failed.", "exception": None, "result": False, } try: # Chiama il metodo principale di WikiUpdater success, message = wiki_updater.update_wiki_from_docs( main_repo_path=main_repo_path, main_repo_remote_url=main_repo_remote_url, # Passa altri argomenti se necessari ) result_payload["status"] = "success" if success else "error" result_payload["message"] = message result_payload["result"] = success log_handler.log_info(f"[Worker] Wiki update result: {message}", func_name=func_name) except Exception as e: log_handler.log_exception(f"[Worker] UNEXPECTED EXCEPTION during wiki update: {e}", func_name=func_name) result_payload["status"] = "error" result_payload["message"] = f"Unexpected error during wiki update: {type(e).__name__}" result_payload["exception"] = e finally: try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error(f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name) log_handler.log_debug("[Worker] Finished: Update Gitea Wiki", func_name=func_name) def run_revert_to_tag_async( action_handler: ActionHandler, repo_path: str, tag_name: str, results_queue: queue.Queue[Dict[str, Any]], ) -> None: """Worker to perform a hard reset to a tag asynchronously.""" func_name: str = "run_revert_to_tag_async" log_handler.log_debug( f"[Worker] Started: Revert to Tag '{tag_name}' in '{repo_path}'", func_name=func_name, ) result_payload: Dict[str, Any] = { "status": "error", "result": False, "message": f"Failed to revert to tag '{tag_name}'.", "exception": None, } try: # Chiama il metodo dell'ActionHandler success: bool = action_handler.execute_revert_to_tag(repo_path, tag_name) # Prepara il risultato di successo result_payload["status"] = "success" result_payload["result"] = success result_payload["message"] = f"Repository successfully reverted to tag '{tag_name}'." log_handler.log_info(f"[Worker] {result_payload['message']}", func_name=func_name) except (GitCommandError, ValueError, Exception) as e: # Cattura qualsiasi eccezione dall'ActionHandler log_handler.log_exception( f"[Worker] EXCEPTION reverting to tag: {e}", func_name=func_name ) result_payload["exception"] = e result_payload["message"] = f"Error reverting to tag '{tag_name}': {e}" # Se l'errore è specifico, potremmo volerlo mostrare in modo diverso if "not found" in str(e).lower(): result_payload["message"] = f"Error: Tag '{tag_name}' not found." finally: # Metti sempre il risultato nella coda, sia in caso di successo che di errore try: results_queue.put(result_payload) except Exception as qe: log_handler.log_error( f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name, ) log_handler.log_debug( f"[Worker] Finished: Revert to Tag '{tag_name}'", func_name=func_name )