This commit is contained in:
Mathias Schneider
2026-09-28 17:37:46 +02:00
parent 95797e4a3b
commit 50406e22d1
5 changed files with 487 additions and 3 deletions
+164
View File
@@ -0,0 +1,164 @@
# 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" <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:
```bash
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`:
```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 <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:
```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 <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:
```bash
git remote -v
```
Falls kein `origin` vorhanden ist:
```bash
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:
```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.