add add file into commit function

This commit is contained in:
VALLONGOL 2025-04-18 16:03:00 +02:00
parent b5dbd75999
commit 688e779494
3 changed files with 171 additions and 7 deletions

View File

@ -114,6 +114,7 @@ class GitSvnSyncApp:
# Passa istanza config manager e profili iniziali # Passa istanza config manager e profili iniziali
config_manager_instance=self.config_manager, config_manager_instance=self.config_manager,
profile_sections_list=self.config_manager.get_profile_sections(), 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) --- # --- Collega widget GUI specifici (che non passano dal costruttore di MainFrame) ---
@ -1825,6 +1826,70 @@ class GitSvnSyncApp:
try: try:
self.master.destroy() self.master.destroy()
except: pass # Ignora errori durante la distruzione 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 --- # --- Application Entry Point ---

View File

@ -1177,4 +1177,49 @@ class GitCommands:
return None return None
except Exception as e: except Exception as e:
self.logger.exception(f"Unexpected error in get_file_content_from_ref for '{ref_path_arg}': {e}") self.logger.exception(f"Unexpected error in get_file_content_from_ref for '{ref_path_arg}': {e}")
return None 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

66
gui.py
View File

@ -381,8 +381,11 @@ class MainFrame(ttk.Frame):
refresh_branches_cb, refresh_branches_cb,
checkout_branch_cb, checkout_branch_cb,
create_branch_cb, create_branch_cb,
refresh_changed_files_cb, # <-- NUOVO PARAMETRO refresh_changed_files_cb,
open_diff_viewer_cb, open_diff_viewer_cb,
add_selected_file_cb
): ):
"""Initializes the MainFrame with tabs.""" """Initializes the MainFrame with tabs."""
super().__init__(master) super().__init__(master)
@ -408,6 +411,7 @@ class MainFrame(ttk.Frame):
self.checkout_branch_callback = checkout_branch_cb self.checkout_branch_callback = checkout_branch_cb
self.create_branch_callback = create_branch_cb self.create_branch_callback = create_branch_cb
self.open_diff_viewer_callback = open_diff_viewer_cb self.open_diff_viewer_callback = open_diff_viewer_cb
self.add_selected_file_callback = add_selected_file_cb
# Store instances and initial data # Store instances and initial data
@ -847,6 +851,9 @@ class MainFrame(ttk.Frame):
self.changed_files_listbox.grid(row=0, column=0, sticky="nsew") self.changed_files_listbox.grid(row=0, column=0, sticky="nsew")
# Associa doppio click all'apertura del diff viewer (verrà collegato in GitUtility) # Associa doppio click all'apertura del diff viewer (verrà collegato in GitUtility)
self.changed_files_listbox.bind("<Double-Button-1>", self._on_changed_file_double_click) self.changed_files_listbox.bind("<Double-Button-1>", self._on_changed_file_double_click)
# Usa <Button-3> per Windows/Linux, <Button-2> o <Control-Button-1> per macOS?
# <Button-3> è spesso il più compatibile per il tasto destro standard.
self.changed_files_listbox.bind("<Button-3>", self._show_changed_files_context_menu)
scrollbar_list = ttk.Scrollbar( scrollbar_list = ttk.Scrollbar(
list_sub_frame, orient=tk.VERTICAL, command=self.changed_files_listbox.yview 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.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.") self.create_tooltip(self.refresh_changes_button, "Refresh the list of changed files.")
# --- FINE MODIFICA --- self.changed_files_context_menu = tk.Menu(self.changed_files_listbox, tearoff=0)
# --- Pulsante Commit Manuale --- (Spostato sotto)
self.commit_button = ttk.Button( self.commit_button = ttk.Button(
frame, # Ora nel frame principale frame, # Ora nel frame principale
text="Commit All Changes Manually", text="Commit All Changes Manually",
@ -1366,3 +1371,52 @@ class MainFrame(ttk.Frame):
# Chiama il metodo del controller (verrà impostato in __init__ di MainFrame) # 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): if hasattr(self, 'open_diff_viewer_callback') and callable(self.open_diff_viewer_callback):
self.open_diff_viewer_callback(file_status_line) 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()