SXXXXXXX_GitUtility/gitutility/async_tasks/async_workers.py
2025-05-05 11:15:42 +02:00

2194 lines
85 KiB
Python

# --- 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)