Depozitele Git funcționează ca un jurnal de adăugare exclusivă (append-only database). Fiecare fișier salvat printr-un commit rămâne stocat în directorul intern .git/objects/ pentru totdeauna. Dacă un dezvoltator a comis accidental un fișier video de 500 MB sau o copie de siguranță a unei baze de date SQL și apoi l-a șters printr-un commit ulterior (git rm fisier.zip), fișierul nu a dispărut din depozit — el continuă să fie descărcat de fiecare membru al echipei la orice operațiune de clone.
1. Diagnosticarea Mărimii și Găsirea Fișierelor Gigantice din Istoric
Pentru a înțelege de ce un director .git măsoară gigabytes întregi, putem lista cele mai mari 10 obiecte de tip blob din întregul istoric al tuturor ramurilor:
# Comandă avansată pentru afișarea primelor 10 fișiere după dimensiune (în bytes):
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| sed -n 's/^blob //p' \
| sort --numeric-sort --key=2 --reverse \
| head -n 10
2. Curățarea Nativă: Reflog Expire și Colectarea Gunoiului (git gc)
Git nu șterge imediat obiectele orfane (deconectate de la ramuri sau tag-uri) pentru a vă permite recuperarea muncii prin git reflog timp de 30-90 de zile. Dacă doriți să forțați eliminarea lor definitivă pentru a reduce spațiul pe disc:
# 1. Verificăm dimensiunea actuală a directorului .git:
du -sh .git
# 2. Expirăm imediat toate înregistrările din reflog (istoricul temporar local):
git reflog expire --expire=now --expire-unreachable=now --all
# 3. Rulăm Garbage Collection agresiv pentru re-împachetarea optimă a fișierelor pack:
git gc --prune=now --aggressive
# 4. Verificăm dimensiunea redusă:
du -sh .git
3. Rescrierea Istoriei cu git-filter-repo (Standardul Modern)
Dacă fișierul uriaș sau o cheie privată secretă (de exemplu id_rsa sau un token AWS) este ancorat într-un commit vechi, git gc nu îl poate șterge, deoarece obiectul este încă referențiat în arborele istoric. Instrumentul oficial recomandat pentru eliminarea completă din tot istoricul este git-filter-repo (scris în Python, de mii de ori mai rapid și sigur decât comanda învechită git filter-branch):
# Instalare git-filter-repo:
pip install git-filter-repo
# sau pe Ubuntu/Debian: sudo apt install git-filter-repo
# SCENARIUL A: Eliminarea unui anumit fișier sau director din TOATE commit-urile din trecut:
git-filter-repo --path backup_2023.sql --invert-paths
# SCENARIUL B: Eliminarea automată a oricărui fișier mai mare de 50 Megabytes:
git-filter-repo --strip-blobs-bigger-than 50M
# SCENARIUL C: Mascarea unei parole sau chei secrete scăpate în cod:
# Creăm un fișier de înlocuire:
echo "PAROLA_SECRETA_ACCIDENTALA ==> [REDACTED_SECRET]" > replace.txt
git-filter-repo --replace-text replace.txt
git-filter-repo rescrie hash-urile tuturor commit-urilor ulterioare fișierului modificat! După rulare, istoricul devine divergent față de serverul remote (GitHub/GitLab).
4. Alternativa Rapidă: BFG Repo-Cleaner
Dacă aveți instalat mediul Java (JVM), BFG Repo-Cleaner este o alternativă extrem de simplă și intuitivă pentru proiecte mari:
# Șterge toate fișierele cu numele 'credentials.env' din întreg istoricul:
bfg --delete-files credentials.env
# Șterge orice fișier mai mare de 100M:
bfg --strip-blobs-bigger-than 100M
# După rularea BFG, este obligatoriu să curățați referințele rămase:
git reflog expire --expire=now --all && git gc --prune=now --aggressive
5. Pași Obligatorii După Rescrierea Istoriei
- Forțarea actualizării pe serverul central:
# Se împing toate ramurile și tag-urile rescrise: git push origin --force --all git push origin --force --tags - Instruirea echipei de dezvoltare: Colegii de echipă nu trebuie să ruleze
git pullpeste depozitul vechi (în caz contrar, Git va fuziona din nou vechile commit-uri cu fișierele gigantice înapoi în istoric!). Fiecare membru al echipei trebuie să își salveze eventualele modificări locale netrimise și să facă un re-clone complet al proiectului.