Created
June 11, 2026 16:52
-
-
Save me-suzy/087308407af30aba34073cc122dfc752 to your computer and use it in GitHub Desktop.
adauga-folder-in-repository V.2.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| """ | |
| adauga-folder-in-repository.py | |
| Adauga toate fisierele dintr-un folder local intr-un repository de pe GitHub. | |
| Flux: | |
| Fereastra 1 -> intreaba in ce repo de pe GitHub sa adaugi fisierele (URL repo). | |
| Fereastra 2 -> intreaba calea folderului local pe care vrei sa-l copiezi in repo, | |
| cu un checkbox: "Copiaza si subfolderele" (recursiv) sau doar fisierele din folder. | |
| Apoi: cloneaza repo-ul, copiaza fisierele, face commit si push. | |
| Necesita: git instalat si configurat (cu credentiale/token pentru push). | |
| """ | |
| import os | |
| import sys | |
| import shutil | |
| import tempfile | |
| import subprocess | |
| import time | |
| import queue | |
| import threading | |
| import tkinter as tk | |
| from tkinter import ttk, filedialog, messagebox, scrolledtext | |
| DIRECTOARE_EXCLUSE = {"__pycache__", "logs", ".git"} | |
| FISIERE_EXCLUSE = {"parent.lock", ".git"} | |
| # ---------------------------------------------------------------------------- | |
| # FEREASTRA 1: alegerea repo-ului de pe GitHub | |
| # ---------------------------------------------------------------------------- | |
| def cere_repo(): | |
| """Afiseaza prima fereastra si intoarce URL-ul repo-ului ales (sau None).""" | |
| rezultat = {"url": None} | |
| win = tk.Tk() | |
| win.title("Pasul 1/2 - Alege repository-ul GitHub") | |
| win.geometry("680x230") | |
| win.resizable(False, False) | |
| tk.Label( | |
| win, | |
| text="In ce repository de pe GitHub vrei sa adaugi fisierele?", | |
| font=("Segoe UI", 11, "bold"), | |
| ).pack(pady=(18, 4)) | |
| tk.Label( | |
| win, | |
| text="Lipeste aici URL-ul complet al repo-ului (ex: https://github.com/me-suzy/NumeRepo)", | |
| font=("Segoe UI", 9), | |
| fg="#555", | |
| ).pack(pady=(0, 8)) | |
| entry = tk.Entry(win, width=80, font=("Consolas", 10)) | |
| entry.pack(pady=6, padx=20) | |
| entry.insert(0, "https://github.com/me-suzy/") | |
| entry.focus_set() | |
| def confirma(): | |
| url = entry.get().strip() | |
| if not url: | |
| messagebox.showwarning("Atentie", "Trebuie sa introduci un URL de repository.") | |
| return | |
| if "github.com" not in url: | |
| if not messagebox.askyesno( | |
| "Confirmare", | |
| "URL-ul nu pare a fi de pe github.com.\nVrei sa continui oricum?", | |
| ): | |
| return | |
| rezultat["url"] = url | |
| win.destroy() | |
| btn = tk.Frame(win) | |
| btn.pack(pady=18) | |
| tk.Button(btn, text="Continua ->", width=16, command=confirma, | |
| bg="#2e7d32", fg="white", font=("Segoe UI", 10, "bold")).pack(side="left", padx=6) | |
| tk.Button(btn, text="Anuleaza", width=12, command=win.destroy).pack(side="left", padx=6) | |
| win.bind("<Return>", lambda e: confirma()) | |
| win.mainloop() | |
| return rezultat["url"] | |
| # ---------------------------------------------------------------------------- | |
| # FEREASTRA 2: alegerea folderului local + checkbox subfoldere | |
| # ---------------------------------------------------------------------------- | |
| def cere_folder(): | |
| """Afiseaza a doua fereastra si intoarce (cale_folder, include_subfoldere) sau (None, None).""" | |
| rezultat = {"folder": None, "recursiv": None} | |
| win = tk.Tk() | |
| win.title("Pasul 2/2 - Alege folderul de copiat") | |
| win.geometry("680x260") | |
| win.resizable(False, False) | |
| tk.Label( | |
| win, | |
| text="Ce folder local vrei sa copiezi complet in repo?", | |
| font=("Segoe UI", 11, "bold"), | |
| ).pack(pady=(18, 8)) | |
| rand = tk.Frame(win) | |
| rand.pack(pady=6, padx=20, fill="x") | |
| entry = tk.Entry(rand, width=62, font=("Consolas", 10)) | |
| entry.pack(side="left", fill="x", expand=True) | |
| entry.insert(0, r"d:\TEST\arcanum_capture\\") | |
| def rasfoieste(): | |
| cale = filedialog.askdirectory(title="Alege folderul de copiat") | |
| if cale: | |
| entry.delete(0, tk.END) | |
| entry.insert(0, cale) | |
| tk.Button(rand, text="Rasfoieste...", command=rasfoieste).pack(side="left", padx=(8, 0)) | |
| var_recursiv = tk.BooleanVar(value=True) | |
| tk.Checkbutton( | |
| win, | |
| text="Copiaza si subfolderele (recursiv), nu doar fisierele din folder", | |
| variable=var_recursiv, | |
| font=("Segoe UI", 10), | |
| ).pack(pady=(14, 4), anchor="w", padx=22) | |
| def confirma(): | |
| folder = entry.get().strip().strip('"') | |
| if not folder: | |
| messagebox.showwarning("Atentie", "Trebuie sa alegi un folder.") | |
| return | |
| if not os.path.isdir(folder): | |
| messagebox.showerror("Eroare", f"Folderul nu exista:\n{folder}") | |
| return | |
| rezultat["folder"] = folder | |
| rezultat["recursiv"] = var_recursiv.get() | |
| win.destroy() | |
| btn = tk.Frame(win) | |
| btn.pack(pady=18) | |
| tk.Button(btn, text="Adauga in repo ->", width=18, command=confirma, | |
| bg="#2e7d32", fg="white", font=("Segoe UI", 10, "bold")).pack(side="left", padx=6) | |
| tk.Button(btn, text="Anuleaza", width=12, command=win.destroy).pack(side="left", padx=6) | |
| win.bind("<Return>", lambda e: confirma()) | |
| win.mainloop() | |
| return rezultat["folder"], rezultat["recursiv"] | |
| # ---------------------------------------------------------------------------- | |
| # FEREASTRA 3: log / progres | |
| # ---------------------------------------------------------------------------- | |
| class FereastraLog: | |
| def __init__(self): | |
| self.win = tk.Tk() | |
| self.win.title("Adaugare fisiere in repository - progres") | |
| self.win.geometry("900x560") | |
| self.win.protocol("WM_DELETE_WINDOW", self._inchide) | |
| self._terminat = False | |
| self.status = tk.StringVar(value="Pregatire...") | |
| tk.Label( | |
| self.win, textvariable=self.status, anchor="w", font=("Segoe UI", 9) | |
| ).pack(fill="x", padx=8, pady=(8, 2)) | |
| self.progress = ttk.Progressbar( | |
| self.win, orient="horizontal", mode="determinate", maximum=100 | |
| ) | |
| self.progress.pack(fill="x", padx=8, pady=(0, 6)) | |
| self.txt = scrolledtext.ScrolledText(self.win, font=("Consolas", 9), wrap="word") | |
| self.txt.pack(fill="both", expand=True, padx=8, pady=(0, 8)) | |
| self.btn = tk.Button(self.win, text="Inchide", width=14, | |
| command=self._inchide, state="disabled") | |
| self.btn.pack(pady=(0, 10)) | |
| self.win.update() | |
| def _inchide(self): | |
| if self._terminat: | |
| self.win.destroy() | |
| def log(self, mesaj): | |
| self.txt.insert(tk.END, mesaj + "\n") | |
| self.txt.see(tk.END) | |
| self.win.update() | |
| def seteaza_progres(self, curent, total, mesaj): | |
| self.progress.stop() | |
| self.progress.configure(mode="determinate", maximum=100) | |
| procent = (curent * 100 / total) if total else 0 | |
| self.progress["value"] = procent | |
| self.status.set(mesaj) | |
| self.win.update_idletasks() | |
| def incepe_asteptare(self, mesaj): | |
| self.progress.stop() | |
| self.progress.configure(mode="indeterminate") | |
| self.progress.start(12) | |
| self.status.set(mesaj) | |
| self.win.update() | |
| def actualizeaza_asteptare(self, mesaj): | |
| self.status.set(mesaj) | |
| self.win.update() | |
| def opreste_asteptare(self): | |
| self.progress.stop() | |
| self.progress.configure(mode="determinate", maximum=100) | |
| self.progress["value"] = 0 | |
| self.win.update_idletasks() | |
| def gata(self): | |
| self._terminat = True | |
| self.progress.stop() | |
| self.progress.configure(mode="determinate", maximum=100) | |
| self.progress["value"] = 100 | |
| self.status.set("Operatie terminata. Poti inchide fereastra.") | |
| self.btn.config(state="normal") | |
| self.win.mainloop() | |
| # ---------------------------------------------------------------------------- | |
| # Operatii git + copiere | |
| # ---------------------------------------------------------------------------- | |
| def ruleaza(comanda, cwd, log, actualizeaza_ui=None, mesaj_asteptare=None): | |
| """Ruleaza o comanda fara sa blocheze fereastra si trimite output-ul in log.""" | |
| log("> " + " ".join(comanda)) | |
| proc = subprocess.Popen( | |
| comanda, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, | |
| text=True, encoding="utf-8", errors="replace", | |
| ) | |
| linii = queue.Queue() | |
| citire_terminata = threading.Event() | |
| def citeste_output(): | |
| try: | |
| for linie in proc.stdout: | |
| linii.put(linie.rstrip()) | |
| finally: | |
| citire_terminata.set() | |
| threading.Thread(target=citeste_output, daemon=True).start() | |
| inceput = time.monotonic() | |
| ultima_actualizare = 0.0 | |
| while proc.poll() is None or not citire_terminata.is_set() or not linii.empty(): | |
| for _ in range(100): | |
| try: | |
| log(linii.get_nowait()) | |
| except queue.Empty: | |
| break | |
| acum = time.monotonic() | |
| if actualizeaza_ui and acum - ultima_actualizare >= 0.2: | |
| secunde = int(acum - inceput) | |
| minute, secunde = divmod(secunde, 60) | |
| text = mesaj_asteptare or "Comanda Git ruleaza" | |
| actualizeaza_ui(f"{text} - timp scurs {minute:02d}:{secunde:02d}") | |
| ultima_actualizare = acum | |
| if proc.poll() is None: | |
| time.sleep(0.05) | |
| while not linii.empty(): | |
| log(linii.get_nowait()) | |
| return proc.returncode | |
| def ruleaza_cu_fereastra(comanda, cwd, log_win, log, mesaj): | |
| """Ruleaza o comanda Git mentinand activa fereastra de progres.""" | |
| log_win.incepe_asteptare(mesaj) | |
| try: | |
| return ruleaza( | |
| comanda, | |
| cwd, | |
| log, | |
| actualizeaza_ui=log_win.actualizeaza_asteptare, | |
| mesaj_asteptare=mesaj, | |
| ) | |
| finally: | |
| log_win.opreste_asteptare() | |
| def formateaza_marime(numar_octeti): | |
| """Formateaza o dimensiune in octeti intr-un text usor de citit.""" | |
| valoare = float(numar_octeti) | |
| for unitate in ("B", "KB", "MB", "GB", "TB"): | |
| if valoare < 1024 or unitate == "TB": | |
| return f"{valoare:.1f} {unitate}" | |
| valoare /= 1024 | |
| def iter_fisiere(sursa, recursiv): | |
| """Genereaza (cale completa, cale relativa, dimensiune), cu excluderi.""" | |
| if recursiv: | |
| for radacina, directoare, fisiere in os.walk(sursa): | |
| directoare[:] = [ | |
| nume for nume in directoare | |
| if nume.casefold() not in DIRECTOARE_EXCLUSE | |
| ] | |
| for f in fisiere: | |
| if f.casefold() in FISIERE_EXCLUSE: | |
| continue | |
| cale = os.path.join(radacina, f) | |
| try: | |
| marime = os.path.getsize(cale) | |
| except (FileNotFoundError, PermissionError): | |
| # Firefox poate sterge/bloca fisierele cache chiar in timpul scanarii. | |
| continue | |
| yield cale, os.path.relpath(cale, sursa), marime | |
| else: | |
| for nume in os.listdir(sursa): | |
| cale = os.path.join(sursa, nume) | |
| if os.path.isfile(cale) and nume.casefold() not in FISIERE_EXCLUSE: | |
| try: | |
| marime = os.path.getsize(cale) | |
| except (FileNotFoundError, PermissionError): | |
| continue | |
| yield cale, nume, marime | |
| def copiaza_fisiere(sursa, destinatie, recursiv, log, progres): | |
| """Copiaza continutul folderului sursa in destinatie si afiseaza progres.""" | |
| log("Scanez fisierele care trebuie copiate...") | |
| fisiere = list(iter_fisiere(sursa, recursiv)) | |
| total_fisiere = len(fisiere) | |
| total_octeti = sum(marime for _cale, _rel, marime in fisiere) | |
| log( | |
| f"De copiat: {total_fisiere} fisiere, " | |
| f"{formateaza_marime(total_octeti)}." | |
| ) | |
| if recursiv: | |
| log("Exclud directoarele: __pycache__, Logs, .git (oriunde apar).") | |
| log("Exclud fisierele: parent.lock, .git.") | |
| log("Fisierele existente in repository vor fi suprascrise.") | |
| if total_fisiere == 0: | |
| return 0 | |
| inceput = time.monotonic() | |
| ultima_actualizare = 0.0 | |
| octeti_copiati = 0 | |
| fisiere_copiate = 0 | |
| fisiere_suprascrise = 0 | |
| fisiere_sarite = [] | |
| for index, (cale_sursa, cale_relativa, marime) in enumerate(fisiere, start=1): | |
| cale_destinatie = os.path.join(destinatie, cale_relativa) | |
| os.makedirs(os.path.dirname(cale_destinatie), exist_ok=True) | |
| exista_deja = os.path.exists(cale_destinatie) | |
| if exista_deja: | |
| try: | |
| os.chmod(cale_destinatie, 0o666) | |
| except OSError: | |
| pass | |
| eroare = None | |
| for incercare in range(3): | |
| try: | |
| shutil.copy2(cale_sursa, cale_destinatie) | |
| eroare = None | |
| break | |
| except OSError as exc: | |
| eroare = exc | |
| if incercare < 2: | |
| time.sleep(0.15) | |
| if eroare is None: | |
| fisiere_copiate += 1 | |
| if exista_deja: | |
| fisiere_suprascrise += 1 | |
| octeti_copiati += marime | |
| else: | |
| fisiere_sarite.append((cale_relativa, str(eroare))) | |
| acum = time.monotonic() | |
| if acum - ultima_actualizare >= 0.5 or index == total_fisiere: | |
| durata = max(acum - inceput, 0.001) | |
| viteza = octeti_copiati / durata | |
| mesaj = ( | |
| f"Procesez {index}/{total_fisiere} | copiate {fisiere_copiate} | " | |
| f"suprascrise {fisiere_suprascrise} | " | |
| f"sarite {len(fisiere_sarite)} | " | |
| f"{formateaza_marime(octeti_copiati)}/" | |
| f"{formateaza_marime(total_octeti)} | " | |
| f"{formateaza_marime(viteza)}/s | {cale_relativa}" | |
| ) | |
| progres(index, total_fisiere, mesaj) | |
| log( | |
| f" {index}/{total_fisiere} procesate, " | |
| f"{fisiere_copiate} copiate, " | |
| f"{fisiere_suprascrise} suprascrise, " | |
| f"{len(fisiere_sarite)} sarite, " | |
| f"{formateaza_marime(octeti_copiati)}..." | |
| ) | |
| ultima_actualizare = acum | |
| if fisiere_sarite: | |
| log( | |
| f"AVERTISMENT: {len(fisiere_sarite)} fisiere au disparut sau au fost " | |
| "blocate in timpul copierii. Operatia continua." | |
| ) | |
| for cale_relativa, eroare in fisiere_sarite[:20]: | |
| log(f" SARIT: {cale_relativa} ({eroare})") | |
| if len(fisiere_sarite) > 20: | |
| log(f" ... si inca {len(fisiere_sarite) - 20} fisiere sarite.") | |
| log( | |
| f"Copiate {fisiere_copiate} fisiere, {formateaza_marime(octeti_copiati)} " | |
| f"({'cu subfoldere' if recursiv else 'doar fisierele din folder'})." | |
| ) | |
| log(f"Fisiere existente suprascrise: {fisiere_suprascrise}.") | |
| return fisiere_copiate | |
| def proceseaza(repo_url, folder, recursiv): | |
| log_win = FereastraLog() | |
| L = log_win.log | |
| L("=" * 60) | |
| L(f"Repository : {repo_url}") | |
| L(f"Folder : {folder}") | |
| L(f"Recursiv : {'DA (cu subfoldere)' if recursiv else 'NU (doar fisierele)'}") | |
| L("=" * 60) | |
| # verifica git | |
| if shutil.which("git") is None: | |
| L("EROARE: git nu este instalat sau nu este in PATH.") | |
| log_win.gata() | |
| return | |
| temp = tempfile.mkdtemp(prefix="repo_upload_") | |
| nume_repo = repo_url.rstrip("/").split("/")[-1].replace(".git", "") | |
| clona = os.path.join(temp, nume_repo or "repo") | |
| try: | |
| L("\n[1/5] Clonez repository-ul...") | |
| if ruleaza_cu_fereastra( | |
| ["git", "clone", "--progress", repo_url, clona], | |
| temp, log_win, L, "Clonez repository-ul" | |
| ) != 0: | |
| L("\nEROARE la clonare. Verifica URL-ul si accesul (token/credentiale).") | |
| return | |
| L("\n[2/5] Copiez fisierele in repository...") | |
| nr = copiaza_fisiere( | |
| folder, clona, recursiv, L, log_win.seteaza_progres | |
| ) | |
| if nr == 0: | |
| L("Nu am gasit fisiere de copiat. Ma opresc.") | |
| return | |
| L("\n[3/5] git add ...") | |
| L( | |
| f"Git indexeaza {nr} fisiere. La volume mari poate dura mai multe " | |
| "minute; bara miscatoare si timpul scurs arata ca operatia continua." | |
| ) | |
| comanda_add = [ | |
| "git", | |
| "-c", "core.autocrlf=false", | |
| "-c", "core.safecrlf=false", | |
| "add", "-f", "-A", | |
| ] | |
| if ruleaza_cu_fereastra( | |
| comanda_add, | |
| clona, log_win, L, | |
| f"NU este blocat: Git indexeaza {nr} fisiere (git add)" | |
| ) != 0: | |
| L("\nEROARE la git add. Verifica mesajul de mai sus.") | |
| return | |
| # verifica daca exista modificari | |
| cod_modificari = ruleaza_cu_fereastra( | |
| ["git", "diff", "--cached", "--quiet", "--exit-code"], | |
| clona, log_win, L, "Verific modificarile pregatite" | |
| ) | |
| if cod_modificari == 0: | |
| L("\nNu exista modificari noi fata de repo (fisierele exista deja identice).") | |
| return | |
| if cod_modificari != 1: | |
| L("\nEROARE la verificarea modificarilor Git.") | |
| return | |
| L("\n[4/5] git commit ...") | |
| mesaj = f"Adauga fisiere din folderul {os.path.basename(folder.rstrip(os.sep))}" | |
| if ruleaza_cu_fereastra( | |
| ["git", "commit", "-m", mesaj], | |
| clona, log_win, L, "Creez commit-ul" | |
| ) != 0: | |
| L("\nEROARE la commit. Verifica numele/emailul Git si mesajul de mai sus.") | |
| return | |
| L("\n[5/5] git push ...") | |
| if ruleaza_cu_fereastra( | |
| ["git", "push", "--progress"], | |
| clona, log_win, L, "Trimit fisierele pe GitHub (git push)" | |
| ) != 0: | |
| L("\nEROARE la push. Verifica autentificarea GitHub (token / git credential manager).") | |
| return | |
| L("\n" + "=" * 60) | |
| L("GATA! Fisierele au fost adaugate si trimise pe GitHub.") | |
| L(repo_url) | |
| L("=" * 60) | |
| except Exception as exc: | |
| L("\n" + "=" * 60) | |
| L("EROARE: operatia nu a putut fi terminata.") | |
| L(str(exc)) | |
| L("=" * 60) | |
| finally: | |
| # curata folderul temporar | |
| log_win.status.set("Curat folderul temporar...") | |
| log_win.win.update_idletasks() | |
| try: | |
| shutil.rmtree(temp, ignore_errors=True) | |
| except Exception: | |
| pass | |
| log_win.gata() | |
| # ---------------------------------------------------------------------------- | |
| def main(): | |
| repo_url = cere_repo() | |
| if not repo_url: | |
| return | |
| folder, recursiv = cere_folder() | |
| if not folder: | |
| return | |
| proceseaza(repo_url, folder, recursiv) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment