import os import subprocess import logging import tkinter as tk from tkinter import ttk, scrolledtext, filedialog, messagebox import configparser # Configurazione CONFIG_FILE = "git_svn_sync.ini" LOG_FILE = "git_svn_sync.log" DEFAULT_PROFILE = "default" GREEN = "#90EE90" # Light Green RED = "#F08080" # Light Coral class GitSvnSyncApp: def __init__(self, master): self.master = master master.title("Git SVN Sync") # Configurazione del logging (come nella versione precedente) self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.INFO) self.log_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") self.file_handler = logging.FileHandler(LOG_FILE) self.file_handler.setLevel(logging.INFO) self.file_handler.setFormatter(self.log_formatter) self.logger.addHandler(self.file_handler) self.log_text = scrolledtext.ScrolledText(master, height=10, width=80) self.log_text.pack(pady=10) self.log_text.config(state=tk.DISABLED) self.text_handler = TextHandler(self.log_text) self.text_handler.setLevel(logging.INFO) self.text_handler.setFormatter(self.log_formatter) self.logger.addHandler(self.text_handler) # Carica le configurazioni self.config = configparser.ConfigParser() self.load_config() # Frame per la selezione del profilo self.profile_frame = ttk.Frame(master) self.profile_frame.pack(pady=5) self.profile_label = ttk.Label(self.profile_frame, text="Profile:") self.profile_label.pack(side=tk.LEFT) self.profile_var = tk.StringVar() self.profile_dropdown = ttk.Combobox(self.profile_frame, textvariable=self.profile_var, state="readonly") self.profile_dropdown.pack(side=tk.LEFT) self.profile_dropdown['values'] = self.config.sections() if DEFAULT_PROFILE in self.config.sections(): self.profile_var.set(DEFAULT_PROFILE) else: self.profile_var.set(self.config.sections()[0] if self.config.sections() else "") self.profile_var.trace("w", self.load_profile_settings) # Chiama load_profile_settings quando il profilo cambia self.add_profile_button = ttk.Button(self.profile_frame, text="Add Profile", command=self.add_profile) self.add_profile_button.pack(side=tk.LEFT, padx=5) self.remove_profile_button = ttk.Button(self.profile_frame, text="Remove Profile", command=self.remove_profile) self.remove_profile_button.pack(side=tk.LEFT) # Etichette e campi di input (organizzati in un frame) self.settings_frame = ttk.Frame(master) self.settings_frame.pack(pady=5) self.svn_path_label = ttk.Label(self.settings_frame, text="SVN Working Copy Path:") self.svn_path_label.grid(row=0, column=0, sticky=tk.W) self.svn_path_entry = ttk.Entry(self.settings_frame, width=60) self.svn_path_entry.grid(row=0, column=1, sticky=tk.W) self.svn_path_browse_button = ttk.Button(self.settings_frame, text="Browse", command=lambda: self.browse_folder(self.svn_path_entry)) self.svn_path_browse_button.grid(row=0, column=2, sticky=tk.W) # Indicatore dello stato del repository SVN self.svn_status_indicator = ttk.Label(self.settings_frame, text="", width=2) self.svn_status_indicator.grid(row=0, column=3, sticky=tk.W) self.usb_path_label = ttk.Label(self.settings_frame, text="USB Drive Path:") self.usb_path_label.grid(row=1, column=0, sticky=tk.W) self.usb_path_entry = ttk.Entry(self.settings_frame, width=60) self.usb_path_entry.grid(row=1, column=1, sticky=tk.W) self.usb_path_browse_button = ttk.Button(self.settings_frame, text="Browse", command=lambda: self.browse_folder(self.usb_path_entry)) self.usb_path_browse_button.grid(row=1, column=2, sticky=tk.W) self.bundle_name_label = ttk.Label(self.settings_frame, text="Bundle Name:") self.bundle_name_label.grid(row=2, column=0, sticky=tk.W) self.bundle_name_entry = ttk.Entry(self.settings_frame, width=60) self.bundle_name_entry.grid(row=2, column=1, sticky=tk.W) self.bundle_updated_name_label = ttk.Label(self.settings_frame, text="Updated Bundle Name:") self.bundle_updated_name_label.grid(row=3, column=0, sticky=tk.W) self.bundle_updated_name_entry = ttk.Entry(self.settings_frame, width=60) self.bundle_updated_name_entry.grid(row=3, column=1, sticky=tk.W) # Pulsanti self.prepare_svn_button = ttk.Button(master, text="Prepare SVN for Git", command=self.prepare_svn_for_git) self.prepare_svn_button.pack(pady=5) self.create_bundle_button = ttk.Button(master, text="Create Bundle", command=self.create_git_bundle) self.create_bundle_button.pack(pady=5) self.fetch_bundle_button = ttk.Button(master, text="Fetch Bundle", command=self.fetch_from_git_bundle) self.fetch_bundle_button.pack(pady=5) # Inizializza i campi con le impostazioni del profilo corrente self.load_profile_settings() # Log iniziale self.logger.info("Applicazione avviata.") def load_config(self): """Carica le configurazioni dal file.""" self.config.read(CONFIG_FILE) # Crea la sezione di default se non esiste if DEFAULT_PROFILE not in self.config.sections(): self.config.add_section(DEFAULT_PROFILE) self.config.set(DEFAULT_PROFILE, "svn_working_copy_path", "/path/to/svn/working/copy") self.config.set(DEFAULT_PROFILE, "usb_drive_path", "/media/usb") self.config.set(DEFAULT_PROFILE, "bundle_name", "mio_bundle.bundle") self.config.set(DEFAULT_PROFILE, "bundle_name_updated", "mio_bundle_aggiornato.bundle") self.save_config() def load_profile_settings(self, *args): """Carica le impostazioni del profilo selezionato nei campi di input.""" profile = self.profile_var.get() if profile and profile in self.config.sections(): svn_path = self.config.get(profile, "svn_working_copy_path", fallback="") self.svn_path_entry.delete(0, tk.END) self.svn_path_entry.insert(0, svn_path) self.usb_path_entry.delete(0, tk.END) self.usb_path_entry.insert(0, self.config.get(profile, "usb_drive_path", fallback="")) self.bundle_name_entry.delete(0, tk.END) self.bundle_name_entry.insert(0, self.config.get(profile, "bundle_name", fallback="")) self.bundle_updated_name_entry.delete(0, tk.END) self.bundle_updated_name_entry.insert(0, self.config.get(profile, "bundle_name_updated", fallback="")) self.update_svn_status(svn_path) # Aggiorna lo stato del repository SVN def save_config(self): """Salva le configurazioni nel file.""" with open(CONFIG_FILE, "w") as configfile: self.config.write(configfile) def save_profile_settings(self): """Salva le impostazioni del profilo corrente.""" profile = self.profile_var.get() if profile: self.config.set(profile, "svn_working_copy_path", self.svn_path_entry.get()) self.config.set(profile, "usb_drive_path", self.usb_path_entry.get()) self.config.set(profile, "bundle_name", self.bundle_name_entry.get()) self.config.set(profile, "bundle_name_updated", self.bundle_updated_name_entry.get()) self.save_config() self.logger.info(f"Impostazioni del profilo '{profile}' salvate.") def add_profile(self): """Aggiunge un nuovo profilo.""" new_profile_name = tk.simpledialog.askstring("Add Profile", "Enter new profile name:") if new_profile_name: if new_profile_name in self.config.sections(): messagebox.showerror("Error", "Profile name already exists.") else: self.config.add_section(new_profile_name) self.save_config() self.profile_dropdown['values'] = self.config.sections() self.profile_var.set(new_profile_name) self.load_profile_settings() self.logger.info(f"Profilo '{new_profile_name}' aggiunto.") def remove_profile(self): """Rimuove il profilo corrente.""" profile = self.profile_var.get() if profile and profile != DEFAULT_PROFILE: # Previeni la cancellazione del profilo di default if messagebox.askyesno("Remove Profile", f"Are you sure you want to remove profile '{profile}'?"): self.config.remove_section(profile) self.save_config() self.profile_dropdown['values'] = self.config.sections() if self.config.sections(): self.profile_var.set(self.config.sections()[0]) # Seleziona il primo profilo disponibile else: self.profile_var.set("") # Nessun profilo disponibile self.load_profile_settings() self.logger.info(f"Profilo '{profile}' rimosso.") else: messagebox.showerror("Error", "Cannot remove the default profile.") def browse_folder(self, entry): """Apre una finestra di dialogo per la selezione di una cartella.""" filename = filedialog.askdirectory() entry.delete(0, tk.END) entry.insert(0, filename) if entry == self.svn_path_entry: self.update_svn_status(filename) # Aggiorna lo stato se il percorso SVN è stato cambiato def update_svn_status(self, svn_path): """Verifica se il repository SVN è già stato preparato per Git e aggiorna l'indicatore.""" if os.path.exists(os.path.join(svn_path, ".git")): self.svn_status_indicator.config(background=GREEN) self.prepare_svn_button.config(state=tk.DISABLED) else: self.svn_status_indicator.config(background=RED) self.prepare_svn_button.config(state=tk.NORMAL) def prepare_svn_for_git(self): """Prepara il repository SVN per l'uso con Git (crea .gitignore, esegue git init).""" self.save_profile_settings() # Salva le impostazioni del profilo corrente svn_path = self.svn_path_entry.get() # Verifica se .git esiste già (anche se non dovrebbe essere possibile arrivare qui con il pulsante disabilitato) if os.path.exists(os.path.join(svn_path, ".git")): messagebox.showinfo("Info", "Il repository SVN è già stato preparato per Git.") return # Crea .gitignore gitignore_path = os.path.join(svn_path, ".gitignore") if not os.path.exists(gitignore_path): with open(gitignore_path, "w") as f: f.write(".svn\n") self.logger.info("Creato file .gitignore.") # Esegue git init try: command = ["git", "init"] self.log_and_execute(command, check=True) self.logger.info("Repository Git inizializzato con successo.") messagebox.showinfo("Success", "Repository SVN preparato per Git con successo.") self.update_svn_status(svn_path) # Aggiorna lo stato dopo la preparazione except Exception as e: self.logger.error(f"Errore durante l'inizializzazione del repository Git: {e}") messagebox.showerror("Error", f"Errore durante l'inizializzazione del repository Git: {e}") def log_and_execute(self, command, check=True): """Esegue un comando, lo logga e gestisce gli errori.""" log_message = f"Esecuzione comando: {' '.join(command)}" self.logger.info(log_message) try: svn_path = self.svn_path_entry.get() cwd = os.path.abspath(svn_path) # Ottieni il percorso assoluto self.logger.info(f"Directory di lavoro: {cwd}") # Aggiungi questa riga per il debug result = subprocess.run(command, cwd=cwd, capture_output=True, text=True, check=check) if result.returncode == 0: self.logger.info(f"Comando eseguito con successo.\nOutput:\n{result.stdout}") else: self.logger.error(f"Comando fallito. Codice di ritorno: {result.returncode}\nOutput:\n{result.stderr}") return result except subprocess.CalledProcessError as e: self.logger.exception(f"Errore durante l'esecuzione del comando: {e}") raise def create_git_bundle(self): """Crea un bundle del repository Git.""" self.save_profile_settings() # Salva le impostazioni del profilo corrente self.logger.info("Creazione del bundle Git...") svn_path = self.svn_path_entry.get() if not os.path.exists(svn_path): self.logger.error(f"Il percorso SVN '{svn_path}' non esiste.") messagebox.showerror("Error", f"Il percorso SVN '{svn_path}' non esiste.") return bundle_path = os.path.join(self.usb_path_entry.get(), self.bundle_name_entry.get()).replace("\\", "/") # Modifica qui command = ["git", "bundle", "create", bundle_path, "--all"] try: self.log_and_execute(command) self.logger.info("Bundle Git creato con successo.") except Exception as e: self.logger.error(f"Errore durante la creazione del bundle: {e}") messagebox.showerror("Error", f"Errore durante la creazione del bundle: {e}") def fetch_from_git_bundle(self): """Recupera le modifiche dal bundle nel repository Git locale.""" self.save_profile_settings() # Salva le impostazioni del profilo corrente self.logger.info("Recupero delle modifiche dal bundle Git...") bundle_path = os.path.join(self.usb_path_entry.get(), self.bundle_updated_name_entry.get()) try: fetch_command = ["git", "fetch", bundle_path, "--all"] self.log_and_execute(fetch_command) merge_command = ["git", "merge", "FETCH_HEAD"] self.log_and_execute(merge_command) self.logger.info("Modifiche recuperate e integrate con successo.") except Exception as e: self.logger.error(f"Errore durante il recupero delle modifiche dal bundle: {e}") messagebox.showerror("Error", f"Errore durante il recupero delle modifiche dal bundle: {e}") class TextHandler(logging.Handler): """Handler per inviare i log a un widget Text di Tkinter.""" def __init__(self, text): super().__init__() self.text = text def emit(self, record): msg = self.format(record) self.text.config(state=tk.NORMAL) self.text.insert(tk.END, msg + "\n") self.text.config(state=tk.DISABLED) self.text.see(tk.END) # Autoscroll def main(): root = tk.Tk() app = GitSvnSyncApp(root) root.mainloop() if __name__ == "__main__": main()