CommunityCodierung & Entwicklunggithub.com

roselletabuena/git-safe-history-rewrite

Git-History sicher umschreiben, ohne Arbeit zu verlieren oder andere zu stören – amend, reset, squash, reorder, drop oder edit von Commits, Commit-Nachricht ändern, Commit aus Branch entfernen oder Force-Push. Verwende dies, wenn der Benutzer nach amend, squash, fixup, „letzten Commit rückgängig machen“, „diesen Commit loswerden“, „einen Commit aus der History entfernen“, „meine Commits vor dem PR aufräumen“ oder Force-Push fragt; sowie immer dann, wenn eine Aufgabe mit `git push --force` enden würde. Es entscheidet, ob Umschreiben überhaupt angemessen ist (ein Commit, den andere bereits gezogen haben, sollte zurückgesetzt, nicht gelöscht werden), erstellt zuerst einen Wiederherstellungspunkt, vermeidet interaktive Editoren, die in einer nicht-interaktiven Shell hängen bleiben, und pusht mit einem Lease statt einem bloßen Force.

Was ist git-safe-history-rewrite?

Diese Skill-Anweisung für KI-Agenten (z. B. Claude Code, Cursor, Codex) beschreibt, wie man Git-History sicher umschreibt, ohne Arbeit zu verlieren oder andere Teammitglieder zu stören. Sie behandelt die wichtigsten Szenarien (amend, reset, squash, reorder, drop, edit, Force-Push) und gibt klare Entscheidungsregeln: Nur lokale oder eigene Feature-Branches dürfen umgeschrieben werden; geteilte oder geschützte Branches (main, develop, release/*) erfordern stattdessen ein `git revert`. Die Anleitung umfasst fünf Schritte: 1) Entscheidung zwischen Rewrite und Revert, 2) Erstellen eines Wiederherstellungspunkts (benannter Backup-Branch), 3) Auswahl des am wenigsten destruktiven Befehls (z. B. reset --keep statt --hard, GIT_SEQUENCE_EDITOR für nicht-interaktives Rebase), 4) Verifikation des Ergebnisses (git diff, git range-diff) und 5) sicheres Publizieren mit `--force-with-lease` und `--force-if-includes`. Es enthält wichtige Hinweise zu Signed-Commits, gestapelten Branches, vergessenen Secrets und der Kommunikation mit dem Benutzer. Die Skill-Anweisung zielt darauf ab, versehentlichen Datenverlust und Konflikte mit Teammitgliedern zu vermeiden.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/roselletabuena/skills/tree/HEAD/plugins/git-workflows/skills/git-safe-history-rewrite

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

History sicher umschreiben

Jedes Umschreiben – commit --amend, reset, rebase, filter-repo – erzeugt neue Commits und verschiebt einen Branch-Zeiger von den alten. Das ist harmlos, solange niemand anders die alten Commits hat, und destruktiv, sobald jemand sie hat: Dessen Klon hat immer noch die alte History, und beim nächsten Push oder Pull kommt sie als Duplikate oder Konflikte zurück. Die erste Frage ist also nie „wie“, sondern „wer hat diese Commits noch?“

1. Entscheiden: umschreiben oder revertieren

git fetch --prune
git status --short --branch                  # ahead/behind und Arbeitsverzeichnisstatus
git branch -r --contains <commit>            # welche Remote-Branches enthalten ihn bereits
git log --oneline @{u}..HEAD                 # Commits, die nur lokal existieren
Wo der Commit lebtMach dies
Nur lokal, nie gepushtUmschreiben ohne Einschränkung.
In einen Branch gepusht, den nur dieser Benutzer bearbeitet (sein PR-Branch)Umschreiben, dann mit Lease pushen (Schritt 5).
Auf einem gemeinsamen oder geschützten Branch: main, master, develop, release/* oder mit anderen Commits darauf aufbauendNicht umschreiben. git revert verwenden.
Bereits released oder deployedgit revert. Ein Umschreiben kann es nicht rückgängig machen.

Wenn der Benutzer sagt „Entferne diesen Commit aus main“, möchte er normalerweise seine Auswirkung loswerden, nicht seinen SHA. git revert <sha> macht das, hält die History ehrlich und benötigt nur einen normalen Push. Für einen Merge-Commit: git revert -m 1 <merge-sha>. Sage, warum du revert gewählt hast, und wenn der Benutzer darauf besteht, den Commit aus einem gemeinsamen Branch zu löschen, nenne die Betroffenen und hole dir vorher ein explizites Ja.

Ein Revert erzeugt Konflikte, wenn spätere Commits dieselben Zeilen bearbeitet haben – genau der Fall, wenn Commits auf dem fehlerhaften aufgebaut wurden. Löse sie wie jeden Konflikt (siehe git-conflict-resolution): Entferne die schlechte Änderung, behalte die spätere Arbeit, dann git revert --continue.

Eine Revert-Falle: Nach dem Revertieren eines Merges bringt ein erneuter Merge desselben Branches nichts zurück, weil Git denkt, diese Commits seien bereits gelandet. Um die Arbeit erneut zu integrieren, revertiere zuerst den Revert.

2. Einen Wiederherstellungspunkt erstellen, bevor etwas angefasst wird

git rev-parse HEAD                           # notieren
git branch backup/<branch>-pre-rewrite       # ein echter Ref, nicht nur das Reflog

Das Reflog zeichnet auch auf, wo der Branch war, aber es ist lokal zu diesem Klon, es läuft ab (90 Tage für erreichbare Einträge, 30 für unerreichbare standardmäßig), und git gc nach reflog expire beendet es. Ein benannter Backup-Branch kostet nichts. Lasse ihn so lange liegen, bis der Benutzer das Ergebnis bestätigt hat.

3. Den am wenigsten destruktiven Befehl wählen

ZielBefehlHinweise
Letzten Commit korrigieren (Nachricht oder vergessene Datei)git commit --amendMit -m "..." oder --no-edit wird kein Editor geöffnet.
Commit rückgängig machen, Änderungen bleiben stagedgit reset --soft HEAD~1
Commit rückgängig machen und unstage, Dateien behaltengit reset --mixed HEAD~1 (Standard)
Branch zurücksetzen, lokale Änderungen nicht verlierengit reset --keep <commit>Bricht ab, wenn es uncommittete Änderungen überschreiben würde. Bevorzuge dies gegenüber --hard.
Auch uncommittete Arbeit verwerfengit reset --hardUncommittete Arbeit ist nicht im Reflog und kann nicht wiederhergestellt werden. Nur nach Bestätigung, dass nichts davon erwünscht ist; wenn unsicher, vorher git stash -u.
Änderungen an einer Datei verwerfengit restore <datei>
Fixups in frühere Commits squashengit commit --fixup=<sha> dann autosquashSiehe unten.
Commits löschen oder neu anordnenrebase -i mit bereitgestelltem TodoSiehe unten.

Ein gescheiterter Pre-Commit-Hook bedeutet, dass kein Commit erstellt wurde. Wenn du dann git commit --amend ausführen, um es „erneut zu versuchen“, veränderst du den vorherigen Commit – die neuen Änderungen landen im falschen. Nach einem Hook-Fehler: Problem beheben, erneut stage und einen normalen neuen Commit erstellen.

Interaktives Rebase ohne interaktiven Editor

git rebase -i öffnet einen Editor, der in einer nicht-interaktiven Shell hängen bleibt. Verlasse dich nicht darauf. Zwei Wege, die funktionieren:

# Fixup-Commits in ihre Ziele folden – akzeptiere das von Git generierte Todo:
git commit --fixup=<target-sha>
GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash <basis>

# Commits löschen oder neu anordnen: schreibe das Todo selbst.
GIT_SEQUENCE_EDITOR="sed -i '/^pick <short-sha> /d'" git rebase -i <basis>

<basis> ist der Commit vor dem ersten, den du ändern willst. Wenn ein Rebase bei einem Konflikt stoppt, löse ihn, git add, dann GIT_EDITOR=true git rebase --continue. Wenn etwas schiefläuft, bringt git rebase --abort dich an den Ausgangspunkt zurück.

4. Überprüfen, dass das Umschreiben das Gewünschte bewirkt hat

Eine Nachricht bearbeiten, neu anordnen oder squash sollte den finalen Tree identisch lassen:

git diff backup/<branch>-pre-rewrite HEAD --stat      # leer = gleicher Inhalt
git range-diff backup/<branch>-pre-rewrite...HEAD     # Commit-für-Commit-Vergleich

Wenn du beabsichtigt hast, Inhalt zu entfernen, sollte der Diff genau diesen Inhalt und nichts anderes zeigen. Alles Unerwartete bedeutet: vom Backup wiederherstellen und neu machen.

5. Veröffentlichen – nur mit Zustimmung des Benutzers

Das Pushen eines Umschreibens ist der nach außen sichtbare, schwer rückgängig zu machende Schritt. Bestätige zuerst, es sei denn, der Benutzer hat bereits nach dem Push gefragt.

git push --force-with-lease=<branch>:<erwarteter-remote-sha> --force-if-includes origin <branch>
  • Nie bare --force. Es überschreibt alles auf dem Remote, einschließlich eines Teamkollegen-Pushes, den du nicht gesehen hast.
  • Nenne den erwarteten SHA. Ein einfaches --force-with-lease vergleicht mit deinem Remote-Tracking-Ref, und ein git fetch im Hintergrund einer IDE aktualisiert diesen Ref leise, sodass der Lease dann nichts schützt. Verwende den SHA aus Schritt 1 (git rev-parse origin/<branch>).
  • --force-if-includes (Git 2.30+) verweigert zusätzlich, wenn der Remote auf etwas verschoben wurde, das dein lokaler Branch nie integriert hat.
  • Nie main/master/develop/release/* force-pushen. Wenn eine Hosting-Regel dies blockiert, ist diese Regel korrekt; umgehe sie nicht.

Nach dem Push teile dem Benutzer mit, dass jeder, der den alten Branch ausgecheckt hat, auf den neuen zurücksetzen (reset) und nicht pullen muss, und dass Review-Kommentare in einem offenen PR ihre Anker verlieren können.

Schutzmaßnahmen

  • Signierte Commits verlieren ihre Signatur beim Umschreiben; mit -S neu signieren oder den Benutzer informieren.
  • Schreibe keine Commits um, die von einer anderen Person autor sind ohne zu fragen, selbst in einem Feature-Branch.
  • Gestapelte Branches oberhalb des umgeschriebenen sind nun veraltet. Verwende git-stacked-pr-rebase, um sie neu zu stapeln, statt sie einzeln zu rebasen.
  • Ein committedes Secret wird durch ein Umschreiben nicht behoben. Rotiere zuerst den Credential; gehe davon aus, dass es kompromittiert ist.
  • Berichte klar: Was der alte Tip war, was der neue ist, wo der Backup-Ref liegt und ob etwas ungepusht ist. Wenn Revert die richtige Wahl war, sage, dass du das getan hast.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Verwandte Skills