add Ahead/Behind function
This commit is contained in:
parent
bebfb9a22a
commit
af83b49380
250
GitUtility.py
250
GitUtility.py
@ -164,6 +164,7 @@ class GitSvnSyncApp:
|
||||
# === Dati / Istanze per la GUI ===
|
||||
config_manager_instance=self.config_manager,
|
||||
profile_sections_list=self.config_manager.get_profile_sections(),
|
||||
refresh_remote_status_cb=self.refresh_remote_status,
|
||||
)
|
||||
print("MainFrame GUI created.")
|
||||
log_handler.log_debug(
|
||||
@ -2371,6 +2372,104 @@ class GitSvnSyncApp:
|
||||
},
|
||||
)
|
||||
|
||||
def refresh_remote_status(self):
|
||||
"""Starts the async check for ahead/behind status."""
|
||||
func_name = "refresh_remote_status"
|
||||
log_handler.log_info(
|
||||
f"--- Action Triggered: Refresh Remote Sync Status ---", func_name=func_name
|
||||
)
|
||||
|
||||
# Validazioni
|
||||
if not hasattr(self, "main_frame") or not self.main_frame.winfo_exists():
|
||||
return
|
||||
svn_path = self._get_and_validate_svn_path("Refresh Sync Status")
|
||||
if not svn_path or not self._is_repo_ready(svn_path):
|
||||
log_handler.log_warning(
|
||||
"Refresh Status skipped: Repo not ready.", func_name=func_name
|
||||
)
|
||||
# Aggiorna label a stato neutro/sconosciuto se repo non pronto
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: (Repo not ready)"
|
||||
)
|
||||
return
|
||||
|
||||
# --- Ottieni branch corrente e upstream ---
|
||||
current_branch = None
|
||||
upstream_branch = None
|
||||
try:
|
||||
current_branch = self.git_commands.get_current_branch_name(svn_path)
|
||||
if current_branch:
|
||||
upstream_branch = self.git_commands.get_branch_upstream(
|
||||
svn_path, current_branch
|
||||
)
|
||||
else:
|
||||
log_handler.log_warning(
|
||||
"Refresh Status: Cannot get status, currently in detached HEAD state.",
|
||||
func_name=func_name,
|
||||
)
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: (Detached HEAD)"
|
||||
)
|
||||
return # Esce se detached
|
||||
|
||||
if not upstream_branch:
|
||||
log_handler.log_info(
|
||||
f"Refresh Status: No upstream configured for branch '{current_branch}'.",
|
||||
func_name=func_name,
|
||||
)
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text=f"Sync Status: Upstream not set for '{current_branch}'"
|
||||
)
|
||||
# Abilita/Disabilita pulsanti Push/Pull in base all'upstream? Forse no qui.
|
||||
# Potremmo disabilitare il pulsante "Refresh Status" stesso?
|
||||
if hasattr(self.main_frame, "refresh_sync_status_button"):
|
||||
self.main_frame.refresh_sync_status_button.config(
|
||||
state=tk.DISABLED
|
||||
) # Disabilita refresh se manca upstream
|
||||
return # Esce se manca upstream
|
||||
|
||||
# Se siamo qui, abbiamo branch e upstream, abilita il pulsante refresh (se era disabilitato)
|
||||
if hasattr(self.main_frame, "refresh_sync_status_button"):
|
||||
self.main_frame.refresh_sync_status_button.config(state=tk.NORMAL)
|
||||
|
||||
except Exception as e:
|
||||
log_handler.log_exception(
|
||||
f"Error getting branch/upstream before status check: {e}",
|
||||
func_name=func_name,
|
||||
)
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Error getting info"
|
||||
)
|
||||
return
|
||||
|
||||
# --- Avvia worker asincrono ---
|
||||
log_handler.log_info(
|
||||
f"Checking ahead/behind status for '{current_branch}' vs '{upstream_branch}'...",
|
||||
func_name=func_name,
|
||||
)
|
||||
# Aggiorna label GUI a "checking..."
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Checking..."
|
||||
)
|
||||
|
||||
args = (self.git_commands, svn_path, current_branch, upstream_branch)
|
||||
self._start_async_operation(
|
||||
async_workers.run_get_ahead_behind_async, # Worker esterno
|
||||
args,
|
||||
{
|
||||
"context": "get_ahead_behind", # Contesto per il risultato
|
||||
"status_msg": f"Checking sync status for '{current_branch}'",
|
||||
# Passa nomi branch nel contesto per riferimento nel risultato
|
||||
"local_branch": current_branch,
|
||||
"upstream_branch": upstream_branch,
|
||||
},
|
||||
)
|
||||
|
||||
def _update_gui_auth_status(self, status: str):
|
||||
"""Updates internal state and calls GUI update for auth indicator."""
|
||||
self.remote_auth_status = status # Aggiorna stato interno
|
||||
@ -2416,6 +2515,8 @@ class GitSvnSyncApp:
|
||||
"Delaying widget re-enable: re-checking connection after interactive auth.",
|
||||
func_name=func_name,
|
||||
)
|
||||
# Non ritardare per errore get_ahead_behind, l'utente può riprovare manualmente
|
||||
# elif task_context == "get_ahead_behind" and status_from_result == 'error': should_reenable_now = False
|
||||
|
||||
# Riabilita i widget se non è necessario attendere
|
||||
if should_reenable_now:
|
||||
@ -2423,11 +2524,12 @@ class GitSvnSyncApp:
|
||||
log_handler.log_debug("Re-enabling widgets.", func_name=func_name)
|
||||
self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||
else:
|
||||
# Se la GUI non c'è, non c'è nulla da fare o riabilitare
|
||||
log_handler.log_warning(
|
||||
"Cannot re-enable widgets, MainFrame missing.",
|
||||
func_name=func_name,
|
||||
)
|
||||
return # Esce dalla funzione se la GUI non c'è più
|
||||
return # Esce dalla funzione
|
||||
|
||||
# --- Estrai dettagli dal risultato ---
|
||||
status = status_from_result # Usa la variabile già ottenuta
|
||||
@ -2513,10 +2615,18 @@ class GitSvnSyncApp:
|
||||
)
|
||||
if status == "success":
|
||||
auth_status = "ok"
|
||||
# (...) log info (...)
|
||||
if result_value == "connected_empty":
|
||||
log_handler.log_info(
|
||||
f"Connection check successful for '{remote_name}' (remote empty/unborn).",
|
||||
func_name=func_name,
|
||||
)
|
||||
else:
|
||||
log_handler.log_info(
|
||||
f"Connection check successful for '{remote_name}'.",
|
||||
func_name=func_name,
|
||||
)
|
||||
self._update_gui_auth_status(auth_status)
|
||||
elif status == "auth_required":
|
||||
# (...) log warning, update gui status, askyesno (...)
|
||||
log_handler.log_warning(
|
||||
f"Authentication required for remote '{remote_name}'.",
|
||||
func_name=func_name,
|
||||
@ -2526,8 +2636,13 @@ class GitSvnSyncApp:
|
||||
if (
|
||||
repo_path_checked
|
||||
and hasattr(self, "main_frame")
|
||||
and self.main_frame.ask_yes_no(...)
|
||||
): # Messaggio invariato
|
||||
and self.main_frame.ask_yes_no(
|
||||
"Authentication Required",
|
||||
f"Authentication is required to connect to remote '{remote_name}'.\n\n"
|
||||
f"Do you want to attempt authentication now?\n"
|
||||
f"(This may open a separate terminal window for credential input.)",
|
||||
)
|
||||
):
|
||||
log_handler.log_info(
|
||||
"User requested interactive authentication attempt.",
|
||||
func_name=func_name,
|
||||
@ -2558,7 +2673,6 @@ class GitSvnSyncApp:
|
||||
self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||
|
||||
elif status == "error":
|
||||
# (... gestione errori connessione/sconosciuto e aggiornamento indicatore ...)
|
||||
error_type = (
|
||||
result_value
|
||||
if result_value
|
||||
@ -2570,7 +2684,6 @@ class GitSvnSyncApp:
|
||||
self.main_frame.show_error("Connection Error", f"{message}")
|
||||
|
||||
elif task_context == "interactive_auth":
|
||||
# (... gestione risultato interactive_auth e riavvio check ...)
|
||||
original_context = context.get("original_context", {})
|
||||
remote_name = original_context.get(
|
||||
"remote_name_checked", remote_name_context or "unknown remote"
|
||||
@ -2584,13 +2697,15 @@ class GitSvnSyncApp:
|
||||
self.main_frame.update_status_bar(
|
||||
f"Authentication successful. Checking status..."
|
||||
)
|
||||
self.check_connection_auth()
|
||||
self.check_connection_auth() # Ri-avvia check silenzioso
|
||||
elif status == "error":
|
||||
log_handler.log_warning(
|
||||
f"Interactive auth attempt for '{remote_name}' failed or error occurred: {message}",
|
||||
func_name=func_name,
|
||||
)
|
||||
self._update_gui_auth_status("failed")
|
||||
self._update_gui_auth_status(
|
||||
"failed"
|
||||
) # Imposta indicatore su fallito
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.show_warning(
|
||||
"Authentication Attempt Failed", f"{message}"
|
||||
@ -2607,7 +2722,7 @@ class GitSvnSyncApp:
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.show_error(
|
||||
"Merge Conflict",
|
||||
f"Merge conflict occurred during pull from '{remote_name_context or 'remote'}'.\n\n" # Usa nome dal contesto se disponibile
|
||||
f"Merge conflict occurred during pull from '{remote_name_context or 'remote'}'.\n\n"
|
||||
f"Please resolve the conflicts manually in:\n{repo_path_conflict}\n\n"
|
||||
f"After resolving, stage the changes and commit them.",
|
||||
)
|
||||
@ -2653,9 +2768,38 @@ class GitSvnSyncApp:
|
||||
func_name=func_name,
|
||||
)
|
||||
|
||||
# --- Gestione specifica per GET_AHEAD_BEHIND ---
|
||||
elif task_context == "get_ahead_behind":
|
||||
if status == "success":
|
||||
ahead, behind = (
|
||||
result_value
|
||||
if isinstance(result_value, tuple)
|
||||
else (None, None)
|
||||
)
|
||||
log_handler.log_info(
|
||||
f"Ahead/Behind status updated: Ahead={ahead}, Behind={behind}",
|
||||
func_name=func_name,
|
||||
)
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
ahead=ahead, behind=behind
|
||||
)
|
||||
elif status == "error":
|
||||
log_handler.log_error(
|
||||
f"Failed to get ahead/behind status: {message}",
|
||||
func_name=func_name,
|
||||
)
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text=f"Sync Status: Error"
|
||||
)
|
||||
|
||||
# --- Gestione risultati altri task (successo) ---
|
||||
elif status == "success":
|
||||
# Determina quali refresh avviare in base al task completato
|
||||
post_action_refresh_needed = (
|
||||
False # Flag per refresh stato ahead/behind
|
||||
)
|
||||
if task_context in [
|
||||
"prepare_repo",
|
||||
"fetch_bundle",
|
||||
@ -2673,15 +2817,19 @@ class GitSvnSyncApp:
|
||||
"push_tags_remote",
|
||||
]:
|
||||
# Logica per popolare refresh_list
|
||||
if task_context == "push_remote": # Push Branch successo
|
||||
if task_context == "push_remote":
|
||||
if self.refresh_commit_history not in refresh_list:
|
||||
refresh_list.append(self.refresh_commit_history)
|
||||
if self.refresh_branch_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_branch_list)
|
||||
elif task_context == "push_tags_remote": # Push Tags successo
|
||||
post_action_refresh_needed = True
|
||||
elif task_context == "push_tags_remote":
|
||||
if self.refresh_tag_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_tag_list)
|
||||
elif task_context == "pull_remote": # Pull successo (non conflitto)
|
||||
post_action_refresh_needed = (
|
||||
True # Anche push tags può cambiare stato relativo
|
||||
)
|
||||
elif task_context == "pull_remote":
|
||||
if self.refresh_commit_history not in refresh_list:
|
||||
refresh_list.append(self.refresh_commit_history)
|
||||
if self.refresh_branch_list not in refresh_list:
|
||||
@ -2690,22 +2838,23 @@ class GitSvnSyncApp:
|
||||
refresh_list.append(self.refresh_tag_list)
|
||||
if self.refresh_changed_files_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_changed_files_list)
|
||||
elif task_context == "fetch_remote": # Fetch successo
|
||||
post_action_refresh_needed = True
|
||||
elif task_context == "fetch_remote":
|
||||
if self.refresh_commit_history not in refresh_list:
|
||||
refresh_list.append(self.refresh_commit_history)
|
||||
if self.refresh_branch_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_branch_list)
|
||||
if self.refresh_tag_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_tag_list)
|
||||
elif task_context == "apply_remote_config": # Apply Config successo
|
||||
refresh_list.append(
|
||||
self.check_connection_auth
|
||||
) # Controlla connessione dopo apply
|
||||
post_action_refresh_needed = True
|
||||
elif task_context == "apply_remote_config":
|
||||
refresh_list.append(self.check_connection_auth)
|
||||
if self.refresh_commit_history not in refresh_list:
|
||||
refresh_list.append(self.refresh_commit_history)
|
||||
if self.refresh_branch_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_branch_list)
|
||||
# Logica refresh per le altre azioni
|
||||
post_action_refresh_needed = True # Controlla stato dopo apply
|
||||
# Logica refresh per le altre azioni (non remote)
|
||||
else:
|
||||
if committed or task_context in [
|
||||
"fetch_bundle",
|
||||
@ -2743,6 +2892,8 @@ class GitSvnSyncApp:
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.update_branch_list(branches, current)
|
||||
self.main_frame.update_history_branch_filter(branches)
|
||||
# Dopo refresh branch, aggiorna anche lo stato sync se possibile
|
||||
post_action_refresh_needed = True
|
||||
elif task_context == "refresh_history":
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.update_history_display(
|
||||
@ -2766,8 +2917,11 @@ class GitSvnSyncApp:
|
||||
branch_to_checkout=new_branch_context,
|
||||
repo_path_override=repo_path_for_updates,
|
||||
)
|
||||
# Se non fa checkout, assicurati che la history venga aggiornata
|
||||
# e anche lo stato sync (perché siamo su un nuovo branch senza upstream)
|
||||
elif self.refresh_commit_history not in refresh_list:
|
||||
refresh_list.append(self.refresh_commit_history)
|
||||
post_action_refresh_needed = True # Aggiorna stato sync dopo creazione branch (sarà 'no upstream')
|
||||
|
||||
# --- Trigger finale dei refresh asincroni raccolti ---
|
||||
if repo_path_for_updates and refresh_list:
|
||||
@ -2789,6 +2943,22 @@ class GitSvnSyncApp:
|
||||
func_name=func_name,
|
||||
)
|
||||
|
||||
# --- Triggera refresh stato ahead/behind se necessario ---
|
||||
if post_action_refresh_needed:
|
||||
# Solo se il repo è ancora valido (potrebbe essere cambiato da un checkout)
|
||||
current_repo_path = self._get_and_validate_svn_path(
|
||||
"Post-Action Sync Status Check"
|
||||
)
|
||||
if current_repo_path: # Usa path corrente, non quello vecchio
|
||||
log_handler.log_debug(
|
||||
f"Triggering remote sync status refresh after '{task_context}'.",
|
||||
func_name=func_name,
|
||||
)
|
||||
# Usa after per separarlo leggermente dal ciclo corrente e permettere GUI update
|
||||
self.master.after(
|
||||
100, self.refresh_remote_status
|
||||
) # Leggero ritardo
|
||||
|
||||
elif status == "warning":
|
||||
# Gestione warning generica: mostra popup
|
||||
if hasattr(self, "main_frame"):
|
||||
@ -2799,6 +2969,7 @@ class GitSvnSyncApp:
|
||||
refresh_list.append(self.refresh_changed_files_list)
|
||||
if self.refresh_branch_list not in refresh_list:
|
||||
refresh_list.append(self.refresh_branch_list)
|
||||
post_action_refresh_needed = True # Aggiorna stato sync anche qui
|
||||
if repo_path_for_updates and refresh_list:
|
||||
log_handler.log_debug(
|
||||
"Triggering refreshes after 'already prepared' warning.",
|
||||
@ -2806,9 +2977,11 @@ class GitSvnSyncApp:
|
||||
)
|
||||
for rf in refresh_list:
|
||||
rf()
|
||||
if post_action_refresh_needed:
|
||||
self.master.after(100, self.refresh_remote_status)
|
||||
|
||||
elif status == "error":
|
||||
# Gestione errori generica (esclusi check_connection, interactive_auth, pull_conflict, push_rejected)
|
||||
# Gestione errori generica (esclusi check_connection, interactive_auth, pull_conflict, push_rejected, get_ahead_behind)
|
||||
log_handler.log_error(
|
||||
f"Error reported for task '{task_context}': {message}",
|
||||
func_name=func_name,
|
||||
@ -2819,7 +2992,7 @@ class GitSvnSyncApp:
|
||||
else message
|
||||
)
|
||||
|
||||
# Gestione errore per fetch_remote
|
||||
# --- Gestione errore per fetch_remote ---
|
||||
if task_context == "fetch_remote":
|
||||
auth_related_error = False
|
||||
conn_related_error = False
|
||||
@ -2847,13 +3020,17 @@ class GitSvnSyncApp:
|
||||
self._update_gui_auth_status("unknown_error")
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.show_error("Fetch Error", f"{message}")
|
||||
# Resetta stato ahead/behind su errore fetch
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Error"
|
||||
)
|
||||
|
||||
# Gestione errore per PULL (non conflitto)
|
||||
# --- Gestione errore per PULL (non conflitto) ---
|
||||
elif task_context == "pull_remote":
|
||||
auth_related_error = False
|
||||
conn_related_error = False
|
||||
upst_related_error = False
|
||||
stderr_low = ""
|
||||
if isinstance(exception, GitCommandError) and exception.stderr:
|
||||
stderr_low = exception.stderr.lower()
|
||||
if any(
|
||||
@ -2865,7 +3042,6 @@ class GitSvnSyncApp:
|
||||
]
|
||||
):
|
||||
auth_related_error = True
|
||||
|
||||
if any(
|
||||
e in stderr_low
|
||||
for e in ["repository not found", "could not resolve host"]
|
||||
@ -2884,8 +3060,12 @@ class GitSvnSyncApp:
|
||||
self._update_gui_auth_status("unknown_error")
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.show_error("Pull Error", f"{message}")
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Error"
|
||||
)
|
||||
|
||||
# Gestione errore per PUSH (non rifiuto) e PUSH TAGS
|
||||
# --- Gestione errore per PUSH (non rifiuto) e PUSH TAGS ---
|
||||
elif (
|
||||
task_context == "push_remote" or task_context == "push_tags_remote"
|
||||
):
|
||||
@ -2918,8 +3098,12 @@ class GitSvnSyncApp:
|
||||
self._update_gui_auth_status("unknown_error")
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.show_error(f"{action_name} Error", f"{message}")
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Error"
|
||||
)
|
||||
|
||||
# Gestione errori per altri task
|
||||
# --- Gestione errori per altri task ---
|
||||
else:
|
||||
# Mostra popup specifici o generico
|
||||
if (
|
||||
@ -2951,6 +3135,11 @@ class GitSvnSyncApp:
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.update_branch_list([], None)
|
||||
self.main_frame.update_history_branch_filter([])
|
||||
# Se fallisce refresh branch, stato sync è sconosciuto
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Unknown"
|
||||
)
|
||||
elif task_context == "refresh_history":
|
||||
if hasattr(self, "main_frame"):
|
||||
self.main_frame.update_history_display(
|
||||
@ -2965,6 +3154,10 @@ class GitSvnSyncApp:
|
||||
self._update_gui_auth_status(
|
||||
"unknown_error"
|
||||
) # Errore durante apply config
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Unknown"
|
||||
)
|
||||
|
||||
# Log finale solo se non è stata gestita una ricorsione/nuovo avvio
|
||||
if should_reenable_now:
|
||||
@ -2999,6 +3192,11 @@ class GitSvnSyncApp:
|
||||
bg_color=self.main_frame.STATUS_RED,
|
||||
duration_ms=10000,
|
||||
)
|
||||
# Resetta anche stato sync su errore grave
|
||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||
self.main_frame.update_ahead_behind_status(
|
||||
status_text="Sync Status: Error"
|
||||
)
|
||||
except Exception as recovery_e:
|
||||
log_handler.log_error(
|
||||
f"Failed to recover GUI after queue processing error: {recovery_e}",
|
||||
|
||||
@ -1205,4 +1205,93 @@ def run_push_tags_async(
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
# --- END OF FILE async_workers.py ---
|
||||
|
||||
@ -10,6 +10,7 @@ import re
|
||||
|
||||
# Importa il nuovo gestore della coda log
|
||||
import log_handler
|
||||
from typing import Tuple, Dict, List
|
||||
|
||||
|
||||
# --- Custom Exception Definition (invariata) ---
|
||||
@ -1656,5 +1657,90 @@ class GitCommands:
|
||||
)
|
||||
return None
|
||||
|
||||
def get_ahead_behind_count(
|
||||
self, working_directory: str, local_branch: str, upstream_branch: str
|
||||
) -> Tuple[int | None, int | None]:
|
||||
"""
|
||||
Gets the number of commits the local branch is ahead and behind its upstream counterpart.
|
||||
|
||||
Args:
|
||||
working_directory (str): Path to the repository.
|
||||
local_branch (str): The name of the local branch.
|
||||
upstream_branch (str): The full name of the upstream branch (e.g., 'origin/main').
|
||||
|
||||
Returns:
|
||||
Tuple[int | None, int | None]: A tuple containing (ahead_count, behind_count).
|
||||
Returns (None, None) if the command fails or
|
||||
branches are invalid/unrelated.
|
||||
"""
|
||||
func_name = "get_ahead_behind_count"
|
||||
log_handler.log_debug(
|
||||
f"Getting ahead/behind count for '{local_branch}'...'{upstream_branch}' in '{working_directory}'",
|
||||
func_name=func_name,
|
||||
)
|
||||
|
||||
# Comando: git rev-list --count --left-right <local_branch>...<upstream_branch>
|
||||
# L'output è nel formato: <behind_count>\t<ahead_count>
|
||||
# Usiamo '...' per la differenza simmetrica.
|
||||
cmd = [
|
||||
"git",
|
||||
"rev-list",
|
||||
"--count",
|
||||
"--left-right",
|
||||
f"{local_branch}...{upstream_branch}",
|
||||
]
|
||||
|
||||
try:
|
||||
# Esegui catturando output, nascondendo console. check=False perché potrebbe fallire
|
||||
# se i branch non esistono o non hanno un antenato comune.
|
||||
result = self.log_and_execute(
|
||||
cmd,
|
||||
working_directory,
|
||||
check=False,
|
||||
capture=True,
|
||||
hide_console=True,
|
||||
log_output_level=logging.DEBUG,
|
||||
)
|
||||
|
||||
if result.returncode == 0 and result.stdout:
|
||||
output = result.stdout.strip()
|
||||
parts = output.split() # Divide per spazio o tab
|
||||
if len(parts) == 2:
|
||||
try:
|
||||
behind_count = int(
|
||||
parts[0]
|
||||
) # Il primo numero è 'behind' (--left-right A...B -> A è left)
|
||||
ahead_count = int(parts[1]) # Il secondo numero è 'ahead'
|
||||
log_handler.log_info(
|
||||
f"Ahead/Behind for '{local_branch}': Ahead={ahead_count}, Behind={behind_count}",
|
||||
func_name=func_name,
|
||||
)
|
||||
return ahead_count, behind_count
|
||||
except ValueError:
|
||||
log_handler.log_error(
|
||||
f"Failed to parse rev-list count output: '{output}'",
|
||||
func_name=func_name,
|
||||
)
|
||||
return None, None
|
||||
else:
|
||||
log_handler.log_error(
|
||||
f"Unexpected output format from rev-list count: '{output}'",
|
||||
func_name=func_name,
|
||||
)
|
||||
return None, None
|
||||
else:
|
||||
# Il comando potrebbe fallire se i branch non sono validi, non hanno antenato comune, ecc.
|
||||
log_handler.log_warning(
|
||||
f"Failed to get ahead/behind count (RC={result.returncode}). Maybe invalid branches or no common history? Stderr: {result.stderr.strip() if result.stderr else 'N/A'}",
|
||||
func_name=func_name,
|
||||
)
|
||||
return None, None # Segnala fallimento
|
||||
|
||||
except Exception as e:
|
||||
log_handler.log_exception(
|
||||
f"Unexpected error getting ahead/behind count: {e}", func_name=func_name
|
||||
)
|
||||
return None, None # Segnala fallimento
|
||||
|
||||
|
||||
# --- END OF FILE git_commands.py ---
|
||||
|
||||
80
gui.py
80
gui.py
@ -448,6 +448,7 @@ class MainFrame(ttk.Frame):
|
||||
pull_remote_cb,
|
||||
push_remote_cb,
|
||||
push_tags_remote_cb,
|
||||
refresh_remote_status_cb,
|
||||
):
|
||||
"""Initializes the MainFrame."""
|
||||
super().__init__(master)
|
||||
@ -483,6 +484,7 @@ class MainFrame(ttk.Frame):
|
||||
self.pull_remote_callback = pull_remote_cb
|
||||
self.push_remote_callback = push_remote_cb
|
||||
self.push_tags_remote_callback = push_tags_remote_cb
|
||||
self.refresh_remote_status_callback = refresh_remote_status_cb
|
||||
|
||||
# Configure style (invariato)
|
||||
self.style = ttk.Style()
|
||||
@ -513,6 +515,7 @@ class MainFrame(ttk.Frame):
|
||||
self.remote_url_var = tk.StringVar()
|
||||
self.remote_name_var = tk.StringVar()
|
||||
self.remote_auth_status_var = tk.StringVar(value="Status: Unknown")
|
||||
self.remote_ahead_behind_var = tk.StringVar(value="Sync Status: Unknown")
|
||||
|
||||
# --- Create UI Elements ---
|
||||
self._create_profile_frame()
|
||||
@ -703,6 +706,42 @@ class MainFrame(ttk.Frame):
|
||||
# Imposta colore iniziale (es. grigio)
|
||||
self._update_auth_status_indicator("unknown")
|
||||
|
||||
# --- Sync Status ---
|
||||
status_frame = ttk.LabelFrame(
|
||||
frame, text="Synchronization Status", padding=(10, 5)
|
||||
)
|
||||
status_frame.pack(pady=(5, 5), fill="x", expand=False)
|
||||
status_frame.columnconfigure(0, weight=1) # Label si espande
|
||||
|
||||
# Label per Ahead/Behind
|
||||
self.ahead_behind_label = ttk.Label(
|
||||
status_frame,
|
||||
textvariable=self.remote_ahead_behind_var, # Collegato alla variabile
|
||||
anchor=tk.W, # Allinea testo a sinistra
|
||||
# relief=tk.SUNKEN, # Forse senza bordo è meglio?
|
||||
padding=(5, 2),
|
||||
)
|
||||
self.ahead_behind_label.grid(row=0, column=0, sticky=tk.EW, padx=(5, 10))
|
||||
self.create_tooltip(
|
||||
self.ahead_behind_label,
|
||||
"Indicates if the local branch is ahead (needs push) or behind (needs pull) the remote branch.",
|
||||
)
|
||||
|
||||
# Pulsante Refresh Status
|
||||
self.refresh_sync_status_button = ttk.Button(
|
||||
status_frame,
|
||||
text="Refresh Status",
|
||||
command=self.refresh_remote_status_callback, # Nuovo callback
|
||||
state=tk.DISABLED, # Abilitato quando repo pronto e upstream configurato?
|
||||
)
|
||||
self.refresh_sync_status_button.grid(
|
||||
row=0, column=1, sticky=tk.E, padx=(0, 5), pady=2
|
||||
)
|
||||
self.create_tooltip(
|
||||
self.refresh_sync_status_button,
|
||||
"Check how many commits the local branch is ahead or behind the remote branch.",
|
||||
)
|
||||
|
||||
# --- Sezione Azioni Remote ---
|
||||
actions_frame = ttk.LabelFrame(frame, text="Remote Actions", padding=(10, 5))
|
||||
actions_frame.pack(pady=10, fill="x", expand=False)
|
||||
@ -1862,6 +1901,7 @@ class MainFrame(ttk.Frame):
|
||||
self.pull_button,
|
||||
self.push_button,
|
||||
self.push_tags_button,
|
||||
self.refresh_sync_status_button,
|
||||
]
|
||||
# log_handler.log_debug(f"Setting action widgets state to: {state}", func_name="set_action_widgets_state") # Usa log_handler
|
||||
for widget in widgets:
|
||||
@ -1893,5 +1933,45 @@ class MainFrame(ttk.Frame):
|
||||
except Exception as e:
|
||||
pass # log_handler.log_error(f"Error setting state for profile dropdown: {e}", func_name="set_action_widgets_state")
|
||||
|
||||
def update_ahead_behind_status(
|
||||
self,
|
||||
status_text: str | None = None,
|
||||
ahead: int | None = None,
|
||||
behind: int | None = None,
|
||||
):
|
||||
"""Updates the ahead/behind status label."""
|
||||
label = getattr(self, "ahead_behind_label", None)
|
||||
var = getattr(self, "remote_ahead_behind_var", None)
|
||||
if not label or not var or not label.winfo_exists():
|
||||
return
|
||||
|
||||
text_to_display = "Sync Status: Unknown" # Default
|
||||
|
||||
if status_text is not None:
|
||||
# Se viene passato un testo specifico (es. errore, no upstream), usa quello
|
||||
text_to_display = status_text
|
||||
elif ahead is not None and behind is not None:
|
||||
# Se abbiamo i conteggi, costruisci il messaggio
|
||||
if ahead == 0 and behind == 0:
|
||||
text_to_display = "Sync Status: Up to date"
|
||||
else:
|
||||
parts = []
|
||||
if ahead > 0:
|
||||
plural_a = "s" if ahead > 1 else ""
|
||||
parts.append(f"{ahead} commit{plural_a} ahead (Push needed)")
|
||||
if behind > 0:
|
||||
plural_b = "s" if behind > 1 else ""
|
||||
parts.append(f"{behind} commit{plural_b} behind (Pull needed)")
|
||||
text_to_display = "Sync Status: " + ", ".join(parts)
|
||||
# else: Se non viene passato testo e i conteggi sono None, rimane "Unknown"
|
||||
|
||||
try:
|
||||
var.set(text_to_display)
|
||||
except Exception as e:
|
||||
log_handler.log_error(
|
||||
f"Failed to update ahead/behind status variable: {e}",
|
||||
func_name="update_ahead_behind_status",
|
||||
)
|
||||
|
||||
|
||||
# --- END OF FILE gui.py ---
|
||||
|
||||
Loading…
Reference in New Issue
Block a user