add ignore update

This commit is contained in:
VALLONGOL 2025-04-18 12:59:43 +02:00
parent 418039e65a
commit 4b74cfbe27
5 changed files with 415 additions and 92 deletions

View File

@ -187,6 +187,40 @@ class GitSvnSyncApp:
self._clear_and_disable_fields() self._clear_and_disable_fields()
#self.logger.info("Application started and initial state set.") #self.logger.info("Application started and initial state set.")
def _handle_gitignore_save(self):
"""
Callback function triggered after .gitignore is saved successfully.
Initiates the process to untrack newly ignored files.
"""
self.logger.info("Callback triggered: .gitignore saved. Checking for files to untrack automatically...")
# Need the svn_path again here
svn_path = self._get_and_validate_svn_path("Automatic Untracking")
if not svn_path:
self.logger.error("Cannot perform automatic untracking: Invalid SVN path after save.")
# Show error? This shouldn't happen if editor opened correctly.
return
try:
# Call the ActionHandler method
untracked = self.action_handler.execute_untrack_files_from_gitignore(svn_path)
if untracked:
self.main_frame.show_info(
"Automatic Untrack",
"Successfully untracked newly ignored files and created commit.\nCheck log for details."
)
# UI might need refreshing after commit - handled after window closes in open_gitignore_editor
else:
# No files needed untracking, or commit failed (ActionHandler logs warnings)
self.logger.info("Automatic untracking check complete. No files untracked or no commit needed.")
except (GitCommandError, ValueError) as e:
self.logger.error(f"Automatic untracking failed: {e}", exc_info=True)
self.main_frame.show_error("Untrack Error", f"Failed automatic untracking:\n{e}")
except Exception as e:
self.logger.exception(f"Unexpected error during automatic untracking: {e}")
self.main_frame.show_error("Untrack Error", f"Unexpected error during untracking:\n{e}")
def on_closing(self): def on_closing(self):
"""Handles the window close event.""" """Handles the window close event."""
# (Come definito precedentemente) # (Come definito precedentemente)
@ -628,22 +662,38 @@ class GitSvnSyncApp:
return abs_path return abs_path
def open_gitignore_editor(self): def open_gitignore_editor(self):
"""Opens the modal editor window for the .gitignore file.""" """Opens the modal editor window for the .gitignore file and
# (Mantenere versione precedente robusta) triggers automatic untracking check on successful save."""
self.logger.info("--- Action: Edit .gitignore ---") self.logger.info("--- Action: Edit .gitignore ---")
svn_path = self._get_and_validate_svn_path("Edit .gitignore") svn_path = self._get_and_validate_svn_path("Edit .gitignore")
if not svn_path: if not svn_path:
return return
gitignore_path = os.path.join(svn_path, ".gitignore") gitignore_path = os.path.join(svn_path, ".gitignore")
self.logger.debug(f"Target: {gitignore_path}") self.logger.debug(f"Target .gitignore path: {gitignore_path}")
try: try:
self.logger.debug("Opening GitignoreEditorWindow...")
# --- MODIFICA: Passa il metodo _handle_gitignore_save come callback ---
GitignoreEditorWindow( GitignoreEditorWindow(
self.master, gitignore_path, self.logger self.master,
) # Blocca fino alla chiusura gitignore_path,
self.logger,
on_save_success_callback=self._handle_gitignore_save # Passa il riferimento al metodo
)
# --- FINE MODIFICA ---
# Execution waits here until the Toplevel window is closed
self.logger.debug("Gitignore editor window closed.") self.logger.debug("Gitignore editor window closed.")
# Note: The untracking logic is now triggered *by* the callback *before* the window closes.
# We might still want to refresh UI elements *after* it closes.
# Refresh status indicator and potentially history/branches after editor closes
self.update_svn_status_indicator(svn_path)
self.refresh_commit_history()
self.refresh_branch_list() # Commit might affect branch display
except Exception as e: except Exception as e:
self.logger.exception(f"Editor error: {e}") self.logger.exception(f"Error during .gitignore editing or post-save action: {e}")
self.main_frame.show_error("Error", f"Editor error:\n{e}") self.main_frame.show_error("Editor Error", f"An error occurred:\n{e}")
# --- Threading Helpers (REMOVED) --- # --- Threading Helpers (REMOVED) ---
# Rimuovi _run_action_with_wait # Rimuovi _run_action_with_wait

View File

@ -545,3 +545,95 @@ class ActionHandler:
except Exception as e: except Exception as e:
self.logger.exception(f"Failed to delete tag '{tag_name}': {e}") self.logger.exception(f"Failed to delete tag '{tag_name}': {e}")
raise Exception("Unexpected tag deletion error") from e raise Exception("Unexpected tag deletion error") from e
def execute_untrack_files_from_gitignore(self, svn_path):
"""
Checks tracked files against current .gitignore rules, untracks newly
ignored files, and creates a commit summarizing which rules caused
the untracking.
Args:
svn_path (str): Path to the repository.
Returns:
bool: True if files were untracked and committed, False otherwise.
Raises:
GitCommandError/ValueError/Exception: If any Git operation fails.
"""
self.logger.info(f"Checking for tracked files to untrack based on .gitignore in '{svn_path}'...")
# --- MODIFICA: Usa un dizionario per raggruppare file per regola ---
# Key: matching pattern (rule), Value: list of file paths matched by that rule
rules_to_files_map = {}
# Lista piatta per il comando git rm
all_files_to_untrack = []
# --- FINE MODIFICA ---
try:
# 1. Get all currently tracked files
tracked_files = self.git_commands.get_tracked_files(svn_path)
# 2. Check each tracked file for matching ignore rules
self.logger.debug(f"Checking {len(tracked_files)} tracked files against ignore rules...")
for file_path in tracked_files:
# Skip checks for the .gitignore file itself
norm_file_path = os.path.normpath(file_path)
if norm_file_path == '.gitignore':
continue
# --- MODIFICA: Ottieni la regola corrispondente ---
# Check if the file *would* be ignored and get the rule
matching_rule = self.git_commands.get_matching_gitignore_rule(svn_path, file_path)
# --- FINE MODIFICA ---
if matching_rule is not None:
self.logger.info(f"Tracked file '{file_path}' now matches ignore rule: '{matching_rule}'")
# --- MODIFICA: Popola il dizionario e la lista ---
if matching_rule not in rules_to_files_map:
rules_to_files_map[matching_rule] = []
rules_to_files_map[matching_rule].append(file_path)
all_files_to_untrack.append(file_path)
# --- FINE MODIFICA ---
# 3. If files need untracking, perform git rm --cached and commit
# --- MODIFICA: Controlla la lista piatta ---
if all_files_to_untrack:
# --- FINE MODIFICA ---
self.logger.info(f"Found {len(all_files_to_untrack)} tracked files that are now ignored.")
# 3a. Untrack the files (remove from index) using the flat list
self.git_commands.remove_from_tracking(svn_path, all_files_to_untrack)
# --- MODIFICA: Crea messaggio di commit riassuntivo basato sulle regole ---
# 3b. Create an automatic commit message summarizing by rule
commit_message = "Chore: Stop tracking files based on .gitignore update.\n\nSummary:\n"
# Ordina le regole per leggibilità (opzionale)
sorted_rules = sorted(rules_to_files_map.keys())
for rule in sorted_rules:
file_count = len(rules_to_files_map[rule])
commit_message += f"- Rule \"{rule}\" untracked {file_count} file(s).\n"
# --- FINE MODIFICA ---
self.logger.info("Creating automatic commit for untracking changes.")
self.logger.debug(f"Commit message:\n{commit_message}") # Log message multi-line
# 3c. Perform the commit
commit_made = self.git_commands.git_commit(svn_path, commit_message)
if commit_made:
self.logger.info("Automatic commit successful.")
return True
else:
self.logger.warning("Untracking performed, but automatic commit reported no changes.")
return False
else:
self.logger.info("No tracked files found matching current .gitignore rules. No action needed.")
return False
except (GitCommandError, ValueError) as e:
self.logger.error(f"Error during automatic untracking process: {e}", exc_info=True)
raise
except Exception as e:
self.logger.exception(f"Unexpected error during automatic untracking: {e}")
raise Exception("Unexpected untracking error") from e

View File

@ -70,97 +70,94 @@ class GitCommands:
raise ValueError("A valid logging.Logger instance is required.") raise ValueError("A valid logging.Logger instance is required.")
self.logger = logger self.logger = logger
def log_and_execute(self, command, working_directory, check=True): def log_and_execute(self, command, working_directory, check=True, log_output_level=logging.INFO):
""" """
Executes a shell command in a specific directory, logs details, Executes a shell command, logs details, handles errors, and controls output logging level.
and handles potential errors.
Args: Args:
command (list): Command and arguments as a list of strings. command (list): Command and arguments as a list of strings.
working_directory (str): The directory to execute the command in. working_directory (str): The directory to execute the command in.
check (bool, optional): If True, raises CalledProcessError (wrapped check (bool, optional): If True, raises GitCommandError on non-zero exit. Defaults to True.
in GitCommandError) if the command returns log_output_level (int, optional): Logging level for stdout/stderr on success.
a non-zero exit code. Defaults to True. Defaults to logging.INFO. Use logging.DEBUG
to hide noisy command output from INFO logs.
Returns: Returns:
subprocess.CompletedProcess: Result object containing stdout, stderr, etc. subprocess.CompletedProcess: Result object.
Raises: Raises:
GitCommandError: For Git-specific errors or execution issues. GitCommandError, ValueError, FileNotFoundError, PermissionError.
ValueError: If working_directory is invalid.
FileNotFoundError: If the command (e.g., 'git') is not found.
PermissionError: If execution permission is denied.
""" """
# Ensure all parts of command are strings for reliable execution and logging # --- FINE MODIFICA ---
safe_command = [str(part) for part in command] safe_command = [str(part) for part in command]
command_str = " ".join(safe_command) command_str = " ".join(safe_command)
log_message = f"Executing: {command_str}" # Log command execution always at DEBUG level
# Log command execution at debug level for less verbosity in standard logs self.logger.debug(f"Executing in '{working_directory}': {command_str}")
self.logger.debug(log_message)
# --- Validate Working Directory --- # --- Validate Working Directory ---
# (Logica validazione working_directory invariata)
if not working_directory: if not working_directory:
msg = "Working directory cannot be None or empty." msg = "Working directory cannot be None or empty."
self.logger.error(msg) self.logger.error(msg)
raise ValueError(msg) raise ValueError(msg)
# Use '.' as a special case for current working directory if needed by caller
if working_directory == ".":
cwd = os.getcwd()
else:
abs_path = os.path.abspath(working_directory) abs_path = os.path.abspath(working_directory)
if not os.path.isdir(abs_path): if not os.path.isdir(abs_path):
msg = f"Working directory does not exist or is not a directory: {abs_path}" msg = f"Working directory does not exist or is not a directory: {abs_path}"
self.logger.error(msg) self.logger.error(msg)
# Raise GitCommandError to signal issue related to command execution context
raise GitCommandError(msg, command=safe_command) raise GitCommandError(msg, command=safe_command)
cwd = abs_path cwd = abs_path
self.logger.debug(f"Effective Working Directory: {cwd}") # self.logger.debug(f"Effective Working Directory: {cwd}") # Already logged above
# --- Execute Command --- # --- Execute Command ---
try: try:
# Platform-specific setup to hide console window on Windows # (Logica startupinfo/creationflags invariata)
startupinfo = None startupinfo = None
# creationflags are used to control process creation (e.g., hide window)
creationflags = 0 creationflags = 0
if os.name == "nt": # Windows specific settings if os.name == "nt":
startupinfo = subprocess.STARTUPINFO() startupinfo = subprocess.STARTUPINFO()
# Prevent console window from showing
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE startupinfo.wShowWindow = subprocess.SW_HIDE
# Alternative flag to completely detach from console (might affect stdio)
# creationflags = subprocess.CREATE_NO_WINDOW
# Run the command using subprocess.run
result = subprocess.run( result = subprocess.run(
safe_command, # Use the command list with string parts safe_command,
cwd=cwd, # Set working directory cwd=cwd,
capture_output=True, # Capture stdout and stderr capture_output=True,
text=True, # Decode output as text (UTF-8 default) text=True,
check=check, # Raise exception on non-zero exit code if True check=check,
encoding="utf-8", # Specify encoding explicitly encoding="utf-8",
errors="replace", # Handle potential decoding errors gracefully errors="replace",
startupinfo=startupinfo, # Windows: hide console window startupinfo=startupinfo,
creationflags=creationflags, # Windows: additional process flags creationflags=creationflags,
) )
# Log command output for debugging and info # Log command output based on specified level for success
# Error output is always logged at ERROR level below
if check or result.returncode == 0:
# --- MODIFICA: Usa log_output_level per l'output di successo ---
# Only log stdout/stderr if the requested level is met by the logger config
if self.logger.isEnabledFor(log_output_level):
stdout_log = result.stdout.strip() if result.stdout else "<no stdout>" stdout_log = result.stdout.strip() if result.stdout else "<no stdout>"
stderr_log = result.stderr.strip() if result.stderr else "<no stderr>" stderr_log = result.stderr.strip() if result.stderr else "<no stderr>"
# Use the passed level for logging the output
# Log success differently based on whether check=True was used self.logger.log(
# If check=True, CalledProcessError would have been raised on failure log_output_level,
# If check=False, we log success only if return code is 0
if check or result.returncode == 0:
self.logger.info(
f"Command successful. Output:\n" f"Command successful. Output:\n"
f"--- stdout ---\n{stdout_log}\n" f"--- stdout ---\n{stdout_log}\n"
f"--- stderr ---\n{stderr_log}\n---" f"--- stderr ---\n{stderr_log}\n---"
) )
# Note: If check=False and returncode != 0, errors are typically else:
# handled by the calling method that analyzes the result. # Log minimal success message if output level is suppressed
self.logger.debug(f"Command successful (output logging suppressed by level).")
# --- FINE MODIFICA ---
return result return result
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
# This block runs only if check=True and the command failed # (Gestione CalledProcessError invariata - logga sempre a ERROR)
stderr_err = e.stderr.strip() if e.stderr else "<no stderr>" stderr_err = e.stderr.strip() if e.stderr else "<no stderr>"
stdout_err = e.stdout.strip() if e.stdout else "<no stdout>" stdout_err = e.stdout.strip() if e.stdout else "<no stdout>"
error_log_msg = ( error_log_msg = (
@ -170,32 +167,26 @@ class GitCommands:
f"--- stdout ---\n{stdout_err}\n---" f"--- stdout ---\n{stdout_err}\n---"
) )
self.logger.error(error_log_msg) self.logger.error(error_log_msg)
# Wrap the original exception in our custom GitCommandError
raise GitCommandError( raise GitCommandError(
f"Git command failed in '{cwd}'.", command=safe_command, stderr=e.stderr f"Git command failed in '{cwd}'.", command=safe_command, stderr=e.stderr
) from e # Preserve original exception context ) from e
except FileNotFoundError as e: except FileNotFoundError as e:
# Handle error if 'git' command (or another part) is not found # (Gestione FileNotFoundError invariata)
error_msg = ( error_msg = f"Command not found: '{safe_command[0]}'. Is Git installed and in system PATH? (Working directory: '{cwd}')"
f"Command not found: '{safe_command[0]}'. Is Git installed "
f"and in system PATH? (Working directory: '{cwd}')"
)
self.logger.error(error_msg) self.logger.error(error_msg)
self.logger.debug( self.logger.debug(f"FileNotFoundError details: {e}")
f"FileNotFoundError details: {e}"
) # Log full details only at debug level
raise GitCommandError(error_msg, command=safe_command) from e raise GitCommandError(error_msg, command=safe_command) from e
except PermissionError as e: except PermissionError as e:
# Handle errors due to lack of execution permissions # (Gestione PermissionError invariata)
error_msg = f"Permission denied executing command in '{cwd}'." error_msg = f"Permission denied executing command in '{cwd}'."
self.logger.error(error_msg) self.logger.error(error_msg)
self.logger.debug(f"PermissionError details: {e}") self.logger.debug(f"PermissionError details: {e}")
raise GitCommandError(error_msg, command=safe_command, stderr=str(e)) from e raise GitCommandError(error_msg, command=safe_command, stderr=str(e)) from e
except Exception as e: except Exception as e:
# Catch any other unexpected errors during subprocess execution # (Gestione Exception generica invariata)
self.logger.exception(f"Unexpected error executing command in '{cwd}': {e}") self.logger.exception(f"Unexpected error executing command in '{cwd}': {e}")
raise GitCommandError( raise GitCommandError(
f"Unexpected command execution error: {e}", command=safe_command f"Unexpected command execution error: {e}", command=safe_command
@ -908,3 +899,173 @@ class GitCommands:
# Catch any other unexpected errors # Catch any other unexpected errors
self.logger.exception(f"Unexpected error cloning from bundle: {e}") self.logger.exception(f"Unexpected error cloning from bundle: {e}")
raise GitCommandError(f"Unexpected clone error: {e}", command=command) from e raise GitCommandError(f"Unexpected clone error: {e}", command=command) from e
def get_tracked_files(self, working_directory):
"""
Retrieves a list of all files currently tracked by Git in the repository.
Logs command output only at DEBUG level.
"""
self.logger.debug(f"Getting tracked files for '{working_directory}'")
command = ["git", "ls-files", "-z"]
try:
# --- MODIFICA: Passa log_output_level=logging.DEBUG ---
result = self.log_and_execute(
command,
working_directory,
check=True,
log_output_level=logging.DEBUG # Log stdout/stderr only if logger level is DEBUG
)
# --- FINE MODIFICA ---
tracked_files = [
f for f in result.stdout.split('\0') if f
]
# Log the *count* at INFO level, the list itself might only appear in DEBUG log now.
self.logger.info(f"Found {len(tracked_files)} tracked files.")
self.logger.debug(f"Tracked file list: {tracked_files}") # Log list at DEBUG
return tracked_files
except (GitCommandError, ValueError) as e:
self.logger.error(f"Failed to list tracked files: {e}")
raise
except Exception as e:
self.logger.exception(f"Unexpected error listing tracked files: {e}")
raise GitCommandError(f"Unexpected error listing files: {e}", command=command) from e
def check_if_would_be_ignored(self, working_directory, path_to_check):
"""
Checks if a given path would be ignored by current .gitignore rules,
regardless of whether it's currently tracked.
Args:
working_directory (str): Path to the Git repository.
path_to_check (str): The relative path within the repo to check.
Returns:
bool: True if the path matches an ignore rule, False otherwise.
Raises:
GitCommandError: If the git command fails unexpectedly.
"""
# Use --no-index to check against .gitignore rules directly,
# not the index (tracked status).
# Use --quiet to suppress output, rely only on exit code.
command = ["git", "check-ignore", "--quiet", "--no-index", "--", path_to_check]
self.logger.debug(f"Checking ignore status for path: '{path_to_check}'")
try:
# check=False because exit code 1 (not ignored) is not an error here
result = self.log_and_execute(command, working_directory, check=False)
if result.returncode == 0:
self.logger.debug(f"Path '{path_to_check}' WOULD be ignored.")
return True # Exit code 0 means it IS ignored
elif result.returncode == 1:
self.logger.debug(f"Path '{path_to_check}' would NOT be ignored.")
return False # Exit code 1 means it is NOT ignored
else:
# Other non-zero exit codes (e.g., 128) indicate an error
error_msg = f"git check-ignore failed with exit code {result.returncode}"
self.logger.error(f"{error_msg}. Stderr: {result.stderr}")
raise GitCommandError(error_msg, command=command, stderr=result.stderr)
except (GitCommandError, ValueError) as e:
self.logger.error(f"Failed check_if_would_be_ignored for '{path_to_check}': {e}")
raise
except Exception as e:
self.logger.exception(f"Unexpected error in check_if_would_be_ignored: {e}")
raise GitCommandError(f"Unexpected check-ignore error: {e}", command=command) from e
def remove_from_tracking(self, working_directory, files_to_untrack):
"""
Removes specified files/directories from Git tracking (index)
without deleting them from the working directory.
Args:
working_directory (str): Path to the Git repository.
files_to_untrack (list): List of relative paths to untrack.
Returns:
bool: True if the command executed successfully (even if nothing was removed).
Raises:
GitCommandError: If the git command fails.
ValueError: If the list of files is empty.
"""
if not files_to_untrack:
raise ValueError("File list cannot be empty for remove_from_tracking.")
self.logger.info(f"Removing {len(files_to_untrack)} items from Git tracking...")
self.logger.debug(f"Items to untrack: {files_to_untrack}")
# Base command
command = ["git", "rm", "--cached", "--"] # "--" separates options from paths
# Add all file paths to the command
command.extend(files_to_untrack)
try:
# check=True ensures an error is raised if `git rm` fails
self.log_and_execute(command, working_directory, check=True)
self.logger.info("Successfully removed items from tracking index.")
return True
except (GitCommandError, ValueError) as e:
self.logger.error(f"Failed to remove items from tracking: {e}")
raise # Re-raise original error
except Exception as e:
self.logger.exception(f"Unexpected error removing from tracking: {e}")
raise GitCommandError(f"Unexpected untrack error: {e}", command=command) from e
def get_matching_gitignore_rule(self, working_directory, path_to_check):
"""
Checks if a given path matches a .gitignore rule and returns the matching pattern.
Args:
working_directory (str): Path to the Git repository.
path_to_check (str): The relative path within the repo to check.
Returns:
str or None: The pattern from .gitignore that matched the path,
or None if the path is not ignored.
Raises:
GitCommandError: If the git command fails unexpectedly.
"""
# Use -v (--verbose) to get the matching rule details.
# Use --no-index to check against .gitignore rules directly.
command = ["git", "check-ignore", "-v", "--no-index", "--", path_to_check]
self.logger.debug(f"Getting matching ignore rule for path: '{path_to_check}'")
try:
# check=False because exit code 1 (not ignored) is not an error here
result = self.log_and_execute(command, working_directory, check=False)
if result.returncode == 0:
# Output format: <source>:<line_num>:<pattern>\t<pathname>
# Example: .gitignore:5:*.log logs/latest.log
output_line = result.stdout.strip()
if output_line and '\t' in output_line:
rule_part = output_line.split('\t', 1)[0]
# Extract pattern (part after the second colon)
parts = rule_part.split(':', 2)
if len(parts) == 3:
pattern = parts[2]
self.logger.debug(f"Path '{path_to_check}' matched rule: '{pattern}'")
return pattern
else:
self.logger.warning(f"Could not parse pattern from check-ignore output: {output_line}")
return None # Indicate parsing failure rather than no match
else:
self.logger.warning(f"Unexpected output format from check-ignore -v: {output_line}")
return None # Indicate unexpected output
elif result.returncode == 1:
# Exit code 1 means the path is NOT ignored
self.logger.debug(f"Path '{path_to_check}' is not ignored by any rule.")
return None
else:
# Other non-zero exit codes (e.g., 128) indicate an error
error_msg = f"git check-ignore -v failed with exit code {result.returncode}"
self.logger.error(f"{error_msg}. Stderr: {result.stderr}")
raise GitCommandError(error_msg, command=command, stderr=result.stderr)
except (GitCommandError, ValueError) as e:
self.logger.error(f"Failed get_matching_gitignore_rule for '{path_to_check}': {e}")
raise
except Exception as e:
self.logger.exception(f"Unexpected error in get_matching_gitignore_rule: {e}")
raise GitCommandError(f"Unexpected check-ignore -v error: {e}", command=command) from e

32
gui.py
View File

@ -106,7 +106,7 @@ class Tooltip:
class GitignoreEditorWindow(tk.Toplevel): class GitignoreEditorWindow(tk.Toplevel):
"""Toplevel window for editing the .gitignore file.""" """Toplevel window for editing the .gitignore file."""
def __init__(self, master, gitignore_path, logger): def __init__(self, master, gitignore_path, logger, on_save_success_callback=None):
super().__init__(master) super().__init__(master)
self.gitignore_path = gitignore_path self.gitignore_path = gitignore_path
if not isinstance(logger, (logging.Logger, logging.LoggerAdapter)): if not isinstance(logger, (logging.Logger, logging.LoggerAdapter)):
@ -115,6 +115,8 @@ class GitignoreEditorWindow(tk.Toplevel):
) )
self.logger = logger self.logger = logger
self.original_content = "" self.original_content = ""
self.on_save_success_callback = on_save_success_callback
self.title(f"Edit {os.path.basename(gitignore_path)}") self.title(f"Edit {os.path.basename(gitignore_path)}")
self.geometry("600x450") self.geometry("600x450")
self.minsize(400, 300) self.minsize(400, 300)
@ -191,9 +193,10 @@ class GitignoreEditorWindow(tk.Toplevel):
return True # Assume changes on error return True # Assume changes on error
def _save_file(self): def _save_file(self):
# (Logica interna di _save_file invariata)
if not self._has_changes(): if not self._has_changes():
self.logger.info("No changes to save.") self.logger.info("No changes to save.")
return True return True # Indicate success even if no changes were made
current_content = self.text_editor.get("1.0", "end-1c") current_content = self.text_editor.get("1.0", "end-1c")
self.logger.info(f"Saving changes to: {self.gitignore_path}") self.logger.info(f"Saving changes to: {self.gitignore_path}")
try: try:
@ -202,26 +205,43 @@ class GitignoreEditorWindow(tk.Toplevel):
self.logger.info(".gitignore saved.") self.logger.info(".gitignore saved.")
self.original_content = current_content self.original_content = current_content
self.text_editor.edit_modified(False) self.text_editor.edit_modified(False)
return True return True # Return True on successful save
except Exception as e: except Exception as e:
self.logger.error(f"Error saving .gitignore: {e}", exc_info=True) self.logger.error(f"Error saving .gitignore: {e}", exc_info=True)
messagebox.showerror("Save Error", f"Error saving:\n{e}", parent=self) messagebox.showerror("Save Error", f"Error saving:\n{e}", parent=self)
return False return False # Return False on error
def _save_and_close(self): def _save_and_close(self):
if self._save_file(): # --- MODIFICA: Chiama il callback dopo il salvataggio ---
if self._save_file(): # Check if save succeeded (or no changes)
self.logger.debug("Save successful, attempting to call success callback.")
# Call the callback if it exists
if self.on_save_success_callback:
try:
self.on_save_success_callback()
except Exception as cb_e:
self.logger.error(f"Error executing on_save_success_callback: {cb_e}", exc_info=True)
# Show an error, maybe? Or just log it.
messagebox.showwarning("Callback Error",
"Saved .gitignore, but failed to run post-save action.\nCheck logs.",
parent=self)
# Proceed to destroy the window regardless of callback success/failure
self.destroy() self.destroy()
# --- FINE MODIFICA ---
# else: If save failed, the error is already shown, do nothing more.
def _on_close(self): def _on_close(self):
# (Logica _on_close invariata, gestisce solo la chiusura/cancel)
if self._has_changes(): if self._has_changes():
res = messagebox.askyesnocancel( res = messagebox.askyesnocancel(
"Unsaved Changes", "Save changes?", parent=self "Unsaved Changes", "Save changes?", parent=self
) )
if res is True: if res is True:
self._save_and_close() self._save_and_close() # This will now trigger the callback if save succeeds
elif res is False: elif res is False:
self.logger.warning("Discarding .gitignore changes.") self.logger.warning("Discarding .gitignore changes.")
self.destroy() self.destroy()
# else: Cancel, do nothing
else: else:
self.destroy() self.destroy()