Communitygithub.com

lguidolin/commit-history-rewrite

Verwenden, wenn ein bestehendes Repository eine unübersichtliche Commit-History hat, die vor der Einführung von release-please an Conventional Commits angepasst werden muss, oder wenn zwischenzeitliche WIP/fixup/merge-Commits bereinigt werden sollen.

Was ist commit-history-rewrite?

Diese Fertigkeit ermöglicht das automatisierte Rewriting der Commit-History eines bestehenden Git-Repositorys, um sie an den Standard der Conventional Commits anzupassen. Sie analysiert vorhandene Commits, klassifiziert sie nach Typ (z.B. `feat`, `fix`, `docs`), identifiziert Squash-Kandidaten wie WIP- oder Merge-Commits und führt das Rewriting mit vollständigen Sicherheitsvorkehrungen durch. Die Fertigkeit ist ideal für Repositories, die vor der Einführung von „release-please“ oder anderer automatisierter Release-Tools eine Bereinigung benötigen, und unterstützt sowohl interaktives Rebase (für <50 Commits) als auch `git-filter-repo` (für größere Historien). Sie garantiert, dass nur Commit-Nachrichten geändert werden, während der Dateibaum, die Autorenschaft und die Chronologie erhalten bleiben. Ein detaillierter Plan wird vor jeder destruktiven Aktion zur Bestätigung vorgelegt, und ein Backup-Branch stellt die vollständige Reversibilität sicher.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/commit-history-rewrite

In Ihrer bevorzugten KI fragen

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

Dokumentation

Commit History Rewrite

Schreibt die Commit-History eines bestehenden Repositorys um, sodass sie den Conventional Commits entspricht. Analysiert Commits, klassifiziert sie nach Typ, identifiziert Squash-Kandidaten und führt das Rewriting mit vollständigen Sicherheitsvorkehrungen durch.

Überblick

History ist Dokumentation. Jede Commit-Nachricht auf main ist eine dauerhafte Aufzeichnung, die zukünftige Entwickler (und Agenten) nutzen, um zu verstehen, was sich geändert hat und warum. Nachrichten wie „fix“, „update“, „wip“ zerstören diese Dokumentation. Release-Please parst Commit-Nachrichten, um Versionssprünge zu bestimmen – nicht konforme Nachrichten führen zu falschen oder gar keinen Releases.

Code ist eine Verbindlichkeit. Hyrums Gesetz gilt auch für die Git-History: Wenn genügend Werkzeuge von Commit-Nachrichten abhängen (Changelogs, Release-Automatisierung, git bisect, git blame), wird das Format dieser Nachrichten zu einem Vertrag. Das Bereinigen der History vor der Einführung von Automatisierung verhindert, dass die alten Nachrichten jedes nachgelagerte Werkzeug verschmutzen.

Diese Fertigkeit schreibt die Nachrichten um, nicht den Code. Der Dateibaum muss vorher und nachher identisch sein – nur die Commit-Metadaten ändern sich.

Wann verwenden

  • Einführung von Conventional Commits in einem Repository mit bestehender, nicht konformer History
  • Commit-Log ist mit WIP, fixup, merge oder bedeutungslosen Nachrichten verunreinigt
  • Vorbereitung eines Repositorys für release-please (das für seine Funktion Conventional Commits benötigt)
  • Bereinigung vor der Veröffentlichung als Open Source oder der Übergabe eines Repositorys

Wann NICHT verwenden

  • Repository wurde bereits mit Conventional Commits veröffentlicht (nichts umzuschreiben)
  • Geteilte Branches mit aktiven Mitarbeitern, die nicht benachrichtigt wurden
  • Es müssen nur die letzten Commits korrigiert werden (git rebase -i HEAD~N direkt verwenden)
  • Repository ist ein Fork, dessen Upstream das Commit-Format vorgibt

Prozess

Schritt 1: Repository bewerten

# Gesamtzahl der Commits zählen
git rev-list --count HEAD

# Zusammenfassung der Commit-Nachrichten anzeigen
git log --oneline --no-merges

# Nach Mitarbeitern suchen (Vielfalt der Autoren)
git shortlog -sne --no-merges

# Dichte der Merge-Commits ermitteln
git log --merges --oneline | wc -l

Entscheidungspunkt: Wenn die Anzahl der Commits < 50 ist, interaktives Rebase verwenden. Wenn >= 50, git-filter-repo verwenden.

Schritt 2: Vorhandene Commits klassifizieren

Jeden Commit anhand von zwei Signalen analysieren: dem Nachrichtentext und den geänderten Dateien. Wenn die Nachricht mehrdeutig ist (z.B. „update stuff“), ist der Diff die Wahrheitsquelle.

Signal 1: Nachrichteninhalt

Signal in der NachrichtZugewiesener Typ
„add“, „new“, „implement“, „create“, „introduce“, „support“feat
„fix“, „bug“, „patch“, „resolve“, „correct“, „handle error“fix
„refactor“, „restructure“, „reorganize“, „simplify“, „extract“, „move“refactor
„update dep“, „bump“, „upgrade“, „version“, „config“, „setup“chore
„test“, „spec“, „coverage“, „assert“test
„doc“, „readme“, „comment“, „typo in doc“docs
„ci“, „workflow“, „pipeline“, „action“, „deploy“ci

Signal 2: Geänderte Dateien (bei mehrdeutiger Nachricht)

git show --stat --format="" <hash>
DateimusterZugewiesener Typ
Nur *.md, docs/**docs
Nur *test*, *spec*, __tests__/**test
Nur .github/workflows/**, .gitlab-ci.yml, Jenkinsfileci
Nur package.json, *.lock, requirements.txt, Cargo.tomlchore
Nur Konfigurationsdateien (.eslintrc, tsconfig.json, .prettierrc)chore

Squash-Kandidaten

SignalAktion
Nachricht ist „WIP“, „wip“, „temp“, „save“, „checkpoint“, einzelnes Wort oder leerIn den nächstgelegenen sinnvollen Commit squashen
Nachricht ist „fixup“, „fix typo“, „oops“, „lint“, „formatting“In den vorherigen squashen
Merge-Commits (aus Non-Squash-Merges)Squash-Kandidat – prüfen, ob der Inhalt bereits auf main ist

Bei mehrdeutigen Commits den vollständigen Diff anzeigen (git show <hash>), um den Typ anhand des Inhalts zu bestimmen.

Schritt 3: Qualitativ hochwertige Nachrichten schreiben

Umgeschriebene Nachrichten müssen den Commit-Nachrichten-Standards folgen:

<typ>[optionaler scope]: <kurze imperative Beschreibung>

[optionaler Body, der das WARUM erklärt, nicht das WAS]

Gute Umschreibungen:

„initial setup“           →  „chore: initial project setup“
„added login“             →  „feat: add user login“
„fix bug“                 →  „fix: resolve login redirect loop“
„Update README.md“        →  „docs: update README with setup instructions“
„updated deps“            →  „chore(deps): update dependencies“

Schlechte Umschreibungen (das nicht tun):

„stuff“                   →  „chore: stuff“          ← immer noch bedeutungslos
„changes“                 →  „feat: changes“         ← keine Beschreibung
„fix“                     →  „fix: fix“              ← redundant

Wenn die ursprüngliche Nachricht bedeutungslos ist, den Diff lesen, um eine angemessene Beschreibung zu schreiben. Wenn der Diff zu umfangreich oder mehrdeutig ist, den Benutzer fragen, worum es bei dem Commit ging.

Schritt 4: Umschreibungsplan präsentieren

Vor jeder destruktiven Aktion dem Benutzer einen vollständigen Plan zeigen:

UMSCHREIBUNGSPLAN
=================
Gesamtanzahl Commits: 47
Umzuschreibende Commits: 32
Zu squashende Commits: 8
Bereits konforme Commits: 7
Erhaltene Autoren: 3 (Alice <alice@co>, Bob <bob@co>, CI Bot <bot@co>)

VORGESCHLAGENE ÄNDERUNGEN:
  abc1234  „initial setup“           →  „chore: initial project setup“
  def5678  „added login“             →  „feat: add user login“
  ghi9012  „wip“                     →  [SQUASHEN in def5678]
  jkl3456  „fix bug“                 →  „fix: resolve login redirect loop“
  mno7890  „Update README.md“        →  „docs: update README with setup instructions“
  pqr1234  „feat: add search“        →  [BEHALTEN – bereits konform]
  ...

SICHERHEIT:
  ✓ Backup-Branch: backup/pre-rewrite-<Zeitstempel>
  ⚠ Force Push nach dem Umschreiben erforderlich
  ⚠ Alle Mitarbeiter müssen nach dem Push neu klonen oder zurücksetzen

UNVERÄNDERT:
  - Dateibaum (keine Inhaltsänderungen)
  - Autorenname, E-Mail und Datum bei jedem Commit
  - Commit-Reihenfolge und Elternbeziehungen (außer bei gesquashten Commits)

Auf explizite Benutzerfreigabe warten, bevor fortgefahren wird. Nicht auf „klingt gut“ reagieren – „ja“ oder eine gleichwertige Bestätigung verlangen.

Schritt 5: Backup erstellen

# Einen Backup-Branch am aktuellen HEAD erstellen
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# Prüfen, ob das Backup existiert
git branch -l 'backup/*'

# Optional: Backup zur zusätzlichen Sicherheit auf das Remote-Push schieben
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

Schritt 6: Umschreiben ausführen

Option A: Interaktives Rebase (< 50 Commits)

# Von der Wurzel aus rebasen
git rebase -i --root

Im Editor:

  • Squash-Kandidaten mit squash oder fixup markieren
  • Nachrichten-Umschreibungen mit reword markieren
  • Commit-Reihenfolge beibehalten

Autorenschaft erhalten: Interaktives Rebase erhält standardmäßig Autorennamen, E-Mail und Datum. NICHT --reset-author verwenden. NICHT --committer-date-is-author-date verwenden, es sei denn, es wurde ausdrücklich darum gebeten.

Option B: git-filter-repo (>= 50 Commits)

Ein Python-Callback-Skript (rewrite-messages.py) erstellen, das alte Nachrichten auf neue abbildet:

import re

REWRITES = {
    b'alter Hash-Präfix': b'neue Nachricht',
    # ... aus dem Plan generiert
}

def rewrite(commit, metadata):
    # Anhand der ursprünglichen Nachricht oder des Hashs nachschlagen
    if commit.original_id in REWRITES:
        commit.message = REWRITES[commit.original_id]

Anwenden mit:

git filter-repo --commit-callback "$(cat rewrite-messages.py)" --force

Autorenschaft erhalten: git-filter-repo erhält standardmäßig die Autorenschaft. NICHT --mailmap oder Autoren-Umschreibungs-Flags verwenden, es sei denn, der Benutzer fordert dies ausdrücklich an.

Commits squashen

Für Squash-Kandidaten in den nächstgelegenen sinnvollen Commit zusammenführen. Der resultierende Commit:

  • Verwendet die Conventional-Commit-Nachricht des sinnvollen Commits
  • Erhält den ursprünglichen Autor des sinnvollen Commits (nicht des Squash-Ziels)
  • Kombiniert alle Änderungen aus dem gesquashten Bereich

Schritt 7: Überprüfen

Alle Überprüfungen durchführen. Jede Prüfung muss bestanden werden, bevor mit dem Push fortgefahren wird.

# 1. Umgeschriebenes Log überprüfen
git log --oneline --no-merges

# 2. ALLE Nachrichten auf Übereinstimmung mit Conventional Commits validieren
FAILURES=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FAILURES" -gt 0 ]]; then
  echo "FEHLGESCHLAGEN: $FAILURES nicht konforme Commits gefunden:"
  git log --format="%h %s" --no-merges | grep -vE "^[a-f0-9]+ (feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+"
  exit 1
fi
echo "BESTANDEN: Alle Commits sind konform"

# 3. NULL Inhaltsänderungen überprüfen (kritisch – beweist, dass nur Nachrichten geändert wurden)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "FEHLGESCHLAGEN: Inhaltsunterschiede erkannt – Rewriting hat Code geändert, nicht nur Nachrichten"
  echo "$DIFF"
  exit 1
fi
echo "BESTANDEN: Dateibaum identisch"

# 4. Autorenschaft erhalten überprüfen
echo "=== Autoren vorher ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== Autoren nachher ==="
git log --format="%an <%ae>" --no-merges | sort -u
# Diese beiden Listen müssen identisch sein

Wenn eine Prüfung fehlschlägt, NICHT pushen. Auf das Backup zurücksetzen und untersuchen:

git reset --hard backup/pre-rewrite-*

Schritt 8: Pushen (mit Bestätigung)

Dies ist destruktiv für Remote-Mitarbeiter. Mit dem Benutzer bestätigen:

⚠ FORCE PUSH WARNUNG
  Branch: main
  Remote: origin
  Aktion: git push --force-with-lease origin main

  Nach diesem Push müssen alle Mitarbeiter Folgendes ausführen:
    git fetch origin
    git reset --hard origin/main

  Geben Sie „ja“ ein, um fortzufahren:
git push --force-with-lease origin main

--force-with-lease (nicht --force) verwenden, um das Überschreiben gleichzeitiger Remote-Änderungen zu verhindern. Wenn --force-with-lease abgelehnt wird, hat nach dem Umschreiben jemand auf den Branch gepusht – vor einem erneuten Versuch untersuchen.

Bereinigung nach dem Umschreiben

Nach erfolgreichem Push und Benachrichtigung der Mitarbeiter:

# Prüfen, ob Remote mit lokal übereinstimmt
git log --oneline -5
git log --oneline -5 origin/main
# Diese müssen identisch sein

# Backup-Branch mindestens 2 Wochen behalten, dann löschen
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

Rationalisierungen

RationalisierungRealität
„Die alte History spielt keine Rolle“Release-Please parst Commit-Nachrichten, um Versionen zu bestimmen. Nicht konforme History führt zu falschen oder keinen Releases. git bisect, git blame und git log --grep sind alle auf aussagekräftige Nachrichten angewiesen.
„Ich mache ab diesem Commit einfach einen Neuanfang“Man verliert Blame, Autorenschaft und Kontext. Ein --orphan-Branch zerstört die Entwicklungsgeschichte. Das Rewriting erhält alles außer schlechten Nachrichten.
„Es ist zu riskant, umzuschreiben“Der Backup-Branch macht dies vollständig reversibel. Der Diff zeigt null Inhaltsänderungen. Wenn etwas schiefgeht: git reset --hard backup/pre-rewrite-*.
„Ich schreibe später um, wenn es wichtig ist“History-Umschreibungen werden mit der Zeit schwieriger, je mehr Leute davon klonen und Branches davon abzweigen. Jetzt erledigen, bevor weitere nachgelagerte Abhängigkeiten entstehen.
„Force Pushing ist gefährlich“--force-with-lease schützt davor, die Arbeit anderer zu überschreiben. In Kombination mit dem Backup-Branch ist dies sicher.
„Die Nachrichten sind nah genug dran“„Nah genug“ bedeutet, dass Release-Please sie nicht parsen wird. Conventional Commits sind ein strenges Format – entweder eine Nachricht ist konform oder nicht.
„Ich kann Releases einfach manuell taggen“Manuelle Tags treiben ab, werden vergessen und entbehren der Changelog-Integration. Der Sinn liegt in der Automatisierung – aber Automatisierung benötigt parsebare Eingaben.

Rote Flaggen

  • Überspringen der Erstellung des Backup-Branches
  • Verwendung von --force anstelle von --force-with-lease
  • Umschreiben eines Branches, an dem andere aktiv arbeiten, ohne Benachrichtigung
  • Inhaltsunterschiede, die im Diff von Schritt 7 auftauchen (bedeutet, dass das Rewriting Code geändert hat, nicht nur Nachrichten)
  • Änderung der Autorenanzahl nach dem Rewriting (bedeutet, dass die Autorenschaft nicht erhalten wurde)
  • Fortfahren ohne Benutzerfreigabe des Umschreibungsplans
  • Umgeschriebene Nachrichten, die immer noch bedeutungslos sind (z.B. "chore: stuff")
  • Squashen von Commits über verschiedene Autoren hinweg ohne Diskussion
  • Backup-Branch nach dem Umschreiben nicht mindestens 2 Wochen behalten

Individual skills in this repo

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

lguidolin/change-hygiene-and-code-craft

Use when writing or refactoring code, structuring a commit or PR, or deciding whether to abstract duplication. Symptoms — mixing reorg with logic changes, a PR doing several things at once, a file growing large, the second copy of similar code, or unsure whether to DRY something up.

lguidolin/cloud-delivery-aks

Use when deploying to Kubernetes or Azure Kubernetes Service (AKS), configuring cloud secrets, setting up progressive rollout/canary, per-PR ephemeral environments, or k8s health probes. Keywords — Kubernetes, AKS, Key Vault, Argo Rollouts, Flagger, canary, blue-green, liveness, readiness, PodDisruptionBudget, HPA, rollback, GHCR.

lguidolin/conventional-commits-and-releases

Use when committing, writing a commit message, opening a PR that will be squash-merged, or configuring automated versioning/changelogs. Keywords — conventional commits, release-please, semver, feat/fix/chore, breaking change, changelog.

lguidolin/defense-in-depth-security

Use when handling untrusted input, secrets, authentication/authorization, or dependencies — or threat-modeling a new surface. Keywords — STRIDE, threat model, least privilege, secrets management, supply chain, dependency scanning, input validation, audit log, defense in depth.

lguidolin/designing-before-building

Use when starting a feature, fixing a non-trivial bug, or about to write implementation code — before any code exists. Symptoms you need this: "this is simple, I'll just code it", reaching for the editor before a design is approved, or an idea that hasn't been turned into a spec and plan.

lguidolin/engineering-constitution

Use when starting work in a project that follows the engineering constitution, orienting to its rules, or deciding which engineering practice applies to a task — spec writing, commits, testing, security, deploys, database, or UI work.

lguidolin/graphql-contract-testing

Use when writing a GraphQL query/mutation that the UI and a test will share, or building route/schema contract or smoke tests. Symptoms — copying a query into a test, a test asserting on query text, schema change that didn't break the UI build, or RLS/permission drift. Keywords — graphql-codegen, typed document, contract test, route smoke test.

lguidolin/init-repo-CI

Use when setting up a new repository with conventional commits, release-please, and CI automation, or when retrofitting an existing repository that lacks automated versioning and PR validation workflows.

lguidolin/interface-craft-and-accessibility

Use when building or styling UI — components, layouts, forms, design tokens — or making accessibility decisions. Keywords — a11y, WCAG, keyboard navigation, focus state, contrast, design system, minimalist UI, component reuse, ARIA, semantic HTML.

lguidolin/merge-gates-and-automation

Use when setting up or changing CI, pre-push hooks, or a task runner, or deciding what must pass before merge. Symptoms — tempted to put authoritative checks only in a local hook, skip CI, bypass with --no-verify, or unsure what gates a merge vs. runs locally.

lguidolin/observability-and-slos

Use when adding logging, metrics, tracing, health checks, SLOs, or alerting — or when building a service surface that needs to be operable and debuggable. Keywords — structured logs, OpenTelemetry, correlation id, RED metrics, liveness, readiness, SLI, SLO, error budget, alerting.

lguidolin/performance-and-scale

Use when working on hot paths, list endpoints, pagination, data-access in loops, or public interfaces/schemas. Symptoms — unbounded queries, N+1 access, no latency budget, optimizing without measuring, or changing an interface many consumers depend on. Keywords — pagination, N+1, Hyrum's Law, performance budget, bundle size.

lguidolin/postgres-postgraphile-rls-and-sql

Use when writing PostgreSQL, PostGraphile config, Row-Level Security policies, SQL schema files, or working on the Browser→App→PostGraphile→Postgres data path. Keywords — RLS, SECURITY DEFINER, search_path, pgSettings, grants, roles, GraphQL depth limit, query cost, statement_timeout, SQL file organization.

lguidolin/recording-decisions

Use when a design or architecture decision has been made and needs to be captured — writing a decision record or ADR, updating a decision index, noting a deferred idea, or superseding a past decision. Keywords — ADR, decision record, rationale, rejected alternatives, dependency index.

lguidolin/resilience-and-deploy-safety

Use when planning a deploy, designing a rollback, or responding to an incident or writing a postmortem. Keywords — deploy safety, rollback, immutable artifact, progressive delivery, canary, blast radius, incident response, blameless postmortem, error budget.

lguidolin/ship-it

Use when the user wants to ship work — push, PR, archive decision records, merge, and clean up. Handles the full lifecycle from committing final changes through post-merge cleanup including converting specs/plans to compact decision records.

lguidolin/tests-as-a-control

Use when writing or modifying tests, when a test breaks during a refactor, or when testing permission/role rules. Symptoms — tempted to edit a test to make it pass, testing only the happy path, a deny-test that started passing, flaky tests, or unsure what to assert.

lguidolin/zero-downtime-migrations

Use when changing a database schema where data must survive the change — adding/removing/renaming columns, constraints, indexes, or backfilling. Symptoms — a destructive migration bundled with a code deploy, a NOT NULL column with a backfill, a table-locking UPDATE, or a rename. Keywords — expand/contract, parallel change, backfill, NOT VALID, CREATE INDEX CONCURRENTLY, graphile-migrate.

Verwandte Skills