From 96a916ca09e8589d19d8154c4711bda0152cdb23 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Fri, 18 Apr 2025 13:29:55 +0200 Subject: [PATCH] add autocommit when create tag --- GitUtility.py | 87 ++++++++++++++++++++++++++++++++++++++++++----- action_handler.py | 87 ++++++++++++++++++++++++++++++++--------------- gui.py | 32 +++++++++++------ 3 files changed, 161 insertions(+), 45 deletions(-) diff --git a/GitUtility.py b/GitUtility.py index 07d584a..66bb284 100644 --- a/GitUtility.py +++ b/GitUtility.py @@ -6,7 +6,7 @@ import datetime import tkinter as tk from tkinter import messagebox, filedialog # Assicurati che filedialog sia importato import logging - +import re # Rimosso zipfile, threading, queue # Import application modules @@ -987,27 +987,98 @@ class GitSvnSyncApp: # Aggiorna GUI in ogni caso (anche con lista vuota o errore) if hasattr(self.main_frame, "update_tag_list"): self.main_frame.update_tag_list(tags_data) + + def _generate_next_tag_suggestion(self, svn_path): + """ + Generates a suggested tag name based on the latest tag matching v.X.X.X.X pattern. + + Args: + svn_path (str): Path to the repository. + + Returns: + str: The suggested tag name (e.g., "v.0.0.0.1" or incremented version). + """ + self.logger.debug("Generating next tag suggestion...") + default_suggestion = "v.0.0.0.1" + latest_valid_tag = None + tag_pattern = re.compile(r"^v\.(\d{1,2})\.(\d{1,2})\.(\d{1,2})\.(\d{1,2})$") + + try: + # Ottieni i tag ordinati, dal più recente + # list_tags restituisce [(name, subject), ...] + tags_data = self.git_commands.list_tags(svn_path) + if not tags_data: + self.logger.debug("No existing tags found. Suggesting default.") + return default_suggestion + + # Cerca il primo tag che corrisponde al pattern + for tag_name, _ in tags_data: + match = tag_pattern.match(tag_name) + if match: + latest_valid_tag = tag_name + self.logger.debug(f"Found latest tag matching pattern: {latest_valid_tag}") + break # Trovato il più recente, esci + + if not latest_valid_tag: + self.logger.debug("No tags matched the pattern v.X.X.X.X. Suggesting default.") + return default_suggestion + + # Estrai e incrementa i numeri + match = tag_pattern.match(latest_valid_tag) # Riesegui match per sicurezza + if not match: # Non dovrebbe succedere, ma controllo difensivo + self.logger.error(f"Internal error: Could not re-match tag {latest_valid_tag}") + return default_suggestion + + v1, v2, v3, v4 = map(int, match.groups()) + + # Incrementa gestendo i riporti da 99 a 0 + v4 += 1 + if v4 > 99: + v4 = 0 + v3 += 1 + if v3 > 99: + v3 = 0 + v2 += 1 + if v2 > 99: + v2 = 0 + v1 += 1 + # Non mettiamo limiti a v1 per ora (può diventare > 99) + + next_tag = f"v.{v1}.{v2}.{v3}.{v4}" + self.logger.debug(f"Generated suggestion: {next_tag}") + return next_tag + + except Exception as e: + self.logger.error(f"Error generating tag suggestion: {e}", exc_info=True) + return default_suggestion # Ritorna il default in caso di errore def create_tag(self): - """Handles 'Create Tag' (Synchronous after dialog).""" + """Handles 'Create Tag' (Synchronous after dialog), suggesting the next tag name.""" self.logger.info("--- Action Triggered: Create Tag ---") svn_path = self._get_and_validate_svn_path("Create Tag") if not svn_path: return + + # --- MODIFICA: Genera suggerimento PRIMA di aprire il dialogo --- + suggested_tag = self._generate_next_tag_suggestion(svn_path) + # --- FINE MODIFICA --- + # Ottieni input utente (Dialog sincrono) - dialog = CreateTagDialog(self.master) + # --- MODIFICA: Passa il suggerimento al Dialog --- + dialog = CreateTagDialog(self.master, suggested_tag_name=suggested_tag) + # --- FINE MODIFICA --- tag_info = dialog.result + if tag_info: tag_name, tag_message = tag_info - if ( - not self.save_profile_settings() - ): # Salva messaggio commit per pre-commit + # (Logica commit pre-tag e chiamata ad action_handler invariata) + if not self.save_profile_settings(): if not self.main_frame.ask_yes_no( "Save Warning", "Could not save profile settings.\nContinue anyway?" ): return commit_message = self.main_frame.get_commit_message() - # Esecuzione sincrona + try: success = self.action_handler.execute_create_tag( svn_path, commit_message, tag_name, tag_message @@ -1016,7 +1087,7 @@ class GitSvnSyncApp: self.main_frame.show_info("Success", f"Tag '{tag_name}' created.") self.refresh_tag_list() self.refresh_commit_history() - self.refresh_branch_list() # Aggiorna UI + self.refresh_branch_list() except (GitCommandError, ValueError) as e: self.logger.error(f"Failed create tag '{tag_name}': {e}", exc_info=True) self.main_frame.show_error("Tag Error", f"Failed:\n{e}") diff --git a/action_handler.py b/action_handler.py index 35430a0..d64a82d 100644 --- a/action_handler.py +++ b/action_handler.py @@ -390,41 +390,74 @@ class ActionHandler: self.logger.exception(f"Unexpected manual commit error: {e}") raise Exception("Unexpected manual commit error") from e - def execute_create_tag(self, svn_path, commit_message, tag_name, tag_message): - """Executes tag creation, including pre-commit using commit_message if needed.""" - # (Keep original or improved version - no backup involved) + def execute_create_tag(self, svn_path, commit_message_ignored, tag_name, tag_message): + """ + Executes tag creation. Always attempts to commit staged changes using + the tag message before creating the annotated tag. + + Args: + svn_path (str): Path to the repository. + commit_message_ignored (str): Commit message from GUI (IGNORED for this function). + tag_name (str): The name for the new tag. + tag_message (str): The annotation message for the tag (also used for the commit). + + Returns: + bool: True if the tag was created successfully. + + Raises: + ValueError: If tag_name or tag_message are empty/invalid (checked by GitCommands). + GitCommandError/Exception: If commit or tag creation fails. + """ + # 1. Validazione Input Essenziali + # La validità del formato tag_name sarà controllata da git_commands.create_tag + # Ma controlliamo che non siano vuoti qui, specialmente tag_message che serve per il commit. + if not tag_name or tag_name.isspace(): + # Questo check è leggermente ridondante perché git lo bloccherebbe, ma è buona pratica validare prima. + raise ValueError("Tag name cannot be empty.") if not tag_message or tag_message.isspace(): - raise ValueError("Tag annotation message cannot be empty.") + # Fondamentale perché ora serve anche per il commit. + raise ValueError("Tag message cannot be empty (used for commit and tag).") + self.logger.info(f"Executing create tag '{tag_name}' for: {svn_path}") - try: # Pre-commit - has_changes = self.git_commands.git_status_has_changes(svn_path) - if has_changes: - self.logger.info("Uncommitted changes detected before tagging.") - if not commit_message or commit_message.isspace(): - raise ValueError( - "Changes exist. Commit message required before tagging." - ) - self.logger.info(f"Performing pre-tag commit: '{commit_message}'") - self.git_commands.git_commit(svn_path, commit_message) + self.logger.info("Attempting pre-tag commit using tag message...") + + try: + # 2. Esegui Commit (con il messaggio del tag) + # La funzione git_commit in GitCommands dovrebbe già gestire: + # - Staging di tutte le modifiche ('git add .') + # - Esecuzione di 'git commit -m "tag_message"' + # - Gestione del caso "nothing to commit" (restituendo False) + # - Sollevare GitCommandError in caso di fallimento reale del commit + commit_made = self.git_commands.git_commit(svn_path, tag_message) + + if commit_made: + self.logger.info(f"Pre-tag commit successful with message: '{tag_message}'") else: - self.logger.info("No uncommitted changes detected.") - except (GitCommandError, ValueError) as e: - self.logger.error(f"Pre-tag commit error: {e}", exc_info=True) - raise e - except Exception as e: - self.logger.exception(f"Unexpected pre-tag commit error: {e}") - raise Exception("Unexpected pre-tag commit error") from e - try: # Create Tag - self.logger.info(f"Proceeding to create tag '{tag_name}'...") + self.logger.info("No changes detected; no pre-tag commit was necessary.") + + # 3. Crea il Tag Annotato (usando lo stesso messaggio) + # Procedi indipendentemente dal fatto che il commit abbia effettivamente + # committato qualcosa, purché non abbia fallito. + self.logger.info(f"Creating annotated tag '{tag_name}'...") + # La funzione create_tag in GitCommands dovrebbe gestire: + # - Validazione del formato del tag_name (regex) + # - Esecuzione di 'git tag -a -m "tag_message"' + # - Gestione dell'errore "tag already exists" self.git_commands.create_tag(svn_path, tag_name, tag_message) self.logger.info(f"Tag '{tag_name}' created successfully.") - return True + return True # L'operazione complessiva è riuscita + except (GitCommandError, ValueError) as e: - self.logger.error(f"Failed create tag '{tag_name}': {e}", exc_info=True) + # Cattura errori specifici da commit o tag + # ValueError potrebbe venire dalla validazione iniziale o da git_commit + self.logger.error(f"Failed to commit or create tag '{tag_name}': {e}", exc_info=True) + # Rilancia l'errore specifico per essere gestito dalla GUI raise e except Exception as e: - self.logger.exception(f"Unexpected tag error: {e}") - raise Exception("Unexpected tag creation error") from e + # Cattura qualsiasi altro errore imprevisto + self.logger.exception(f"Unexpected error during commit/tag process for '{tag_name}': {e}") + # Rilancia un'eccezione generica ma informativa + raise Exception(f"Unexpected error creating tag '{tag_name}'") from e def execute_checkout_tag(self, svn_path, tag_name): """Executes checkout for the specified tag after checking changes.""" diff --git a/gui.py b/gui.py index a781ca6..27e3fba 100644 --- a/gui.py +++ b/gui.py @@ -249,10 +249,14 @@ class GitignoreEditorWindow(tk.Toplevel): # --- Create Tag Dialog --- # (No changes needed here) class CreateTagDialog(simpledialog.Dialog): - def __init__(self, parent, title="Create New Tag"): + def __init__(self, parent, title="Create New Tag", suggested_tag_name=""): self.tag_name_var = tk.StringVar() self.tag_message_var = tk.StringVar() self.result = None + # --- MODIFICA: Memorizza il suggerimento --- + self.suggested_tag_name = suggested_tag_name + # --- FINE MODIFICA --- + # Chiamare super() alla fine o dopo aver inizializzato le variabili usate in body/validate super().__init__(parent, title=title) def body(self, master): @@ -264,6 +268,11 @@ class CreateTagDialog(simpledialog.Dialog): ) self.name_entry = ttk.Entry(frame, textvariable=self.tag_name_var, width=40) self.name_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew") + # --- MODIFICA: Imposta valore iniziale --- + if self.suggested_tag_name: + self.tag_name_var.set(self.suggested_tag_name) + # --- FINE MODIFICA --- + ttk.Label(frame, text="Tag Message:").grid( row=1, column=0, padx=5, pady=5, sticky="w" ) @@ -271,24 +280,27 @@ class CreateTagDialog(simpledialog.Dialog): frame, textvariable=self.tag_message_var, width=40 ) self.message_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew") - return self.name_entry + # Ritorna il widget che deve avere il focus iniziale + return self.name_entry # O self.message_entry se si preferisce + # La validazione del nome tag ora avviene in GitCommands.create_tag + # Manteniamo solo i controlli per non vuoto. def validate(self): name = self.tag_name_var.get().strip() msg = self.tag_message_var.get().strip() if not name: - messagebox.showwarning("Input Error", "Tag name empty.", parent=self) + messagebox.showwarning("Input Error", "Tag name cannot be empty.", parent=self) return 0 if not msg: - messagebox.showwarning("Input Error", "Tag message empty.", parent=self) - return 0 - pattern = r"^(?![./]|.*([./]{2,}|[.]$|\.lock$))[^ \t\n\r\f\v~^:?*[\\]+(?