# --- FILE: async_workers.py --- import os import queue import logging # Usato solo per i livelli, non per loggare direttamente import datetime # Necessario per alcuni messaggi # Importa i moduli necessari per la logica interna e le dipendenze import log_handler from git_commands import GitCommands, GitCommandError from action_handler import ActionHandler from backup_handler import BackupHandler from remote_actions import RemoteActionHandler # 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 ): """Worker to fetch tag list asynchronously.""" func_name = "run_refresh_tags_async" log_handler.log_debug( f"[Worker] Started: Refresh Tags for '{repo_path}'", func_name=func_name ) try: # Chiama il metodo corretto in GitCommands tags_data = git_commands.list_tags(repo_path) count = len(tags_data) message = f"Tags refreshed ({count} found)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Metti il risultato nella coda results_queue.put( {"status": "success", "result": tags_data, "message": message} ) except (GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing tags: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "result": [ ("(Error)", "") ], # Formato atteso dalla GUI in caso di errore "message": f"Error refreshing tags: {type(e).__name__}", } ) finally: log_handler.log_debug("[Worker] Finished: Refresh Tags", func_name=func_name) def run_refresh_branches_async( git_commands: GitCommands, repo_path: str, results_queue: queue.Queue ): """Worker to fetch branch list asynchronously.""" func_name = "run_refresh_branches_async" log_handler.log_debug( f"[Worker] Started: Refresh Branches for '{repo_path}'", func_name=func_name ) try: # Chiama il metodo corretto in GitCommands branches, current = git_commands.list_branches(repo_path) count = len(branches) curr_disp = current if current else "None (Detached?)" message = f"Branches refreshed ({count} found). Current: {curr_disp}" log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Metti il risultato (una tupla) nella coda results_queue.put( {"status": "success", "result": (branches, current), "message": message} ) except (GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing branches: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "result": ( ["(Error)"], None, ), # Formato atteso dalla GUI in caso di errore "message": f"Error refreshing branches: {type(e).__name__}", } ) finally: log_handler.log_debug( "[Worker] Finished: Refresh Branches", func_name=func_name ) def run_refresh_history_async( git_commands: GitCommands, repo_path: str, branch_filter: str | None, log_scope: str, # Descrizione per i log (es. 'All History' o "'branch_name'") results_queue: queue.Queue, ): """Worker to fetch commit history asynchronously.""" func_name = "run_refresh_history_async" log_handler.log_debug( f"[Worker] Started: Refresh History ({log_scope}) for '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo corretto in GitCommands log_data = git_commands.get_commit_log( repo_path, max_count=200, branch=branch_filter ) count = len(log_data) message = f"History refreshed ({count} entries for {log_scope})." log_handler.log_info(f"[Worker] {message}", func_name=func_name) # Metti il risultato (lista di stringhe) nella coda results_queue.put({"status": "success", "result": log_data, "message": message}) except (GitCommandError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing history: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "result": ["(Error retrieving history)"], # Formato atteso dalla GUI "message": f"Error refreshing history: {type(e).__name__}", } ) finally: 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 ): """Worker to get status of changed files asynchronously.""" func_name = "run_refresh_changes_async" log_handler.log_debug( f"[Worker] Started: Refresh Changes for '{repo_path}'", func_name=func_name ) try: # Chiama il metodo corretto in GitCommands files_status_list = git_commands.get_status_short(repo_path) count = len(files_status_list) log_handler.log_info(f"[Worker] Found {count} changes.", func_name=func_name) message = ( f"Ready ({count} changes detected)." if count > 0 else "Ready (No changes detected)." ) # Logga prima di mettere in coda per debug log_handler.log_debug( f"[Worker] Preparing result for queue. Status: success, Count: {count}", func_name=func_name, ) results_queue.put( {"status": "success", "result": files_status_list, "message": message} ) log_handler.log_debug( "[Worker] Successfully PUT result in queue.", func_name=func_name ) except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION refreshing changes: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "result": ["(Error refreshing changes)"], # Formato atteso "message": f"Error refreshing changes: {type(e).__name__}", } ) finally: log_handler.log_debug("[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 ): """Worker to prepare repository asynchronously.""" func_name = "run_prepare_async" log_handler.log_debug( f"[Worker] Started: Prepare Repo for '{repo_path}'", func_name=func_name ) try: # Chiama il metodo corretto in ActionHandler success = action_handler.execute_prepare_repo(repo_path) message = "Repository prepared successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "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) results_queue.put( { "status": "warning", "result": True, "message": str(e), } # Segnala come warning ) else: # Rilancia altri ValueError log_handler.log_exception( f"[Worker] VALUE ERROR preparing repo: {e}", func_name=func_name ) results_queue.put( {"status": "error", "exception": e, "message": f"Error preparing: {e}"} ) except (GitCommandError, IOError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION preparing repo: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error preparing repository: {type(e).__name__}", } ) finally: log_handler.log_debug("[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, excluded_dirs: set, results_queue: queue.Queue, ): """Worker to create Git bundle asynchronously.""" func_name = "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, ) try: # Chiama il metodo corretto in ActionHandler result_path = 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 if result_path: message = f"Bundle created: {os.path.basename(result_path)}" else: # Potrebbe non essere stato creato per repo vuoto o nessun cambiamento + no commit message = "Bundle creation finished (no file generated - repo empty or no changes?)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put( { "status": "success", "result": result_path, # Può essere None "message": message, "committed": autocommit_enabled, # Indica se è stato TENTATO un autocommit } ) except (IOError, GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating bundle: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error creating bundle: {type(e).__name__}", } ) finally: log_handler.log_debug("[Worker] Finished: Create Bundle", func_name=func_name) def run_fetch_bundle_async( action_handler: ActionHandler, target_repo_path_str: str, # Può essere dir non esistente bundle_full_path: str, profile_name: str, autobackup_enabled: bool, backup_base_dir: str, excluded_extensions: set, excluded_dirs: set, results_queue: queue.Queue, ): """Worker to fetch/clone from Git bundle asynchronously.""" func_name = "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, ) try: # Chiama il metodo corretto in ActionHandler success = 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 = "Fetch/Clone from bundle completed successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "message": message}) except (FileNotFoundError, IOError, GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION fetching bundle: {e}", func_name=func_name ) # Controlla se è un errore di conflitto merge is_conflict = False if isinstance(e, GitCommandError) and "merge conflict" in str(e).lower(): is_conflict = True log_handler.log_error( "[Worker] Merge conflict detected during fetch.", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error fetching from bundle: {type(e).__name__}", "conflict": is_conflict, # Flag per gestione specifica errore "repo_path": target_repo_path_str, # Passa path per messaggio conflitto } ) finally: log_handler.log_debug("[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, excluded_dirs: set, results_queue: queue.Queue, ): """Worker to create manual backup asynchronously.""" func_name = "run_manual_backup_async" log_handler.log_debug( f"[Worker] Started: Manual Backup for '{repo_path}' (Profile: {profile_name})", func_name=func_name, ) try: # Chiama il metodo corretto in BackupHandler result_path = 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 = datetime.datetime.now().strftime("%H:%M:%S") 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) results_queue.put( {"status": "success", "result": result_path, "message": message} ) except (IOError, ValueError, PermissionError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating manual backup: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error creating backup: {type(e).__name__}", } ) finally: log_handler.log_debug("[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, ): """Worker to perform manual commit asynchronously.""" func_name = "run_commit_async" log_handler.log_debug( f"[Worker] Started: Commit for '{repo_path}'", func_name=func_name ) try: # Chiama il metodo corretto in ActionHandler committed = action_handler.execute_manual_commit(repo_path, commit_message) # Messaggio basato sull'esito if committed: message = "Commit successful." else: message = "Commit finished (no changes detected)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put( { "status": "success", "result": committed, "message": message, "committed": committed, # Flag esplicito se commit avvenuto } ) except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION committing: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error committing changes: {type(e).__name__}", } ) finally: log_handler.log_debug("[Worker] Finished: Commit", func_name=func_name) def run_untrack_async( action_handler: ActionHandler, repo_path: str, results_queue: queue.Queue ): """Worker to untrack files based on .gitignore asynchronously.""" func_name = "run_untrack_async" log_handler.log_debug( f"[Worker] Started: Untrack Files Check for '{repo_path}'", func_name=func_name ) try: # Chiama il metodo corretto in ActionHandler committed = action_handler.execute_untrack_files_from_gitignore(repo_path) # Messaggio basato sull'esito if committed: message = "Untracking complete: Automatic commit created." else: message = "Untrack check complete (no tracked files matched .gitignore)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put( { "status": "success", "result": committed, "message": message, "committed": committed, # Flag se commit avvenuto } ) except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION untracking files: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error during untracking operation: {type(e).__name__}", } ) finally: log_handler.log_debug( "[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, ): """Worker to add a file to staging asynchronously.""" func_name = "run_add_file_async" log_handler.log_debug( f"[Worker] Started: Add File '{relative_path}' in '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo corretto in GitCommands success = git_commands.add_file(repo_path, relative_path) message = f"File '{os.path.basename(relative_path)}' added to staging area." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "message": message}) except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION adding file: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error adding file '{os.path.basename(relative_path)}': {type(e).__name__}", } ) finally: 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, ): """Worker to create an annotated tag asynchronously.""" func_name = "run_create_tag_async" log_handler.log_debug( f"[Worker] Started: Create Tag '{tag_name}' in '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo corretto in ActionHandler (passa None per arg 'ignored') success = action_handler.execute_create_tag( repo_path, None, tag_name, tag_message ) message = f"Tag '{tag_name}' created successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put( { "status": "success", "result": success, "message": message, "committed": True, # Tag annotato implica commit } ) except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating tag: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error creating tag '{tag_name}': {type(e).__name__}", } ) finally: 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, ): """Worker to checkout a tag asynchronously.""" func_name = "run_checkout_tag_async" log_handler.log_debug( f"[Worker] Started: Checkout Tag '{tag_name}' in '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo corretto in ActionHandler success = action_handler.execute_checkout_tag(repo_path, tag_name) message = f"Checked out tag '{tag_name}' (Detached HEAD)." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "message": message}) except (ValueError, GitCommandError, Exception) as e: # Cattura ValueError specificamente per 'uncommitted changes' log_handler.log_exception( f"[Worker] EXCEPTION checking out tag: {e}", func_name=func_name ) if isinstance(e, ValueError) and "Uncommitted changes" in str(e): msg = f"Checkout failed: Uncommitted changes exist." else: msg = f"Error checking out tag '{tag_name}': {type(e).__name__}" results_queue.put({"status": "error", "exception": e, "message": msg}) finally: 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, ): """Worker to create a branch asynchronously.""" func_name = "run_create_branch_async" log_handler.log_debug( f"[Worker] Started: Create Branch '{branch_name}' in '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo corretto in ActionHandler success = action_handler.execute_create_branch(repo_path, branch_name) message = f"Branch '{branch_name}' created successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "message": message}) except (GitCommandError, ValueError, Exception) as e: log_handler.log_exception( f"[Worker] EXCEPTION creating branch: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error creating branch '{branch_name}': {type(e).__name__}", } ) finally: 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, ): """Worker to checkout a branch asynchronously.""" func_name = "run_checkout_branch_async" log_handler.log_debug( f"[Worker] Started: Checkout Branch '{branch_name}' in '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo corretto in ActionHandler success = action_handler.execute_switch_branch(repo_path, branch_name) message = f"Switched to branch '{branch_name}'." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "message": message}) except (ValueError, GitCommandError, Exception) as e: # Cattura ValueError per 'uncommitted changes' log_handler.log_exception( f"[Worker] EXCEPTION checking out branch: {e}", func_name=func_name ) if isinstance(e, ValueError) and "Uncommitted changes" in str(e): msg = f"Checkout failed: Uncommitted changes exist." else: msg = f"Error checking out branch '{branch_name}': {type(e).__name__}" results_queue.put({"status": "error", "exception": e, "message": msg}) finally: log_handler.log_debug( f"[Worker] Finished: Checkout Branch '{branch_name}'", func_name=func_name ) # === Worker per Azioni Remote (Nuove) === def run_apply_remote_config_async( remote_action_handler: RemoteActionHandler, repo_path: str, remote_name: str, remote_url: str, results_queue: queue.Queue, ): # (Implementazione precedente invariata) func_name = "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, ) try: success = remote_action_handler.apply_remote_config( repo_path, remote_name, remote_url ) message = f"Remote '{remote_name}' configuration applied successfully." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({"status": "success", "result": success, "message": message}) except (GitCommandError, ValueError) as e: log_handler.log_error( f"[Worker] EXCEPTION applying remote config: {e}", func_name=func_name ) results_queue.put( { "status": "error", "exception": e, "message": f"Error applying remote config: {e}", } ) except Exception as e: log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION applying remote config: {e}", func_name=func_name, ) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error applying remote config: {type(e).__name__}", } ) finally: 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 ): """ Worker to check remote connection and potential auth issues using 'git ls-remote'. Does NOT prompt for credentials. """ func_name = "run_check_connection_async" log_handler.log_debug( f"[Worker] Started: Check Connection/Auth for '{remote_name}' in '{repo_path}'", func_name=func_name ) try: # Esegui ls-remote catturando output, senza check=True result = git_commands.git_ls_remote(repo_path, remote_name) # Analizza il risultato if result.returncode == 0: # Successo: connessione e autenticazione (se necessaria) OK message = f"Connection to remote '{remote_name}' successful." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put({ "status": "success", "result": "connected", "message": message }) elif result.returncode == 2: # RC=2: Connesso con successo, ma il remote è vuoto (no refs) o "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) results_queue.put({ "status": "success", "result": "connected_empty", "message": message }) else: # Errore: analizza stderr per capire la causa stderr_lower = result.stderr.lower() if result.stderr else "" log_handler.log_warning( f"[Worker] ls-remote failed (RC={result.returncode}). Stderr: {stderr_lower}", func_name=func_name ) # Controlla errori comuni di autenticazione/permessi auth_errors = [ "authentication failed", "permission denied", "could not read username", "fatal: could not read password", ] # ---<<< MODIFICA: Aggiorna connection_errors >>>--- connection_errors = [ "repository not found", "could not resolve host", "name or service not known", "network is unreachable", # Aggiunti basati sui log e casi comuni "failed to connect", # <<< AGGIUNTO "unable to access", # <<< AGGIUNTO "could not connect", # <<< AGGIUNTO (più specifico) "connection timed out", # <<< AGGIUNTO (altro errore comune) ] # ---<<< FINE MODIFICA >>>--- 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) results_queue.put({ "status": "auth_required", "result": "authentication needed", "message": message, "exception": GitCommandError(message, stderr=result.stderr) }) elif any(err in stderr_lower for err in connection_errors): # Ora dovrebbe matchare l'errore dei log message = f"Failed to connect to remote '{remote_name}': Repository or host not found/reachable." log_handler.log_error(f"[Worker] {message}", func_name=func_name) results_queue.put({ "status": "error", # Manteniamo 'error' come status principale "result": "connection_failed", # Ma usiamo un 'result' specifico "message": message, "exception": GitCommandError(message, stderr=result.stderr) }) else: # Errore generico di Git (ora meno probabile per errori di connessione) message = f"Failed to check remote '{remote_name}'. Check logs for details. (RC={result.returncode})" # Aggiunto RC al messaggio log_handler.log_error(f"[Worker] Unknown error checking remote. Stderr: {result.stderr}", func_name=func_name) results_queue.put({ "status": "error", "result": "unknown_error", "message": message, "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) results_queue.put({ "status": "error", "exception": e, "result": "worker_exception", "message": f"Unexpected error checking connection: {type(e).__name__}" }) finally: 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, ): """ Worker to attempt an interactive Git operation (fetch) to trigger credential prompts. This worker intentionally does NOT capture output or hide the console. """ func_name = "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, ) try: # Esegui git fetch in modalità interattiva (no capture, no hide) result = git_commands.git_fetch_interactive(repo_path, remote_name) # Controlla solo il codice di ritorno if result.returncode == 0: # Successo (presumibilmente l'utente ha autenticato) message = f"Interactive authentication attempt for '{remote_name}' seems successful." log_handler.log_info(f"[Worker] {message}", func_name=func_name) results_queue.put( { "status": "success", "result": "auth_attempt_success", "message": message, } ) else: # Fallimento (utente ha annullato, credenziali errate, altro errore) 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) results_queue.put( { "status": "error", "result": "auth_attempt_failed", "message": message, "exception": GitCommandError(message, stderr=None), } # Non abbiamo stderr qui ) 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, ) results_queue.put( { "status": "error", "exception": e, "result": "worker_exception", "message": f"Unexpected error during interactive auth: {type(e).__name__}", } ) finally: log_handler.log_debug( f"[Worker] Finished: Interactive Auth Attempt for '{remote_name}'", func_name=func_name, ) # --- Placeholder per future funzioni worker remote --- def run_fetch_remote_async( remote_action_handler: RemoteActionHandler, # Dipendenza dall'handler delle azioni remote repo_path: str, remote_name: str, results_queue: queue.Queue, ): """ Worker function to execute 'git fetch' asynchronously. Executed in a separate thread. """ func_name = "run_fetch_remote_async" log_handler.log_debug( f"[Worker] Started: Fetch Remote '{remote_name}' for '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo execute_remote_fetch che contiene la logica e l'analisi dell'errore result_info = remote_action_handler.execute_remote_fetch(repo_path, remote_name) # result_info contiene già {'status': '...', 'message': '...', 'exception': ...} log_handler.log_info( f"[Worker] Fetch result status for '{remote_name}': {result_info.get('status')}", func_name=func_name, ) # Metti il dizionario del risultato direttamente nella coda results_queue.put(result_info) except Exception as e: # Cattura eccezioni impreviste sollevate da execute_remote_fetch stesso # (es., errori di validazione o eccezioni non gestite all'interno) log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during fetch execution: {e}", func_name=func_name, ) # Metti un risultato di errore generico nella coda results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during fetch operation: {type(e).__name__}", } ) finally: log_handler.log_debug( f"[Worker] Finished: Fetch Remote '{remote_name}'", func_name=func_name ) def run_pull_remote_async( remote_action_handler: RemoteActionHandler, # Dipendenza per eseguire il pull git_commands: GitCommands, # Necessario per ottenere il branch corrente repo_path: str, remote_name: str, # branch_name non è più necessario passarlo qui, lo otteniamo nel worker results_queue: queue.Queue, ): """ Worker function to execute 'git pull' asynchronously. Executed in a separate thread. """ func_name = "run_pull_remote_async" log_handler.log_debug( f"[Worker] Started: Pull Remote '{remote_name}' for '{repo_path}'", func_name=func_name, ) try: # --- Ottieni il branch corrente --- # È necessario conoscere il branch corrente per passarlo a execute_remote_pull # (anche se git pull di base non lo richiede, la nostra logica di action lo usa) # e per log/messaggi più chiari. current_branch_name = git_commands.get_current_branch_name( repo_path ) # Assumendo che questo metodo esista/venga aggiunto a GitCommands if not current_branch_name: # Se non riusciamo a determinare il branch (es. detached HEAD), non possiamo fare pull standard raise ValueError( "Cannot perform pull: Unable to determine current branch (possibly detached HEAD)." ) log_handler.log_debug( f"[Worker] Current branch identified as: '{current_branch_name}'", func_name=func_name, ) # --- Chiama l'Action Handler --- # execute_remote_pull contiene la logica per: # 1. Controllare modifiche non committate # 2. Eseguire git pull # 3. Analizzare l'esito (successo, conflitto, errore auth/conn/altro) result_info = remote_action_handler.execute_remote_pull( repo_path, remote_name, current_branch_name ) # result_info è il dizionario restituito da execute_remote_pull log_handler.log_info( f"[Worker] Pull result status for '{remote_name}': {result_info.get('status')}", func_name=func_name, ) # --- Aggiungi informazioni extra per la gestione dei conflitti --- if result_info.get("status") == "conflict": result_info["repo_path"] = ( repo_path # Assicura che il path sia nel risultato per il messaggio GUI ) # Metti il dizionario del risultato direttamente nella coda results_queue.put(result_info) except (GitCommandError, ValueError) as e: # Cattura errori dalla determinazione del branch o altri errori noti log_handler.log_error( f"[Worker] Handled EXCEPTION during pull setup/execution: {e}", func_name=func_name, ) results_queue.put( { "status": "error", "exception": e, "message": f"Pull failed: {e}", # Usa messaggio eccezione } ) except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during pull operation: {e}", func_name=func_name, ) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during pull operation: {type(e).__name__}", } ) finally: log_handler.log_debug( f"[Worker] Finished: Pull Remote '{remote_name}'", func_name=func_name ) def run_push_remote_async( remote_action_handler: RemoteActionHandler, # Dipendenza per eseguire push git_commands: GitCommands, # Necessario per ottenere il branch corrente repo_path: str, remote_name: str, # branch_name viene determinato internamente # force flag non necessario qui, gestito da execute_remote_push se necessario results_queue: queue.Queue, ): """ Worker function to execute 'git push' for the current branch asynchronously. Executed in a separate thread. Handles upstream setting. """ func_name = "run_push_remote_async" log_handler.log_debug( f"[Worker] Started: Push Remote '{remote_name}' for '{repo_path}'", func_name=func_name, ) 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 push: Unable to determine current branch (possibly detached HEAD)." ) log_handler.log_debug( f"[Worker] Current branch identified as: '{current_branch_name}'", func_name=func_name, ) # --- Chiama l'Action Handler --- # execute_remote_push contiene la logica per: # 1. Controllare e impostare l'upstream se necessario # 2. Eseguire git push # 3. Analizzare l'esito (successo, rifiutato, errore auth/conn/altro) # NOTA: Il flag 'force' qui è sempre False per il push standard dal pulsante GUI. # Un eventuale push forzato richiederebbe un pulsante/conferma separata. result_info = remote_action_handler.execute_remote_push( repo_path=repo_path, remote_name=remote_name, current_branch_name=current_branch_name, force=False, # Push standard ) # result_info è il dizionario restituito da execute_remote_push log_handler.log_info( f"[Worker] Push result status for '{current_branch_name}' to '{remote_name}': {result_info.get('status')}", func_name=func_name, ) # --- Aggiungi informazioni extra per la gestione GUI --- if result_info.get("status") == "rejected": result_info["branch_name"] = ( current_branch_name # Passa il nome del branch nel risultato ) # Metti il dizionario del risultato direttamente nella coda results_queue.put(result_info) except (GitCommandError, ValueError) as e: # Cattura errori dalla determinazione del branch o altri errori noti log_handler.log_error( f"[Worker] Handled EXCEPTION during push setup/execution: {e}", func_name=func_name, ) results_queue.put( { "status": "error", "exception": e, "message": f"Push failed: {e}", # Usa messaggio eccezione } ) except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during push operation: {e}", func_name=func_name, ) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during push operation: {type(e).__name__}", } ) finally: log_handler.log_debug( f"[Worker] Finished: Push Remote '{remote_name}'", func_name=func_name ) def run_push_tags_async( remote_action_handler: RemoteActionHandler, # Dipendenza per eseguire push tags repo_path: str, remote_name: str, results_queue: queue.Queue, ): """ Worker function to execute 'git push --tags' asynchronously. Executed in a separate thread. """ func_name = "run_push_tags_async" log_handler.log_debug( f"[Worker] Started: Push Tags to '{remote_name}' for '{repo_path}'", func_name=func_name, ) try: # --- Chiama l'Action Handler --- result_info = remote_action_handler.execute_push_tags(repo_path, remote_name) # result_info è il dizionario restituito da execute_push_tags log_handler.log_info( f"[Worker] Push tags result status for '{remote_name}': {result_info.get('status')}", func_name=func_name, ) # Metti il dizionario del risultato direttamente nella coda results_queue.put(result_info) except (GitCommandError, ValueError) as e: # Cattura errori noti sollevati da execute_push_tags (principalmente validazione) log_handler.log_error( f"[Worker] Handled EXCEPTION during push tags execution: {e}", func_name=func_name, ) results_queue.put( { "status": "error", "exception": e, "message": f"Push tags failed: {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, ) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during push tags operation: {type(e).__name__}", } ) finally: 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, # Dipendenza repo_path: str, local_branch: str, upstream_branch: str, results_queue: queue.Queue, ): """ Worker function to get ahead/behind commit counts asynchronously. Executed in a separate thread. """ func_name = "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, ) ahead_count = None behind_count = None status = "error" # Default a errore message = f"Could not determine ahead/behind status for branch '{local_branch}'." exception_obj = None try: # Chiama il metodo in GitCommands per ottenere i conteggi 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 (non None) if ahead_count is not None and behind_count is not None: status = "success" # Costruisci un messaggio descrittivo if ahead_count == 0 and behind_count == 0: message = ( f"Branch '{local_branch}' is up to date with '{upstream_branch}'." ) else: parts = [] 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}'." ) log_handler.log_info(f"[Worker] {message}", func_name=func_name) else: # Se get_ahead_behind_count ha restituito None, significa che c'è stato un errore # nel comando git o nell'analisi. Il messaggio di default va bene. log_handler.log_warning( f"[Worker] Failed to get valid ahead/behind counts for '{local_branch}'.", func_name=func_name, ) status = "error" # Mantiene lo stato di errore # Potremmo cercare di recuperare un'eccezione se disponibile, ma è complesso qui 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, ) status = "error" message = f"Unexpected error getting ahead/behind status: {type(e).__name__}" exception_obj = e # Metti il risultato nella coda results_queue.put( { "status": status, "result": (ahead_count, behind_count), # La tupla con i conteggi (o None) "message": message, "exception": exception_obj, # Passa l'eccezione se c'è stata } ) 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, # Dipendenza per eseguire clone remote_url: str, local_clone_path: str, # Path completo dove clonare profile_name_to_create: str, # Nome del profilo da creare post-clone results_queue: queue.Queue, ): """ Worker function to execute 'git clone' asynchronously. Executed in a separate thread. Args: git_commands (GitCommands): Instance to execute git commands. remote_url (str): URL of the repository to clone. local_clone_path (str): Full path to the target directory for the clone. profile_name_to_create (str): The name for the new profile upon success. results_queue (queue.Queue): Queue to put the result dictionary. """ func_name = "run_clone_remote_async" log_handler.log_debug( f"[Worker] Started: Clone from '{remote_url}' into '{local_clone_path}'", func_name=func_name, ) result_info = {"status": "unknown", "message": "Clone not completed."} # Default try: # Il controllo sull'esistenza della directory di destinazione # è stato fatto PRIMA di avviare questo worker (in GitUtility.py). # Qui eseguiamo direttamente il comando. # Chiama il metodo git_clone (che ha check=False) clone_result = git_commands.git_clone(remote_url, local_clone_path) # Analizza il risultato del comando clone if clone_result.returncode == 0: # Successo result_info["status"] = "success" result_info["message"] = ( f"Repository cloned successfully into '{os.path.basename(local_clone_path)}'." ) # Passa i dati necessari per creare il profilo nel risultato result_info["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_info['message']}", func_name=func_name, ) else: # Errore durante il clone result_info["status"] = "error" stderr_full = clone_result.stderr if clone_result.stderr else "" stderr_lower = 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", "fatal: could not read password", ] connection_errors = [ "repository not found", "could not resolve host", "name or service not known", "network is unreachable", ] path_errors = [ "already exists and is not an empty directory", "could not create work tree", ] # Anche se controllato prima, può succedere if any(err in stderr_lower for err in auth_errors): result_info["message"] = ( f"Authentication required or failed for cloning '{remote_url}'." ) result_info["exception"] = GitCommandError( result_info["message"], stderr=clone_result.stderr ) # Potremmo impostare uno stato specifico 'auth_required' se vogliamo distinguerlo # result_info['result'] = 'authentication needed' # Opzionale elif any(err in stderr_lower for err in connection_errors): result_info["message"] = ( f"Failed to connect while cloning: Repository or host '{remote_url}' not found/reachable." ) result_info["exception"] = GitCommandError( result_info["message"], stderr=clone_result.stderr ) # result_info['result'] = 'connection_failed' # Opzionale elif any(err in stderr_lower for err in path_errors): result_info["message"] = ( f"Clone failed: Target directory '{local_clone_path}' already exists and is not empty, or could not be created." ) result_info["exception"] = GitCommandError( result_info["message"], stderr=clone_result.stderr ) # result_info['result'] = 'path_error' # Opzionale else: # Errore generico di Git result_info["message"] = ( f"Clone from '{remote_url}' failed (RC={clone_result.returncode}). Check logs." ) result_info["exception"] = GitCommandError( result_info["message"], stderr=clone_result.stderr ) # result_info['result'] = 'unknown_error' # Opzionale # Metti il risultato in coda results_queue.put(result_info) 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, ) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during clone operation: {type(e).__name__}", "result": "worker_exception", # Stato specifico per errore worker } ) finally: log_handler.log_debug( f"[Worker] Finished: Clone Remote '{remote_url}'", func_name=func_name ) def run_refresh_remote_branches_async( git_commands: GitCommands, # Dipendenza repo_path: str, remote_name: str, results_queue: queue.Queue, ): """ Worker function to get the list of remote branches asynchronously. Executed in a separate thread. """ func_name = "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, ) remote_branches = [] # Default a lista vuota status = "error" # Default a errore message = f"Could not retrieve remote branches for '{remote_name}'." exception_obj = None try: # Chiama il metodo in GitCommands per ottenere la lista remote_branches = git_commands.git_list_remote_branches(repo_path, remote_name) # Se siamo qui senza eccezioni, il comando è andato a buon fine # (anche se la lista potrebbe essere vuota) status = "success" count = len(remote_branches) if count > 0: message = f"Found {count} remote branches for '{remote_name}'." else: message = ( f"No remote branches found for '{remote_name}' (or remote invalid)." ) log_handler.log_info(f"[Worker] {message}", func_name=func_name) except Exception as e: # Cattura eccezioni impreviste (GitCommandError dovrebbe essere gestito dentro git_list_remote_branches restituendo []) log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION refreshing remote branches: {e}", func_name=func_name, ) status = "error" message = f"Unexpected error listing remote branches: {type(e).__name__}" exception_obj = e remote_branches = ["(Error)"] # Segnaposto per la GUI # Metti il risultato nella coda # 'result' contiene la lista dei nomi dei branch (o ['(Error)']) results_queue.put( { "status": status, "result": remote_branches, "message": message, "exception": exception_obj, } ) 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, # Dipendenza dall'ActionHandler locale repo_path: str, new_local_branch_name: str, remote_tracking_branch_full_name: str, # Es. 'origin/main' results_queue: queue.Queue, ): """ Worker function to checkout a remote branch as a new local tracking branch asynchronously. Executed in a separate thread. """ func_name = "run_checkout_tracking_branch_async" log_handler.log_debug( f"[Worker] Started: Checkout Remote Branch '{remote_tracking_branch_full_name}' " f"as Local '{new_local_branch_name}' in '{repo_path}'", func_name=func_name, ) try: # Chiama il metodo execute_checkout_tracking_branch che contiene la logica # (controllo stato, esecuzione git checkout -b, analisi risultato) result_info = 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_info contiene già {'status': '...', 'message': '...', 'exception': ...} log_handler.log_info( f"[Worker] Checkout tracking branch result status: {result_info.get('status')}", func_name=func_name, ) # Metti il dizionario del risultato direttamente nella coda results_queue.put(result_info) except Exception as e: # Cattura eccezioni impreviste sollevate da execute_checkout_tracking_branch # (es., errori di validazione iniziali o eccezioni non gestite all'interno) log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION during checkout tracking branch execution: {e}", func_name=func_name, ) # Metti un risultato di errore generico nella coda results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during checkout operation: {type(e).__name__}", "result": "worker_exception", } ) finally: 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, # Dipendenza repo_path: str, branch_name: str, force: bool, results_queue: queue.Queue ): """ Worker function to delete a local branch asynchronously. Executed in a separate thread. """ func_name = "run_delete_local_branch_async" action_type = "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) try: # Chiama il metodo execute_delete_local_branch che contiene la logica # (controllo branch corrente, esecuzione git branch -d/-D, analisi risultato) result_info = action_handler.execute_delete_local_branch( repo_path=repo_path, branch_name=branch_name, force=force ) # result_info contiene già {'status': '...', 'message': '...', 'exception': ...} log_handler.log_info( f"[Worker] Delete local branch '{branch_name}' result status: {result_info.get('status')}", func_name=func_name ) # Metti il dizionario del risultato direttamente nella coda results_queue.put(result_info) except ValueError as ve: # Cattura specificamente il ValueError sollevato se si tenta di cancellare il branch corrente log_handler.log_error(f"[Worker] Handled EXCEPTION during delete branch setup: {ve}", func_name=func_name) results_queue.put( { "status": "error", "exception": ve, # Passa l'eccezione originale "message": str(ve), # Usa il messaggio del ValueError } ) except Exception as e: # Cattura eccezioni impreviste sollevate da execute_delete_local_branch log_handler.log_exception(f"[Worker] UNEXPECTED EXCEPTION during delete local branch: {e}", func_name=func_name) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error deleting local branch: {type(e).__name__}", "result": "worker_exception" } ) finally: 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, # Dipendenza per eseguire merge git_commands: GitCommands, # Dipendenza per ottenere branch corrente repo_path: str, branch_to_merge: str, no_ff: bool, results_queue: queue.Queue ): """ Worker function to merge a local branch into the current branch asynchronously. Executed in a separate thread. """ func_name = "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_info = {} # Inizializza dizionario risultato try: # --- Ottieni il branch corrente --- # Necessario per passarlo a execute_merge_local_branch per validazione current_branch_name = git_commands.get_current_branch_name(repo_path) if not current_branch_name: # Caso Detached HEAD 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 --- # execute_merge_local_branch contiene la logica per: # 1. Validare input e stato (branch corrente, branch esiste, no modifiche) # 2. Eseguire git merge # 3. Analizzare l'esito (successo, conflitto, errore) result_info = 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 ) # Logga l'esito log_handler.log_info( f"[Worker] Merge local branch '{branch_to_merge}' result status: {result_info.get('status')}", func_name=func_name ) # --- Aggiungi informazioni extra per gestione GUI in caso di conflitto --- if result_info.get('status') == 'conflict': result_info['repo_path'] = repo_path # Path per messaggio utente result_info['branch_merged_into'] = current_branch_name # Branch dove risolvere # Metti il dizionario del risultato nella coda results_queue.put(result_info) except ValueError as ve: # Cattura specificamente i ValueError sollevati dai check in action_handler # (es. merge into self, branch non esiste, detached HEAD, uncommitted changes) log_handler.log_error(f"[Worker] Handled VALIDATION EXCEPTION during merge setup: {ve}", func_name=func_name) results_queue.put( { "status": "error", "exception": ve, "message": f"Merge failed: {ve}", # Usa il messaggio chiaro del ValueError } ) except Exception as e: # Cattura eccezioni impreviste log_handler.log_exception(f"[Worker] UNEXPECTED EXCEPTION during merge operation: {e}", func_name=func_name) results_queue.put( { "status": "error", "exception": e, "message": f"Unexpected error during merge operation: {type(e).__name__}", "result": "worker_exception" } ) finally: 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, # Dipendenza 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 ): """ Worker function to get the list of changed files between two references using 'git diff-tree'. Executed in a separate thread. """ func_name = "run_compare_branches_async" log_handler.log_debug(f"[Worker] Started: Compare Branches '{ref1}' vs '{ref2}' in '{repo_path}'", func_name=func_name) changed_files = [] # Lista dei file cambiati "Status\tPath" status = 'error' # Default a errore message = f"Could not compare '{ref1}' and '{ref2}'." exception_obj = None try: # --- Opzionale: Eseguire Fetch prima del confronto? --- # Se ref2 è un branch remoto, potremmo voler fare fetch prima per avere lo stato più recente. # Questo però rallenta l'operazione. Per ora, confrontiamo con lo stato locale conosciuto. # if '/' in ref2: # Heuristica semplice per branch remoto # try: # remote_name = ref2.split('/')[0] # log_handler.log_debug(f"[Worker] Fetching '{remote_name}' before comparison.", func_name=func_name) # fetch_res = git_commands.git_fetch(repo_path, remote_name) # if fetch_res.returncode != 0: # log_handler.log_warning(f"[Worker] Pre-compare fetch failed (RC={fetch_res.returncode}). Comparing with potentially stale data.", func_name=func_name) # except Exception as fetch_e: # log_handler.log_warning(f"[Worker] Error during pre-compare fetch: {fetch_e}. Comparing with potentially stale data.", func_name=func_name) # --- Fine Fetch Opzionale --- # Chiama il metodo in GitCommands per ottenere la lista dei file differenti return_code, changed_files_list = git_commands.git_diff_tree( working_directory=repo_path, ref1=ref1, ref2=ref2 ) # Verifica l'esito del comando git diff-tree if return_code == 0: status = 'success' changed_files = changed_files_list # La lista dei file nel formato "Status\tPath" count = len(changed_files) if count > 0: message = f"Comparison complete: Found {count} differences between '{ref1}' and '{ref2}'." else: message = f"No differences found between '{ref1}' and '{ref2}'." log_handler.log_info(f"[Worker] {message}", func_name=func_name) else: # Errore durante l'esecuzione di git diff-tree (ref invalidi?) status = 'error' 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) # L'errore specifico è già stato loggato da git_diff_tree exception_obj = GitCommandError(message, stderr="See previous logs") # stderr non disponibile qui direttamente 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) status = 'error' message = f"Unexpected error comparing branches: {type(e).__name__}" exception_obj = e changed_files = ["(Error)"] # Segnaposto per la GUI # Metti il risultato nella coda # 'result' contiene la lista dei file nel formato "Status\tPath" results_queue.put( { "status": status, "result": changed_files, # Lista di stringhe "message": message, "exception": exception_obj } ) 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, # Hash del commit (può essere breve) results_queue: queue.Queue ): """ Worker to fetch detailed information about a specific commit, including metadata and the list of changed files. Executed in a separate thread. """ 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 ) # Dizionario per contenere i risultati commit_details: Dict[str, Any] = { 'hash_full': None, 'author_name': None, 'author_email': None, 'author_date': None, 'subject': None, 'body': "", # Messaggio completo (potrebbe essere multilinea) 'files_changed': [] # Lista di tuple (status, path1, [path2 for R/C]) } status: str = "error" # Default a errore message: str = f"Could not retrieve details for commit '{commit_hash}'." exception_obj: Optional[Exception] = None try: # --- 1. Ottieni Metadati e Lista File con 'git show --name-status' --- # Usiamo un formato specifico per estrarre facilmente i metadati principali # Usiamo %H per hash completo, %an/%ae per autore, %ad per data autore, %s per soggetto # Separatori specifici (es. |||) per dividere i campi in modo affidabile # --name-status alla fine per ottenere la lista dei file # -z usa NUL come terminatore per i nomi file (più robusto) separator: str = "|||---|||" # Separatore improbabile nel contenuto 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", # Usa NUL terminators per i file commit_hash # Usa l'hash fornito (breve o completo) ] log_handler.log_debug( f"[Worker] Executing git show command: {' '.join(show_cmd)}", func_name=func_name ) # Esegui catturando l'output, check=False per gestire commit non validi show_result = git_commands.log_and_execute( command=show_cmd, working_directory=repo_path, check=False, # Gestiamo noi se il commit non viene trovato capture=True, hide_console=True, log_output_level=logging.DEBUG ) # Controlla se il comando 'git show' ha avuto successo if show_result.returncode == 0 and show_result.stdout: # --- Parsing dell'Output --- # L'output sarà: Metadati formattati\n\nLista file separati da NUL\0 output_parts: List[str] = show_result.stdout.split('\n\n', 1) metadata_line: str = output_parts[0] files_part_raw: str = output_parts[1] if len(output_parts) > 1 else "" # Estrai metadati dalla prima parte 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 ) # Non considerarlo un errore fatale, potremmo avere solo i file # Estrai lista file dalla seconda parte (gestione NUL terminator) 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): # Ogni voce file dovrebbe avere status e path # Rinominati/Copiati (R/C) hanno status, old_path, new_path status_char: str = file_entries[i].strip() if status_char.startswith(('R', 'C')): # Renamed/Copied if i + 2 < len(file_entries): # Rimuovi score (es. R100 -> R) status_code = status_char[0] old_path = file_entries[i+1] new_path = file_entries[i+2] parsed_files.append((status_code, old_path, new_path)) i += 3 # Avanza di 3 elementi else: log_handler.log_warning(f"[Worker] Incomplete R/C entry: {file_entries[i:]}", func_name=func_name) break # Esce dal loop se i dati sono incompleti elif status_char: # Added, Modified, Deleted, Type Changed if i + 1 < len(file_entries): file_path = file_entries[i+1] parsed_files.append((status_char[0], file_path, None)) # None per new_path i += 2 # Avanza di 2 elementi else: log_handler.log_warning(f"[Worker] Incomplete A/M/D/T entry: {file_entries[i:]}", func_name=func_name) break else: # Ignora elementi vuoti (potrebbero esserci NUL consecutivi?) i += 1 commit_details['files_changed'] = parsed_files log_handler.log_debug( f"[Worker] Parsed {len(parsed_files)} changed files.", func_name=func_name ) # --- 2. Ottieni Corpo Messaggio Commit con 'git show -s --format=%B' --- # Questo comando estrae solo il messaggio completo (soggetto + corpo) 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, # Dovrebbe funzionare se show precedente ha funzionato capture=True, hide_console=True ) if body_result.returncode == 0: commit_details['body'] = body_result.stdout.strip() # Subject è già nel body, ma lo teniamo separato per potenziali usi futuri 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 ) # Non fatale, usa solo il subject se disponibile # Se siamo arrivati qui, l'operazione ha avuto successo status = "success" message = f"Details retrieved successfully for commit '{commit_hash}'." log_handler.log_info(f"[Worker] {message}", func_name=func_name) elif "unknown revision or path not in the working tree" in (show_result.stderr or "").lower(): # Caso specifico: hash commit non valido o non trovato status = "error" message = f"Commit hash '{commit_hash}' not found or invalid." log_handler.log_error(f"[Worker] {message}", func_name=func_name) exception_obj = ValueError(message) # Usa ValueError per input errato else: # Altro errore da 'git show' status = "error" stderr_msg = show_result.stderr.strip() if show_result.stderr else "Unknown git show error" message = f"Failed to get commit details (RC={show_result.returncode}): {stderr_msg}" log_handler.log_error(f"[Worker] {message}", func_name=func_name) exception_obj = GitCommandError(message, stderr=show_result.stderr) except GitCommandError as git_err: # Errore durante l'esecuzione di un comando git (es. permessi, repo corrotto) log_handler.log_exception( f"[Worker] GitCommandError getting commit details: {git_err}", func_name=func_name ) status = "error" message = f"Git error retrieving commit details: {git_err}" exception_obj = git_err except Exception as e: # Errore imprevisto nel worker log_handler.log_exception( f"[Worker] UNEXPECTED EXCEPTION getting commit details: {e}", func_name=func_name ) status = "error" message = f"Unexpected error retrieving commit details: {type(e).__name__}" exception_obj = e # --- Metti il risultato nella coda --- results_queue.put({ "status": status, "message": message, "result": commit_details, # Passa il dizionario con i dettagli "exception": exception_obj }) log_handler.log_debug( f"[Worker] Finished: Get details for commit '{commit_hash}'", func_name=func_name )