prima versione
This commit is contained in:
commit
5b195acca8
309
Git_Utility.py
Normal file
309
Git_Utility.py
Normal file
@ -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()
|
||||||
Loading…
Reference in New Issue
Block a user