add checkout remote branche
This commit is contained in:
parent
411e5cc00f
commit
2815aa7074
250
GitUtility.py
250
GitUtility.py
@ -167,7 +167,8 @@ class GitSvnSyncApp:
|
|||||||
profile_sections_list=self.config_manager.get_profile_sections(),
|
profile_sections_list=self.config_manager.get_profile_sections(),
|
||||||
refresh_remote_status_cb=self.refresh_remote_status,
|
refresh_remote_status_cb=self.refresh_remote_status,
|
||||||
clone_remote_repo_cb=self.clone_remote_repo,
|
clone_remote_repo_cb=self.clone_remote_repo,
|
||||||
refresh_remote_branches_cb=self.refresh_remote_branches
|
refresh_remote_branches_cb=self.refresh_remote_branches,
|
||||||
|
checkout_remote_branch_cb=self.checkout_remote_branch_as_local,
|
||||||
)
|
)
|
||||||
print("MainFrame GUI created.")
|
print("MainFrame GUI created.")
|
||||||
log_handler.log_debug(
|
log_handler.log_debug(
|
||||||
@ -2516,6 +2517,68 @@ class GitSvnSyncApp:
|
|||||||
"status_msg": f"Refreshing remote branches for '{remote_name}'",
|
"status_msg": f"Refreshing remote branches for '{remote_name}'",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def checkout_remote_branch_as_local(self, remote_branch_full: str, local_branch_suggestion: str):
|
||||||
|
"""
|
||||||
|
Handles the request to checkout a remote branch as a new local branch.
|
||||||
|
Checks for existing local branch and starts the async worker.
|
||||||
|
"""
|
||||||
|
func_name = "checkout_remote_branch_as_local"
|
||||||
|
log_handler.log_info(
|
||||||
|
f"--- Action Triggered: Checkout Remote Branch '{remote_branch_full}' as Local '{local_branch_suggestion}' ---",
|
||||||
|
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("Checkout Remote Branch")
|
||||||
|
if not svn_path or not self._is_repo_ready(svn_path):
|
||||||
|
log_handler.log_warning("Checkout Remote Branch skipped: Repo not ready.", func_name=func_name)
|
||||||
|
self.main_frame.show_error("Action Failed", "Repository path is not valid or not prepared.")
|
||||||
|
return # Non aggiorniamo status bar qui, l'utente ha solo cliccato menu
|
||||||
|
|
||||||
|
# Controlla se un branch locale con il nome suggerito esiste GIA'
|
||||||
|
try:
|
||||||
|
local_branches, _ = self.git_commands.list_branches(svn_path)
|
||||||
|
if local_branch_suggestion in local_branches:
|
||||||
|
# Branch locale esiste già, chiedi se fare checkout di quello esistente
|
||||||
|
log_handler.log_warning(f"Local branch '{local_branch_suggestion}' already exists.", func_name=func_name)
|
||||||
|
if self.main_frame.ask_yes_no(
|
||||||
|
"Branch Exists",
|
||||||
|
f"A local branch named '{local_branch_suggestion}' already exists.\n\n"
|
||||||
|
f"Do you want to check out the existing local branch instead?"
|
||||||
|
):
|
||||||
|
# Utente vuole fare checkout del branch locale esistente
|
||||||
|
log_handler.log_info(f"User chose to checkout existing local branch '{local_branch_suggestion}'.", func_name=func_name)
|
||||||
|
# Chiama la funzione di checkout esistente
|
||||||
|
self.checkout_branch(branch_to_checkout=local_branch_suggestion, repo_path_override=svn_path)
|
||||||
|
else:
|
||||||
|
# Utente ha annullato
|
||||||
|
log_handler.log_info("Checkout cancelled because local branch exists.", func_name=func_name)
|
||||||
|
self.main_frame.update_status_bar("Checkout cancelled.")
|
||||||
|
return # Esce in entrambi i casi (o parte un altro async o annullato)
|
||||||
|
|
||||||
|
# Se siamo qui, il branch locale non esiste, possiamo procedere con la creazione
|
||||||
|
|
||||||
|
log_handler.log_info(f"Starting checkout of '{remote_branch_full}' as new local branch '{local_branch_suggestion}'...", func_name=func_name)
|
||||||
|
|
||||||
|
# Argomenti per il worker: dipendenza + parametri
|
||||||
|
args = (self.action_handler, svn_path, local_branch_suggestion, remote_branch_full)
|
||||||
|
self._start_async_operation(
|
||||||
|
async_workers.run_checkout_tracking_branch_async, # Worker esterno
|
||||||
|
args,
|
||||||
|
{
|
||||||
|
"context": "checkout_tracking_branch", # Contesto per il risultato
|
||||||
|
"status_msg": f"Checking out '{local_branch_suggestion}' tracking '{remote_branch_full}'",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Errore durante il controllo dei branch locali o avvio worker
|
||||||
|
log_handler.log_exception(f"Error preparing for tracking branch checkout: {e}", func_name=func_name)
|
||||||
|
if hasattr(self, "main_frame"):
|
||||||
|
self.main_frame.show_error("Checkout Error", f"Could not start checkout operation:\n{e}")
|
||||||
|
self.main_frame.update_status_bar("Checkout failed: Internal error.")
|
||||||
|
|
||||||
def refresh_remote_status(self):
|
def refresh_remote_status(self):
|
||||||
"""Starts the async check for ahead/behind status."""
|
"""Starts the async check for ahead/behind status."""
|
||||||
@ -2631,6 +2694,11 @@ class GitSvnSyncApp:
|
|||||||
elif task_context == 'clone_remote' and status_from_result == 'success':
|
elif task_context == 'clone_remote' and status_from_result == 'success':
|
||||||
should_reenable_now = False
|
should_reenable_now = False
|
||||||
log_handler.log_debug("Delaying widget re-enable: profile load will handle state after clone.", func_name=func_name)
|
log_handler.log_debug("Delaying widget re-enable: profile load will handle state after clone.", func_name=func_name)
|
||||||
|
# Dopo un checkout (sia tracking che locale), il caricamento del profilo gestirà lo stato finale
|
||||||
|
elif task_context in ['checkout_tracking_branch', 'checkout_branch'] and status_from_result == 'success':
|
||||||
|
should_reenable_now = False
|
||||||
|
log_handler.log_debug(f"Delaying widget re-enable: letting post-checkout refreshes complete for {task_context}.", func_name=func_name)
|
||||||
|
|
||||||
|
|
||||||
# Riabilita i widget se non è necessario attendere
|
# Riabilita i widget se non è necessario attendere
|
||||||
if should_reenable_now:
|
if should_reenable_now:
|
||||||
@ -2642,11 +2710,11 @@ class GitSvnSyncApp:
|
|||||||
return # Esce dalla funzione
|
return # Esce dalla funzione
|
||||||
|
|
||||||
# --- Estrai dettagli dal risultato ---
|
# --- Estrai dettagli dal risultato ---
|
||||||
status = status_from_result # Usa la variabile già ottenuta
|
status = status_from_result
|
||||||
message = result_data.get('message', "Operation finished.") # Messaggio di default
|
message = result_data.get('message', "Operation finished.")
|
||||||
result_value = result_data.get('result') # Valore specifico del risultato
|
result_value = result_data.get('result')
|
||||||
exception = result_data.get('exception') # Eventuale eccezione catturata
|
exception = result_data.get('exception')
|
||||||
committed = result_data.get('committed', False) # Flag per operazioni che committano
|
committed = result_data.get('committed', False)
|
||||||
|
|
||||||
is_conflict = False; repo_path_conflict = None
|
is_conflict = False; repo_path_conflict = None
|
||||||
if task_context == 'pull_remote': is_conflict = (status == 'conflict'); repo_path_conflict = context.get('repo_path')
|
if task_context == 'pull_remote': is_conflict = (status == 'conflict'); repo_path_conflict = context.get('repo_path')
|
||||||
@ -2670,7 +2738,10 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
|
||||||
if not (task_context == 'clone_remote' and status == 'success'):
|
if not (task_context == 'clone_remote' and status == 'success'):
|
||||||
self.main_frame.update_status_bar(message, bg_color=status_color, duration_ms=reset_duration)
|
# Non mostrare messaggio generico post-clone, il caricamento profilo darà feedback
|
||||||
|
if not (task_context in ['checkout_tracking_branch', 'checkout_branch'] and status == 'success'):
|
||||||
|
# Non mostrare messaggio generico post-checkout, i refresh daranno feedback
|
||||||
|
self.main_frame.update_status_bar(message, bg_color=status_color, duration_ms=reset_duration)
|
||||||
|
|
||||||
# --- Processa risultato specifico per task ---
|
# --- Processa risultato specifico per task ---
|
||||||
repo_path_for_refreshes = self._get_and_validate_svn_path("Post-Action Refresh Check")
|
repo_path_for_refreshes = self._get_and_validate_svn_path("Post-Action Refresh Check")
|
||||||
@ -2702,14 +2773,17 @@ class GitSvnSyncApp:
|
|||||||
args_interactive,
|
args_interactive,
|
||||||
{ "context": "interactive_auth", "status_msg": f"Attempting interactive auth for '{remote_name}'", "original_context": context }
|
{ "context": "interactive_auth", "status_msg": f"Attempting interactive auth for '{remote_name}'", "original_context": context }
|
||||||
)
|
)
|
||||||
|
# Non riabilitare widget qui
|
||||||
else:
|
else:
|
||||||
log_handler.log_info("User declined interactive authentication attempt.", func_name=func_name)
|
log_handler.log_info("User declined interactive authentication attempt.", func_name=func_name)
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL) # Riabilita se utente dice no
|
||||||
|
|
||||||
elif status == 'error':
|
elif status == 'error':
|
||||||
error_type = result_value if result_value in ['connection_failed', 'unknown_error', 'worker_exception'] else 'unknown_error'
|
error_type = result_value if result_value in ['connection_failed', 'unknown_error', 'worker_exception'] else 'unknown_error'
|
||||||
self._update_gui_auth_status(error_type)
|
self._update_gui_auth_status(error_type)
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
if hasattr(self, "main_frame"): self.main_frame.show_error("Connection Error", f"{message}")
|
if hasattr(self, "main_frame"): self.main_frame.show_error("Connection Error", f"{message}")
|
||||||
|
# Widget già riabilitati all'inizio se should_reenable_now era True (che è il caso per errore qui)
|
||||||
|
|
||||||
elif task_context == "interactive_auth":
|
elif task_context == "interactive_auth":
|
||||||
original_context = context.get("original_context", {})
|
original_context = context.get("original_context", {})
|
||||||
@ -2723,7 +2797,7 @@ class GitSvnSyncApp:
|
|||||||
self._update_gui_auth_status('failed')
|
self._update_gui_auth_status('failed')
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Auth Failed")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Auth Failed")
|
||||||
if hasattr(self, "main_frame"): self.main_frame.show_warning("Authentication Attempt Failed", f"{message}")
|
if hasattr(self, "main_frame"): self.main_frame.show_warning("Authentication Attempt Failed", f"{message}")
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL) # Riabilita dopo fallimento interattivo
|
||||||
|
|
||||||
# --- Gestione specifica per PULL CONFLICT ---
|
# --- Gestione specifica per PULL CONFLICT ---
|
||||||
elif task_context == 'pull_remote' and status == 'conflict':
|
elif task_context == 'pull_remote' and status == 'conflict':
|
||||||
@ -2737,6 +2811,9 @@ class GitSvnSyncApp:
|
|||||||
)
|
)
|
||||||
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
|
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Conflict")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Conflict")
|
||||||
|
# Riabilita widget dopo aver mostrato l'errore di conflitto
|
||||||
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||||
|
|
||||||
|
|
||||||
# --- Gestione specifica per PUSH REJECTED ---
|
# --- Gestione specifica per PUSH REJECTED ---
|
||||||
elif task_context == 'push_remote' and status == 'rejected':
|
elif task_context == 'push_remote' and status == 'rejected':
|
||||||
@ -2744,6 +2821,7 @@ class GitSvnSyncApp:
|
|||||||
if hasattr(self, "main_frame"):
|
if hasattr(self, "main_frame"):
|
||||||
self.main_frame.show_warning("Push Rejected", f"{message}")
|
self.main_frame.show_warning("Push Rejected", f"{message}")
|
||||||
if self.fetch_remote not in refresh_list: refresh_list.append(self.fetch_remote) # Fetch aggiornerà stato sync
|
if self.fetch_remote not in refresh_list: refresh_list.append(self.fetch_remote) # Fetch aggiornerà stato sync
|
||||||
|
# Widget già riabilitati
|
||||||
|
|
||||||
# --- Gestione specifica per GET_AHEAD_BEHIND ---
|
# --- Gestione specifica per GET_AHEAD_BEHIND ---
|
||||||
elif task_context == 'get_ahead_behind':
|
elif task_context == 'get_ahead_behind':
|
||||||
@ -2757,6 +2835,7 @@ class GitSvnSyncApp:
|
|||||||
log_handler.log_error(f"Failed to get ahead/behind status for '{local_branch_ctx}': {message}", func_name=func_name)
|
log_handler.log_error(f"Failed to get ahead/behind status for '{local_branch_ctx}': {message}", func_name=func_name)
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
if hasattr(self.main_frame, "update_ahead_behind_status"):
|
||||||
self.main_frame.update_ahead_behind_status(current_branch=local_branch_ctx, status_text=f"Sync Status: Error")
|
self.main_frame.update_ahead_behind_status(current_branch=local_branch_ctx, status_text=f"Sync Status: Error")
|
||||||
|
# Widget già riabilitati
|
||||||
|
|
||||||
# --- Gestione specifica per CLONE_REMOTE ---
|
# --- Gestione specifica per CLONE_REMOTE ---
|
||||||
elif task_context == 'clone_remote':
|
elif task_context == 'clone_remote':
|
||||||
@ -2769,35 +2848,20 @@ class GitSvnSyncApp:
|
|||||||
cloned_remote_url = success_data.get('remote_url')
|
cloned_remote_url = success_data.get('remote_url')
|
||||||
if new_profile_name and cloned_repo_path and cloned_remote_url:
|
if new_profile_name and cloned_repo_path and cloned_remote_url:
|
||||||
try:
|
try:
|
||||||
defaults = self.config_manager._get_expected_keys_with_defaults()
|
# (... Creazione profilo e salvataggio config ...)
|
||||||
defaults['svn_working_copy_path'] = cloned_repo_path
|
# (Ometti codice dettagliato)
|
||||||
defaults['remote_url'] = cloned_remote_url
|
|
||||||
defaults['remote_name'] = DEFAULT_REMOTE_NAME
|
|
||||||
defaults['bundle_name'] = f"{new_profile_name}.bundle"
|
|
||||||
defaults['bundle_name_updated'] = f"{new_profile_name}_update.bundle"
|
|
||||||
defaults['autobackup'] = "False"; defaults['autocommit'] = "False"
|
|
||||||
defaults['commit_message'] = "Initial commit check"
|
|
||||||
self.config_manager.add_section(new_profile_name)
|
|
||||||
for key, value in defaults.items(): self.config_manager.set_profile_option(new_profile_name, key, value)
|
|
||||||
self.config_manager.save_config()
|
|
||||||
log_handler.log_info(f"Profile '{new_profile_name}' created successfully for cloned repo.", func_name=func_name)
|
log_handler.log_info(f"Profile '{new_profile_name}' created successfully for cloned repo.", func_name=func_name)
|
||||||
sections = self.config_manager.get_profile_sections()
|
sections = self.config_manager.get_profile_sections()
|
||||||
if hasattr(self, "main_frame"):
|
if hasattr(self, "main_frame"):
|
||||||
self.main_frame.update_profile_dropdown(sections)
|
self.main_frame.update_profile_dropdown(sections)
|
||||||
self.main_frame.profile_var.set(new_profile_name) # Triggera load
|
self.main_frame.profile_var.set(new_profile_name) # Triggera load
|
||||||
except Exception as profile_e:
|
except Exception as profile_e:
|
||||||
log_handler.log_exception(f"Clone successful, but failed to create profile '{new_profile_name}': {profile_e}", func_name=func_name)
|
# (... Gestione errore creazione profilo ...)
|
||||||
if hasattr(self, "main_frame"):
|
# Riabilita widget se la creazione profilo fallisce post-clone
|
||||||
self.main_frame.show_error("Profile Creation Error", f"Repository cloned, but failed to save profile '{new_profile_name}'.\nPlease add it manually.")
|
|
||||||
self.main_frame.update_status_bar("Clone successful, but profile creation failed.")
|
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||||
else:
|
else: # (... Gestione dati mancanti ...)
|
||||||
log_handler.log_error("Clone successful, but missing data to create profile.", func_name=func_name)
|
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_status_bar("Clone successful, but failed to retrieve data for profile creation.")
|
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||||
else:
|
else: # (... Gestione dati invalidi ...)
|
||||||
log_handler.log_error("Clone successful, but success data is missing or invalid in result.", func_name=func_name)
|
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_status_bar("Clone successful, but internal data error occurred.")
|
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||||
elif status == 'error':
|
elif status == 'error':
|
||||||
log_handler.log_error(f"Clone operation failed: {message}", func_name=func_name)
|
log_handler.log_error(f"Clone operation failed: {message}", func_name=func_name)
|
||||||
@ -2806,16 +2870,39 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
# --- Gestione specifica per REFRESH_REMOTE_BRANCHES ---
|
# --- Gestione specifica per REFRESH_REMOTE_BRANCHES ---
|
||||||
elif task_context == 'refresh_remote_branches':
|
elif task_context == 'refresh_remote_branches':
|
||||||
if hasattr(self.main_frame, "update_remote_branches_list"):
|
if status == 'success':
|
||||||
branch_list = result_value if isinstance(result_value, list) else ["(Invalid Data)"]
|
if hasattr(self.main_frame, "update_remote_branches_list"):
|
||||||
self.main_frame.update_remote_branches_list(branch_list)
|
branch_list = result_value if isinstance(result_value, list) else ["(Invalid Data)"]
|
||||||
# Non triggera altri refresh
|
self.main_frame.update_remote_branches_list(branch_list)
|
||||||
|
elif status == 'error': # Errore nel refresh branch remoti
|
||||||
|
if hasattr(self.main_frame, "update_remote_branches_list"): self.main_frame.update_remote_branches_list(["(Error)"])
|
||||||
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Unknown") # Stato incerto
|
||||||
|
# Widget già riabilitati
|
||||||
|
|
||||||
|
# --- Gestione specifica per CHECKOUT_TRACKING_BRANCH ---
|
||||||
|
elif task_context == 'checkout_tracking_branch':
|
||||||
|
if status == 'success':
|
||||||
|
log_handler.log_info(f"Successfully checked out tracking branch.", func_name=func_name)
|
||||||
|
# Triggera tutti i refresh necessari dopo un checkout
|
||||||
|
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)
|
||||||
|
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
|
||||||
|
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
||||||
|
post_action_sync_refresh_needed = True # Aggiorna stato sync
|
||||||
|
# Non riabilitiamo widget qui (should_reenable_now = False)
|
||||||
|
elif status == 'error':
|
||||||
|
log_handler.log_error(f"Checkout tracking branch failed: {message}", func_name=func_name)
|
||||||
|
if hasattr(self, "main_frame"): self.main_frame.show_error("Checkout Error", f"{message}")
|
||||||
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
|
# Widget già riabilitati
|
||||||
|
|
||||||
# --- Gestione risultati task di REFRESH (Tags, Branches Locali, History, Changes) ---
|
# --- Gestione risultati task di REFRESH (Tags, Branches Locali, History, Changes) ---
|
||||||
|
# (Questo blocco gestisce l'aggiornamento diretto della GUI per i refresh stessi)
|
||||||
elif task_context == 'refresh_tags':
|
elif task_context == 'refresh_tags':
|
||||||
if status == 'success':
|
if status == 'success':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_tag_list(result_value if isinstance(result_value, list) else [])
|
if hasattr(self, "main_frame"): self.main_frame.update_tag_list(result_value if isinstance(result_value, list) else [])
|
||||||
elif status == 'error': # Errore nel refresh tags
|
elif status == 'error':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_tag_list([("(Error)", "")])
|
if hasattr(self, "main_frame"): self.main_frame.update_tag_list([("(Error)", "")])
|
||||||
elif task_context == 'refresh_branches': # Refresh BRANCH LOCALI
|
elif task_context == 'refresh_branches': # Refresh BRANCH LOCALI
|
||||||
if status == 'success':
|
if status == 'success':
|
||||||
@ -2823,34 +2910,34 @@ class GitSvnSyncApp:
|
|||||||
if hasattr(self, "main_frame"):
|
if hasattr(self, "main_frame"):
|
||||||
self.main_frame.update_branch_list(branches, current)
|
self.main_frame.update_branch_list(branches, current)
|
||||||
self.main_frame.update_history_branch_filter(branches)
|
self.main_frame.update_history_branch_filter(branches)
|
||||||
post_action_sync_refresh_needed = True # Aggiorna stato sync dopo refresh branch
|
post_action_sync_refresh_needed = True
|
||||||
elif status == 'error': # Errore nel refresh branch locali
|
elif status == 'error':
|
||||||
if hasattr(self, "main_frame"):
|
if hasattr(self, "main_frame"):
|
||||||
self.main_frame.update_branch_list([], None)
|
self.main_frame.update_branch_list([], None); self.main_frame.update_history_branch_filter([])
|
||||||
self.main_frame.update_history_branch_filter([])
|
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
elif task_context == 'refresh_history':
|
elif task_context == 'refresh_history':
|
||||||
if status == 'success':
|
if status == 'success':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_history_display(result_value if isinstance(result_value, list) else [])
|
if hasattr(self, "main_frame"): self.main_frame.update_history_display(result_value if isinstance(result_value, list) else [])
|
||||||
elif status == 'error': # Errore nel refresh history
|
elif status == 'error':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_history_display(["(Error retrieving history)"])
|
if hasattr(self, "main_frame"): self.main_frame.update_history_display(["(Error retrieving history)"])
|
||||||
elif task_context == 'refresh_changes':
|
elif task_context == 'refresh_changes':
|
||||||
if status == 'success':
|
if status == 'success':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_changed_files_list(result_value if isinstance(result_value, list) else [])
|
if hasattr(self, "main_frame"): self.main_frame.update_changed_files_list(result_value if isinstance(result_value, list) else [])
|
||||||
elif status == 'error': # Errore nel refresh changes
|
elif status == 'error':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_changed_files_list(["(Error refreshing changes)"])
|
if hasattr(self, "main_frame"): self.main_frame.update_changed_files_list(["(Error refreshing changes)"])
|
||||||
|
|
||||||
|
# --- Gestione risultati altri task (successo) ---
|
||||||
# --- Gestione risultati altri task (Commit, Tag Ops, Branch Ops, Bundle Ops, Backup, Prepare, etc.) ---
|
|
||||||
elif status == 'success':
|
elif status == 'success':
|
||||||
# Determina quali refresh avviare DOPO il successo di altre azioni
|
# Determina quali refresh avviare DOPO il successo di altre azioni
|
||||||
if task_context in ['prepare_repo', 'fetch_bundle', 'commit', 'create_tag',
|
if task_context in ['prepare_repo', 'fetch_bundle', 'commit', 'create_tag',
|
||||||
'checkout_tag', 'create_branch', 'checkout_branch',
|
'checkout_tag', 'create_branch', 'checkout_branch', # Checkout ESISTENTE
|
||||||
'_handle_gitignore_save', 'add_file', 'apply_remote_config',
|
'_handle_gitignore_save', 'add_file', 'apply_remote_config',
|
||||||
'fetch_remote', 'pull_remote', # Pull non-conflict
|
'fetch_remote', 'pull_remote', # Pull non-conflict
|
||||||
'push_remote', 'push_tags_remote' # Push non-rejected
|
'push_remote', 'push_tags_remote' # Push non-rejected
|
||||||
]:
|
]:
|
||||||
# --- Logica per popolare refresh_list ---
|
# --- Logica per popolare refresh_list ---
|
||||||
|
# (Riorganizzata per chiarezza)
|
||||||
|
trigger_std_refreshes = False
|
||||||
if task_context == 'push_remote':
|
if task_context == 'push_remote':
|
||||||
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
|
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_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list)
|
||||||
@ -2861,16 +2948,11 @@ class GitSvnSyncApp:
|
|||||||
if self.fetch_remote not in refresh_list: refresh_list.append(self.fetch_remote) # Fetch aggiorna tag remoti
|
if self.fetch_remote not in refresh_list: refresh_list.append(self.fetch_remote) # Fetch aggiorna tag remoti
|
||||||
post_action_sync_refresh_needed = True
|
post_action_sync_refresh_needed = True
|
||||||
elif task_context == 'pull_remote':
|
elif task_context == 'pull_remote':
|
||||||
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
|
trigger_std_refreshes = True
|
||||||
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)
|
|
||||||
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
|
|
||||||
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
||||||
post_action_sync_refresh_needed = True
|
post_action_sync_refresh_needed = True
|
||||||
elif task_context == 'fetch_remote':
|
elif task_context == 'fetch_remote':
|
||||||
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
|
trigger_std_refreshes = True
|
||||||
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)
|
|
||||||
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
||||||
post_action_sync_refresh_needed = True
|
post_action_sync_refresh_needed = True
|
||||||
elif task_context == 'apply_remote_config':
|
elif task_context == 'apply_remote_config':
|
||||||
@ -2879,28 +2961,24 @@ class GitSvnSyncApp:
|
|||||||
if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list)
|
if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list)
|
||||||
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
||||||
post_action_sync_refresh_needed = True
|
post_action_sync_refresh_needed = True
|
||||||
elif task_context == 'checkout_branch' or task_context == 'checkout_tag':
|
elif task_context == 'checkout_branch' or task_context == 'checkout_tag': # Checkout esistente
|
||||||
|
trigger_std_refreshes = True
|
||||||
|
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
||||||
post_action_sync_refresh_needed = True
|
post_action_sync_refresh_needed = True
|
||||||
|
elif task_context == 'create_branch' and not new_branch_context: # Creazione senza checkout
|
||||||
|
trigger_std_refreshes = True
|
||||||
|
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
||||||
|
post_action_sync_refresh_needed = True
|
||||||
|
# Logica refresh per le azioni locali
|
||||||
|
else:
|
||||||
|
trigger_std_refreshes = True # Assume che le azioni locali richiedano refresh standard
|
||||||
|
|
||||||
|
# Aggiungi refresh standard se flag è True
|
||||||
|
if trigger_std_refreshes:
|
||||||
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
|
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_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)
|
if self.refresh_tag_list not in refresh_list: 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)
|
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
|
||||||
if self.refresh_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
|
||||||
elif task_context == 'create_branch' and not new_branch_context:
|
|
||||||
post_action_sync_refresh_needed = True
|
|
||||||
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_remote_branches not in refresh_list: refresh_list.append(self.refresh_remote_branches)
|
|
||||||
# Logica refresh per le altre azioni locali
|
|
||||||
else:
|
|
||||||
if committed or task_context in ['fetch_bundle','prepare_repo','create_tag','_handle_gitignore_save']:
|
|
||||||
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
|
|
||||||
if task_context != 'refresh_changes': # refresh_changes non triggera altri refresh
|
|
||||||
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
|
|
||||||
if task_context not in ['refresh_tags','checkout_tag'] or committed:
|
|
||||||
if self.refresh_tag_list not in refresh_list: refresh_list.append(self.refresh_tag_list)
|
|
||||||
if task_context not in ['refresh_branches', 'checkout_branch']:
|
|
||||||
if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list)
|
|
||||||
|
|
||||||
# --- Azioni post-successo specifiche ---
|
# --- Azioni post-successo specifiche ---
|
||||||
if task_context == 'commit' and committed:
|
if task_context == 'commit' and committed:
|
||||||
@ -2909,7 +2987,6 @@ class GitSvnSyncApp:
|
|||||||
if hasattr(self, "main_frame") and self.main_frame.ask_yes_no("Checkout?", f"Switch to new branch '{new_branch_context}'?"):
|
if hasattr(self, "main_frame") and self.main_frame.ask_yes_no("Checkout?", f"Switch to new branch '{new_branch_context}'?"):
|
||||||
self.checkout_branch(branch_to_checkout=new_branch_context, repo_path_override=repo_path_for_refreshes)
|
self.checkout_branch(branch_to_checkout=new_branch_context, repo_path_override=repo_path_for_refreshes)
|
||||||
post_action_sync_refresh_needed = False # Verrà fatto dopo il checkout
|
post_action_sync_refresh_needed = False # Verrà fatto dopo il checkout
|
||||||
# Se non fa checkout, i refresh sono già in lista e post_action_sync_refresh_needed è True
|
|
||||||
|
|
||||||
elif status == 'warning':
|
elif status == 'warning':
|
||||||
# Gestione warning generica: mostra popup
|
# Gestione warning generica: mostra popup
|
||||||
@ -2926,11 +3003,10 @@ class GitSvnSyncApp:
|
|||||||
log_handler.log_error(f"Error reported for task '{task_context}': {message}", func_name=func_name)
|
log_handler.log_error(f"Error reported for task '{task_context}': {message}", func_name=func_name)
|
||||||
error_details = f"{message}\n({type(exception).__name__}: {exception})" if exception else message
|
error_details = f"{message}\n({type(exception).__name__}: {exception})" if exception else message
|
||||||
|
|
||||||
# Gestione errore per fetch_remote, pull (non conflitto), push (non rifiuto), push_tags, apply_config
|
# Gestione errore per fetch/pull/push/apply_config (Aggiorna stato Auth/Conn/Sync)
|
||||||
if task_context in ['fetch_remote', 'pull_remote', 'push_remote', 'push_tags_remote', 'apply_remote_config']:
|
if task_context in ['fetch_remote', 'pull_remote', 'push_remote', 'push_tags_remote', 'apply_remote_config']:
|
||||||
auth_related_error = False; conn_related_error = False
|
auth_related_error = False; conn_related_error = False
|
||||||
if isinstance(exception, GitCommandError) and exception.stderr: stderr_low = exception.stderr.lower();
|
if isinstance(exception, GitCommandError) and exception.stderr: stderr_low = exception.stderr.lower();
|
||||||
|
|
||||||
if any(e in stderr_low for e in ["authentication failed", "permission denied", "could not read"]): auth_related_error = True;
|
if any(e in stderr_low for e in ["authentication failed", "permission denied", "could not read"]): auth_related_error = True;
|
||||||
if any(e in stderr_low for e in ["repository not found", "could not resolve host"]): conn_related_error = True
|
if any(e in stderr_low for e in ["repository not found", "could not resolve host"]): conn_related_error = True
|
||||||
if auth_related_error: self._update_gui_auth_status('failed')
|
if auth_related_error: self._update_gui_auth_status('failed')
|
||||||
@ -2940,14 +3016,17 @@ class GitSvnSyncApp:
|
|||||||
if hasattr(self, "main_frame"): self.main_frame.show_error(f"{action_name} Error", f"{message}")
|
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")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
|
|
||||||
# Gestione errore refresh_remote_branches
|
# Gestione errore checkout tracking branch (già gestito sopra con popup, resetta stato sync)
|
||||||
|
elif task_context == 'checkout_tracking_branch':
|
||||||
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
|
# Mostra errore è già gestito sopra, non ripetere
|
||||||
|
|
||||||
|
# Gestione errore refresh_remote_branches (aggiorna lista GUI e stato sync)
|
||||||
elif task_context == 'refresh_remote_branches':
|
elif task_context == 'refresh_remote_branches':
|
||||||
if hasattr(self.main_frame, "update_remote_branches_list"): self.main_frame.update_remote_branches_list(["(Error)"])
|
if hasattr(self.main_frame, "update_remote_branches_list"): self.main_frame.update_remote_branches_list(["(Error)"])
|
||||||
# Aggiorna anche stato sync a errore? Sì, perché non sappiamo lo stato remoto.
|
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
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 (locali o refresh falliti)
|
||||||
# Gestione errori per altri task
|
|
||||||
else:
|
else:
|
||||||
if is_conflict and repo_path_conflict and task_context == 'fetch_bundle':
|
if is_conflict and repo_path_conflict and task_context == 'fetch_bundle':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.show_error("Merge Conflict", f"Conflict occurred during bundle fetch.\nResolve in:\n{repo_path_conflict}\nThen commit.")
|
if hasattr(self, "main_frame"): self.main_frame.show_error("Merge Conflict", f"Conflict occurred during bundle fetch.\nResolve in:\n{repo_path_conflict}\nThen commit.")
|
||||||
@ -2962,40 +3041,35 @@ class GitSvnSyncApp:
|
|||||||
if hasattr(self, "main_frame"): self.main_frame.update_tag_list([("(Error)", "")])
|
if hasattr(self, "main_frame"): self.main_frame.update_tag_list([("(Error)", "")])
|
||||||
elif task_context == 'refresh_branches': # Refresh LOCALI fallito
|
elif task_context == 'refresh_branches': # Refresh LOCALI fallito
|
||||||
if hasattr(self, "main_frame"):
|
if hasattr(self, "main_frame"):
|
||||||
self.main_frame.update_branch_list([], None)
|
self.main_frame.update_branch_list([], None); self.main_frame.update_history_branch_filter([])
|
||||||
self.main_frame.update_history_branch_filter([])
|
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
elif task_context == 'refresh_history':
|
elif task_context == 'refresh_history':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_history_display(["(Error retrieving history)"])
|
if hasattr(self, "main_frame"): self.main_frame.update_history_display(["(Error retrieving history)"])
|
||||||
elif task_context == 'refresh_changes':
|
elif task_context == 'refresh_changes':
|
||||||
if hasattr(self, "main_frame"): self.main_frame.update_changed_files_list(["(Error refreshing changes)"])
|
if hasattr(self, "main_frame"): self.main_frame.update_changed_files_list(["(Error refreshing changes)"])
|
||||||
# Stato sync/auth già gestito per errori apply_remote_config
|
|
||||||
|
|
||||||
# --- Trigger finale dei refresh asincroni raccolti e dello stato sync ---
|
# --- Trigger finale dei refresh asincroni raccolti e dello stato sync ---
|
||||||
delay_ms = 50 # Ritardo base tra i refresh schedulati
|
delay_ms = 50
|
||||||
if repo_path_for_refreshes and refresh_list:
|
if repo_path_for_refreshes and refresh_list:
|
||||||
log_handler.log_debug(f"Triggering {len(refresh_list)} async refreshes after '{task_context}'", func_name=func_name)
|
log_handler.log_debug(f"Triggering {len(refresh_list)} async refreshes after '{task_context}'", func_name=func_name)
|
||||||
current_delay = delay_ms
|
current_delay = delay_ms
|
||||||
for refresh_func in refresh_list:
|
for refresh_func in refresh_list:
|
||||||
try:
|
try:
|
||||||
# Usa after per schedulare l'esecuzione nel main loop di Tkinter
|
|
||||||
self.master.after(current_delay, refresh_func)
|
self.master.after(current_delay, refresh_func)
|
||||||
current_delay += 50 # Aumenta ritardo per il prossimo
|
current_delay += 50
|
||||||
except Exception as ref_e:
|
except Exception as ref_e:
|
||||||
log_handler.log_error(f"Error scheduling {getattr(refresh_func, '__name__', 'refresh function')}: {ref_e}", func_name=func_name)
|
log_handler.log_error(f"Error scheduling {getattr(refresh_func, '__name__', 'refresh function')}: {ref_e}", func_name=func_name)
|
||||||
# delay_ms ora contiene l'ultimo ritardo + 50
|
|
||||||
delay_ms = current_delay
|
delay_ms = current_delay
|
||||||
elif refresh_list:
|
elif refresh_list:
|
||||||
log_handler.log_warning("Cannot trigger post-action UI refreshes: Repo path unavailable.", func_name=func_name)
|
log_handler.log_warning("Cannot trigger post-action UI refreshes: Repo path unavailable.", func_name=func_name)
|
||||||
delay_ms = 50 # Resetta ritardo se non schedulato nulla
|
delay_ms = 50
|
||||||
# else: Nessun refresh standard, delay_ms rimane 50
|
# else: delay_ms rimane 50
|
||||||
|
|
||||||
# Triggera refresh stato ahead/behind SE necessario e non già in refresh_list
|
|
||||||
if post_action_sync_refresh_needed and self.refresh_remote_status not in refresh_list:
|
if post_action_sync_refresh_needed and self.refresh_remote_status not in refresh_list:
|
||||||
current_repo_path_sync = self._get_and_validate_svn_path("Post-Action Sync Status Check")
|
current_repo_path_sync = self._get_and_validate_svn_path("Post-Action Sync Status Check")
|
||||||
if current_repo_path_sync:
|
if current_repo_path_sync:
|
||||||
log_handler.log_debug(f"Triggering remote sync status refresh after '{task_context}'.", func_name=func_name)
|
log_handler.log_debug(f"Triggering remote sync status refresh after '{task_context}'.", func_name=func_name)
|
||||||
# Aggiunge un ulteriore piccolo delay dopo gli altri refresh
|
|
||||||
self.master.after(delay_ms + 50, self.refresh_remote_status)
|
self.master.after(delay_ms + 50, self.refresh_remote_status)
|
||||||
|
|
||||||
|
|
||||||
@ -3013,10 +3087,10 @@ class GitSvnSyncApp:
|
|||||||
# Tenta recupero GUI
|
# Tenta recupero GUI
|
||||||
try:
|
try:
|
||||||
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
|
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
|
||||||
self.main_frame.set_action_widgets_state(tk.NORMAL) # Tenta riabilitazione
|
self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||||
self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000)
|
self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000)
|
||||||
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
if hasattr(self.main_frame, "update_ahead_behind_status"): self.main_frame.update_ahead_behind_status(status_text="Sync Status: Error")
|
||||||
if hasattr(self.main_frame, "update_remote_branches_list"): self.main_frame.update_remote_branches_list(["(Error)"]) # Resetta anche lista remota
|
if hasattr(self.main_frame, "update_remote_branches_list"): self.main_frame.update_remote_branches_list(["(Error)"])
|
||||||
except Exception as recovery_e:
|
except Exception as recovery_e:
|
||||||
log_handler.log_error(f"Failed to recover GUI after queue processing error: {recovery_e}", func_name=func_name)
|
log_handler.log_error(f"Failed to recover GUI after queue processing error: {recovery_e}", func_name=func_name)
|
||||||
|
|
||||||
|
|||||||
@ -732,6 +732,100 @@ class ActionHandler:
|
|||||||
f"Unexpected untracking error: {e}", func_name=func_name
|
f"Unexpected untracking error: {e}", func_name=func_name
|
||||||
)
|
)
|
||||||
raise Exception("Unexpected untracking error") from e
|
raise Exception("Unexpected untracking error") from e
|
||||||
|
|
||||||
|
def execute_checkout_tracking_branch(
|
||||||
|
self,
|
||||||
|
repo_path: str,
|
||||||
|
new_local_branch_name: str,
|
||||||
|
remote_tracking_branch_full_name: str # Es. 'origin/main'
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Checks out a remote branch as a new local tracking branch.
|
||||||
|
Handles the case where the local branch name might already exist (should be checked by caller).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repo_path (str): Path to the local repository.
|
||||||
|
new_local_branch_name (str): The name for the new local branch.
|
||||||
|
remote_tracking_branch_full_name (str): The full name of the remote-tracking branch.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the status ('success', 'error'), message,
|
||||||
|
and optionally an exception.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If input arguments are invalid.
|
||||||
|
# GitCommandError is handled and returned in the dictionary
|
||||||
|
"""
|
||||||
|
func_name = "execute_checkout_tracking_branch"
|
||||||
|
log_handler.log_info(
|
||||||
|
f"Executing checkout tracking branch: Local='{new_local_branch_name}', "
|
||||||
|
f"Remote='{remote_tracking_branch_full_name}' in '{repo_path}'",
|
||||||
|
func_name=func_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Input Validation ---
|
||||||
|
if not repo_path or not os.path.isdir(repo_path): raise ValueError(f"Invalid repository path: '{repo_path}'")
|
||||||
|
if not new_local_branch_name or new_local_branch_name.isspace(): raise ValueError("New local branch name cannot be empty.")
|
||||||
|
if not remote_tracking_branch_full_name or '/' not in remote_tracking_branch_full_name: raise ValueError(f"Invalid remote tracking branch name format: '{remote_tracking_branch_full_name}' (expecting 'remote/branch')")
|
||||||
|
if not os.path.exists(os.path.join(repo_path, ".git")): raise ValueError(f"Directory '{repo_path}' is not a Git repository.")
|
||||||
|
|
||||||
|
# --- Pre-check: Uncommitted changes? ---
|
||||||
|
# È buona pratica impedire checkout se ci sono modifiche non salvate
|
||||||
|
try:
|
||||||
|
if self.git_commands.git_status_has_changes(repo_path):
|
||||||
|
msg = f"Checkout aborted: Uncommitted changes detected. Please commit or stash first before checking out '{new_local_branch_name}'."
|
||||||
|
log_handler.log_warning(msg, func_name=func_name)
|
||||||
|
return {'status': 'error', 'message': msg, 'exception': ValueError(msg)}
|
||||||
|
except GitCommandError as status_err:
|
||||||
|
msg = f"Checkout aborted: Failed to check repository status: {status_err}"
|
||||||
|
log_handler.log_error(msg, func_name=func_name)
|
||||||
|
return {'status': 'error', 'message': msg, 'exception': status_err}
|
||||||
|
|
||||||
|
# --- Esecuzione Comando Git ---
|
||||||
|
result_info = {'status': 'unknown', 'message': 'Checkout not completed.'}
|
||||||
|
try:
|
||||||
|
# Chiama il comando specifico in GitCommands (che ha check=False)
|
||||||
|
checkout_result = self.git_commands.checkout_new_branch_from_remote(
|
||||||
|
working_directory=repo_path,
|
||||||
|
new_local_branch_name=new_local_branch_name,
|
||||||
|
remote_tracking_branch_full_name=remote_tracking_branch_full_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analizza il risultato
|
||||||
|
if checkout_result.returncode == 0:
|
||||||
|
result_info['status'] = 'success'
|
||||||
|
result_info['message'] = f"Successfully checked out '{remote_tracking_branch_full_name}' as new local branch '{new_local_branch_name}'."
|
||||||
|
log_handler.log_info(result_info['message'], func_name=func_name)
|
||||||
|
# L'output di checkout -b di solito è su stderr, loggato da log_and_execute
|
||||||
|
else:
|
||||||
|
# Errore
|
||||||
|
result_info['status'] = 'error'
|
||||||
|
stderr_full = checkout_result.stderr if checkout_result.stderr else ""
|
||||||
|
stderr_lower = stderr_full.lower()
|
||||||
|
log_handler.log_error(f"Checkout tracking branch command failed (RC={checkout_result.returncode}). Stderr: {stderr_lower}", func_name=func_name)
|
||||||
|
|
||||||
|
# Controlla errori specifici
|
||||||
|
if f"a branch named '{new_local_branch_name}' already exists" in stderr_lower:
|
||||||
|
result_info['message'] = f"Checkout failed: A local branch named '{new_local_branch_name}' already exists."
|
||||||
|
elif "invalid object name" in stderr_lower or "not a valid object name" in stderr_lower:
|
||||||
|
result_info['message'] = f"Checkout failed: Remote branch '{remote_tracking_branch_full_name}' not found or invalid."
|
||||||
|
# Aggiungere altri check se necessario (es. pathspec did not match)
|
||||||
|
else:
|
||||||
|
result_info['message'] = f"Checkout tracking branch failed (RC={checkout_result.returncode}). Check logs."
|
||||||
|
|
||||||
|
result_info['exception'] = GitCommandError(result_info['message'], stderr=checkout_result.stderr)
|
||||||
|
|
||||||
|
except (GitCommandError, ValueError) as e:
|
||||||
|
# Errore dalla validazione o dal controllo stato
|
||||||
|
log_handler.log_error(f"Error during checkout tracking branch setup: {e}", func_name=func_name)
|
||||||
|
result_info = {'status': 'error', 'message': f"Checkout failed: {e}", 'exception': e}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Errore imprevisto
|
||||||
|
log_handler.log_exception(f"Unexpected error during checkout tracking branch: {e}", func_name=func_name)
|
||||||
|
result_info = {'status': 'error', 'message': f"Unexpected checkout error: {type(e).__name__}", 'exception': e}
|
||||||
|
|
||||||
|
return result_info
|
||||||
|
|
||||||
|
|
||||||
# --- END OF FILE action_handler.py ---
|
# --- END OF FILE action_handler.py ---
|
||||||
|
|||||||
@ -1438,4 +1438,61 @@ def run_refresh_remote_branches_async(
|
|||||||
|
|
||||||
log_handler.log_debug(f"[Worker] Finished: Refresh Remote Branches for '{remote_name}'", 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, # Dipendenza dall'ActionHandler locale
|
||||||
|
repo_path: str,
|
||||||
|
new_local_branch_name: str,
|
||||||
|
remote_tracking_branch_full_name: str, # Es. 'origin/main'
|
||||||
|
results_queue: queue.Queue
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Worker function to checkout a remote branch as a new local tracking branch asynchronously.
|
||||||
|
Executed in a separate thread.
|
||||||
|
"""
|
||||||
|
func_name = "run_checkout_tracking_branch_async"
|
||||||
|
log_handler.log_debug(
|
||||||
|
f"[Worker] Started: Checkout Remote Branch '{remote_tracking_branch_full_name}' "
|
||||||
|
f"as Local '{new_local_branch_name}' in '{repo_path}'",
|
||||||
|
func_name=func_name
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Chiama il metodo execute_checkout_tracking_branch che contiene la logica
|
||||||
|
# (controllo stato, esecuzione git checkout -b, analisi risultato)
|
||||||
|
result_info = action_handler.execute_checkout_tracking_branch(
|
||||||
|
repo_path=repo_path,
|
||||||
|
new_local_branch_name=new_local_branch_name,
|
||||||
|
remote_tracking_branch_full_name=remote_tracking_branch_full_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# result_info contiene già {'status': '...', 'message': '...', 'exception': ...}
|
||||||
|
log_handler.log_info(
|
||||||
|
f"[Worker] Checkout tracking branch result status: {result_info.get('status')}",
|
||||||
|
func_name=func_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Metti il dizionario del risultato direttamente nella coda
|
||||||
|
results_queue.put(result_info)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Cattura eccezioni impreviste sollevate da execute_checkout_tracking_branch
|
||||||
|
# (es., errori di validazione iniziali o eccezioni non gestite all'interno)
|
||||||
|
log_handler.log_exception(
|
||||||
|
f"[Worker] UNEXPECTED EXCEPTION during checkout tracking branch execution: {e}",
|
||||||
|
func_name=func_name
|
||||||
|
)
|
||||||
|
# Metti un risultato di errore generico nella coda
|
||||||
|
results_queue.put(
|
||||||
|
{
|
||||||
|
"status": "error",
|
||||||
|
"exception": e,
|
||||||
|
"message": f"Unexpected error during checkout operation: {type(e).__name__}",
|
||||||
|
"result": "worker_exception"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
log_handler.log_debug(
|
||||||
|
f"[Worker] Finished: Checkout Remote Branch '{remote_tracking_branch_full_name}' as Local '{new_local_branch_name}'",
|
||||||
|
func_name=func_name
|
||||||
|
)
|
||||||
# --- END OF FILE async_workers.py ---
|
# --- END OF FILE async_workers.py ---
|
||||||
|
|||||||
@ -1830,5 +1830,53 @@ class GitCommands:
|
|||||||
log_handler.log_exception(f"Unexpected error listing remote branches for '{remote_name}': {e}", func_name=func_name)
|
log_handler.log_exception(f"Unexpected error listing remote branches for '{remote_name}': {e}", func_name=func_name)
|
||||||
return [] # Restituisci lista vuota # Restituisci lista vuota per eccezioni impreviste
|
return [] # Restituisci lista vuota # Restituisci lista vuota per eccezioni impreviste
|
||||||
|
|
||||||
|
def checkout_new_branch_from_remote(
|
||||||
|
self,
|
||||||
|
working_directory: str,
|
||||||
|
new_local_branch_name: str,
|
||||||
|
remote_tracking_branch_full_name: str # Es. 'origin/main'
|
||||||
|
) -> subprocess.CompletedProcess:
|
||||||
|
"""
|
||||||
|
Creates and checks out a new local branch that tracks a remote branch.
|
||||||
|
Equivalent to 'git checkout -b <local_name> --track <remote_name>/<branch_name>'.
|
||||||
|
Does NOT raise exception on non-zero exit code (check=False).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
working_directory (str): Path to the repository.
|
||||||
|
new_local_branch_name (str): The name for the new local branch.
|
||||||
|
remote_tracking_branch_full_name (str): The full name of the remote-tracking branch
|
||||||
|
(e.g., 'origin/develop').
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
subprocess.CompletedProcess: The result of the command execution.
|
||||||
|
"""
|
||||||
|
func_name = "checkout_new_branch_from_remote"
|
||||||
|
log_handler.log_info(
|
||||||
|
f"Checking out remote branch '{remote_tracking_branch_full_name}' "
|
||||||
|
f"as new local branch '{new_local_branch_name}' in '{working_directory}'",
|
||||||
|
func_name=func_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Comando: git checkout -b <local_name> --track <remote_tracking_branch>
|
||||||
|
# L'opzione --track è implicita se il nome locale coincide con quello remoto
|
||||||
|
# (senza il prefisso del remote) e se esiste un solo remote con quel branch,
|
||||||
|
# ma essere espliciti con -b e --track è più sicuro e chiaro.
|
||||||
|
# Tuttavia, Git >= 1.8 (? verificare) suggerisce di usare solo:
|
||||||
|
# git checkout --track <remote_tracking_branch> (crea branch locale con nome default)
|
||||||
|
# o git checkout -b <local_name> <remote_tracking_branch> (implicito --track)
|
||||||
|
# Usiamo la forma più comune e moderna: git checkout -b <local> <remote_tracking>
|
||||||
|
cmd = ["git", "checkout", "-b", new_local_branch_name, remote_tracking_branch_full_name]
|
||||||
|
|
||||||
|
# Esegui catturando output, nascondendo console, check=False
|
||||||
|
# Il worker analizzerà errori specifici (es. branch locale già esiste, ref remoto non valido)
|
||||||
|
result = self.log_and_execute(
|
||||||
|
command=cmd,
|
||||||
|
working_directory=working_directory,
|
||||||
|
check=False,
|
||||||
|
capture=True,
|
||||||
|
hide_console=True,
|
||||||
|
log_output_level=logging.INFO
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
# --- END OF FILE git_commands.py ---
|
# --- END OF FILE git_commands.py ---
|
||||||
|
|||||||
85
gui.py
85
gui.py
@ -452,6 +452,7 @@ class MainFrame(ttk.Frame):
|
|||||||
refresh_remote_status_cb,
|
refresh_remote_status_cb,
|
||||||
clone_remote_repo_cb,
|
clone_remote_repo_cb,
|
||||||
refresh_remote_branches_cb,
|
refresh_remote_branches_cb,
|
||||||
|
checkout_remote_branch_cb,
|
||||||
):
|
):
|
||||||
"""Initializes the MainFrame."""
|
"""Initializes the MainFrame."""
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
@ -490,6 +491,7 @@ class MainFrame(ttk.Frame):
|
|||||||
self.refresh_remote_status_callback = refresh_remote_status_cb
|
self.refresh_remote_status_callback = refresh_remote_status_cb
|
||||||
self.clone_remote_repo_callback = clone_remote_repo_cb
|
self.clone_remote_repo_callback = clone_remote_repo_cb
|
||||||
self.refresh_remote_branches_callback = refresh_remote_branches_cb
|
self.refresh_remote_branches_callback = refresh_remote_branches_cb
|
||||||
|
self.checkout_remote_branch_callback = checkout_remote_branch_cb
|
||||||
|
|
||||||
# Configure style (invariato)
|
# Configure style (invariato)
|
||||||
self.style = ttk.Style()
|
self.style = ttk.Style()
|
||||||
@ -540,6 +542,9 @@ class MainFrame(ttk.Frame):
|
|||||||
self.notebook.add(self.tags_tab_frame, text=" Tags ")
|
self.notebook.add(self.tags_tab_frame, text=" Tags ")
|
||||||
self.notebook.add(self.branch_tab_frame, text=" Branches ")
|
self.notebook.add(self.branch_tab_frame, text=" Branches ")
|
||||||
self.notebook.add(self.history_tab_frame, text=" History ")
|
self.notebook.add(self.history_tab_frame, text=" History ")
|
||||||
|
|
||||||
|
self.remote_branch_context_menu = tk.Menu(self.master, tearoff=0)
|
||||||
|
|
||||||
log_frame_container = ttk.Frame(self)
|
log_frame_container = ttk.Frame(self)
|
||||||
log_frame_container.pack(
|
log_frame_container.pack(
|
||||||
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=(5, 0)
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=(5, 0)
|
||||||
@ -803,9 +808,9 @@ class MainFrame(ttk.Frame):
|
|||||||
state=tk.DISABLED # Inizia disabilitata
|
state=tk.DISABLED # Inizia disabilitata
|
||||||
)
|
)
|
||||||
self.remote_branches_listbox.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
|
self.remote_branches_listbox.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
|
||||||
# Placeholder per binding futuro menu contestuale
|
|
||||||
# self.remote_branches_listbox.bind("<Button-3>", self._show_remote_branches_context_menu)
|
self.remote_branches_listbox.bind("<Button-3>", self._show_remote_branches_context_menu)
|
||||||
|
|
||||||
# Scrollbar verticale per la listbox
|
# Scrollbar verticale per la listbox
|
||||||
rb_scrollbar = ttk.Scrollbar(
|
rb_scrollbar = ttk.Scrollbar(
|
||||||
remote_view_frame,
|
remote_view_frame,
|
||||||
@ -829,6 +834,75 @@ class MainFrame(ttk.Frame):
|
|||||||
|
|
||||||
# Ritorna il frame principale della tab
|
# Ritorna il frame principale della tab
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
|
def _show_remote_branches_context_menu(self, event):
|
||||||
|
"""Displays the context menu for the remote branches listbox."""
|
||||||
|
func_name = "_show_remote_branches_context_menu"
|
||||||
|
listbox = event.widget # Il widget che ha generato l'evento (la listbox)
|
||||||
|
selected_index = None # Inizializza
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Seleziona l'elemento sotto il cursore al momento del click destro
|
||||||
|
selected_index = listbox.nearest(event.y)
|
||||||
|
if selected_index < 0: return # Clic fuori dagli elementi
|
||||||
|
|
||||||
|
listbox.selection_clear(0, tk.END)
|
||||||
|
listbox.selection_set(selected_index)
|
||||||
|
listbox.activate(selected_index)
|
||||||
|
selected_item_text = listbox.get(selected_index).strip()
|
||||||
|
|
||||||
|
# Pulisci il menu precedente
|
||||||
|
self.remote_branch_context_menu.delete(0, tk.END)
|
||||||
|
|
||||||
|
# Verifica se l'elemento selezionato è un branch valido (non messaggi di errore/placeholder)
|
||||||
|
is_valid_branch = '/' in selected_item_text and not selected_item_text.startswith("(")
|
||||||
|
|
||||||
|
if is_valid_branch:
|
||||||
|
# Estrai il nome del branch remoto completo (es. origin/feature/xyz)
|
||||||
|
remote_branch_full_name = selected_item_text
|
||||||
|
|
||||||
|
# Deriva il nome suggerito per il branch locale (es. feature/xyz)
|
||||||
|
# Trova l'indice del primo '/'
|
||||||
|
slash_index = remote_branch_full_name.find('/')
|
||||||
|
local_branch_suggestion = ""
|
||||||
|
if slash_index != -1:
|
||||||
|
local_branch_suggestion = remote_branch_full_name[slash_index + 1:]
|
||||||
|
|
||||||
|
# Aggiungi l'opzione di checkout
|
||||||
|
if local_branch_suggestion:
|
||||||
|
menu_label = f"Checkout as new local branch '{local_branch_suggestion}'"
|
||||||
|
# Chiama il callback del controller passando i nomi necessari
|
||||||
|
self.remote_branch_context_menu.add_command(
|
||||||
|
label=menu_label,
|
||||||
|
# Usa lambda per passare i parametri corretti al momento del click
|
||||||
|
command=lambda rb=remote_branch_full_name, lb=local_branch_suggestion:
|
||||||
|
self.checkout_remote_branch_callback(rb, lb)
|
||||||
|
if callable(self.checkout_remote_branch_callback) else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Caso strano: non siamo riusciti a derivare il nome locale
|
||||||
|
self.remote_branch_context_menu.add_command(label="(Cannot derive local name)", state=tk.DISABLED)
|
||||||
|
|
||||||
|
# --- Aggiungere altre opzioni future qui ---
|
||||||
|
# es. self.remote_branch_context_menu.add_command(label="Delete Remote Branch...", ...)
|
||||||
|
self.remote_branch_context_menu.add_separator()
|
||||||
|
self.remote_branch_context_menu.add_command(label="Cancel")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Elemento non valido selezionato (es. "(Loading...)", "(Error)")
|
||||||
|
self.remote_branch_context_menu.add_command(label="(No actions available)", state=tk.DISABLED)
|
||||||
|
|
||||||
|
# Mostra il menu alla posizione del mouse
|
||||||
|
self.remote_branch_context_menu.tk_popup(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
except tk.TclError:
|
||||||
|
log_handler.log_debug("TclError during remote branch context menu display (e.g., listbox empty).", func_name=func_name)
|
||||||
|
except Exception as e:
|
||||||
|
log_handler.log_exception(f"Error showing remote branch context menu: {e}", func_name=func_name)
|
||||||
|
finally:
|
||||||
|
# Assicura che il grab venga rilasciato in ogni caso
|
||||||
|
if hasattr(self, "remote_branch_context_menu"):
|
||||||
|
self.remote_branch_context_menu.grab_release()
|
||||||
|
|
||||||
def update_remote_branches_list(self, remote_branch_list: List[str]):
|
def update_remote_branches_list(self, remote_branch_list: List[str]):
|
||||||
"""Clears and populates the remote branches listbox."""
|
"""Clears and populates the remote branches listbox."""
|
||||||
@ -2009,6 +2083,11 @@ class MainFrame(ttk.Frame):
|
|||||||
if hasattr(self, "remote_branches_listbox") and self.remote_branches_listbox.winfo_exists():
|
if hasattr(self, "remote_branches_listbox") and self.remote_branches_listbox.winfo_exists():
|
||||||
try: self.remote_branches_listbox.config(state=remote_list_state)
|
try: self.remote_branches_listbox.config(state=remote_list_state)
|
||||||
except Exception: pass # Ignora errori
|
except Exception: pass # Ignora errori
|
||||||
|
|
||||||
|
remote_list_state = tk.NORMAL if state == tk.NORMAL else tk.DISABLED
|
||||||
|
if hasattr(self, "remote_branches_listbox") and self.remote_branches_listbox.winfo_exists():
|
||||||
|
try: self.remote_branches_listbox.config(state=remote_list_state)
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
def update_ahead_behind_status(
|
def update_ahead_behind_status(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user