add ignore update
This commit is contained in:
parent
418039e65a
commit
4b74cfbe27
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@
|
||||
.log
|
||||
dist
|
||||
build
|
||||
.ini
|
||||
.ini
|
||||
@ -186,6 +186,40 @@ class GitSvnSyncApp:
|
||||
self.logger.warning("No initial profile set during initial load.")
|
||||
self._clear_and_disable_fields()
|
||||
#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):
|
||||
"""Handles the window close event."""
|
||||
@ -628,22 +662,38 @@ class GitSvnSyncApp:
|
||||
return abs_path
|
||||
|
||||
def open_gitignore_editor(self):
|
||||
"""Opens the modal editor window for the .gitignore file."""
|
||||
# (Mantenere versione precedente robusta)
|
||||
"""Opens the modal editor window for the .gitignore file and
|
||||
triggers automatic untracking check on successful save."""
|
||||
self.logger.info("--- Action: Edit .gitignore ---")
|
||||
svn_path = self._get_and_validate_svn_path("Edit .gitignore")
|
||||
if not svn_path:
|
||||
return
|
||||
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:
|
||||
self.logger.debug("Opening GitignoreEditorWindow...")
|
||||
# --- MODIFICA: Passa il metodo _handle_gitignore_save come callback ---
|
||||
GitignoreEditorWindow(
|
||||
self.master, gitignore_path, self.logger
|
||||
) # Blocca fino alla chiusura
|
||||
self.master,
|
||||
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.")
|
||||
# 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:
|
||||
self.logger.exception(f"Editor error: {e}")
|
||||
self.main_frame.show_error("Error", f"Editor error:\n{e}")
|
||||
self.logger.exception(f"Error during .gitignore editing or post-save action: {e}")
|
||||
self.main_frame.show_error("Editor Error", f"An error occurred:\n{e}")
|
||||
|
||||
# --- Threading Helpers (REMOVED) ---
|
||||
# Rimuovi _run_action_with_wait
|
||||
|
||||
@ -545,3 +545,95 @@ class ActionHandler:
|
||||
except Exception as e:
|
||||
self.logger.exception(f"Failed to delete tag '{tag_name}': {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
|
||||
|
||||
317
git_commands.py
317
git_commands.py
@ -70,97 +70,94 @@ class GitCommands:
|
||||
raise ValueError("A valid logging.Logger instance is required.")
|
||||
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,
|
||||
and handles potential errors.
|
||||
Executes a shell command, logs details, handles errors, and controls output logging level.
|
||||
|
||||
Args:
|
||||
command (list): Command and arguments as a list of strings.
|
||||
working_directory (str): The directory to execute the command in.
|
||||
check (bool, optional): If True, raises CalledProcessError (wrapped
|
||||
in GitCommandError) if the command returns
|
||||
a non-zero exit code. Defaults to True.
|
||||
check (bool, optional): If True, raises GitCommandError on non-zero exit. Defaults to True.
|
||||
log_output_level (int, optional): Logging level for stdout/stderr on success.
|
||||
Defaults to logging.INFO. Use logging.DEBUG
|
||||
to hide noisy command output from INFO logs.
|
||||
|
||||
Returns:
|
||||
subprocess.CompletedProcess: Result object containing stdout, stderr, etc.
|
||||
subprocess.CompletedProcess: Result object.
|
||||
|
||||
Raises:
|
||||
GitCommandError: For Git-specific errors or execution issues.
|
||||
ValueError: If working_directory is invalid.
|
||||
FileNotFoundError: If the command (e.g., 'git') is not found.
|
||||
PermissionError: If execution permission is denied.
|
||||
GitCommandError, ValueError, FileNotFoundError, PermissionError.
|
||||
"""
|
||||
# Ensure all parts of command are strings for reliable execution and logging
|
||||
# --- FINE MODIFICA ---
|
||||
safe_command = [str(part) for part in command]
|
||||
command_str = " ".join(safe_command)
|
||||
log_message = f"Executing: {command_str}"
|
||||
# Log command execution at debug level for less verbosity in standard logs
|
||||
self.logger.debug(log_message)
|
||||
# Log command execution always at DEBUG level
|
||||
self.logger.debug(f"Executing in '{working_directory}': {command_str}")
|
||||
|
||||
# --- Validate Working Directory ---
|
||||
# (Logica validazione working_directory invariata)
|
||||
if not working_directory:
|
||||
msg = "Working directory cannot be None or empty."
|
||||
self.logger.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
abs_path = os.path.abspath(working_directory)
|
||||
if not os.path.isdir(abs_path):
|
||||
msg = f"Working directory does not exist or is not a directory: {abs_path}"
|
||||
self.logger.error(msg)
|
||||
# Raise GitCommandError to signal issue related to command execution context
|
||||
raise GitCommandError(msg, command=safe_command)
|
||||
|
||||
cwd = abs_path
|
||||
self.logger.debug(f"Effective Working Directory: {cwd}")
|
||||
# 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)
|
||||
if not os.path.isdir(abs_path):
|
||||
msg = f"Working directory does not exist or is not a directory: {abs_path}"
|
||||
self.logger.error(msg)
|
||||
raise GitCommandError(msg, command=safe_command)
|
||||
cwd = abs_path
|
||||
# self.logger.debug(f"Effective Working Directory: {cwd}") # Already logged above
|
||||
|
||||
# --- Execute Command ---
|
||||
try:
|
||||
# Platform-specific setup to hide console window on Windows
|
||||
# (Logica startupinfo/creationflags invariata)
|
||||
startupinfo = None
|
||||
# creationflags are used to control process creation (e.g., hide window)
|
||||
creationflags = 0
|
||||
if os.name == "nt": # Windows specific settings
|
||||
if os.name == "nt":
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
# Prevent console window from showing
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
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(
|
||||
safe_command, # Use the command list with string parts
|
||||
cwd=cwd, # Set working directory
|
||||
capture_output=True, # Capture stdout and stderr
|
||||
text=True, # Decode output as text (UTF-8 default)
|
||||
check=check, # Raise exception on non-zero exit code if True
|
||||
encoding="utf-8", # Specify encoding explicitly
|
||||
errors="replace", # Handle potential decoding errors gracefully
|
||||
startupinfo=startupinfo, # Windows: hide console window
|
||||
creationflags=creationflags, # Windows: additional process flags
|
||||
safe_command,
|
||||
cwd=cwd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=check,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
startupinfo=startupinfo,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
|
||||
# Log command output for debugging and info
|
||||
stdout_log = result.stdout.strip() if result.stdout else "<no stdout>"
|
||||
stderr_log = result.stderr.strip() if result.stderr else "<no stderr>"
|
||||
|
||||
# Log success differently based on whether check=True was used
|
||||
# If check=True, CalledProcessError would have been raised on failure
|
||||
# If check=False, we log success only if return code is 0
|
||||
# Log command output based on specified level for success
|
||||
# Error output is always logged at ERROR level below
|
||||
if check or result.returncode == 0:
|
||||
self.logger.info(
|
||||
f"Command successful. Output:\n"
|
||||
f"--- stdout ---\n{stdout_log}\n"
|
||||
f"--- stderr ---\n{stderr_log}\n---"
|
||||
)
|
||||
# Note: If check=False and returncode != 0, errors are typically
|
||||
# handled by the calling method that analyzes the result.
|
||||
# --- 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>"
|
||||
stderr_log = result.stderr.strip() if result.stderr else "<no stderr>"
|
||||
# Use the passed level for logging the output
|
||||
self.logger.log(
|
||||
log_output_level,
|
||||
f"Command successful. Output:\n"
|
||||
f"--- stdout ---\n{stdout_log}\n"
|
||||
f"--- stderr ---\n{stderr_log}\n---"
|
||||
)
|
||||
else:
|
||||
# Log minimal success message if output level is suppressed
|
||||
self.logger.debug(f"Command successful (output logging suppressed by level).")
|
||||
# --- FINE MODIFICA ---
|
||||
|
||||
return result
|
||||
|
||||
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>"
|
||||
stdout_err = e.stdout.strip() if e.stdout else "<no stdout>"
|
||||
error_log_msg = (
|
||||
@ -170,36 +167,30 @@ class GitCommands:
|
||||
f"--- stdout ---\n{stdout_err}\n---"
|
||||
)
|
||||
self.logger.error(error_log_msg)
|
||||
# Wrap the original exception in our custom GitCommandError
|
||||
raise GitCommandError(
|
||||
f"Git command failed in '{cwd}'.", command=safe_command, stderr=e.stderr
|
||||
) from e # Preserve original exception context
|
||||
) from e
|
||||
|
||||
except FileNotFoundError as e:
|
||||
# Handle error if 'git' command (or another part) is not found
|
||||
error_msg = (
|
||||
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.debug(
|
||||
f"FileNotFoundError details: {e}"
|
||||
) # Log full details only at debug level
|
||||
raise GitCommandError(error_msg, command=safe_command) from e
|
||||
# (Gestione FileNotFoundError invariata)
|
||||
error_msg = f"Command not found: '{safe_command[0]}'. Is Git installed and in system PATH? (Working directory: '{cwd}')"
|
||||
self.logger.error(error_msg)
|
||||
self.logger.debug(f"FileNotFoundError details: {e}")
|
||||
raise GitCommandError(error_msg, command=safe_command) from e
|
||||
|
||||
except PermissionError as e:
|
||||
# Handle errors due to lack of execution permissions
|
||||
error_msg = f"Permission denied executing command in '{cwd}'."
|
||||
self.logger.error(error_msg)
|
||||
self.logger.debug(f"PermissionError details: {e}")
|
||||
raise GitCommandError(error_msg, command=safe_command, stderr=str(e)) from e
|
||||
# (Gestione PermissionError invariata)
|
||||
error_msg = f"Permission denied executing command in '{cwd}'."
|
||||
self.logger.error(error_msg)
|
||||
self.logger.debug(f"PermissionError details: {e}")
|
||||
raise GitCommandError(error_msg, command=safe_command, stderr=str(e)) from e
|
||||
|
||||
except Exception as e:
|
||||
# Catch any other unexpected errors during subprocess execution
|
||||
self.logger.exception(f"Unexpected error executing command in '{cwd}': {e}")
|
||||
raise GitCommandError(
|
||||
f"Unexpected command execution error: {e}", command=safe_command
|
||||
) from e
|
||||
# (Gestione Exception generica invariata)
|
||||
self.logger.exception(f"Unexpected error executing command in '{cwd}': {e}")
|
||||
raise GitCommandError(
|
||||
f"Unexpected command execution error: {e}", command=safe_command
|
||||
) from e
|
||||
|
||||
# --- Core Repo Operations ---
|
||||
def prepare_svn_for_git(self, working_directory):
|
||||
@ -908,3 +899,173 @@ class GitCommands:
|
||||
# Catch any other unexpected errors
|
||||
self.logger.exception(f"Unexpected error cloning from bundle: {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
32
gui.py
@ -106,7 +106,7 @@ class Tooltip:
|
||||
class GitignoreEditorWindow(tk.Toplevel):
|
||||
"""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)
|
||||
self.gitignore_path = gitignore_path
|
||||
if not isinstance(logger, (logging.Logger, logging.LoggerAdapter)):
|
||||
@ -115,6 +115,8 @@ class GitignoreEditorWindow(tk.Toplevel):
|
||||
)
|
||||
self.logger = logger
|
||||
self.original_content = ""
|
||||
self.on_save_success_callback = on_save_success_callback
|
||||
|
||||
self.title(f"Edit {os.path.basename(gitignore_path)}")
|
||||
self.geometry("600x450")
|
||||
self.minsize(400, 300)
|
||||
@ -191,9 +193,10 @@ class GitignoreEditorWindow(tk.Toplevel):
|
||||
return True # Assume changes on error
|
||||
|
||||
def _save_file(self):
|
||||
# (Logica interna di _save_file invariata)
|
||||
if not self._has_changes():
|
||||
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")
|
||||
self.logger.info(f"Saving changes to: {self.gitignore_path}")
|
||||
try:
|
||||
@ -202,26 +205,43 @@ class GitignoreEditorWindow(tk.Toplevel):
|
||||
self.logger.info(".gitignore saved.")
|
||||
self.original_content = current_content
|
||||
self.text_editor.edit_modified(False)
|
||||
return True
|
||||
return True # Return True on successful save
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error saving .gitignore: {e}", exc_info=True)
|
||||
messagebox.showerror("Save Error", f"Error saving:\n{e}", parent=self)
|
||||
return False
|
||||
return False # Return False on error
|
||||
|
||||
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()
|
||||
# --- FINE MODIFICA ---
|
||||
# else: If save failed, the error is already shown, do nothing more.
|
||||
|
||||
def _on_close(self):
|
||||
# (Logica _on_close invariata, gestisce solo la chiusura/cancel)
|
||||
if self._has_changes():
|
||||
res = messagebox.askyesnocancel(
|
||||
"Unsaved Changes", "Save changes?", parent=self
|
||||
)
|
||||
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:
|
||||
self.logger.warning("Discarding .gitignore changes.")
|
||||
self.destroy()
|
||||
# else: Cancel, do nothing
|
||||
else:
|
||||
self.destroy()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user