Files
it-know-how/git/Git-History-Truncation.md
T
2026-09-28 17:37:46 +02:00

5.0 KiB

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:

git clone --shallow-since="2024-01-01" <REPOSITORY_URL>

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:

git clone --mirror <REPOSITORY_URL> 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:

sudo pacman -S git-filter-repo

Pruefen:

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:

464e39c1234567890abcdef1234567890abcdef

Dann werden seine Eltern entfernt:

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:

git filter-repo --force \
  --commit-callback 'if commit.original_id.startswith(b"464e39c"): commit.parents = []'

Die vollstaendige ID kann vorher ermittelt werden:

git rev-parse 464e39c

Aus:

A -- B -- C -- D -- E

wird dadurch sinngemaess:

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:

git checkout --orphan temp <CUTOFF_COMMIT>
git commit -m "Start truncated history"
git rebase --onto temp <CUTOFF_COMMIT> main
git branch -D temp

Dabei ist <CUTOFF_COMMIT> 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:

git log --all --graph --oneline --decorate
git status

Der neue Root-Commit darf keine parent-Zeile haben:

git show --no-patch --pretty=raw <NEUER_ROOT_COMMIT>

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:

git remote -v

Falls kein origin vorhanden ist:

git remote add origin <REPOSITORY_URL>
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:

git push --force origin main

Alle lokalen Branches und Tags pushen:

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:

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.