add Ahead/Behind function

This commit is contained in:
VALLONGOL 2025-04-23 10:11:43 +02:00
parent bebfb9a22a
commit af83b49380
4 changed files with 479 additions and 26 deletions

View File

@ -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}",

View File

@ -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 ---

View File

@ -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
View File

@ -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 ---