versione con tab
This commit is contained in:
parent
7f15ca6caa
commit
59d32cb479
1324
GitTool.py
1324
GitTool.py
File diff suppressed because it is too large
Load Diff
BIN
GitUtility.ico
Normal file
BIN
GitUtility.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
310
GitUtility.py
310
GitUtility.py
@ -1,10 +1,12 @@
|
|||||||
# GitUtility.py
|
# GitUtility.py
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
# import shutil # Not needed anymore
|
||||||
import datetime
|
import datetime
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
import logging
|
import logging
|
||||||
import zipfile
|
# import zipfile # Not needed anymore
|
||||||
|
|
||||||
# Import application modules
|
# Import application modules
|
||||||
from config_manager import ConfigManager, DEFAULT_PROFILE, DEFAULT_BACKUP_DIR
|
from config_manager import ConfigManager, DEFAULT_PROFILE, DEFAULT_BACKUP_DIR
|
||||||
@ -63,7 +65,7 @@ class GitSvnSyncApp:
|
|||||||
self.main_frame = MainFrame(
|
self.main_frame = MainFrame(
|
||||||
master,
|
master,
|
||||||
load_profile_settings_cb=self.load_profile_settings,
|
load_profile_settings_cb=self.load_profile_settings,
|
||||||
browse_folder_cb=self.browse_folder,
|
# browse_folder_cb REMOVED - Handled within MainFrame now
|
||||||
update_svn_status_cb=self.update_svn_status_indicator,
|
update_svn_status_cb=self.update_svn_status_indicator,
|
||||||
# Core Action Callbacks
|
# Core Action Callbacks
|
||||||
prepare_svn_for_git_cb=self.ui_prepare_svn,
|
prepare_svn_for_git_cb=self.ui_prepare_svn,
|
||||||
@ -132,10 +134,8 @@ class GitSvnSyncApp:
|
|||||||
return None
|
return None
|
||||||
if not hasattr(self.main_frame, 'svn_path_entry'):
|
if not hasattr(self.main_frame, 'svn_path_entry'):
|
||||||
self.logger.error(f"{operation_name}: SVN path widget missing.")
|
self.logger.error(f"{operation_name}: SVN path widget missing.")
|
||||||
# Try showing error if main_frame exists, otherwise just log
|
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
self.main_frame.show_error("Internal Error",
|
self.main_frame.show_error("Internal Error", "SVN Path widget missing.")
|
||||||
"SVN Path widget not found.")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
svn_path_str = self.main_frame.svn_path_entry.get()
|
svn_path_str = self.main_frame.svn_path_entry.get()
|
||||||
@ -147,13 +147,8 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
abs_path = os.path.abspath(svn_path_str)
|
abs_path = os.path.abspath(svn_path_str)
|
||||||
if not os.path.isdir(abs_path):
|
if not os.path.isdir(abs_path):
|
||||||
self.logger.error(
|
self.logger.error(f"{operation_name}: Invalid directory path: {abs_path}")
|
||||||
f"{operation_name}: Invalid directory path: {abs_path}"
|
self.main_frame.show_error("Input Error", f"Invalid SVN path:\n{abs_path}")
|
||||||
)
|
|
||||||
self.main_frame.show_error(
|
|
||||||
"Input Error",
|
|
||||||
f"Invalid SVN path (not a directory):\n{abs_path}"
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.logger.debug(f"{operation_name}: Validated SVN path: {abs_path}")
|
self.logger.debug(f"{operation_name}: Validated SVN path: {abs_path}")
|
||||||
@ -168,32 +163,72 @@ class GitSvnSyncApp:
|
|||||||
if not hasattr(self.main_frame, 'usb_path_entry'):
|
if not hasattr(self.main_frame, 'usb_path_entry'):
|
||||||
self.logger.error(f"{operation_name}: USB path widget missing.")
|
self.logger.error(f"{operation_name}: USB path widget missing.")
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
self.main_frame.show_error("Internal Error",
|
self.main_frame.show_error("Internal Error", "USB Path widget missing.")
|
||||||
"USB Path widget not found.")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
usb_path_str = self.main_frame.usb_path_entry.get()
|
usb_path_str = self.main_frame.usb_path_entry.get()
|
||||||
usb_path_str = usb_path_str.strip()
|
usb_path_str = usb_path_str.strip()
|
||||||
if not usb_path_str:
|
if not usb_path_str:
|
||||||
self.logger.error(f"{operation_name}: Bundle Target Dir path empty.")
|
self.logger.error(f"{operation_name}: Bundle Target Dir path empty.")
|
||||||
self.main_frame.show_error("Input Error",
|
self.main_frame.show_error("Input Error", "Bundle Target Dir empty.")
|
||||||
"Bundle Target Directory cannot be empty.")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
abs_path = os.path.abspath(usb_path_str)
|
abs_path = os.path.abspath(usb_path_str)
|
||||||
if not os.path.isdir(abs_path):
|
if not os.path.isdir(abs_path):
|
||||||
self.logger.error(
|
self.logger.error(f"{operation_name}: Invalid Bundle Target: {abs_path}")
|
||||||
f"{operation_name}: Invalid Bundle Target directory: {abs_path}"
|
self.main_frame.show_error("Input Error", f"Invalid Bundle Target:\n{abs_path}")
|
||||||
)
|
|
||||||
self.main_frame.show_error(
|
|
||||||
"Input Error",
|
|
||||||
f"Invalid Bundle Target path (not a directory):\n{abs_path}"
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.logger.debug(f"{operation_name}: Validated Bundle Target path: {abs_path}")
|
self.logger.debug(f"{operation_name}: Validated Bundle Target path: {abs_path}")
|
||||||
return abs_path
|
return abs_path
|
||||||
|
|
||||||
|
# --- ADDED: Helper to parse exclusions directly here ---
|
||||||
|
def _parse_exclusions(self, profile_name):
|
||||||
|
"""
|
||||||
|
Parses exclusion string from config for the given profile.
|
||||||
|
Needed here because ActionHandler might not have direct ConfigManager access,
|
||||||
|
and exclusions are needed for backup steps within actions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
profile_name (str): The name of the profile.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (set of excluded extensions (lowercase, starting with '.'),
|
||||||
|
set of excluded base directory names (lowercase))
|
||||||
|
Raises:
|
||||||
|
ValueError: If exclusion string parsing fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get exclusion string from config
|
||||||
|
exclude_str = self.config_manager.get_profile_option(
|
||||||
|
profile_name, "backup_exclude_extensions", fallback=""
|
||||||
|
)
|
||||||
|
excluded_extensions = set()
|
||||||
|
# Define standard directories to always exclude (lowercase for comparison)
|
||||||
|
excluded_dirs_base = {".git", ".svn"}
|
||||||
|
|
||||||
|
if exclude_str:
|
||||||
|
raw_extensions = exclude_str.split(',')
|
||||||
|
for ext in raw_extensions:
|
||||||
|
clean_ext = ext.strip().lower()
|
||||||
|
if not clean_ext:
|
||||||
|
continue # Skip empty parts
|
||||||
|
# Ensure extension starts with a dot
|
||||||
|
if not clean_ext.startswith('.'):
|
||||||
|
clean_ext = '.' + clean_ext
|
||||||
|
excluded_extensions.add(clean_ext)
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
f"Parsed Exclusions '{profile_name}' - "
|
||||||
|
f"Ext: {excluded_extensions}, Dirs: {excluded_dirs_base}"
|
||||||
|
)
|
||||||
|
return excluded_extensions, excluded_dirs_base
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error parsing exclusions for '{profile_name}': {e}",
|
||||||
|
exc_info=True)
|
||||||
|
# Raise a specific error to indicate parsing failure
|
||||||
|
raise ValueError(f"Could not parse backup exclusions: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
# --- Profile Handling Wrappers ---
|
# --- Profile Handling Wrappers ---
|
||||||
def load_profile_settings(self, profile_name):
|
def load_profile_settings(self, profile_name):
|
||||||
@ -207,15 +242,14 @@ class GitSvnSyncApp:
|
|||||||
profile_data = self.profile_handler.load_profile_data(profile_name)
|
profile_data = self.profile_handler.load_profile_data(profile_name)
|
||||||
if not profile_data:
|
if not profile_data:
|
||||||
# Handler logs error, show message and clear UI
|
# Handler logs error, show message and clear UI
|
||||||
self.main_frame.show_error("Load Error",
|
self.main_frame.show_error("Load Error", f"Could not load '{profile_name}'.")
|
||||||
f"Could not load profile '{profile_name}'.")
|
|
||||||
self._clear_and_disable_fields()
|
self._clear_and_disable_fields()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update GUI fields with loaded data if frame exists
|
# Update GUI fields with loaded data if frame exists
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
mf = self.main_frame # Alias
|
mf = self.main_frame # Alias
|
||||||
# Update Repository Frame widgets
|
# Update all widgets based on loaded data
|
||||||
mf.svn_path_entry.delete(0, tk.END)
|
mf.svn_path_entry.delete(0, tk.END)
|
||||||
mf.svn_path_entry.insert(0, profile_data.get("svn_working_copy_path", ""))
|
mf.svn_path_entry.insert(0, profile_data.get("svn_working_copy_path", ""))
|
||||||
mf.usb_path_entry.delete(0, tk.END)
|
mf.usb_path_entry.delete(0, tk.END)
|
||||||
@ -224,10 +258,8 @@ class GitSvnSyncApp:
|
|||||||
mf.bundle_name_entry.insert(0, profile_data.get("bundle_name", ""))
|
mf.bundle_name_entry.insert(0, profile_data.get("bundle_name", ""))
|
||||||
mf.bundle_updated_name_entry.delete(0, tk.END)
|
mf.bundle_updated_name_entry.delete(0, tk.END)
|
||||||
mf.bundle_updated_name_entry.insert(0, profile_data.get("bundle_name_updated", ""))
|
mf.bundle_updated_name_entry.insert(0, profile_data.get("bundle_name_updated", ""))
|
||||||
# Update Commit/Tag Frame widgets
|
|
||||||
mf.autocommit_var.set(profile_data.get("autocommit", False)) # Bool
|
mf.autocommit_var.set(profile_data.get("autocommit", False)) # Bool
|
||||||
mf.commit_message_var.set(profile_data.get("commit_message", ""))
|
mf.commit_message_var.set(profile_data.get("commit_message", ""))
|
||||||
# Update Backup Frame widgets
|
|
||||||
mf.autobackup_var.set(profile_data.get("autobackup", False)) # Bool
|
mf.autobackup_var.set(profile_data.get("autobackup", False)) # Bool
|
||||||
mf.backup_dir_var.set(profile_data.get("backup_dir", DEFAULT_BACKUP_DIR))
|
mf.backup_dir_var.set(profile_data.get("backup_dir", DEFAULT_BACKUP_DIR))
|
||||||
mf.backup_exclude_extensions_var.set(
|
mf.backup_exclude_extensions_var.set(
|
||||||
@ -264,24 +296,24 @@ class GitSvnSyncApp:
|
|||||||
profile = self.main_frame.profile_var.get()
|
profile = self.main_frame.profile_var.get()
|
||||||
if not profile:
|
if not profile:
|
||||||
self.main_frame.show_error("Save Error", "No profile selected.")
|
self.main_frame.show_error("Save Error", "No profile selected.")
|
||||||
return # Don't proceed
|
return False # Indicate failure to caller if needed
|
||||||
|
|
||||||
# Gather data from GUI
|
# Gather data from GUI
|
||||||
current_data = self._get_data_from_gui()
|
current_data = self._get_data_from_gui()
|
||||||
if current_data is None: # Check if reading GUI failed
|
if current_data is None: # Check if reading GUI failed
|
||||||
self.main_frame.show_error("Internal Error", "Could not read GUI data.")
|
self.main_frame.show_error("Internal Error", "Could not read GUI data.")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Delegate saving to ProfileHandler
|
# Delegate saving to ProfileHandler
|
||||||
success = self.profile_handler.save_profile_data(profile, current_data)
|
success = self.profile_handler.save_profile_data(profile, current_data)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
# Give positive feedback
|
|
||||||
self.main_frame.show_info("Saved", f"Settings saved for '{profile}'.")
|
self.main_frame.show_info("Saved", f"Settings saved for '{profile}'.")
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
# Error message likely shown by handler/save method
|
# Error message likely shown by handler/save method
|
||||||
self.logger.error(f"Saving settings failed for profile '{profile}'.")
|
# self.main_frame.show_error("Save Error", f"Failed save settings.")
|
||||||
# self.main_frame.show_error("Save Error", "Failed to save settings.")
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _get_data_from_gui(self):
|
def _get_data_from_gui(self):
|
||||||
@ -292,17 +324,16 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
mf = self.main_frame
|
mf = self.main_frame
|
||||||
# Read values from all relevant widgets/variables
|
# Read values from all relevant widgets/variables
|
||||||
data = {
|
data = {}
|
||||||
"svn_working_copy_path": mf.svn_path_entry.get(),
|
data["svn_working_copy_path"] = mf.svn_path_entry.get()
|
||||||
"usb_drive_path": mf.usb_path_entry.get(),
|
data["usb_drive_path"] = mf.usb_path_entry.get()
|
||||||
"bundle_name": mf.bundle_name_entry.get(),
|
data["bundle_name"] = mf.bundle_name_entry.get()
|
||||||
"bundle_name_updated": mf.bundle_updated_name_entry.get(),
|
data["bundle_name_updated"] = mf.bundle_updated_name_entry.get()
|
||||||
"autocommit": mf.autocommit_var.get(), # Gets boolean
|
data["autocommit"] = mf.autocommit_var.get() # Gets boolean
|
||||||
"commit_message": mf.commit_message_var.get(),
|
data["commit_message"] = mf.commit_message_var.get()
|
||||||
"autobackup": mf.autobackup_var.get(), # Gets boolean
|
data["autobackup"] = mf.autobackup_var.get() # Gets boolean
|
||||||
"backup_dir": mf.backup_dir_var.get(),
|
data["backup_dir"] = mf.backup_dir_var.get()
|
||||||
"backup_exclude_extensions": mf.backup_exclude_extensions_var.get()
|
data["backup_exclude_extensions"] = mf.backup_exclude_extensions_var.get()
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -315,19 +346,16 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
new_name = new_name.strip()
|
new_name = new_name.strip()
|
||||||
if not new_name:
|
if not new_name:
|
||||||
self.main_frame.show_error("Error", "Profile name cannot be empty."); return
|
self.main_frame.show_error("Error", "Profile name empty."); return
|
||||||
|
|
||||||
# Delegate adding logic
|
# Delegate adding logic
|
||||||
success = self.profile_handler.add_new_profile(new_name)
|
success = self.profile_handler.add_new_profile(new_name)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
# Update GUI dropdown and select the new profile
|
|
||||||
sections = self.profile_handler.get_profile_list()
|
sections = self.profile_handler.get_profile_list()
|
||||||
self.main_frame.update_profile_dropdown(sections)
|
self.main_frame.update_profile_dropdown(sections)
|
||||||
self.main_frame.profile_var.set(new_name) # Triggers load
|
self.main_frame.profile_var.set(new_name) # Triggers load
|
||||||
self.main_frame.show_info("Profile Added", f"Profile '{new_name}' created.")
|
self.main_frame.show_info("Profile Added", f"Profile '{new_name}' created.")
|
||||||
else:
|
else:
|
||||||
# Handler logged the reason (exists or error)
|
|
||||||
self.main_frame.show_error("Error", f"Could not add profile '{new_name}'.")
|
self.main_frame.show_error("Error", f"Could not add profile '{new_name}'.")
|
||||||
|
|
||||||
|
|
||||||
@ -350,10 +378,9 @@ class GitSvnSyncApp:
|
|||||||
self.main_frame.update_profile_dropdown(sections) # Triggers load
|
self.main_frame.update_profile_dropdown(sections) # Triggers load
|
||||||
self.main_frame.show_info("Removed", f"Profile '{profile}' removed.")
|
self.main_frame.show_info("Removed", f"Profile '{profile}' removed.")
|
||||||
else:
|
else:
|
||||||
# Handler logged the reason
|
|
||||||
self.main_frame.show_error("Error", f"Failed to remove '{profile}'.")
|
self.main_frame.show_error("Error", f"Failed to remove '{profile}'.")
|
||||||
else:
|
else:
|
||||||
self.logger.info("Profile removal cancelled.")
|
self.logger.info("Removal cancelled.")
|
||||||
|
|
||||||
|
|
||||||
# --- GUI Interaction Wrappers ---
|
# --- GUI Interaction Wrappers ---
|
||||||
@ -369,7 +396,7 @@ class GitSvnSyncApp:
|
|||||||
self.logger.debug(f"Selected: {directory}")
|
self.logger.debug(f"Selected: {directory}")
|
||||||
entry_widget.delete(0, tk.END)
|
entry_widget.delete(0, tk.END)
|
||||||
entry_widget.insert(0, directory)
|
entry_widget.insert(0, directory)
|
||||||
# Update status if SVN path changed
|
# Trigger status update if SVN path changed
|
||||||
if entry_widget == self.main_frame.svn_path_entry:
|
if entry_widget == self.main_frame.svn_path_entry:
|
||||||
self.update_svn_status_indicator(directory)
|
self.update_svn_status_indicator(directory)
|
||||||
else:
|
else:
|
||||||
@ -385,10 +412,10 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
mf = self.main_frame # Alias
|
mf = self.main_frame # Alias
|
||||||
# Update indicator & Prepare button via MainFrame method
|
# Update indicator & Prepare button via GUI method
|
||||||
mf.update_svn_indicator(is_ready)
|
mf.update_svn_indicator(is_ready)
|
||||||
|
|
||||||
# Determine states for other widgets based on validity/readiness
|
# Determine states for other dependent widgets
|
||||||
gitignore_state = tk.NORMAL if is_valid else tk.DISABLED
|
gitignore_state = tk.NORMAL if is_valid else tk.DISABLED
|
||||||
repo_ready_state = tk.NORMAL if is_ready else tk.DISABLED
|
repo_ready_state = tk.NORMAL if is_ready else tk.DISABLED
|
||||||
|
|
||||||
@ -425,7 +452,7 @@ class GitSvnSyncApp:
|
|||||||
"""Opens the modal editor window for .gitignore."""
|
"""Opens the modal editor window for .gitignore."""
|
||||||
self.logger.info("--- Action Triggered: Edit .gitignore ---")
|
self.logger.info("--- Action Triggered: 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: return # Stop if path invalid
|
if not svn_path: return
|
||||||
|
|
||||||
gitignore_path = os.path.join(svn_path, ".gitignore")
|
gitignore_path = os.path.join(svn_path, ".gitignore")
|
||||||
self.logger.debug(f"Target .gitignore path: {gitignore_path}")
|
self.logger.debug(f"Target .gitignore path: {gitignore_path}")
|
||||||
@ -449,9 +476,12 @@ class GitSvnSyncApp:
|
|||||||
if not svn_path: return
|
if not svn_path: return
|
||||||
|
|
||||||
# Save settings before action
|
# Save settings before action
|
||||||
|
# Use ui_save_settings which returns True/False
|
||||||
if not self.ui_save_settings():
|
if not self.ui_save_settings():
|
||||||
self.logger.warning("Prepare SVN: Failed save settings first.")
|
self.logger.warning("Prepare SVN: Failed to save settings first.")
|
||||||
# Ask user?
|
# Ask user if they want to continue?
|
||||||
|
# if not self.main_frame.ask_yes_no("Warning", "Could not save settings.\nContinue anyway?"):
|
||||||
|
# return
|
||||||
|
|
||||||
# Delegate execution to ActionHandler
|
# Delegate execution to ActionHandler
|
||||||
try:
|
try:
|
||||||
@ -491,7 +521,7 @@ class GitSvnSyncApp:
|
|||||||
# Ensure .bundle extension
|
# Ensure .bundle extension
|
||||||
if not bundle_name.lower().endswith(".bundle"):
|
if not bundle_name.lower().endswith(".bundle"):
|
||||||
bundle_name += ".bundle"
|
bundle_name += ".bundle"
|
||||||
mf = self.main_frame
|
mf = self.main_frame # Alias
|
||||||
mf.bundle_name_entry.delete(0, tk.END)
|
mf.bundle_name_entry.delete(0, tk.END)
|
||||||
mf.bundle_name_entry.insert(0, bundle_name)
|
mf.bundle_name_entry.insert(0, bundle_name)
|
||||||
bundle_full_path = os.path.join(usb_path, bundle_name)
|
bundle_full_path = os.path.join(usb_path, bundle_name)
|
||||||
@ -669,24 +699,26 @@ class GitSvnSyncApp:
|
|||||||
# Get commit message from the GUI entry
|
# Get commit message from the GUI entry
|
||||||
commit_msg = self.main_frame.commit_message_var.get().strip()
|
commit_msg = self.main_frame.commit_message_var.get().strip()
|
||||||
if not commit_msg:
|
if not commit_msg:
|
||||||
self.main_frame.show_error("Commit Error", "Commit message cannot be empty.")
|
self.main_frame.show_error("Commit Error", "Commit message empty.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save settings first? Optional, but saves the message if typed.
|
# Save settings first? Optional, but saves the message if user typed it.
|
||||||
if not self.ui_save_settings():
|
if not self.ui_save_settings():
|
||||||
self.logger.warning("Manual Commit: Could not save settings.")
|
self.logger.warning("Manual Commit: Could not save settings.")
|
||||||
# Ask user if they want to continue?
|
# Ask user?
|
||||||
|
|
||||||
# Delegate commit execution to ActionHandler
|
# Delegate commit execution to ActionHandler
|
||||||
try:
|
try:
|
||||||
commit_made = self.action_handler.execute_manual_commit(svn_path, commit_msg)
|
commit_made = self.action_handler.execute_manual_commit(
|
||||||
|
svn_path, commit_msg
|
||||||
|
)
|
||||||
if commit_made:
|
if commit_made:
|
||||||
self.main_frame.show_info("Success", "Changes committed.")
|
self.main_frame.show_info("Success", "Changes committed.")
|
||||||
# Optionally clear message field after successful commit
|
# Clear message field after successful commit? Optional.
|
||||||
# self.main_frame.commit_message_var.set("")
|
# self.main_frame.commit_message_var.set("")
|
||||||
else:
|
else:
|
||||||
# git_commit already logged "nothing to commit"
|
# git_commit already logged "nothing to commit"
|
||||||
self.main_frame.show_info("Info", "No changes were detected to commit.")
|
self.main_frame.show_info("Info", "No changes to commit.")
|
||||||
|
|
||||||
except (GitCommandError, ValueError) as e:
|
except (GitCommandError, ValueError) as e:
|
||||||
self.logger.error(f"Manual commit failed: {e}")
|
self.logger.error(f"Manual commit failed: {e}")
|
||||||
@ -708,10 +740,10 @@ class GitSvnSyncApp:
|
|||||||
self.main_frame.update_tag_list([]) # Clear list
|
self.main_frame.update_tag_list([]) # Clear list
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Fetch tags and update GUI
|
||||||
try:
|
try:
|
||||||
# Get tag data (list of tuples) from GitCommands
|
# list_tags returns list of tuples (name, subject)
|
||||||
tags_data = self.git_commands.list_tags(svn_path)
|
tags_data = self.git_commands.list_tags(svn_path)
|
||||||
# Update the GUI listbox
|
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
self.main_frame.update_tag_list(tags_data)
|
self.main_frame.update_tag_list(tags_data)
|
||||||
self.logger.info(f"Tag list updated ({len(tags_data)} tags).")
|
self.logger.info(f"Tag list updated ({len(tags_data)} tags).")
|
||||||
@ -729,9 +761,10 @@ class GitSvnSyncApp:
|
|||||||
if not svn_path: return
|
if not svn_path: return
|
||||||
profile = self.main_frame.profile_var.get()
|
profile = self.main_frame.profile_var.get()
|
||||||
if not profile:
|
if not profile:
|
||||||
self.main_frame.show_error("Error", "No profile selected."); return
|
self.main_frame.show_error("Error", "No profile selected.")
|
||||||
|
return
|
||||||
|
|
||||||
# Get commit message from GUI (for potential pre-commit)
|
# Get commit message from GUI (needed by action handler for pre-commit)
|
||||||
commit_msg = self.main_frame.commit_message_var.get().strip()
|
commit_msg = self.main_frame.commit_message_var.get().strip()
|
||||||
|
|
||||||
# Save settings before action (saves commit message)
|
# Save settings before action (saves commit message)
|
||||||
@ -739,32 +772,40 @@ class GitSvnSyncApp:
|
|||||||
self.logger.warning("Create Tag: Could not save settings first.")
|
self.logger.warning("Create Tag: Could not save settings first.")
|
||||||
# Ask user?
|
# Ask user?
|
||||||
|
|
||||||
# Open Dialog first to get Tag Name and Tag Message
|
# --- Open Dialog to get Tag Name and Tag Message ---
|
||||||
self.logger.debug("Opening create tag dialog...")
|
self.logger.debug("Opening create tag dialog...")
|
||||||
dialog = CreateTagDialog(self.master)
|
dialog = CreateTagDialog(self.master) # Parent is the main Tk window
|
||||||
tag_info = dialog.result # Returns (tag_name, tag_message) or None
|
tag_info = dialog.result # Returns (tag_name, tag_message) or None
|
||||||
|
|
||||||
if not tag_info:
|
if not tag_info:
|
||||||
self.logger.info("Tag creation cancelled in dialog."); return
|
# User cancelled the dialog
|
||||||
|
self.logger.info("Tag creation cancelled by user in dialog.")
|
||||||
|
return
|
||||||
|
|
||||||
tag_name, tag_message = tag_info
|
tag_name, tag_message = tag_info
|
||||||
self.logger.info(f"User provided tag: '{tag_name}', msg: '{tag_message}'")
|
self.logger.info(f"User provided tag: '{tag_name}', msg: '{tag_message}'")
|
||||||
|
|
||||||
# Delegate Execution (including potential pre-commit) to ActionHandler
|
# --- Delegate Execution to ActionHandler ---
|
||||||
try:
|
try:
|
||||||
|
# ActionHandler manages pre-commit logic based on commit_msg presence
|
||||||
success = self.action_handler.execute_create_tag(
|
success = self.action_handler.execute_create_tag(
|
||||||
svn_path, commit_msg, tag_name, tag_message
|
svn_path, commit_msg, tag_name, tag_message
|
||||||
)
|
)
|
||||||
if success:
|
# execute_create_tag raises exceptions on failure
|
||||||
|
if success: # Should be true if no exception
|
||||||
self.logger.info(f"Tag '{tag_name}' created successfully.")
|
self.logger.info(f"Tag '{tag_name}' created successfully.")
|
||||||
self.main_frame.show_info("Success", f"Tag '{tag_name}' created.")
|
self.main_frame.show_info("Success", f"Tag '{tag_name}' created.")
|
||||||
self.refresh_tag_list() # Update list
|
self.refresh_tag_list() # Update list after successful creation
|
||||||
except ValueError as e: # Catch specific errors like "commit message required"
|
except ValueError as e:
|
||||||
|
# Catch specific errors like "commit message required"
|
||||||
self.logger.error(f"Tag creation validation failed: {e}")
|
self.logger.error(f"Tag creation validation failed: {e}")
|
||||||
self.main_frame.show_error("Tag Error", str(e))
|
self.main_frame.show_error("Tag Error", str(e))
|
||||||
except GitCommandError as e: # Catch Git command errors (commit or tag)
|
except GitCommandError as e:
|
||||||
|
# Catch Git command errors (from commit or tag)
|
||||||
self.logger.error(f"Tag creation failed (Git Error): {e}")
|
self.logger.error(f"Tag creation failed (Git Error): {e}")
|
||||||
self.main_frame.show_error("Tag Error", f"Git command failed:\n{e}")
|
self.main_frame.show_error("Tag Error", f"Git command failed:\n{e}")
|
||||||
except Exception as e: # Catch unexpected errors
|
except Exception as e:
|
||||||
|
# Catch unexpected errors
|
||||||
self.logger.exception(f"Unexpected error creating tag: {e}")
|
self.logger.exception(f"Unexpected error creating tag: {e}")
|
||||||
self.main_frame.show_error("Error", f"Unexpected error:\n{e}")
|
self.main_frame.show_error("Error", f"Unexpected error:\n{e}")
|
||||||
|
|
||||||
@ -777,7 +818,8 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
selected_tag = self.main_frame.get_selected_tag() # Gets name
|
selected_tag = self.main_frame.get_selected_tag() # Gets name
|
||||||
if not selected_tag:
|
if not selected_tag:
|
||||||
self.main_frame.show_error("Selection Error", "Select a tag."); return
|
self.main_frame.show_error("Selection Error", "Select a tag.")
|
||||||
|
return
|
||||||
|
|
||||||
self.logger.info(f"Attempting checkout for tag: {selected_tag}")
|
self.logger.info(f"Attempting checkout for tag: {selected_tag}")
|
||||||
|
|
||||||
@ -790,7 +832,7 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
# Save settings before action? Optional.
|
# Save settings before action? Optional.
|
||||||
if not self.ui_save_settings():
|
if not self.ui_save_settings():
|
||||||
self.logger.warning("Checkout Tag: Could not save settings.")
|
self.logger.warning("Checkout Tag: Could not save profile settings.")
|
||||||
|
|
||||||
# Delegate execution to ActionHandler
|
# Delegate execution to ActionHandler
|
||||||
try:
|
try:
|
||||||
@ -798,7 +840,7 @@ class GitSvnSyncApp:
|
|||||||
if success:
|
if success:
|
||||||
self.main_frame.show_info("Success",
|
self.main_frame.show_info("Success",
|
||||||
f"Checked out tag '{selected_tag}'.\n\nNOTE: In 'detached HEAD'.")
|
f"Checked out tag '{selected_tag}'.\n\nNOTE: In 'detached HEAD'.")
|
||||||
# Update display after successful checkout
|
# Update branch display after successful checkout
|
||||||
self.update_current_branch_display()
|
self.update_current_branch_display()
|
||||||
except ValueError as e: # Catch specific errors like "uncommitted changes"
|
except ValueError as e: # Catch specific errors like "uncommitted changes"
|
||||||
self.main_frame.show_error("Checkout Blocked", str(e))
|
self.main_frame.show_error("Checkout Blocked", str(e))
|
||||||
@ -820,7 +862,7 @@ class GitSvnSyncApp:
|
|||||||
self.main_frame.show_error("Selection Error", "Select a tag to delete.")
|
self.main_frame.show_error("Selection Error", "Select a tag to delete.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Confirmation
|
# Confirmation dialog
|
||||||
msg = f"Delete tag '{selected_tag}' permanently?\nCannot be easily undone."
|
msg = f"Delete tag '{selected_tag}' permanently?\nCannot be easily undone."
|
||||||
if not self.main_frame.ask_yes_no("Confirm Delete Tag", msg):
|
if not self.main_frame.ask_yes_no("Confirm Delete Tag", msg):
|
||||||
self.logger.info("Tag deletion cancelled.")
|
self.logger.info("Tag deletion cancelled.")
|
||||||
@ -856,8 +898,10 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
# Fetch and update GUI
|
# Fetch and update GUI
|
||||||
try:
|
try:
|
||||||
|
# Assumes git_commands method exists and returns list of names
|
||||||
branches = self.git_commands.list_branches(svn_path)
|
branches = self.git_commands.list_branches(svn_path)
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
|
# Update listbox and potentially current branch display implicitly
|
||||||
self.main_frame.update_branch_list(branches)
|
self.main_frame.update_branch_list(branches)
|
||||||
self.logger.info(f"Branch list updated ({len(branches)} branches).")
|
self.logger.info(f"Branch list updated ({len(branches)} branches).")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -871,22 +915,25 @@ class GitSvnSyncApp:
|
|||||||
"""Gets the current branch and updates the display label."""
|
"""Gets the current branch and updates the display label."""
|
||||||
self.logger.debug("Updating current branch display...")
|
self.logger.debug("Updating current branch display...")
|
||||||
svn_path = self._get_and_validate_svn_path("Update Branch Display")
|
svn_path = self._get_and_validate_svn_path("Update Branch Display")
|
||||||
current_branch_name = "<N/A>" # Default
|
current_branch_name = "<N/A>" # Default value
|
||||||
|
|
||||||
# Only query git if repo is ready
|
# Only query git if repo is ready
|
||||||
if svn_path and os.path.exists(os.path.join(svn_path, ".git")):
|
if svn_path and os.path.exists(os.path.join(svn_path, ".git")):
|
||||||
try:
|
try:
|
||||||
|
# Use GitCommands to get branch name
|
||||||
branch_name = self.git_commands.get_current_branch(svn_path)
|
branch_name = self.git_commands.get_current_branch(svn_path)
|
||||||
# Handle return values from get_current_branch
|
# Update display text based on result
|
||||||
if branch_name == "(DETACHED HEAD)":
|
if branch_name == "(DETACHED HEAD)":
|
||||||
current_branch_name = branch_name
|
current_branch_name = branch_name # Show detached state clearly
|
||||||
elif branch_name == "<Error>":
|
elif branch_name == "<Error>":
|
||||||
current_branch_name = "<Error>"
|
current_branch_name = "<Error>" # Show error state
|
||||||
elif branch_name:
|
elif branch_name:
|
||||||
current_branch_name = branch_name
|
current_branch_name = branch_name # Show actual branch name
|
||||||
else: # Should not happen if logic in get_current_branch is correct
|
else:
|
||||||
|
# Fallback if method returns None unexpectedly
|
||||||
current_branch_name = "<Unknown>"
|
current_branch_name = "<Unknown>"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Handle exceptions during git command execution
|
||||||
self.logger.error(f"Failed to get current branch: {e}")
|
self.logger.error(f"Failed to get current branch: {e}")
|
||||||
current_branch_name = "<Error>"
|
current_branch_name = "<Error>"
|
||||||
|
|
||||||
@ -912,18 +959,15 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
# Delegate execution to ActionHandler
|
# Delegate execution to ActionHandler
|
||||||
try:
|
try:
|
||||||
# TODO: Add start_point logic if needed later
|
# Assuming ActionHandler has execute_create_branch
|
||||||
|
# TODO: Add start_point logic later if needed
|
||||||
success = self.action_handler.execute_create_branch(
|
success = self.action_handler.execute_create_branch(
|
||||||
svn_path, new_branch_name
|
svn_path, new_branch_name
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
self.main_frame.show_info("Success", f"Branch '{new_branch_name}' created.")
|
self.main_frame.show_info("Success", f"Branch '{new_branch_name}' created.")
|
||||||
self.refresh_branch_list() # Update list
|
self.refresh_branch_list() # Update list
|
||||||
# Ask user if they want to switch?
|
# Optionally ask to switch
|
||||||
# switch_q = f"Switch to new branch '{new_branch_name}'?"
|
|
||||||
# if self.main_frame.ask_yes_no("Switch Branch?", switch_q):
|
|
||||||
# self.ui_switch_branch(new_branch_name) # Needs method adjustment
|
|
||||||
|
|
||||||
except (GitCommandError, ValueError) as e:
|
except (GitCommandError, ValueError) as e:
|
||||||
self.main_frame.show_error("Error", f"Could not create branch:\n{e}")
|
self.main_frame.show_error("Error", f"Could not create branch:\n{e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -941,7 +985,7 @@ class GitSvnSyncApp:
|
|||||||
if not selected_branch:
|
if not selected_branch:
|
||||||
self.main_frame.show_error("Error", "Select a branch."); return
|
self.main_frame.show_error("Error", "Select a branch."); return
|
||||||
|
|
||||||
# Avoid switching to the same branch
|
# Prevent switching to the same branch
|
||||||
current_branch = self.main_frame.current_branch_var.get()
|
current_branch = self.main_frame.current_branch_var.get()
|
||||||
if selected_branch == current_branch:
|
if selected_branch == current_branch:
|
||||||
self.main_frame.show_info("Info", f"Already on branch '{selected_branch}'.")
|
self.main_frame.show_info("Info", f"Already on branch '{selected_branch}'.")
|
||||||
@ -952,14 +996,15 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
# Delegate execution (ActionHandler checks for changes)
|
# Delegate execution (ActionHandler checks for changes)
|
||||||
try:
|
try:
|
||||||
|
# Assuming ActionHandler has execute_switch_branch
|
||||||
success = self.action_handler.execute_switch_branch(
|
success = self.action_handler.execute_switch_branch(
|
||||||
svn_path, selected_branch
|
svn_path, selected_branch
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
self.main_frame.show_info("Success", f"Switched to branch '{selected_branch}'.")
|
self.main_frame.show_info("Success", f"Switched to branch '{selected_branch}'.")
|
||||||
# Update UI after successful switch
|
# Update UI after successful switch
|
||||||
self.update_current_branch_display()
|
self.update_current_branch_display() # Update label immediately
|
||||||
self.refresh_branch_list() # Update highlight
|
self.refresh_branch_list() # Update highlight in list
|
||||||
# else: Handler raises error
|
# else: Handler raises error
|
||||||
except ValueError as e: # Catch specific errors like uncommitted changes
|
except ValueError as e: # Catch specific errors like uncommitted changes
|
||||||
self.main_frame.show_error("Switch Blocked", str(e))
|
self.main_frame.show_error("Switch Blocked", str(e))
|
||||||
@ -997,13 +1042,15 @@ class GitSvnSyncApp:
|
|||||||
|
|
||||||
# Delegate execution
|
# Delegate execution
|
||||||
try:
|
try:
|
||||||
|
# Attempt safe delete first
|
||||||
success = self.action_handler.execute_delete_branch(
|
success = self.action_handler.execute_delete_branch(
|
||||||
svn_path, selected_branch, force=False # Attempt safe delete first
|
svn_path, selected_branch, force=False
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
self.main_frame.show_info("Success", f"Branch '{selected_branch}' deleted.")
|
self.main_frame.show_info("Success", f"Branch '{selected_branch}' deleted.")
|
||||||
self.refresh_branch_list() # Update list
|
self.refresh_branch_list() # Update list
|
||||||
# else: Handler raises error
|
# else: Handler should raise error
|
||||||
|
|
||||||
except GitCommandError as e:
|
except GitCommandError as e:
|
||||||
# Handle specific errors, like 'not fully merged'
|
# Handle specific errors, like 'not fully merged'
|
||||||
if "not fully merged" in str(e).lower():
|
if "not fully merged" in str(e).lower():
|
||||||
@ -1036,8 +1083,8 @@ class GitSvnSyncApp:
|
|||||||
def _clear_and_disable_fields(self):
|
def _clear_and_disable_fields(self):
|
||||||
"""Clears relevant GUI fields and disables most buttons."""
|
"""Clears relevant GUI fields and disables most buttons."""
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
mf = self.main_frame
|
mf = self.main_frame # Alias
|
||||||
# Clear Repo frame
|
# Clear Repo frame fields
|
||||||
mf.svn_path_entry.delete(0, tk.END)
|
mf.svn_path_entry.delete(0, tk.END)
|
||||||
mf.usb_path_entry.delete(0, tk.END)
|
mf.usb_path_entry.delete(0, tk.END)
|
||||||
mf.bundle_name_entry.delete(0, tk.END)
|
mf.bundle_name_entry.delete(0, tk.END)
|
||||||
@ -1045,12 +1092,13 @@ class GitSvnSyncApp:
|
|||||||
# Clear Commit/Tag/Branch frame fields
|
# Clear Commit/Tag/Branch frame fields
|
||||||
mf.commit_message_var.set("")
|
mf.commit_message_var.set("")
|
||||||
mf.autocommit_var.set(False)
|
mf.autocommit_var.set(False)
|
||||||
mf.update_tag_list([])
|
mf.update_tag_list([]) # Clear tag listbox
|
||||||
mf.update_branch_list([])
|
mf.update_branch_list([]) # Clear branch listbox
|
||||||
mf.set_current_branch_display("<N/A>")
|
mf.set_current_branch_display("<N/A>")
|
||||||
# Reset indicator and dependent buttons
|
# Reset indicator and dependent buttons/widgets
|
||||||
self.update_svn_status_indicator("") # Disables state-dependent
|
# This handles disabling Prepare, EditGitignore, Commit/Tag/Branch widgets
|
||||||
# Disable general action buttons
|
self.update_svn_status_indicator("")
|
||||||
|
# Disable general action buttons explicitly
|
||||||
self._disable_general_buttons()
|
self._disable_general_buttons()
|
||||||
self.logger.debug("GUI fields cleared/reset. Buttons disabled.")
|
self.logger.debug("GUI fields cleared/reset. Buttons disabled.")
|
||||||
|
|
||||||
@ -1058,7 +1106,7 @@ class GitSvnSyncApp:
|
|||||||
def _disable_general_buttons(self):
|
def _disable_general_buttons(self):
|
||||||
"""Disables buttons generally requiring only a loaded profile."""
|
"""Disables buttons generally requiring only a loaded profile."""
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
# List of general button attribute names
|
# List of general action button attribute names in main_frame
|
||||||
button_names = [
|
button_names = [
|
||||||
'create_bundle_button', 'fetch_bundle_button',
|
'create_bundle_button', 'fetch_bundle_button',
|
||||||
'manual_backup_button', 'save_settings_button'
|
'manual_backup_button', 'save_settings_button'
|
||||||
@ -1073,11 +1121,11 @@ class GitSvnSyncApp:
|
|||||||
def _enable_function_buttons(self):
|
def _enable_function_buttons(self):
|
||||||
"""
|
"""
|
||||||
Enables general action buttons. State-dependent buttons rely on
|
Enables general action buttons. State-dependent buttons rely on
|
||||||
update_svn_status_indicator.
|
update_svn_status_indicator for their state.
|
||||||
"""
|
"""
|
||||||
if hasattr(self, 'main_frame'):
|
if hasattr(self, 'main_frame'):
|
||||||
general_state = tk.NORMAL
|
general_state = tk.NORMAL
|
||||||
# List of general button attribute names
|
# List of general action button attribute names
|
||||||
button_names = [
|
button_names = [
|
||||||
'create_bundle_button', 'fetch_bundle_button',
|
'create_bundle_button', 'fetch_bundle_button',
|
||||||
'manual_backup_button', 'save_settings_button'
|
'manual_backup_button', 'save_settings_button'
|
||||||
@ -1089,7 +1137,7 @@ class GitSvnSyncApp:
|
|||||||
button.config(state=general_state)
|
button.config(state=general_state)
|
||||||
|
|
||||||
# Ensure state-dependent buttons reflect the current status
|
# Ensure state-dependent buttons reflect the current status
|
||||||
# This call handles Prepare, EditGitignore, Commit/Tag/Branch widgets
|
# This call updates Prepare, EditGitignore, Commit/Tag/Branch widget states
|
||||||
current_svn_path = ""
|
current_svn_path = ""
|
||||||
if hasattr(self.main_frame, 'svn_path_entry'):
|
if hasattr(self.main_frame, 'svn_path_entry'):
|
||||||
current_svn_path = self.main_frame.svn_path_entry.get()
|
current_svn_path = self.main_frame.svn_path_entry.get()
|
||||||
@ -1113,13 +1161,51 @@ class GitSvnSyncApp:
|
|||||||
print(f"FATAL ERROR (and GUI error: {e}): {message}")
|
print(f"FATAL ERROR (and GUI error: {e}): {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def resource_path(relative_path):
|
||||||
|
""" Ottiene il percorso assoluto della risorsa, funziona per dev e per PyInstaller """
|
||||||
|
try:
|
||||||
|
# PyInstaller crea una cartella temporanea e salva il percorso in _MEIPASS
|
||||||
|
base_path = sys._MEIPASS
|
||||||
|
except Exception:
|
||||||
|
# _MEIPASS non esiste, siamo in modalità sviluppo normale
|
||||||
|
base_path = os.path.abspath(".") # Usa la directory corrente
|
||||||
|
|
||||||
|
return os.path.join(base_path, relative_path)
|
||||||
|
|
||||||
# --- Application Entry Point ---
|
# --- Application Entry Point ---
|
||||||
def main():
|
def main():
|
||||||
"""Main function: Creates Tkinter root and runs the application."""
|
"""Main function: Creates Tkinter root and runs the application."""
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
# Adjust min size for the new layout with tabs
|
# Adjust min size for the new layout with tabs
|
||||||
# May need adjustment based on final widget sizes
|
root.minsize(750, 650) # Adjusted min height after tabbing
|
||||||
root.minsize(750, 650) # Adjusted height back down slightly
|
|
||||||
|
# --- Imposta l'icona della finestra ---
|
||||||
|
try:
|
||||||
|
# Assumendo che 'app_icon.ico' sia nella stessa dir dello script
|
||||||
|
# o aggiunto correttamente a PyInstaller
|
||||||
|
icon_path = resource_path("GitUtility.ico")
|
||||||
|
# Usa wm_iconbitmap per Windows
|
||||||
|
if os.path.exists(icon_path):
|
||||||
|
# wm_iconbitmap si aspetta un file .ico su Windows
|
||||||
|
# Per Linux/Mac, si userebbe iconphoto con un PhotoImage (PNG)
|
||||||
|
if os.name == 'nt': # Solo per Windows
|
||||||
|
root.wm_iconbitmap(icon_path)
|
||||||
|
else:
|
||||||
|
# Su Linux/Mac potresti usare iconphoto con un file PNG
|
||||||
|
# icon_img = tk.PhotoImage(file=resource_path("app_icon.png"))
|
||||||
|
# root.iconphoto(True, icon_img) # 'True' per default icon
|
||||||
|
# Nota: Dovresti aggiungere app_icon.png con --add-data
|
||||||
|
pass # Per ora non facciamo nulla su altri OS
|
||||||
|
else:
|
||||||
|
# Log se l'icona non viene trovata nel percorso atteso
|
||||||
|
logging.warning(f"Window icon file not found at: {icon_path}")
|
||||||
|
except tk.TclError as e:
|
||||||
|
# Logga se c'è un errore nel caricare/impostare l'icona
|
||||||
|
logging.warning(f"Could not set window icon: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
# Logga altri errori imprevisti
|
||||||
|
logging.warning(f"Unexpected error setting window icon: {e}", exc_info=True)
|
||||||
|
|
||||||
app = None # Initialize app variable
|
app = None # Initialize app variable
|
||||||
try:
|
try:
|
||||||
app = GitSvnSyncApp(root)
|
app = GitSvnSyncApp(root)
|
||||||
@ -1150,7 +1236,7 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Setup basic logging immediately at startup
|
# Setup basic logging immediately at startup
|
||||||
# This ensures logs are captured even if setup_logger fails later
|
|
||||||
log_format = "%(asctime)s - %(levelname)s - [%(module)s:%(funcName)s:%(lineno)d] - %(message)s"
|
log_format = "%(asctime)s - %(levelname)s - [%(module)s:%(funcName)s:%(lineno)d] - %(message)s"
|
||||||
logging.basicConfig(level=logging.INFO, format=log_format) # Consider level=logging.DEBUG for more detail
|
# Consider level=logging.DEBUG for more detail during development
|
||||||
|
logging.basicConfig(level=logging.INFO, format=log_format)
|
||||||
main()
|
main()
|
||||||
44
GitUtility.spec
Normal file
44
GitUtility.spec
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['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,
|
||||||
|
a.scripts,
|
||||||
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
|
name='GitUtility',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
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=[],
|
||||||
|
name='GitUtility',
|
||||||
|
)
|
||||||
@ -22,6 +22,7 @@ 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(self, svn_path, profile_name,
|
def _perform_backup_if_enabled(self, svn_path, profile_name,
|
||||||
autobackup_enabled, backup_base_dir,
|
autobackup_enabled, backup_base_dir,
|
||||||
excluded_extensions, excluded_dirs):
|
excluded_extensions, excluded_dirs):
|
||||||
@ -37,15 +38,16 @@ class ActionHandler:
|
|||||||
excluded_dirs (set): Directory names to exclude.
|
excluded_dirs (set): Directory names to exclude.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
IOError: If the backup process fails.
|
IOError: If the backup process fails for any reason.
|
||||||
"""
|
"""
|
||||||
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
|
return # Backup not needed, proceed successfully
|
||||||
|
|
||||||
self.logger.info("Autobackup enabled. Starting backup...")
|
self.logger.info("Autobackup enabled. Starting backup...")
|
||||||
try:
|
try:
|
||||||
# Delegate backup creation to the BackupHandler instance
|
# Delegate backup creation to the BackupHandler instance
|
||||||
|
# It will raise exceptions on failure
|
||||||
backup_path = self.backup_handler.create_zip_backup(
|
backup_path = self.backup_handler.create_zip_backup(
|
||||||
svn_path,
|
svn_path,
|
||||||
backup_base_dir,
|
backup_base_dir,
|
||||||
@ -55,11 +57,12 @@ class ActionHandler:
|
|||||||
)
|
)
|
||||||
# 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, failure indicated by exception
|
# 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
|
||||||
raise IOError(f"Autobackup failed: {backup_e}") from backup_e
|
raise IOError(f"Autobackup failed: {backup_e}") from backup_e
|
||||||
|
|
||||||
|
|
||||||
@ -85,17 +88,14 @@ class ActionHandler:
|
|||||||
raise ValueError("Repository is already prepared.")
|
raise ValueError("Repository is already prepared.")
|
||||||
|
|
||||||
# Attempt preparation using GitCommands
|
# Attempt preparation using GitCommands
|
||||||
|
# Any exception (GitCommandError, ValueError, IOError) will be caught
|
||||||
try:
|
try:
|
||||||
self.git_commands.prepare_svn_for_git(svn_path)
|
self.git_commands.prepare_svn_for_git(svn_path)
|
||||||
self.logger.info("Repository prepared successfully.")
|
self.logger.info("Repository prepared successfully.")
|
||||||
return True
|
return True
|
||||||
except (GitCommandError, ValueError, IOError) as e:
|
|
||||||
# Log and re-raise known errors for the UI layer to handle
|
|
||||||
self.logger.error(f"Failed to prepare repository: {e}", exc_info=True)
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log and re-raise unexpected errors
|
# Log and re-raise any exception from prepare_svn_for_git
|
||||||
self.logger.exception(f"Unexpected error during preparation: {e}")
|
self.logger.error(f"Failed to prepare repository: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ class ActionHandler:
|
|||||||
commit_msg_to_use = commit_message if commit_message else \
|
commit_msg_to_use = commit_message if commit_message else \
|
||||||
f"Autocommit '{profile_name}' before bundle"
|
f"Autocommit '{profile_name}' before bundle"
|
||||||
self.logger.debug(f"Using autocommit message: '{commit_msg_to_use}'")
|
self.logger.debug(f"Using autocommit message: '{commit_msg_to_use}'")
|
||||||
# Perform commit (raises error on failure)
|
# 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.
|
||||||
self.logger.info("Autocommit attempt finished.")
|
self.logger.info("Autocommit attempt finished.")
|
||||||
@ -172,7 +172,7 @@ class ActionHandler:
|
|||||||
try:
|
try:
|
||||||
os.remove(bundle_full_path)
|
os.remove(bundle_full_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
self.logger.warning(f"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
|
||||||
@ -205,7 +205,7 @@ class ActionHandler:
|
|||||||
# Delegate to GitCommands; it raises GitCommandError on conflict/failure
|
# Delegate to GitCommands; it raises GitCommandError on conflict/failure
|
||||||
self.git_commands.fetch_from_git_bundle(svn_path, bundle_full_path)
|
self.git_commands.fetch_from_git_bundle(svn_path, bundle_full_path)
|
||||||
self.logger.info("Fetch/merge process completed successfully.")
|
self.logger.info("Fetch/merge process completed successfully.")
|
||||||
# No return value 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)
|
||||||
@ -221,13 +221,13 @@ class ActionHandler:
|
|||||||
commit_message (str): The commit message (must not be empty).
|
commit_message (str): The commit message (must not be empty).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if commit made, False if no changes.
|
bool: True if a commit was made, False if no changes were committed.
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If commit_message is empty.
|
ValueError: If commit_message is empty.
|
||||||
GitCommandError/Exception: If commit command fails.
|
GitCommandError/Exception: If commit command fails.
|
||||||
"""
|
"""
|
||||||
if not commit_message:
|
if not commit_message:
|
||||||
# Should be validated by caller (UI layer)
|
# 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.")
|
||||||
|
|
||||||
@ -274,9 +274,10 @@ class ActionHandler:
|
|||||||
|
|
||||||
# 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 on success/no changes
|
# git_commit raises error on failure, returns bool otherwise
|
||||||
self.git_commands.git_commit(svn_path, commit_message)
|
commit_made = self.git_commands.git_commit(svn_path, commit_message)
|
||||||
self.logger.info("Pre-tag commit attempt finished.")
|
# Log based on return value? git_commit logs details.
|
||||||
|
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.")
|
||||||
|
|
||||||
@ -303,11 +304,11 @@ class ActionHandler:
|
|||||||
|
|
||||||
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 for changes.
|
Executes checkout for the specified tag after checking changes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
svn_path (str): Validated path to repository.
|
svn_path (str): Validated path to repository.
|
||||||
tag_name (str): The tag name to check out (already validated by caller).
|
tag_name (str): The tag name to check out (validated by caller).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True on successful checkout.
|
bool: True on successful checkout.
|
||||||
@ -316,7 +317,7 @@ class ActionHandler:
|
|||||||
GitCommandError/Exception: If status check or checkout fails.
|
GitCommandError/Exception: If status check or checkout fails.
|
||||||
"""
|
"""
|
||||||
if not tag_name:
|
if not tag_name:
|
||||||
raise ValueError("Tag name required for checkout.") # Should be caught earlier
|
raise ValueError("Tag name required for checkout.")
|
||||||
|
|
||||||
self.logger.info(f"Executing checkout tag '{tag_name}' in: {svn_path}")
|
self.logger.info(f"Executing checkout tag '{tag_name}' in: {svn_path}")
|
||||||
|
|
||||||
@ -336,11 +337,15 @@ class ActionHandler:
|
|||||||
|
|
||||||
# --- Execute Checkout ---
|
# --- Execute Checkout ---
|
||||||
try:
|
try:
|
||||||
# git_commands.checkout_tag raises GitCommandError 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 no exception, assume 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:
|
||||||
|
# This path should theoretically not be reached
|
||||||
|
self.logger.error("Checkout command reported failure unexpectedly.")
|
||||||
|
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(f"Failed to checkout tag '{tag_name}': {e}",
|
||||||
@ -365,6 +370,7 @@ class ActionHandler:
|
|||||||
self.git_commands.create_branch(svn_path, branch_name, start_point)
|
self.git_commands.create_branch(svn_path, branch_name, start_point)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Log and re-raise error
|
||||||
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
|
||||||
|
|
||||||
@ -388,9 +394,10 @@ 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
|
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
|
||||||
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
|
||||||
@ -401,6 +408,7 @@ class ActionHandler:
|
|||||||
success = self.git_commands.checkout_branch(svn_path, branch_name)
|
success = self.git_commands.checkout_branch(svn_path, branch_name)
|
||||||
return success
|
return success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Catch errors during switch (e.g., branch not found)
|
||||||
self.logger.error(f"Failed switch to branch '{branch_name}': {e}",
|
self.logger.error(f"Failed switch to branch '{branch_name}': {e}",
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
raise
|
raise
|
||||||
@ -417,27 +425,27 @@ class ActionHandler:
|
|||||||
"""
|
"""
|
||||||
if not branch_name:
|
if not branch_name:
|
||||||
raise ValueError("Branch name required for delete.")
|
raise ValueError("Branch name required for delete.")
|
||||||
# Add checks for main/master? Done in UI layer.
|
# Add checks for main/master? UI layer handles this.
|
||||||
|
|
||||||
self.logger.info(f"Executing delete branch '{branch_name}' (force={force}).")
|
self.logger.info(f"Executing delete branch '{branch_name}' (force={force}).")
|
||||||
try:
|
try:
|
||||||
# Delegate to git_commands, raises error on failure
|
# Delegate to git_commands, raises error on failure
|
||||||
success = self.git_commands.delete_branch(svn_path, branch_name, force)
|
success = self.git_commands.delete_branch(svn_path, branch_name, force)
|
||||||
return success
|
return success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Catch errors (like not fully merged if force=False)
|
# Catch errors (like not merged, needs force)
|
||||||
self.logger.error(f"Failed delete branch '{branch_name}': {e}",
|
self.logger.error(f"Failed delete branch '{branch_name}': {e}",
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
# --- ADDED: Delete Tag Action ---
|
|
||||||
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.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
svn_path (str): Validated path to repository.
|
svn_path (str): Validated path to repository.
|
||||||
tag_name (str): The tag name to delete (validated by caller).
|
tag_name (str): The tag name to delete (already validated).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True on successful deletion.
|
bool: True on successful deletion.
|
||||||
@ -445,7 +453,7 @@ class ActionHandler:
|
|||||||
GitCommandError/ValueError/Exception: If delete fails.
|
GitCommandError/ValueError/Exception: If delete fails.
|
||||||
"""
|
"""
|
||||||
if not tag_name:
|
if not tag_name:
|
||||||
# Should be validated by caller (UI layer)
|
# Should be validated by caller
|
||||||
raise ValueError("Tag name required for deletion.")
|
raise ValueError("Tag name required for deletion.")
|
||||||
|
|
||||||
self.logger.info(f"Executing delete tag '{tag_name}' in: {svn_path}")
|
self.logger.info(f"Executing delete tag '{tag_name}' in: {svn_path}")
|
||||||
|
|||||||
0
create_exe.bat
Normal file
0
create_exe.bat
Normal file
Loading…
Reference in New Issue
Block a user