Git Historie bereinigen Teil 2

Sensible Daten oder zu hoher Speicherplatzverbrauch: Es gibt gute Gründe den Wunsch zu hegen, die Git-Historie zu verändern. In diesem Blogbeitrag habe ich erklärt, wie man mit Hilfe von BFG Dateien aus der Git-Historie tilgt. Ein Schwachpunkt von BFG ist die fehlende Unterstützung von direkten Pfaden, sodass man nicht gezielt Dateien oder Ordner in Unterordnern aus der Historie entfernen kann. Damit ist es Zeit, alternative Lösungen anzusehen.


Neben dem offiziell nicht empfohlenen git filter branch zählt git-filter-repo zu dem Werkzeug, die Bereinigung der Historie durchzuführen. Nach einer kurzen Installation analysieren wir zunächst das Repository und finden beispielsweise die größten Ordner in der Historie:

git filter-repo --analyze

Nun werden im Ordner .git/filter-repo/analysis allerhand TXT-Dateien generiert:

  • directories-all-sizes.txt
  • extensions-all-sizes.txt
  • path-all-sizes.txt
  • ...

Es lohnt sich, die Datei directories-all-sizes.txt genauer anzusehen:

=== All directories by reverse size ===

Format: unpacked size, packed size, date deleted, directory name

  4624417043 3796607988 <present> <toplevel>
  4475940396 3778033787 <present> wp-content
  4060236681 3694449320 <present> wp-content/uploads
   305163809   70576241 <present> wp-content/plugins
   123818107   15442735 <present> wp-includes
...

Es kommt oft vor, dass man längst ignorierte und aus dem HEAD entfernte Daten in der Historie liegen hat (beispielsweise den WordPress-Medienordner wp-content/uploads/ oder ein versehentlich gepushter node_modules- oder vendor-Ordner).

Generell empfiehlt git-filter-repo nach der Bereinigung das Pushen in ein neues, leeres Repository. Hier sind zahlreiche Gründe aufgeführt, warum dies sinnvoll ist und viele Probleme vermeidet. Trotzdem kann es vorkommen, dass man in dasselbe Repository pushen will und auch das ist unter Beachtung einiger Hinweise möglich.

Wichtig ist, dass die großen Code-Hosting-Plattformen GitHub und GitLab unterschiedliche Vorgehensweisen empfehlen, die sich teilweise voneinander unterscheiden. Auf GitHub entfernen wir beispielsweise wp-content/uploads/ mit Hilfe der folgenden Schritte mit git-filter-repo aus der Historie:

mkdir tmp-repo
cd tmp-repo
git clone git@github.com:foo/bar.git .
cp .git/config /tmp/config-backup
git filter-repo --invert-paths --path wp-content/uploads/
# option 1: same repo
  mv /tmp/config-backup .git/config
  git push origin --force --all
# option 2: new repo
  git remote add origin git@github.com:foo/bar-new.git
  git push origin --force --all
cd ..
rm -rf tmp-repo

Wir können nun auch remote die Größe prüfen (die Änderung der Größe via API sowie in der UI kann bis zu 24h dauern). Dazu öffnet man die Repository-Einstellungen (falls das Repository zu einer Organisation gehört, muss man vorher seinen eigenen Account zur Organisation hinzufügen). Nun sehen wir die Größe:

GitHub: Speicherplatz vor der Bereinigung
GitHub: Speicherplatz nach der Bereinigung

Auf GitLab ist das Prozedere etwas anders:

mkdir tmp-repo
cd tmp-repo
# option 1: same repo
  # Settings > General > Advanced > Export project > download tar.gz file into tmp-repo
  tar xzf 20*.tar.gz
  git clone --bare --mirror project.bundle
  cd project.git
  git filter-repo --invert-paths --path wp-content/uploads/
  cp ./filter-repo/commit-map /tmp/commit-map-1
  # copying the commit-map has to be done after every single command from git filter-repo
  # you need the commit-map files later
  git remote remove origin
  git remote add origin git@gitlab.com:foo/bar.git
  # Settings > Repository > Protected branches/Protected branches >
  # enable "Allowed to force push to main/master"
  git push origin --force 'refs/heads/*'
  git push origin --force 'refs/tags/*'
  git push origin --force 'refs/replace/*'
  # Settings > Repository > Protected branches/Protected branches >
  # disable "Allowed to force push to main/master"
  date
  # wait 30 minutes (😱)
  date
  # Settings > Repository > upload /tmp/commit-map-X
# option 2: new repo
  git clone git@gitlab.com:foo/bar.git .
  git filter-repo --invert-paths --path wp-content/uploads/
  git remote add origin git@gitlab.com:foo/bar-new.git
  # Settings > Repository > Protected branches/Protected branches >
  # enable "Allowed to force push to main/master"
  git push origin --force --all
  # Settings > Repository > Protected branches/Protected branches >
  # disable "Allowed to force push to main/master"
cd ..
rm -rf tmp-repo

Nach einer weiteren Wartezeit von ~5 Minuten können wir unter Settings > Usage Quotas den Speicherplatz ansehen:

GitLab: Speicherplatz vor der Bereinigung
GitLab: Speicherplatz nach der Bereinigung

Nach dem Entfernen ist es wichtig, dass alle beteiligten Entwickler bei den letzten Schritten mitwirken: Führt ein User mit seiner eigenen lokalen Kopie nun einen normalen push durch, würde dies dazu führen, dass die großen Dateien wieder in das zentrale Repository wandern. Deshalb empfehlen sich die folgenden 3 Möglichkeiten:

  • "poor man's fresh clone"
    • rm -rf .git && git clone xxx temp && mv temp/.git ./.git && rm -rf temp
    • Bei geänderten Dateien (je nach Anwendungsfall): git checkout -- . bzw. git add -A . && git commit -m "Push obscure file changes." && git push
  • "start from scratch"
    • rm -rf repo && git clone xxx .
  • "ugly pull with rebase"
    • git pull -r
    • Hier holt man hat immer noch die unbereinigte Historie, überschreibt aber in den meisten Fällen aber nicht mehr versehentlich das Remote-Repository mit der großen lokalen Variante

Im Zuge der aktuellen Quotas (insbesondere durch die neuen Einschränkungen von GitLab) lohnt es sich allemal, die Größe der Historie seiner Repositories zu prüfen und ggf. zu bereinigen:

GitHub FreeGitLab Free
Max file size limit100 MB
Max repo size limit5.000 MB
Max repo count limit
Max overall size limit5.000 MB

Abschließend lohnt es sich auch, einen Blick auf eine selbst gehostete, kostenlose Variante wie Gitea zu werfen. Mit wenig Aufwand kann man auf einem sehr schlanken Server eine selbstgehostete Git-Instanz (GUI per SSL gesichert, Backup inklusive, Steuerung über mächtige API) hosten, die sich obendrein hervorragend konfigurieren lässt und auch in Sachen Datenschutz überlegen ist. Hier lassen sich übrigens ebenfalls mit Hilfe von git-filter-repo Repositories einfach verschlanken:

mkdir tmp-repo
cd tmp-repo
git clone git@git.tld.com:foo/bar.git .
cp .git/config /tmp/config-backup
git filter-repo --invert-paths --path wp-content/uploads/
# option 1: same repo
  mv /tmp/config-backup .git/config
  git push origin --mirror
  # login on the remote command line and run in the repo-folder
  sudo -u git git reflog expire --expire=now --all
  sudo -u git git gc --aggressive --prune=now
  # if you face memory limit issues, modify the git configuration
  sudo -u git git config --global pack.windowMemory "100m"
  sudo -u git git config --global pack.packSizeLimit "100m"
  sudo -u git git config --global pack.threads "1"
  # if in web ui the size does not change, make a slight
  # modification to a file and push again normally
# option 2: new repo
  git remote add origin git@git.tld.com:foo/bar-new.git
  git push origin --force --all
cd ..
rm -rf tmp-repo

Hier ist speziell der Befehl sudo -u git git gc --aggressive --prune=now wichtig (das per Cron laufende git gc hat sonst eine zu lange prune-Time von 2 Wochen).

Zurück