From 688e779494beb2ae112b756a08327976247cc41c Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Fri, 18 Apr 2025 16:03:00 +0200 Subject: [PATCH] add add file into commit function --- GitUtility.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ git_commands.py | 47 ++++++++++++++++++++++++++++++++++- gui.py | 66 ++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 171 insertions(+), 7 deletions(-) diff --git a/GitUtility.py b/GitUtility.py index 227c4eb..a391a87 100644 --- a/GitUtility.py +++ b/GitUtility.py @@ -114,6 +114,7 @@ class GitSvnSyncApp: # Passa istanza config manager e profili iniziali config_manager_instance=self.config_manager, profile_sections_list=self.config_manager.get_profile_sections(), + add_selected_file_cb=self.add_selected_file, ) # --- Collega widget GUI specifici (che non passano dal costruttore di MainFrame) --- @@ -1825,6 +1826,70 @@ class GitSvnSyncApp: try: self.master.destroy() except: pass # Ignora errori durante la distruzione + + def add_selected_file(self, file_status_line): + """Handles the 'Add to Staging Area' action from the context menu.""" + self.logger.info(f"--- Action Triggered: Add File for '{file_status_line}' ---") + if hasattr(self, 'main_frame'): + self.main_frame.update_status_bar("Processing: Adding file to staging...") + + # Ottieni percorso repo + svn_path = self._get_and_validate_svn_path("Add File") + if not svn_path: + if hasattr(self, 'main_frame'): self.main_frame.update_status_bar("Add failed: Invalid path.") + return + + # Estrai percorso relativo pulito + # Usa la stessa logica di open_diff_viewer o il metodo da diff_viewer + relative_path = "" + try: + # Assumi che _clean_relative_path sia in DiffViewerWindow, potremmo duplicarlo + # o creare un helper comune se diventa troppo ripetitivo. + # Per ora usiamo una logica simile a quella in open_diff_viewer + line = file_status_line.strip('\x00').strip() + if line.startswith("??") and len(line) > 3: + relative_path = line[3:].strip() + if relative_path.startswith('"') and relative_path.endswith('"'): + relative_path = relative_path[1:-1] + else: + # Se non inizia con '??', l'opzione non doveva essere attiva. Logga errore. + self.logger.error(f"Add action triggered for non-untracked file: '{file_status_line}'") + if hasattr(self, 'main_frame'): + self.main_frame.show_error("Error", "Cannot add non-untracked file.") + self.main_frame.update_status_bar("Add failed: File not untracked.") + return + + if not relative_path: + raise ValueError("Could not extract file path.") + + except Exception as e: + self.logger.error(f"Error parsing file path for add: {e}") + if hasattr(self, 'main_frame'): + self.main_frame.show_error("Error", f"Could not determine file to add from:\n{file_status_line}") + self.main_frame.update_status_bar("Add failed: Parse error.") + return + + # Esegui il comando git add + status_final = "Ready." + try: + success = self.git_commands.add_file(svn_path, relative_path) + if success: + status_final = f"File '{os.path.basename(relative_path)}' added to staging." + self.logger.info(status_final) + # Aggiorna la lista dei file modificati per mostrare il nuovo stato + self.refresh_changed_files_list() + # else: add_file solleverà eccezione in caso di fallimento + + except (GitCommandError, ValueError) as e: + self.logger.error(f"Failed to add file '{relative_path}': {e}", exc_info=True) + status_final = f"Error adding file: {type(e).__name__}" + if hasattr(self, 'main_frame'): self.main_frame.show_error("Add Error", f"Failed to add file:\n{e}") + except Exception as e: + self.logger.exception(f"Unexpected error adding file '{relative_path}': {e}") + status_final = "Error: Unexpected add failure." + if hasattr(self, 'main_frame'): self.main_frame.show_error("Error", f"Unexpected error:\n{e}") + finally: + if hasattr(self, 'main_frame'): self.main_frame.update_status_bar(status_final) # --- Application Entry Point --- diff --git a/git_commands.py b/git_commands.py index 729b7c2..e9e171d 100644 --- a/git_commands.py +++ b/git_commands.py @@ -1177,4 +1177,49 @@ class GitCommands: return None except Exception as e: self.logger.exception(f"Unexpected error in get_file_content_from_ref for '{ref_path_arg}': {e}") - return None \ No newline at end of file + return None + + def add_file(self, working_directory, file_path): + """ + Adds a specific file to the Git staging area (index). + + Args: + working_directory (str): Path to the Git repository. + file_path (str): Relative path to the file within the repo to add. + + Returns: + bool: True if the command executed successfully. + + Raises: + GitCommandError: If git add fails (e.g., file doesn't exist, permissions). + ValueError: If file_path is empty. + """ + if not file_path: + raise ValueError("File path cannot be empty for git add.") + + # Normalizza il percorso per sicurezza, anche se git add è flessibile + # git_style_path = file_path.replace(os.path.sep, '/') # Non strettamente necessario per add + self.logger.info(f"Adding file to staging area: '{file_path}' in '{working_directory}'") + + # Costruisci comando semplice + command = ["git", "add", "--", file_path] # "--" per sicurezza con nomi file strani + + try: + # Esegui il comando. L'output non è molto informativo di solito. + # Logghiamolo a DEBUG. check=True solleva eccezione su errore. + self.log_and_execute(command, working_directory, check=True, log_output_level=logging.DEBUG) + self.logger.info(f"File '{file_path}' added to staging area successfully.") + return True + except GitCommandError as add_error: + # Logga e rilancia l'errore specifico di git add + self.logger.error(f"Failed to add file '{file_path}': {add_error}", exc_info=True) + # Rendi l'errore un po' più specifico se possibile + stderr = add_error.stderr.lower() if add_error.stderr else "" + if "did not match any files" in stderr: + raise GitCommandError(f"File not found or invalid: '{file_path}'", command=command, stderr=add_error.stderr) from add_error + else: + raise add_error # Rilancia errore generico git + except Exception as e: + # Errore imprevisto + self.logger.exception(f"Unexpected error adding file '{file_path}': {e}") + raise GitCommandError(f"Unexpected add error: {e}", command=command) from e \ No newline at end of file diff --git a/gui.py b/gui.py index 1ed73c6..ef23310 100644 --- a/gui.py +++ b/gui.py @@ -381,8 +381,11 @@ class MainFrame(ttk.Frame): refresh_branches_cb, checkout_branch_cb, create_branch_cb, - refresh_changed_files_cb, # <-- NUOVO PARAMETRO - open_diff_viewer_cb, + refresh_changed_files_cb, + open_diff_viewer_cb, + add_selected_file_cb + + ): """Initializes the MainFrame with tabs.""" super().__init__(master) @@ -408,6 +411,7 @@ class MainFrame(ttk.Frame): self.checkout_branch_callback = checkout_branch_cb self.create_branch_callback = create_branch_cb self.open_diff_viewer_callback = open_diff_viewer_cb + self.add_selected_file_callback = add_selected_file_cb # Store instances and initial data @@ -847,6 +851,9 @@ class MainFrame(ttk.Frame): self.changed_files_listbox.grid(row=0, column=0, sticky="nsew") # Associa doppio click all'apertura del diff viewer (verrà collegato in GitUtility) self.changed_files_listbox.bind("", self._on_changed_file_double_click) + # Usa per Windows/Linux, o per macOS? + # è spesso il più compatibile per il tasto destro standard. + self.changed_files_listbox.bind("", self._show_changed_files_context_menu) scrollbar_list = ttk.Scrollbar( list_sub_frame, orient=tk.VERTICAL, command=self.changed_files_listbox.yview @@ -865,10 +872,8 @@ class MainFrame(ttk.Frame): ) self.refresh_changes_button.grid(row=1, column=0, sticky="w", padx=(0, 5), pady=(5, 0)) self.create_tooltip(self.refresh_changes_button, "Refresh the list of changed files.") - # --- FINE MODIFICA --- - - - # --- Pulsante Commit Manuale --- (Spostato sotto) + self.changed_files_context_menu = tk.Menu(self.changed_files_listbox, tearoff=0) + self.commit_button = ttk.Button( frame, # Ora nel frame principale text="Commit All Changes Manually", @@ -1366,3 +1371,52 @@ class MainFrame(ttk.Frame): # Chiama il metodo del controller (verrà impostato in __init__ di MainFrame) if hasattr(self, 'open_diff_viewer_callback') and callable(self.open_diff_viewer_callback): self.open_diff_viewer_callback(file_status_line) + + def _show_changed_files_context_menu(self, event): + """Shows the context menu for the changed files listbox.""" + # Seleziona l'elemento sotto il cursore + try: + # Identifica l'indice dell'elemento su cui si è cliccato + index = self.changed_files_listbox.nearest(event.y) + # Se l'indice è valido, selezionalo visivamente (opzionale ma carino) + # Cancella selezioni precedenti e seleziona quella nuova + self.changed_files_listbox.selection_clear(0, tk.END) + self.changed_files_listbox.selection_set(index) + self.changed_files_listbox.activate(index) # Evidenzia riga + except tk.TclError: + # Errore se si clicca su area vuota, non fare nulla + return + + # Ottieni la riga di stato selezionata + selection_indices = self.changed_files_listbox.curselection() + if not selection_indices: return # Nessuna selezione valida + selected_line = self.changed_files_listbox.get(selection_indices[0]) + + # Pulisci il menu precedente + self.changed_files_context_menu.delete(0, tk.END) + + # Controlla se il file è Untracked ('??') + is_untracked = selected_line.strip().startswith('??') + + # Aggiungi l'opzione "Add" solo se è untracked e abbiamo la callback + if is_untracked and hasattr(self, 'add_selected_file_callback') and callable(self.add_selected_file_callback): + self.changed_files_context_menu.add_command( + label="Add to Staging Area", + # Chiama la callback del controller passando la linea selezionata + command=lambda line=selected_line: self.add_selected_file_callback(line) + ) + else: + # Opzionalmente, aggiungi una voce disabilitata o nessun'azione + self.changed_files_context_menu.add_command(label="Add to Staging Area", state=tk.DISABLED) + pass # Nessuna azione applicabile per ora + + # Aggiungi altre eventuali azioni qui (es. "View Changes", "Discard Changes" - future) + # self.changed_files_context_menu.add_separator() + # self.changed_files_context_menu.add_command(label="View Changes (Diff)", ...) + + # Mostra il menu alla posizione del mouse + try: + self.changed_files_context_menu.tk_popup(event.x_root, event.y_root) + finally: + # Assicura che il grab venga rilasciato + self.changed_files_context_menu.grab_release()