add ignore update
This commit is contained in:
parent
418039e65a
commit
4b74cfbe27
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
317
git_commands.py
317
git_commands.py
@ -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
|
||||||
abs_path = os.path.abspath(working_directory)
|
if working_directory == ".":
|
||||||
if not os.path.isdir(abs_path):
|
cwd = os.getcwd()
|
||||||
msg = f"Working directory does not exist or is not a directory: {abs_path}"
|
else:
|
||||||
self.logger.error(msg)
|
abs_path = os.path.abspath(working_directory)
|
||||||
# Raise GitCommandError to signal issue related to command execution context
|
if not os.path.isdir(abs_path):
|
||||||
raise GitCommandError(msg, command=safe_command)
|
msg = f"Working directory does not exist or is not a directory: {abs_path}"
|
||||||
|
self.logger.error(msg)
|
||||||
cwd = abs_path
|
raise GitCommandError(msg, command=safe_command)
|
||||||
self.logger.debug(f"Effective Working Directory: {cwd}")
|
cwd = abs_path
|
||||||
|
# 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
|
||||||
stdout_log = result.stdout.strip() if result.stdout else "<no stdout>"
|
# Error output is always logged at ERROR level below
|
||||||
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
|
|
||||||
if check or result.returncode == 0:
|
if check or result.returncode == 0:
|
||||||
self.logger.info(
|
# --- MODIFICA: Usa log_output_level per l'output di successo ---
|
||||||
f"Command successful. Output:\n"
|
# Only log stdout/stderr if the requested level is met by the logger config
|
||||||
f"--- stdout ---\n{stdout_log}\n"
|
if self.logger.isEnabledFor(log_output_level):
|
||||||
f"--- stderr ---\n{stderr_log}\n---"
|
stdout_log = result.stdout.strip() if result.stdout else "<no stdout>"
|
||||||
)
|
stderr_log = result.stderr.strip() if result.stderr else "<no stderr>"
|
||||||
# Note: If check=False and returncode != 0, errors are typically
|
# Use the passed level for logging the output
|
||||||
# handled by the calling method that analyzes the result.
|
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
|
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,36 +167,30 @@ 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 "
|
self.logger.error(error_msg)
|
||||||
f"and in system PATH? (Working directory: '{cwd}')"
|
self.logger.debug(f"FileNotFoundError details: {e}")
|
||||||
)
|
raise GitCommandError(error_msg, command=safe_command) from e
|
||||||
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
|
|
||||||
|
|
||||||
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
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
# --- Core Repo Operations ---
|
# --- Core Repo Operations ---
|
||||||
def prepare_svn_for_git(self, working_directory):
|
def prepare_svn_for_git(self, working_directory):
|
||||||
@ -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
32
gui.py
@ -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()
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user