change gui with notebook tab

This commit is contained in:
VALLONGOL 2025-04-14 09:02:34 +02:00
parent 23a5c593ed
commit a982c35c7d
7 changed files with 2682 additions and 2681 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,17 @@
# -*- mode: python ; coding: utf-8 -*- # -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis( a = Analysis(scripts=['C:\\src\\____GitProjects\\GitUtility\\GitUtility.py'], pathex=['C:\\src\\____GitProjects\\GitUtility'], binaries=[], datas=[('C:\\src\\____GitProjects\\GitUtility\\git_svn_sync.ini', '.')], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=None, noarchive=False,)
['GitUtility.py'],
pathex=[],
binaries=[],
datas=[('git_svn_sync.ini', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE( pyz = PYZ(a.pure, a.zipped_data, cipher=None)
pyz,
a.scripts, exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='GitUtility',
[],
exclude_binaries=True,
name='GitUtility',
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
upx=True, upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[], upx_exclude=[],
name='GitUtility', runtime_tmpdir=None,
) console=False,)
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='GitUtility',)

View File

@ -6,10 +6,13 @@ import os
from git_commands import GitCommands, GitCommandError from git_commands import GitCommands, GitCommandError
from backup_handler import BackupHandler from backup_handler import BackupHandler
class ActionHandler: class ActionHandler:
"""Handles the execution logic for core application actions.""" """Handles the execution logic for core application actions."""
def __init__(self, logger, git_commands: GitCommands, backup_handler: BackupHandler): def __init__(
self, logger, git_commands: GitCommands, backup_handler: BackupHandler
):
""" """
Initializes the ActionHandler. Initializes the ActionHandler.
@ -22,10 +25,15 @@ class ActionHandler:
self.git_commands = git_commands self.git_commands = git_commands
self.backup_handler = backup_handler self.backup_handler = backup_handler
def _perform_backup_if_enabled(
def _perform_backup_if_enabled(self, svn_path, profile_name, self,
autobackup_enabled, backup_base_dir, svn_path,
excluded_extensions, excluded_dirs): profile_name,
autobackup_enabled,
backup_base_dir,
excluded_extensions,
excluded_dirs,
):
""" """
Performs backup if enabled. Raises IOError on backup failure. Performs backup if enabled. Raises IOError on backup failure.
@ -53,7 +61,7 @@ class ActionHandler:
backup_base_dir, backup_base_dir,
profile_name, profile_name,
excluded_extensions, excluded_extensions,
excluded_dirs excluded_dirs,
) )
# Log success if backup handler doesn't raise an error # Log success if backup handler doesn't raise an error
self.logger.info(f"Backup completed successfully: {backup_path}") self.logger.info(f"Backup completed successfully: {backup_path}")
@ -65,7 +73,6 @@ class ActionHandler:
# Standardize on IOError for backup failures passed up # Standardize on IOError for backup failures passed up
raise IOError(f"Autobackup failed: {backup_e}") from backup_e raise IOError(f"Autobackup failed: {backup_e}") from backup_e
def execute_prepare_repo(self, svn_path): def execute_prepare_repo(self, svn_path):
""" """
Executes the 'prepare repository' action. Executes the 'prepare repository' action.
@ -98,11 +105,18 @@ class ActionHandler:
self.logger.error(f"Failed to prepare repository: {e}", exc_info=True) self.logger.error(f"Failed to prepare repository: {e}", exc_info=True)
raise raise
def execute_create_bundle(
def execute_create_bundle(self, svn_path, bundle_full_path, profile_name, self,
autobackup_enabled, backup_base_dir, svn_path,
autocommit_enabled, commit_message, bundle_full_path,
excluded_extensions, excluded_dirs): profile_name,
autobackup_enabled,
backup_base_dir,
autocommit_enabled,
commit_message,
excluded_extensions,
excluded_dirs,
):
""" """
Executes 'create bundle', including backup and commit logic. Executes 'create bundle', including backup and commit logic.
@ -126,8 +140,12 @@ class ActionHandler:
# --- Backup Step --- # --- Backup Step ---
# _perform_backup_if_enabled raises IOError on failure # _perform_backup_if_enabled raises IOError on failure
self._perform_backup_if_enabled( self._perform_backup_if_enabled(
svn_path, profile_name, autobackup_enabled, backup_base_dir, svn_path,
excluded_extensions, excluded_dirs profile_name,
autobackup_enabled,
backup_base_dir,
excluded_extensions,
excluded_dirs,
) )
# --- Autocommit Step --- # --- Autocommit Step ---
@ -139,9 +157,14 @@ class ActionHandler:
if has_changes: if has_changes:
self.logger.info("Changes detected, performing autocommit...") self.logger.info("Changes detected, performing autocommit...")
# Use provided message or generate default # Use provided message or generate default
commit_msg_to_use = commit_message if commit_message else \ commit_msg_to_use = (
f"Autocommit '{profile_name}' before bundle" commit_message
self.logger.debug(f"Using autocommit message: '{commit_msg_to_use}'") if commit_message
else f"Autocommit '{profile_name}' before bundle"
)
self.logger.debug(
f"Using autocommit message: '{commit_msg_to_use}'"
)
# Perform commit (raises error on failure, returns bool) # Perform commit (raises error on failure, returns bool)
self.git_commands.git_commit(svn_path, commit_msg_to_use) self.git_commands.git_commit(svn_path, commit_msg_to_use)
# Log based on return value? git_commit already logs detail. # Log based on return value? git_commit already logs detail.
@ -179,10 +202,16 @@ class ActionHandler:
self.logger.error(f"Bundle creation failed: {bundle_e}", exc_info=True) self.logger.error(f"Bundle creation failed: {bundle_e}", exc_info=True)
raise bundle_e raise bundle_e
def execute_fetch_bundle(
def execute_fetch_bundle(self, svn_path, bundle_full_path, profile_name, self,
autobackup_enabled, backup_base_dir, svn_path,
excluded_extensions, excluded_dirs): bundle_full_path,
profile_name,
autobackup_enabled,
backup_base_dir,
excluded_extensions,
excluded_dirs,
):
""" """
Executes the 'fetch bundle' action, including backup logic. Executes the 'fetch bundle' action, including backup logic.
@ -195,8 +224,12 @@ class ActionHandler:
# --- Backup Step --- # --- Backup Step ---
# Raises IOError on failure # Raises IOError on failure
self._perform_backup_if_enabled( self._perform_backup_if_enabled(
svn_path, profile_name, autobackup_enabled, backup_base_dir, svn_path,
excluded_extensions, excluded_dirs profile_name,
autobackup_enabled,
backup_base_dir,
excluded_extensions,
excluded_dirs,
) )
# --- Fetch and Merge Step --- # --- Fetch and Merge Step ---
@ -211,7 +244,6 @@ class ActionHandler:
self.logger.error(f"Fetch/merge failed: {fetch_e}", exc_info=True) self.logger.error(f"Fetch/merge failed: {fetch_e}", exc_info=True)
raise fetch_e raise fetch_e
def execute_manual_commit(self, svn_path, commit_message): def execute_manual_commit(self, svn_path, commit_message):
""" """
Executes a manual commit with the provided message. Executes a manual commit with the provided message.
@ -242,7 +274,6 @@ class ActionHandler:
self.logger.error(f"Manual commit failed: {e}", exc_info=True) self.logger.error(f"Manual commit failed: {e}", exc_info=True)
raise raise
def execute_create_tag(self, svn_path, commit_message, tag_name, tag_message): def execute_create_tag(self, svn_path, commit_message, tag_name, tag_message):
""" """
Executes tag creation, including pre-commit using commit_message if needed. Executes tag creation, including pre-commit using commit_message if needed.
@ -277,14 +308,15 @@ class ActionHandler:
# git_commit raises error on failure, returns bool otherwise # git_commit raises error on failure, returns bool otherwise
commit_made = self.git_commands.git_commit(svn_path, commit_message) commit_made = self.git_commands.git_commit(svn_path, commit_message)
# Log based on return value? git_commit logs details. # Log based on return value? git_commit logs details.
self.logger.info(f"Pre-tag commit attempt finished (made={commit_made}).") self.logger.info(
f"Pre-tag commit attempt finished (made={commit_made})."
)
else: else:
self.logger.info("No uncommitted changes detected before tagging.") self.logger.info("No uncommitted changes detected before tagging.")
except Exception as e: except Exception as e:
# Catch errors during status check or commit # Catch errors during status check or commit
self.logger.error(f"Error during pre-tag commit step: {e}", self.logger.error(f"Error during pre-tag commit step: {e}", exc_info=True)
exc_info=True)
raise # Re-raise commit-related errors raise # Re-raise commit-related errors
# --- Create Tag Step --- # --- Create Tag Step ---
@ -297,11 +329,9 @@ class ActionHandler:
return True # Indicate success return True # Indicate success
except Exception as e: except Exception as e:
# Catch errors during tag creation # Catch errors during tag creation
self.logger.error(f"Failed to create tag '{tag_name}': {e}", self.logger.error(f"Failed to create tag '{tag_name}': {e}", exc_info=True)
exc_info=True)
raise # Re-raise tag creation errors raise # Re-raise tag creation errors
def execute_checkout_tag(self, svn_path, tag_name): def execute_checkout_tag(self, svn_path, tag_name):
""" """
Executes checkout for the specified tag after checking changes. Executes checkout for the specified tag after checking changes.
@ -331,8 +361,7 @@ class ActionHandler:
self.logger.debug("No uncommitted changes found.") self.logger.debug("No uncommitted changes found.")
except Exception as e: except Exception as e:
# Catch errors during status check # Catch errors during status check
self.logger.error(f"Status check error before checkout: {e}", self.logger.error(f"Status check error before checkout: {e}", exc_info=True)
exc_info=True)
raise # Re-raise status check errors raise # Re-raise status check errors
# --- Execute Checkout --- # --- Execute Checkout ---
@ -348,11 +377,11 @@ class ActionHandler:
raise GitCommandError("Checkout failed for unknown reason.") raise GitCommandError("Checkout failed for unknown reason.")
except Exception as e: except Exception as e:
# Catch errors during checkout (e.g., tag not found) # Catch errors during checkout (e.g., tag not found)
self.logger.error(f"Failed to checkout tag '{tag_name}': {e}", self.logger.error(
exc_info=True) f"Failed to checkout tag '{tag_name}': {e}", exc_info=True
)
raise # Re-raise checkout errors raise # Re-raise checkout errors
# --- Branch Actions --- # --- Branch Actions ---
def execute_create_branch(self, svn_path, branch_name, start_point=None): def execute_create_branch(self, svn_path, branch_name, start_point=None):
""" """
@ -363,8 +392,10 @@ class ActionHandler:
Returns: True on success. Returns: True on success.
Raises: GitCommandError/ValueError/Exception on failure. Raises: GitCommandError/ValueError/Exception on failure.
""" """
self.logger.info(f"Executing create branch '{branch_name}' " self.logger.info(
f"from '{start_point or 'HEAD'}'.") f"Executing create branch '{branch_name}' "
f"from '{start_point or 'HEAD'}'."
)
try: try:
# Delegate to git_commands, raises error on failure # Delegate to git_commands, raises error on failure
self.git_commands.create_branch(svn_path, branch_name, start_point) self.git_commands.create_branch(svn_path, branch_name, start_point)
@ -374,7 +405,6 @@ class ActionHandler:
self.logger.error(f"Failed to create branch: {e}", exc_info=True) self.logger.error(f"Failed to create branch: {e}", exc_info=True)
raise raise
def execute_switch_branch(self, svn_path, branch_name): def execute_switch_branch(self, svn_path, branch_name):
""" """
Executes branch switch after checking for changes. Executes branch switch after checking for changes.
@ -398,8 +428,7 @@ class ActionHandler:
self.logger.debug("No uncommitted changes found.") self.logger.debug("No uncommitted changes found.")
except Exception as e: except Exception as e:
# Catch errors during status check # Catch errors during status check
self.logger.error(f"Status check error before switch: {e}", self.logger.error(f"Status check error before switch: {e}", exc_info=True)
exc_info=True)
raise # Re-raise status check errors raise # Re-raise status check errors
# --- Execute Switch --- # --- Execute Switch ---
@ -409,11 +438,11 @@ class ActionHandler:
return success return success
except Exception as e: except Exception as e:
# Catch errors during switch (e.g., branch not found) # Catch errors during switch (e.g., branch not found)
self.logger.error(f"Failed switch to branch '{branch_name}': {e}", self.logger.error(
exc_info=True) f"Failed switch to branch '{branch_name}': {e}", exc_info=True
)
raise raise
def execute_delete_branch(self, svn_path, branch_name, force=False): def execute_delete_branch(self, svn_path, branch_name, force=False):
""" """
Executes branch deletion. Executes branch deletion.
@ -434,11 +463,11 @@ class ActionHandler:
return success return success
except Exception as e: except Exception as e:
# Catch errors (like not merged, needs force) # Catch errors (like not merged, needs force)
self.logger.error(f"Failed delete branch '{branch_name}': {e}", self.logger.error(
exc_info=True) f"Failed delete branch '{branch_name}': {e}", exc_info=True
)
raise raise
def execute_delete_tag(self, svn_path, tag_name): def execute_delete_tag(self, svn_path, tag_name):
""" """
Executes deletion for the specified tag. Executes deletion for the specified tag.
@ -464,6 +493,5 @@ class ActionHandler:
return success # Should be True if no exception return success # Should be True if no exception
except Exception as e: except Exception as e:
# Catch and re-raise errors from git_commands.delete_tag # Catch and re-raise errors from git_commands.delete_tag
self.logger.error(f"Failed to delete tag '{tag_name}': {e}", self.logger.error(f"Failed to delete tag '{tag_name}': {e}", exc_info=True)
exc_info=True)
raise # Re-raise for the UI layer to handle raise # Re-raise for the UI layer to handle

View File

@ -4,6 +4,7 @@ import datetime
import zipfile import zipfile
import logging import logging
class BackupHandler: class BackupHandler:
"""Handles the creation of ZIP backups with exclusions.""" """Handles the creation of ZIP backups with exclusions."""
@ -20,8 +21,14 @@ class BackupHandler:
# to ConfigManager based on the current profile selected in the UI. # to ConfigManager based on the current profile selected in the UI.
# The create_zip_backup method now receives the parsed exclusions directly. # The create_zip_backup method now receives the parsed exclusions directly.
def create_zip_backup(self, source_repo_path, backup_base_dir, def create_zip_backup(
profile_name, excluded_extensions, excluded_dirs_base): self,
source_repo_path,
backup_base_dir,
profile_name,
excluded_extensions,
excluded_dirs_base,
):
""" """
Creates a timestamped ZIP backup of the source repository directory, Creates a timestamped ZIP backup of the source repository directory,
respecting provided exclusions. respecting provided exclusions.
@ -60,16 +67,17 @@ class BackupHandler:
# exist_ok=True prevents error if directory already exists # exist_ok=True prevents error if directory already exists
os.makedirs(backup_base_dir, exist_ok=True) os.makedirs(backup_base_dir, exist_ok=True)
except OSError as e: except OSError as e:
self.logger.error(f"Cannot create backup directory: {e}", self.logger.error(f"Cannot create backup directory: {e}", exc_info=True)
exc_info=True)
# Re-raise as IOError for the caller to potentially handle differently # Re-raise as IOError for the caller to potentially handle differently
raise IOError(f"Could not create backup directory: {e}") from e raise IOError(f"Could not create backup directory: {e}") from e
# --- 2. Construct Backup Filename --- # --- 2. Construct Backup Filename ---
now_str = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") now_str = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# Sanitize profile name for use in filename (remove potentially invalid chars) # Sanitize profile name for use in filename (remove potentially invalid chars)
safe_profile = "".join(c for c in profile_name safe_profile = (
if c.isalnum() or c in '_-').rstrip() or "profile" "".join(c for c in profile_name if c.isalnum() or c in "_-").rstrip()
or "profile"
)
backup_filename = f"{now_str}_backup_{safe_profile}.zip" backup_filename = f"{now_str}_backup_{safe_profile}.zip"
backup_full_path = os.path.join(backup_base_dir, backup_filename) backup_full_path = os.path.join(backup_base_dir, backup_filename)
self.logger.info(f"Target backup ZIP file: {backup_full_path}") self.logger.info(f"Target backup ZIP file: {backup_full_path}")
@ -82,9 +90,9 @@ class BackupHandler:
try: try:
# Open ZIP file with settings for compression and large files # Open ZIP file with settings for compression and large files
zip_f = zipfile.ZipFile(backup_full_path, 'w', zip_f = zipfile.ZipFile(
compression=zipfile.ZIP_DEFLATED, backup_full_path, "w", compression=zipfile.ZIP_DEFLATED, allowZip64=True
allowZip64=True) )
# Walk through the source directory tree # Walk through the source directory tree
for root, dirs, files in os.walk(source_repo_path, topdown=True): for root, dirs, files in os.walk(source_repo_path, topdown=True):
@ -111,8 +119,10 @@ class BackupHandler:
file_ext_lower = file_ext.lower() file_ext_lower = file_ext.lower()
# Check exclusion rules (case-insensitive filename or extension) # Check exclusion rules (case-insensitive filename or extension)
if filename.lower() in excluded_dirs_base or \ if (
file_ext_lower in excluded_extensions: filename.lower() in excluded_dirs_base
or file_ext_lower in excluded_extensions
):
path_excluded = os.path.join(root, filename) path_excluded = os.path.join(root, filename)
self.logger.debug(f"Excluding file: {path_excluded}") self.logger.debug(f"Excluding file: {path_excluded}")
files_excluded += 1 files_excluded += 1
@ -134,7 +144,7 @@ class BackupHandler:
# Log error writing a specific file but allow backup to continue # Log error writing a specific file but allow backup to continue
self.logger.error( self.logger.error(
f"Error writing file '{file_full_path}' to ZIP: {write_e}", f"Error writing file '{file_full_path}' to ZIP: {write_e}",
exc_info=True exc_info=True,
) )
# Consider marking the backup as potentially incomplete # Consider marking the backup as potentially incomplete

File diff suppressed because it is too large Load Diff

1585
gui.py

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,10 @@
# profile_handler.py # profile_handler.py
import logging import logging
# Assuming ConfigManager handles its own constants now # Assuming ConfigManager handles its own constants now
from config_manager import ConfigManager, DEFAULT_PROFILE, DEFAULT_BACKUP_DIR from config_manager import ConfigManager, DEFAULT_PROFILE, DEFAULT_BACKUP_DIR
class ProfileHandler: class ProfileHandler:
"""Handles loading, saving, adding, removing profiles via ConfigManager.""" """Handles loading, saving, adding, removing profiles via ConfigManager."""
@ -50,15 +52,16 @@ class ProfileHandler:
# Convert loaded string values back to appropriate types if needed # Convert loaded string values back to appropriate types if needed
# Example: Booleans # Example: Booleans
profile_data["autocommit"] = \ profile_data["autocommit"] = (
str(profile_data.get("autocommit", "False")).lower() == "true" str(profile_data.get("autocommit", "False")).lower() == "true"
profile_data["autobackup"] = \ )
profile_data["autobackup"] = (
str(profile_data.get("autobackup", "False")).lower() == "true" str(profile_data.get("autobackup", "False")).lower() == "true"
)
self.logger.debug(f"Loaded data for '{profile_name}': {profile_data}") self.logger.debug(f"Loaded data for '{profile_name}': {profile_data}")
return profile_data return profile_data
def save_profile_data(self, profile_name, profile_data): def save_profile_data(self, profile_name, profile_data):
""" """
Saves the provided data dictionary to the specified profile name. Saves the provided data dictionary to the specified profile name.
@ -90,21 +93,19 @@ class ProfileHandler:
# Assume other types can be directly converted to string # Assume other types can be directly converted to string
value_to_save = str(value) if value is not None else "" value_to_save = str(value) if value is not None else ""
self.config_manager.set_profile_option( self.config_manager.set_profile_option(profile_name, key, value_to_save)
profile_name, key, value_to_save
)
# Persist changes to the configuration file # Persist changes to the configuration file
self.config_manager.save_config() self.config_manager.save_config()
self.logger.info(f"Profile '{profile_name}' saved successfully.") self.logger.info(f"Profile '{profile_name}' saved successfully.")
return True return True
except Exception as e: except Exception as e:
self.logger.error(f"Error saving profile '{profile_name}': {e}", self.logger.error(
exc_info=True) f"Error saving profile '{profile_name}': {e}", exc_info=True
)
# Let the caller (main app) show the error message # Let the caller (main app) show the error message
return False return False
def add_new_profile(self, profile_name): def add_new_profile(self, profile_name):
""" """
Adds a new profile with default settings. Adds a new profile with default settings.
@ -143,11 +144,11 @@ class ProfileHandler:
return True return True
except Exception as e: except Exception as e:
# Log unexpected errors during profile addition # Log unexpected errors during profile addition
self.logger.error(f"Error adding profile '{profile_name}': {e}", self.logger.error(
exc_info=True) f"Error adding profile '{profile_name}': {e}", exc_info=True
)
return False return False
def remove_existing_profile(self, profile_name): def remove_existing_profile(self, profile_name):
""" """
Removes an existing profile (cannot remove the default profile). Removes an existing profile (cannot remove the default profile).
@ -180,10 +181,13 @@ class ProfileHandler:
return True return True
else: else:
# ConfigManager should log the reason for failure # ConfigManager should log the reason for failure
self.logger.error(f"ConfigManager reported failure removing '{profile_name}'.") self.logger.error(
f"ConfigManager reported failure removing '{profile_name}'."
)
return False return False
except Exception as e: except Exception as e:
# Log unexpected errors during removal # Log unexpected errors during removal
self.logger.error(f"Error removing profile '{profile_name}': {e}", self.logger.error(
exc_info=True) f"Error removing profile '{profile_name}': {e}", exc_info=True
)
return False return False