Nach längerer Pause zum Thema Versionsverwaltung mit Git geht es an dieser Stelle ein wenig weiter. Git-Repositorys beinhalten bekanntlich ihre vollständige Historie, so dass alle Änderungen immer in der Historie verbleiben. Problematisch wird das aber, wenn Zugangsdaten im Repository gelandet sind. Ein bloßes Entfernen funktioniert da leider nicht, da die Historie ja vollständig da ist.
Ein einfaches Beispiel sieht man etwa, wenn man bei GitHub nach „delete id_rsa“ sucht, was ein privater SSH-Key ist, der nicht eigentlich in ein Git-Repository landen sollte. Genauso findet man auf GitHub Commit-Messages mit dem Inhalt „remove password“.
Wenn man die entsprechenden Passwörter oder SSH-Private-Keys ändert bzw. ersetzt, dann muss man natürlich nicht viel mehr in dem Git-Repository ändern. Anders sieht es aus, wenn die Datei bzw. die Zeilen vollständig entfernt werden soll. Die Historie muss dafür (maximal) vollständig angepasst werden, was für jede Person, die mit dem Repository arbeitet, heißt, dass es vermutlich am einfachsten ist, das Projekt einmal neu zu klonen.
Abhilfe um gleich eine Vielzahl von Commits anzupassen bietet das Subkommando
filter-branch
. Dies kann nicht nur zum Entfernen von Passwörtern oder anderen
Credentials dienen, sondern auch, wenn etwa große Binärdateien im Repository sind,
die entfernt werden sollen.
Um das Verfahren auszuprobieren, kann man sich ein einfaches Repository anlegen, in dem zahlreiche Commits erzeugt werden, um dieses Repository dann aufzuräumen.
$ mkdir test-repo
$ for i in {1..500}; do echo "Commit $i" >> index; git add -A; git commit -m "Commit $i"; done
Diese Bash-For-Schleife legt im Test-Repository die Datei index
an und schreibt
in jedem Durchlauf den Durchlaufzähler in die Datei, mit dem Wort “Commit” vorne
dran. Nach jeder Zeile wird dann ein Commit mit selbigen Inhalt in der Commit-Message
gespeichert.
Das Repository wäre somit vorbereitet. Filter-Branch hat diverse Filter, die man
anwenden kann. Eines ist etwa der tree-filter
, mit dem man dateibasiert arbeitet.
Eine einfache Methode eine Datei aus allen Commits wäre es, ein rm -f index
auszuführen, was in jedem Commit die Datei entfernen würde. Händisch wäre das
ja zu aufwändig, weshalb man folgenden Befehl ausführen kann:
$ git filter-branch --tree-filter 'rm -f index' HEAD
Statt die Datei zu entfernen, kann man auch Inhalte der Datei verändern, etwa
mit sed
. Dazu muss dann der Befehl entsprechend angepasst werden. Es gibt
auch noch weitere Filter, wie etwa der subdirectory-filter
. Dieser kann genutzt werden,
um ganze Unterverzeichnisse in ein eigenes Repository zu überführen. Die Historie
des restlichen Repositorys wird dabei ignoriert, sofern sie nicht relevant ist.
Wesentlicher Vorteil ist dabei, dass die vollständige Historie eines Subdirectories
erhalten bleibt, sodass man auch nach dem Extrahieren frühere Änderungen
nachvollziehen kann.
Alternative: BFG Repo-Cleaner
Eine Alternative zum reinen Filter-Branch ist der BFG Repo-Cleaner.
Im Gegensatz zu Filter-Branch ist die Nutzung deutlich einfacher und es soll auch
schneller sein. Das Tool ist in Java geschrieben und besitzt ein paar nette
Features, die man sich bei Filter-Branch erstmal selbst zusammen schreiben müsste.
Ein Beispiel ist etwa die automatische Entfernung von Dateien, die größer sind als eine
spezifizierte Größe. Dazu besitzt bfg den Parameter --strip-blobs-bigger-than
.
Das vorherige Beispiel mit dem Löschen der Datei funktioniert zwar prinzipiell
mit Filter-Branch auch einfach, allerdings beachtet es immer nur den aktuellen
Pfad. Wenn etwa eine oder mehrere zu löschende Dateien im Repository zuvor herum geschoben
wurde, dann hilft bfg mit dem Parameter --delete-files
schon viel mehr, da es
immer die Dateien mit den entsprechenden Namen löscht.
Für viele dürfte der BFG Repo-Cleaner ein einfaches Interface bieten, um die Historie vollständig anzupassen und trotzdem viel und auch schneller zu arbeiten. In der Regel erfolgt die Nutzung von Filter-Branch auch häufig nur einmalig, weshalb sich einige Sachen nur mit gewissen Aufwand durchführen lassen, die mit BFG einfacher und wohl auch schneller gehen. Bedacht werden sollte aber immer, dass sehr viele Commits angepasst werden. Man sollte dies nur tun, wenn es wirklich nötig ist und auch die Mitarbeiter entsprechend vorher informieren, damit kein bzw. nur kurz ein kaputtes Repository lokal vorhanden ist!
Vielen Dank an Dirk der mich auf BFG Repo-Cleaner hinwies. Mehr zu Git findet sich auf meinem Blog in der Git-Kategorie.