change gui with notebook tab
This commit is contained in:
parent
23a5c593ed
commit
a982c35c7d
2052
GitUtility.py
2052
GitUtility.py
File diff suppressed because it is too large
Load Diff
@ -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',
|
||||||
[],
|
debug=False,
|
||||||
exclude_binaries=True,
|
bootloader_ignore_signals=False,
|
||||||
name='GitUtility',
|
strip=False,
|
||||||
debug=False,
|
upx=True,
|
||||||
bootloader_ignore_signals=False,
|
upx_exclude=[],
|
||||||
strip=False,
|
runtime_tmpdir=None,
|
||||||
upx=True,
|
console=False,)
|
||||||
console=False,
|
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='GitUtility',)
|
||||||
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=[],
|
|
||||||
name='GitUtility',
|
|
||||||
)
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
@ -42,7 +50,7 @@ class ActionHandler:
|
|||||||
"""
|
"""
|
||||||
if not autobackup_enabled:
|
if not autobackup_enabled:
|
||||||
self.logger.debug("Autobackup disabled, skipping backup.")
|
self.logger.debug("Autobackup disabled, skipping backup.")
|
||||||
return # Backup not needed, proceed successfully
|
return # Backup not needed, proceed successfully
|
||||||
|
|
||||||
self.logger.info("Autobackup enabled. Starting backup...")
|
self.logger.info("Autobackup enabled. Starting backup...")
|
||||||
try:
|
try:
|
||||||
@ -53,18 +61,17 @@ 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}")
|
||||||
# No explicit return value needed on success
|
# No explicit return value needed on success
|
||||||
|
|
||||||
except Exception as backup_e:
|
except Exception as backup_e:
|
||||||
# Log error and re-raise as IOError to signal critical failure
|
# Log error and re-raise as IOError to signal critical failure
|
||||||
self.logger.error(f"Backup failed: {backup_e}", exc_info=True)
|
self.logger.error(f"Backup failed: {backup_e}", exc_info=True)
|
||||||
# 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):
|
||||||
"""
|
"""
|
||||||
@ -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.
|
||||||
@ -162,27 +185,33 @@ class ActionHandler:
|
|||||||
bundle_exists = os.path.exists(bundle_full_path)
|
bundle_exists = os.path.exists(bundle_full_path)
|
||||||
bundle_not_empty = bundle_exists and os.path.getsize(bundle_full_path) > 0
|
bundle_not_empty = bundle_exists and os.path.getsize(bundle_full_path) > 0
|
||||||
if bundle_exists and bundle_not_empty:
|
if bundle_exists and bundle_not_empty:
|
||||||
self.logger.info("Git bundle created successfully.")
|
self.logger.info("Git bundle created successfully.")
|
||||||
return bundle_full_path # Return path on success
|
return bundle_full_path # Return path on success
|
||||||
else:
|
else:
|
||||||
# Bundle empty or not created (logged by GitCommands)
|
# Bundle empty or not created (logged by GitCommands)
|
||||||
self.logger.warning("Bundle file not created or is empty.")
|
self.logger.warning("Bundle file not created or is empty.")
|
||||||
if bundle_exists and not bundle_not_empty:
|
if bundle_exists and not bundle_not_empty:
|
||||||
# Clean up empty file
|
# Clean up empty file
|
||||||
try:
|
try:
|
||||||
os.remove(bundle_full_path)
|
os.remove(bundle_full_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
self.logger.warning("Could not remove empty bundle file.")
|
self.logger.warning("Could not remove empty bundle file.")
|
||||||
return None # Indicate non-fatal issue (empty bundle)
|
return None # Indicate non-fatal issue (empty bundle)
|
||||||
except Exception as bundle_e:
|
except Exception as bundle_e:
|
||||||
# Log and re-raise bundle creation errors
|
# Log and re-raise bundle creation errors
|
||||||
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 ---
|
||||||
@ -207,10 +240,9 @@ class ActionHandler:
|
|||||||
self.logger.info("Fetch/merge process completed successfully.")
|
self.logger.info("Fetch/merge process completed successfully.")
|
||||||
# No return needed, success indicated by no exception
|
# No return needed, success indicated by no exception
|
||||||
except Exception as fetch_e:
|
except Exception as fetch_e:
|
||||||
# Log and re-raise any error from fetch/merge
|
# Log and re-raise any error from fetch/merge
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@ -227,9 +259,9 @@ class ActionHandler:
|
|||||||
GitCommandError/Exception: If commit command fails.
|
GitCommandError/Exception: If commit command fails.
|
||||||
"""
|
"""
|
||||||
if not commit_message:
|
if not commit_message:
|
||||||
# This validation should ideally happen before calling this method
|
# This validation should ideally happen before calling this method
|
||||||
self.logger.error("Manual commit attempt with empty message.")
|
self.logger.error("Manual commit attempt with empty message.")
|
||||||
raise ValueError("Commit message cannot be empty.")
|
raise ValueError("Commit message cannot be empty.")
|
||||||
|
|
||||||
self.logger.info(f"Executing manual commit for: {svn_path}")
|
self.logger.info(f"Executing manual commit for: {svn_path}")
|
||||||
try:
|
try:
|
||||||
@ -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.
|
||||||
@ -270,22 +301,23 @@ class ActionHandler:
|
|||||||
# Block if changes exist but no message provided
|
# Block if changes exist but no message provided
|
||||||
msg = "Changes exist. Commit message required before tagging."
|
msg = "Changes exist. Commit message required before tagging."
|
||||||
self.logger.error(f"Tag creation blocked: {msg}")
|
self.logger.error(f"Tag creation blocked: {msg}")
|
||||||
raise ValueError(msg) # Raise error for UI layer
|
raise ValueError(msg) # Raise error for UI layer
|
||||||
|
|
||||||
# Perform the pre-tag commit with the provided message
|
# Perform the pre-tag commit with the provided message
|
||||||
self.logger.debug(f"Performing pre-tag commit: '{commit_message}'")
|
self.logger.debug(f"Performing pre-tag commit: '{commit_message}'")
|
||||||
# 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 ---
|
||||||
self.logger.info(f"Proceeding to create tag '{tag_name}'...")
|
self.logger.info(f"Proceeding to create tag '{tag_name}'...")
|
||||||
@ -294,13 +326,11 @@ class ActionHandler:
|
|||||||
# It raises ValueError for invalid name, GitCommandError for exists/fail
|
# It raises ValueError for invalid name, GitCommandError for exists/fail
|
||||||
self.git_commands.create_tag(svn_path, tag_name, tag_message)
|
self.git_commands.create_tag(svn_path, tag_name, tag_message)
|
||||||
self.logger.info(f"Tag '{tag_name}' created successfully.")
|
self.logger.info(f"Tag '{tag_name}' created successfully.")
|
||||||
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):
|
||||||
"""
|
"""
|
||||||
@ -327,31 +357,30 @@ class ActionHandler:
|
|||||||
if has_changes:
|
if has_changes:
|
||||||
msg = "Uncommitted changes exist. Commit or stash first."
|
msg = "Uncommitted changes exist. Commit or stash first."
|
||||||
self.logger.error(f"Checkout blocked: {msg}")
|
self.logger.error(f"Checkout blocked: {msg}")
|
||||||
raise ValueError(msg) # Raise specific error for UI
|
raise ValueError(msg) # Raise specific error for UI
|
||||||
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 ---
|
||||||
try:
|
try:
|
||||||
# git_commands.checkout_tag raises error on failure
|
# git_commands.checkout_tag raises error on failure
|
||||||
checkout_success = self.git_commands.checkout_tag(svn_path, tag_name)
|
checkout_success = self.git_commands.checkout_tag(svn_path, tag_name)
|
||||||
if checkout_success:
|
if checkout_success:
|
||||||
self.logger.info(f"Tag '{tag_name}' checked out.")
|
self.logger.info(f"Tag '{tag_name}' checked out.")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# This path should theoretically not be reached
|
# This path should theoretically not be reached
|
||||||
self.logger.error("Checkout command reported failure unexpectedly.")
|
self.logger.error("Checkout command reported failure unexpectedly.")
|
||||||
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.
|
||||||
@ -394,13 +424,12 @@ class ActionHandler:
|
|||||||
if has_changes:
|
if has_changes:
|
||||||
msg = "Uncommitted changes exist. Commit or stash first."
|
msg = "Uncommitted changes exist. Commit or stash first."
|
||||||
self.logger.error(f"Switch blocked: {msg}")
|
self.logger.error(f"Switch blocked: {msg}")
|
||||||
raise ValueError(msg) # Raise specific error for UI
|
raise ValueError(msg) # Raise specific error for UI
|
||||||
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 ---
|
||||||
try:
|
try:
|
||||||
@ -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.
|
||||||
@ -461,9 +490,8 @@ class ActionHandler:
|
|||||||
# Delegate deletion to GitCommands method
|
# Delegate deletion to GitCommands method
|
||||||
# This raises GitCommandError if tag not found or other git error
|
# This raises GitCommandError if tag not found or other git error
|
||||||
success = self.git_commands.delete_tag(svn_path, tag_name)
|
success = self.git_commands.delete_tag(svn_path, tag_name)
|
||||||
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
|
|
||||||
|
|||||||
@ -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}")
|
||||||
@ -78,13 +86,13 @@ class BackupHandler:
|
|||||||
files_added = 0
|
files_added = 0
|
||||||
files_excluded = 0
|
files_excluded = 0
|
||||||
dirs_excluded = 0
|
dirs_excluded = 0
|
||||||
zip_f = None # Initialize zip file object outside try block
|
zip_f = None # Initialize zip file object outside try block
|
||||||
|
|
||||||
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):
|
||||||
@ -101,8 +109,8 @@ class BackupHandler:
|
|||||||
if excluded_dirs_now:
|
if excluded_dirs_now:
|
||||||
dirs_excluded += len(excluded_dirs_now)
|
dirs_excluded += len(excluded_dirs_now)
|
||||||
for ex_dir in excluded_dirs_now:
|
for ex_dir in excluded_dirs_now:
|
||||||
path_excluded = os.path.join(root, ex_dir)
|
path_excluded = os.path.join(root, ex_dir)
|
||||||
self.logger.debug(f"Excluding directory: {path_excluded}")
|
self.logger.debug(f"Excluding directory: {path_excluded}")
|
||||||
|
|
||||||
# --- File Exclusion and Addition ---
|
# --- File Exclusion and Addition ---
|
||||||
for filename in files:
|
for filename in files:
|
||||||
@ -111,12 +119,14 @@ 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
|
||||||
continue # Skip this file
|
continue # Skip this file
|
||||||
|
|
||||||
# If not excluded, add file to ZIP
|
# If not excluded, add file to ZIP
|
||||||
file_full_path = os.path.join(root, filename)
|
file_full_path = os.path.join(root, filename)
|
||||||
@ -129,14 +139,14 @@ class BackupHandler:
|
|||||||
files_added += 1
|
files_added += 1
|
||||||
# Log progress periodically for large backups
|
# Log progress periodically for large backups
|
||||||
if files_added % 500 == 0:
|
if files_added % 500 == 0:
|
||||||
self.logger.debug(f"Added {files_added} files...")
|
self.logger.debug(f"Added {files_added} files...")
|
||||||
except Exception as write_e:
|
except Exception as write_e:
|
||||||
# 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
|
||||||
|
|
||||||
# Log final summary after successful walk and write attempts
|
# Log final summary after successful walk and write attempts
|
||||||
self.logger.info(f"Backup ZIP creation finished: {backup_full_path}")
|
self.logger.info(f"Backup ZIP creation finished: {backup_full_path}")
|
||||||
@ -148,15 +158,15 @@ class BackupHandler:
|
|||||||
return backup_full_path
|
return backup_full_path
|
||||||
|
|
||||||
except (OSError, zipfile.BadZipFile) as e:
|
except (OSError, zipfile.BadZipFile) as e:
|
||||||
# Handle OS errors (permissions, disk space) and ZIP format errors
|
# Handle OS errors (permissions, disk space) and ZIP format errors
|
||||||
self.logger.error(f"Error creating backup ZIP: {e}", exc_info=True)
|
self.logger.error(f"Error creating backup ZIP: {e}", exc_info=True)
|
||||||
# Re-raise as IOError for the caller to potentially handle specifically
|
# Re-raise as IOError for the caller to potentially handle specifically
|
||||||
raise IOError(f"Failed to create backup ZIP: {e}") from e
|
raise IOError(f"Failed to create backup ZIP: {e}") from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Catch any other unexpected error during the process
|
# Catch any other unexpected error during the process
|
||||||
self.logger.exception(f"Unexpected error during ZIP backup: {e}")
|
self.logger.exception(f"Unexpected error during ZIP backup: {e}")
|
||||||
# Re-raise the original exception
|
# Re-raise the original exception
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
# Ensure the ZIP file is always closed, even if errors occurred
|
# Ensure the ZIP file is always closed, even if errors occurred
|
||||||
if zip_f:
|
if zip_f:
|
||||||
@ -167,16 +177,16 @@ class BackupHandler:
|
|||||||
zip_exists = os.path.exists(backup_full_path)
|
zip_exists = os.path.exists(backup_full_path)
|
||||||
# Check if zip exists but no files were actually added
|
# Check if zip exists but no files were actually added
|
||||||
if zip_exists and files_added == 0:
|
if zip_exists and files_added == 0:
|
||||||
self.logger.warning(f"Backup ZIP is empty: {backup_full_path}")
|
self.logger.warning(f"Backup ZIP is empty: {backup_full_path}")
|
||||||
try:
|
try:
|
||||||
# Attempt to remove the empty zip file
|
# Attempt to remove the empty zip file
|
||||||
os.remove(backup_full_path)
|
os.remove(backup_full_path)
|
||||||
self.logger.info("Removed empty backup ZIP file.")
|
self.logger.info("Removed empty backup ZIP file.")
|
||||||
except OSError as rm_e:
|
except OSError as rm_e:
|
||||||
# Log error if removal fails, but don't stop execution
|
# Log error if removal fails, but don't stop execution
|
||||||
self.logger.error(f"Failed remove empty backup ZIP: {rm_e}")
|
self.logger.error(f"Failed remove empty backup ZIP: {rm_e}")
|
||||||
elif not zip_exists and files_added > 0:
|
elif not zip_exists and files_added > 0:
|
||||||
# This case indicates an issue if files were supposedly added
|
# This case indicates an issue if files were supposedly added
|
||||||
# but the zip file doesn't exist at the end (perhaps deleted?)
|
# but the zip file doesn't exist at the end (perhaps deleted?)
|
||||||
self.logger.error("Backup process finished but ZIP file missing.")
|
self.logger.error("Backup process finished but ZIP file missing.")
|
||||||
# Consider raising an error here if this state is critical
|
# Consider raising an error here if this state is critical
|
||||||
|
|||||||
1163
git_commands.py
1163
git_commands.py
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||||
@ -129,13 +130,13 @@ class ProfileHandler:
|
|||||||
defaults = self.config_manager._get_expected_keys_with_defaults()
|
defaults = self.config_manager._get_expected_keys_with_defaults()
|
||||||
defaults["bundle_name"] = f"{profile_name}_repo.bundle"
|
defaults["bundle_name"] = f"{profile_name}_repo.bundle"
|
||||||
defaults["bundle_name_updated"] = f"{profile_name}_update.bundle"
|
defaults["bundle_name_updated"] = f"{profile_name}_update.bundle"
|
||||||
defaults["svn_working_copy_path"] = "" # Start empty
|
defaults["svn_working_copy_path"] = "" # Start empty
|
||||||
defaults["usb_drive_path"] = "" # Start empty
|
defaults["usb_drive_path"] = "" # Start empty
|
||||||
|
|
||||||
# Set all options for the new profile section
|
# Set all options for the new profile section
|
||||||
# ConfigManager.set_profile_option creates the section if needed
|
# ConfigManager.set_profile_option creates the section if needed
|
||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
self.config_manager.set_profile_option(profile_name, key, value)
|
self.config_manager.set_profile_option(profile_name, key, value)
|
||||||
|
|
||||||
# Save the configuration file
|
# Save the configuration file
|
||||||
self.config_manager.save_config()
|
self.config_manager.save_config()
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user