diff --git a/GitUtility.py b/GitUtility.py index 806c6cc..e9e0642 100644 --- a/GitUtility.py +++ b/GitUtility.py @@ -1834,122 +1834,198 @@ class GitSvnSyncApp: def _check_completion_queue(self, results_queue, context): """Checks result queue, updates GUI (incl. status bar color).""" task_context = context.get('context', 'unknown') - # log_handler.log_debug(f"Checking completion queue for context: {task_context}", func_name="_check_completion_queue") # Mantenuto commentato per ora + # log_handler.log_debug(f"Checking completion queue for context: {task_context}", func_name="_check_completion_queue") # Mantenuto commentato try: + # Tenta di ottenere un risultato dalla coda senza bloccare result_data = results_queue.get_nowait() log_handler.log_info(f"Result received for '{task_context}'. Status: {result_data.get('status')}", func_name="_check_completion_queue") # --- 1. Re-enable GUI Widgets --- + # Riabilita i widget principali prima di processare il risultato log_handler.log_debug("Re-enabling widgets.", func_name="_check_completion_queue") - 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: + log_handler.log_warning("Cannot re-enable widgets, MainFrame missing.", func_name="_check_completion_queue") + # Potrebbe essere necessario uscire qui se la GUI non c'è più + return # --- 2. Extract Details --- + # Estrae i dati dal dizionario del risultato, con fallback sicuri status = result_data.get('status') message = result_data.get('message') result = result_data.get('result') exception = result_data.get('exception') - committed = result_data.get('committed', False) - is_conflict = result_data.get('conflict', False) - repo_path_conflict = result_data.get('repo_path') - new_branch_context = context.get('new_branch_name') + committed = result_data.get('committed', False) # Flag per operazioni che potrebbero committare + is_conflict = result_data.get('conflict', False) # Flag specifico per conflitti merge + repo_path_conflict = result_data.get('repo_path') # Percorso repo in caso di conflitto + new_branch_context = context.get('new_branch_name') # Info dal contesto originale se si crea un branch # --- 3. Update Status Bar (con colore e reset temporizzato) --- - # (Logica status bar invariata) + # Imposta colore e durata del messaggio nella status bar in base allo stato status_color = None - reset_duration = 5000 # Resetta colore dopo 5 secondi + reset_duration = 5000 # Default reset 5 secondi if status == 'success': status_color = self.main_frame.STATUS_GREEN elif status == 'warning': status_color = self.main_frame.STATUS_YELLOW - reset_duration = 7000 + reset_duration = 7000 # Warning dura un po' di più elif status == 'error': status_color = self.main_frame.STATUS_RED - reset_duration = 10000 + reset_duration = 10000 # Errore dura di più + # Aggiorna la status bar tramite la funzione helper in MainFrame self.main_frame.update_status_bar(message, bg_color=status_color, duration_ms=reset_duration) # --- 4. Process Result & Trigger Updates --- + # Ottieni il percorso corrente del repo per eventuali refresh necessari repo_path_for_updates = self._get_and_validate_svn_path("Post-Action Update") + # Gestione basata sullo stato del risultato ('success', 'warning', 'error') if status == 'success': - refresh_list = [] - # (Logica per popolare refresh_list invariata) + refresh_list = [] # Lista di funzioni di refresh da chiamare dopo + + # --- Logica per determinare quali refresh avviare --- + # Se l'operazione ha modificato lo stato del repo (commit, fetch, prepare, checkout, ecc.) if task_context in ['prepare_repo', 'fetch_bundle', 'commit', 'create_tag', 'checkout_tag', 'create_branch', 'checkout_branch', '_handle_gitignore_save_async', 'add_file']: - if committed or task_context in ['fetch_bundle','prepare_repo','create_tag','_handle_gitignore_save_async']: refresh_list.append(self.refresh_commit_history) - if task_context != 'refresh_changes': refresh_list.append(self.refresh_changed_files_list) - if task_context not in ['refresh_tags','checkout_tag'] or committed: refresh_list.append(self.refresh_tag_list) - if task_context != 'refresh_branches': refresh_list.append(self.refresh_branch_list) + # Aggiorna la history se c'è stato un commit o se l'operazione lo richiede implicitamente + if committed or task_context in ['fetch_bundle','prepare_repo','create_tag','_handle_gitignore_save_async']: + if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history) + # Aggiorna sempre la lista dei file modificati tranne se l'azione era proprio il refresh dei file + if task_context != 'refresh_changes': + if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list) + # Aggiorna i tag (tranne se si è fatto checkout di un tag o refresh dei tag stessi), o se c'è stato commit + 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) + # Aggiorna i branch (tranne se si è fatto refresh dei branch) + if task_context != 'refresh_branches': + if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list) - # Gestione aggiornamenti diretti post-refresh + # --- Gestione aggiornamenti diretti post-operazioni di refresh --- + # Se l'azione era un refresh, aggiorna direttamente la GUI con i dati ricevuti if task_context == 'refresh_tags': - self.main_frame.update_tag_list(result if result else []) + self.main_frame.update_tag_list(result if isinstance(result, list) else []) # Assicura sia una lista elif task_context == 'refresh_branches': - branches, current = result if result else ([], None) + # Estrai i dati con un fallback sicuro + branches, current = result if isinstance(result, tuple) and len(result) == 2 else ([], None) + # Log di debug aggiunto qui + log_handler.log_debug( + f"Preparing to call update_branch_list. Task Context: '{task_context}'. " + f"Branches type: {type(branches)}, Count: {len(branches) if isinstance(branches, list) else 'N/A'}. Current: {repr(current)}. " + f"Raw result: {repr(result)}", + func_name="_check_completion_queue" + ) self.main_frame.update_branch_list(branches, current) - self.main_frame.update_history_branch_filter(branches) + # Aggiorna anche il filtro nella tab history + self.main_frame.update_history_branch_filter(branches) # Usa solo la lista branches elif task_context == 'refresh_history': - self.main_frame.update_history_display(result if result else []) + # Assicura sia una lista + log_lines_result = result if isinstance(result, list) else [] + # Log di debug aggiunto qui + log_handler.log_debug( + f"Preparing to call update_history_display. Task Context: '{task_context}'. " + f"Result type: {type(result)}. Lines count: {len(log_lines_result)}. " + f"Raw result sample: {repr(log_lines_result[:5])}", # Usa la variabile già controllata + func_name="_check_completion_queue" + ) + self.main_frame.update_history_display(log_lines_result) elif task_context == 'refresh_changes': - # ---<<< INIZIO MODIFICA DEBUG >>>--- - # Logga esattamente cosa sta per essere passato a update_changed_files_list + # Log di debug aggiunto qui log_handler.log_debug( f"Preparing to call update_changed_files_list. " f"Task Context: '{task_context}'. Result type: {type(result)}. Result value: {repr(result)}", func_name="_check_completion_queue" ) - # ---<<< FINE MODIFICA DEBUG >>>--- - self.main_frame.update_changed_files_list(result if result else []) + self.main_frame.update_changed_files_list(result if isinstance(result, list) else []) # Assicura sia lista - # (Altre gestioni di successo invariate: commit, create_branch checkout, etc.) - if task_context == 'commit' and committed: self.main_frame.clear_commit_message() + + # --- Gestione azioni post-successo specifiche --- + if task_context == 'commit' and committed: + # Pulisce il messaggio di commit solo se è stato fatto un commit effettivo + self.main_frame.clear_commit_message() if task_context == 'create_branch' and new_branch_context: + # Chiede se fare checkout del nuovo branch creato if self.main_frame.ask_yes_no("Checkout?", f"Switch to new branch '{new_branch_context}'?"): + # Avvia un'altra operazione asincrona per il checkout self.checkout_branch(branch_to_checkout=new_branch_context) - else: # Refresh history if not checking out + else: + # Se non fa checkout, assicurati che la history venga aggiornata if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history) - # Trigger collected async refreshes + # --- Trigger finale dei refresh asincroni raccolti --- if repo_path_for_updates and refresh_list: log_handler.log_debug(f"Triggering {len(refresh_list)} async refreshes for '{task_context}'", func_name="_check_completion_queue") + # Avvia ogni funzione di refresh nella lista (sono anch'esse asincrone) for refresh_func in refresh_list: - try: refresh_func() - except Exception as ref_e: log_handler.log_error(f"Error triggering {refresh_func.__name__}: {ref_e}", func_name="_check_completion_queue") + try: + refresh_func() + except Exception as ref_e: + log_handler.log_error(f"Error triggering {refresh_func.__name__}: {ref_e}", func_name="_check_completion_queue") elif refresh_list: + # Se la lista refresh non è vuota ma manca il path, logga un warning log_handler.log_warning("Cannot trigger UI refreshes: Repo path unavailable.", func_name="_check_completion_queue") elif status == 'warning': - # (gestione warning invariata) - self.main_frame.show_warning("Operation Info", message) - if "already prepared" in message: self.refresh_changed_files_list() + # Gestione dei warning: mostra un popup informativo + if hasattr(self, "main_frame"): + self.main_frame.show_warning("Operation Info", message) + # Caso specifico: repo già preparato + if "already prepared" in message: + if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list) + if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list) + # Avvia refresh post warning + if repo_path_for_updates and refresh_list: + for refresh_func in refresh_list: + try: refresh_func() + except Exception as ref_e: log_handler.log_error(f"Error triggering refresh after warning: {ref_e}", func_name="_check_completion_queue") + elif status == 'error': - # (gestione errore invariata) - error_details = f"{message}\n({exception})" if exception else message - if is_conflict and repo_path_conflict: self.main_frame.show_error("Merge Conflict", f"Conflict occurred.\nResolve in:\n{repo_path_conflict}\nThen commit.") - elif "Uncommitted changes" in message: self.main_frame.show_warning("Action Blocked", f"{exception}\nCommit or stash first.") - else: self.main_frame.show_error("Error: Operation Failed", error_details) - # Aggiornamento liste con errore (opzionale, dipende dal task) + # Gestione degli errori: mostra un popup di errore + error_details = f"{message}\n({type(exception).__name__}: {exception})" if exception else message + if hasattr(self, "main_frame"): + # Mostra popup specifici per errori comuni + if is_conflict and repo_path_conflict: + self.main_frame.show_error("Merge Conflict", f"Conflict occurred.\nResolve in:\n{repo_path_conflict}\nThen commit.") + elif "Uncommitted changes" in str(exception): # Controlla messaggio eccezione + self.main_frame.show_warning("Action Blocked", f"{exception}\nCommit or stash first.") + else: + # Errore generico + self.main_frame.show_error("Error: Operation Failed", error_details) + + # Aggiorna le liste della GUI per mostrare lo stato di errore (se applicabile) if task_context == 'refresh_tags': self.main_frame.update_tag_list([("(Error)", "")]) - elif task_context == 'refresh_branches': self.main_frame.update_branch_list([], None); self.main_frame.update_history_branch_filter([]) + elif task_context == 'refresh_branches': + self.main_frame.update_branch_list([], None) # Pulisci lista branch + self.main_frame.update_history_branch_filter([]) # Pulisci anche il filtro history elif task_context == 'refresh_history': self.main_frame.update_history_display(["(Error retrieving history)"]) elif task_context == 'refresh_changes': self.main_frame.update_changed_files_list(["(Error refreshing changes)"]) + # Log finale per il processamento di questo risultato log_handler.log_debug(f"Finished processing result for context '{task_context}'.", func_name="_check_completion_queue") except queue.Empty: - # Reschedule check - self.master.after(self.ASYNC_QUEUE_CHECK_INTERVAL_MS, self._check_completion_queue, results_queue, context) + # La coda è vuota, significa che non c'erano risultati pronti. + # Pianifica un altro controllo in futuro. + if hasattr(self, "master") and self.master.winfo_exists(): + self.master.after(self.ASYNC_QUEUE_CHECK_INTERVAL_MS, self._check_completion_queue, results_queue, context) except Exception as e: + # Errore critico durante il processamento della coda stessa log_handler.log_exception(f"Critical error processing completion queue for {task_context}: {e}", func_name="_check_completion_queue") - try: self.main_frame.set_action_widgets_state(tk.NORMAL) # Attempt recovery - except: pass - self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000) + # Tenta di riabilitare i widget come misura di sicurezza + try: + if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): + self.main_frame.set_action_widgets_state(tk.NORMAL) + except: pass # Ignora errori nel tentativo di recupero + # Mostra errore generico nella status bar + if hasattr(self, "main_frame") and self.main_frame.winfo_exists(): + self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000) # --- Punto di Ingresso (invariato) --- diff --git a/gui.py b/gui.py index 2004b9d..15a50b9 100644 --- a/gui.py +++ b/gui.py @@ -1181,44 +1181,68 @@ class MainFrame(ttk.Frame): pass def update_branch_list(self, branches, current_branch): - if ( - not hasattr(self, "branch_listbox") - or not self.branch_listbox.winfo_exists() - ): + """Clears and populates the branch listbox.""" + func_name = "update_branch_list (GUI)" # Nome specifico per i log + # ---<<< INIZIO MODIFICA DEBUG & ERRORE >>>--- + log_handler.log_debug( + f"Received branches type={type(branches)}, count={len(branches) if isinstance(branches, list) else 'N/A'}, " + f"current={repr(current_branch)}", + func_name=func_name + ) + listbox = getattr(self, "branch_listbox", None) + if not listbox or not listbox.winfo_exists(): + log_handler.log_error("branch_listbox not available for update.", func_name=func_name) return + try: - self.branch_listbox.config(state=tk.NORMAL) - self.branch_listbox.delete(0, tk.END) + listbox.config(state=tk.NORMAL) + listbox.delete(0, tk.END) sel_idx = -1 - if branches: + + # Assicurati che 'branches' sia una lista prima di iterare + if isinstance(branches, list) and branches: + # Resetta colore (se era grigio o rosso) try: - if self.branch_listbox.cget("fg") == "grey": - self.branch_listbox.config( - fg=self.style.lookup("TListbox", "foreground") - ) - except tk.TclError: - pass + default_fg = self.style.lookup("TListbox", "foreground") + if listbox.cget("fg") != default_fg: + listbox.config(fg=default_fg) + except tk.TclError: pass # Ignora errori di stile + + # Popola la lista for i, branch in enumerate(branches): prefix = "* " if branch == current_branch else " " - self.branch_listbox.insert(tk.END, f"{prefix}{branch}") - if branch == current_branch: - sel_idx = i - else: - self.branch_listbox.insert(tk.END, "(No local branches)") - self.branch_listbox.config(fg="grey") + # Assicura che branch sia una stringa prima di inserire + listbox.insert(tk.END, f"{prefix}{str(branch)}") + if branch == current_branch: + sel_idx = i + elif isinstance(branches, list) and not branches: # Lista vuota valida + listbox.insert(tk.END, "(No local branches)") + listbox.config(fg="grey") + else: # Caso in cui branches non è una lista (errore?) + log_handler.log_warning(f"Invalid data received for branches: {repr(branches)}", func_name=func_name) + listbox.insert(tk.END, "(Invalid data received)") + listbox.config(fg="orange") + + # Imposta selezione e vista if sel_idx >= 0: - self.branch_listbox.selection_set(sel_idx) - self.branch_listbox.see(sel_idx) - self.branch_listbox.config(state=tk.NORMAL) - self.branch_listbox.yview_moveto(0.0) + listbox.selection_set(sel_idx) + listbox.see(sel_idx) + + listbox.config(state=tk.NORMAL) # Mantieni selezionabile + listbox.yview_moveto(0.0) + except Exception as e: - print(f"ERROR updating branch list GUI: {e}", file=sys.stderr) - try: - self.branch_listbox.delete(0, tk.END) - self.branch_listbox.insert(tk.END, "(Error)") - self.branch_listbox.config(fg="red") - except: - pass + log_handler.log_exception(f"Error updating branch list GUI: {e}", func_name=func_name) + # Fallback: Mostra errore nella listbox + try: + if listbox.winfo_exists(): + listbox.config(state=tk.NORMAL) + listbox.delete(0, tk.END) + listbox.insert(tk.END, "(Error)") + listbox.config(fg="red", state=tk.DISABLED) # Disabilita su errore + except Exception as fallback_e: + log_handler.log_error(f"Error displaying fallback error in branch listbox: {fallback_e}", func_name=func_name) + # ---<<< FINE MODIFICA DEBUG & ERRORE >>>--- def get_selected_tag(self): if hasattr(self, "tag_listbox") and self.tag_listbox.winfo_exists(): @@ -1264,26 +1288,62 @@ class MainFrame(ttk.Frame): pass def update_history_display(self, log_lines): - if not hasattr(self, "history_text") or not self.history_text.winfo_exists(): + """Clears and populates the history ScrolledText widget.""" + func_name = "update_history_display (GUI)" # Nome specifico per i log + # ---<<< INIZIO MODIFICA DEBUG & ERRORE >>>--- + log_handler.log_debug( + f"Received log_lines type={type(log_lines)}, count={len(log_lines) if isinstance(log_lines, list) else 'N/A'}. " + f"Sample: {repr(log_lines[:5]) if isinstance(log_lines, list) else repr(log_lines)}", + func_name=func_name + ) + history_widget = getattr(self, "history_text", None) + if not history_widget or not history_widget.winfo_exists(): + log_handler.log_error("history_text widget not available for update.", func_name=func_name) return + try: - self.history_text.config(state=tk.NORMAL) - self.history_text.delete("1.0", tk.END) - self.history_text.insert( - tk.END, "\n".join(log_lines) if log_lines else "(No history found)" - ) - self.history_text.config(state=tk.DISABLED) - self.history_text.yview_moveto(0.0) - self.history_text.xview_moveto(0.0) + history_widget.config(state=tk.NORMAL) + history_widget.delete("1.0", tk.END) + + # Assicurati che log_lines sia una lista prima di fare join + if isinstance(log_lines, list): + if log_lines: + # Resetta colore (se era rosso o altro) + try: + # Potrebbe non esserci un colore foreground specifico per ScrolledText nello stile + # Potremmo impostare a nero o lasciare il default del widget + default_fg = 'black' # Assumiamo nero come default sicuro + if history_widget.cget("fg") != default_fg: + history_widget.config(fg=default_fg) + except tk.TclError: pass + + # Unisci le linee (assicurati siano stringhe) + text_to_insert = "\n".join(map(str, log_lines)) + history_widget.insert(tk.END, text_to_insert) + else: # Lista vuota valida + history_widget.insert(tk.END, "(No history found)") + history_widget.config(fg="grey") # Colore grigio per indicare vuoto + else: # log_lines non è una lista (errore?) + log_handler.log_warning(f"Invalid data received for history: {repr(log_lines)}", func_name=func_name) + history_widget.insert(tk.END, f"(Invalid data received: {repr(log_lines)})") + history_widget.config(fg="orange") # Arancione per dato inatteso + + history_widget.config(state=tk.DISABLED) # Rendi read-only + history_widget.yview_moveto(0.0) + history_widget.xview_moveto(0.0) + except Exception as e: - print(f"ERROR updating history GUI: {e}", file=sys.stderr) - try: - self.history_text.config(state=tk.NORMAL) - self.history_text.delete("1.0", tk.END) - self.history_text.insert(tk.END, "(Error)") - self.history_text.config(state=tk.DISABLED, fg="red") - except: - pass + log_handler.log_exception(f"Error updating history GUI: {e}", func_name=func_name) + # Fallback: Mostra errore nel widget di testo + try: + if history_widget.winfo_exists(): + history_widget.config(state=tk.NORMAL) + history_widget.delete("1.0", tk.END) + history_widget.insert(tk.END, "(Error displaying history)") + history_widget.config(state=tk.DISABLED, fg="red") # Rosso e disabilitato + except Exception as fallback_e: + log_handler.log_error(f"Error displaying fallback error in history widget: {fallback_e}", func_name=func_name) + # ---<<< FINE MODIFICA DEBUG & ERRORE >>>--- def update_history_branch_filter(self, branches_tags, current_ref=None): if (