From 5b195acca850e32deba586d68bb0c49cfc9f9d42 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 24 Mar 2025 11:31:44 +0100 Subject: [PATCH] prima versione --- Git_Utility.py | 309 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 Git_Utility.py diff --git a/Git_Utility.py b/Git_Utility.py new file mode 100644 index 0000000..33afca6 --- /dev/null +++ b/Git_Utility.py @@ -0,0 +1,309 @@ +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() \ No newline at end of file