# Git-History kuerzen Wenn ein Repository zu gross geworden ist und die alte Historie nicht mehr benoetigt wird, kann die Git-History ab einem bestimmten Commit abgeschnitten werden. Dabei wird die Historie **umgeschrieben**: Der Commit-Hash des neuen Anfangs und die Hashes aller folgenden Commits aendern sich. > **Achtung:** Das ist eine destruktive Operation fuer die bisherige Historie. Vorher unbedingt ein Backup erstellen und mit allen anderen Personen abstimmen, die mit dem Repository arbeiten. ## Nicht verwechseln: Shallow Clone Ein flacher Clone verbirgt alte Historie nur lokal: ```bash git clone --shallow-since="2024-01-01" ``` Das Repository selbst wird dadurch nicht gekuerzt. Fuer eine dauerhafte Kuerzung muss die Historie umgeschrieben und anschliessend mit Force-Push auf das Remote uebertragen werden. ## Vorbereitung: Backup erstellen Am sichersten arbeitet man mit einem Mirror-Clone: ```bash git clone --mirror repository-backup.git cd repository-backup.git ``` Den urspruenglichen Clone bzw. das Backup erst loeschen, wenn das Ergebnis geprueft wurde. ## `git-filter-repo` unter Arch Linux installieren Das Arch-Paket heisst `git-filter-repo`: ```bash sudo pacman -S git-filter-repo ``` Pruefen: ```bash git filter-repo --version ``` Ein systemweites `pip install` funktioniert auf Arch Linux wegen PEP 668 normalerweise nicht. Die Paketinstallation ist vorzuziehen. ## Einen Commit zum neuen Root machen Angenommen, dieser Commit soll der neue Anfang der Historie werden: ```text 464e39c1234567890abcdef1234567890abcdef ``` Dann werden seine Eltern entfernt: ```bash git filter-repo --force \ --commit-callback 'if commit.original_id == b"464e39c1234567890abcdef1234567890abcdef": commit.parents = []' ``` `commit.original_id` enthaelt die vollstaendige 40-stellige Commit-ID. Eine kurze ID wie `464e39c` funktioniert mit `==` nicht. Alternativ kann ein eindeutiger Prefix so verwendet werden: ```bash git filter-repo --force \ --commit-callback 'if commit.original_id.startswith(b"464e39c"): commit.parents = []' ``` Die vollstaendige ID kann vorher ermittelt werden: ```bash git rev-parse 464e39c ``` Aus: ```text A -- B -- C -- D -- E ``` wird dadurch sinngemaess: ```text C' -- D' -- E' ``` Der Commit `C` bleibt inhaltlich und mit seinen Metadaten erhalten, bekommt aber wegen der entfernten Eltern einen neuen Hash. Auch `D` und `E` erhalten neue Hashes. ## Den Cutoff-Commit ebenfalls verwerfen Wenn der Cutoff-Commit selbst nicht erhalten bleiben soll, kann ein neuer Root-Commit mit seinem Dateistand erstellt und die spaetere Historie darauf rebased werden: ```bash git checkout --orphan temp git commit -m "Start truncated history" git rebase --onto temp main git branch -D temp ``` Dabei ist `` der **letzte Commit, der verworfen werden soll**. Der neue Root-Commit enthaelt den Dateistand dieses Commits, ist aber ein neuer Commit. Bei Merge-Commits oder komplexen Branches kann die Rebase-Variante Konflikte verursachen. In solchen Faellen ist `git-filter-repo` in der Regel die bessere Wahl. ## Ergebnis pruefen Vor dem Push die neue Historie kontrollieren: ```bash git log --all --graph --oneline --decorate git status ``` Der neue Root-Commit darf keine `parent`-Zeile haben: ```bash git show --no-patch --pretty=raw ``` Auch der aktuelle Dateistand sollte unveraendert sein. Einen Vergleich kann man beispielsweise vor dem Umschreiben speichern oder mit dem Backup pruefen. ## Remote wieder hinzufuegen `git-filter-repo` entfernt das Remote oft absichtlich als Sicherheitsmassnahme. Deshalb zuerst pruefen: ```bash git remote -v ``` Falls kein `origin` vorhanden ist: ```bash git remote add origin git ls-remote origin ``` `origin/main` ist eine Branch-Referenz und kein Remote-Name. Der Push verwendet daher `origin main`, nicht `origin/main`. ## Umschriebene Historie pushen Nur den Hauptbranch pushen: ```bash git push --force origin main ``` Alle lokalen Branches und Tags pushen: ```bash git push --force --all origin git push --force --tags origin ``` Das Remote kann den Push wegen Branch-Schutzregeln ablehnen. In diesem Fall muss der Schutz voruebergehend angepasst werden. ## Nacharbeiten und wichtige Folgen - Alle Commit-Links, Pull Requests und Tags koennen durch die neuen Hashes ungueltig werden. - Andere Clone enthalten weiterhin die alte Historie und muessen neu geklont oder manuell angepasst werden. - Andere Branches oder Tags koennen die alte Historie weiterhin erreichbar machen. - Ein Force-Push kann nicht ohne Weiteres rueckgaengig gemacht werden; das Backup aufbewahren. Alte, nicht mehr erreichbare Objekte koennen nach der Pruefung lokal bereinigt werden: ```bash git reflog expire --expire=now --all git gc --prune=now ``` `git gc --aggressive` ist normalerweise nicht notwendig und kann bei grossen Repositories lange dauern. Auf einem Hosting-Dienst werden nicht mehr erreichbare Objekte eventuell erst spaeter durch dessen eigene Garbage Collection geloescht.