Communitygithub.com

lguidolin/commit-history-rewrite

À utiliser lorsqu'un dépôt existant a un historique de commits désordonné qui doit être normalisé en commits conventionnels avant d'adopter release-please, ou lorsque des commits intermédiaires WIP/fixup/merge doivent être nettoyés.

Qu'est-ce que commit-history-rewrite ?

Cette compétence permet de réécrire l'historique des commits d'un dépôt existant pour le conformer aux commits conventionnels (Conventional Commits). Elle analyse chaque commit, le classifie par type (feat, fix, refactor, chore, test, docs, ci), identifie les candidats à l'écrasement (squash), et exécute la réécriture avec des garde-fous complets : sauvegarde automatique, plan de réécriture soumis à l'approbation de l'utilisateur, vérifications de conformité et d'intégrité du contenu, et poussée sécurisée via --force-with-lease. La compétence est conçue pour les agents autonomes (Claude Code, Cursor, Codex) et les ingénieurs préparant un dépôt pour l'adoption de release-please. Elle préserve scrupuleusement les auteurs, les dates et l'arbre de fichiers (seuls les messages changent). Elle propose deux méthodes d'exécution : rebase interactif pour moins de 50 commits, ou git-filter-repo avec un script Python pour les historiques plus volumineux. Elle inclut des vérifications post-réécriture, notamment la validation que tous les messages suivent le format strict des commits conventionnels et que les différences de contenu sont nulles.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Réécriture d'historique de commits

Réécrire l'historique des commits d'un dépôt existant pour le conformer aux commits conventionnels. Analyse les commits, les classifie par type, identifie les candidats à l'écrasement (squash) et exécute la réécriture avec des garde-fous complets.

Aperçu

L'historique est une documentation. Chaque message de commit sur main est un enregistrement permanent que les futurs ingénieurs (et agents) utiliseront pour comprendre ce qui a changé et pourquoi. Des messages comme "fix", "update", "wip" détruisent cette documentation. Release-please analyse les messages de commit pour déterminer les incréments de version — des messages non conformes produisent des versions erronées ou aucune sortie.

Le code est un passif. La loi de Hyrum s'applique aussi à l'historique git : avec suffisamment d'outils dépendant des messages de commit (changelogs, automatisation des versions, git bisect, git blame), le format de ces messages devient un contrat. Nettoyer l'historique avant d'adopter l'automatisation empêche les anciens messages de polluer tous les outils en aval.

Cette compétence réécrit les messages, pas le code. L'arborescence des fichiers doit être identique avant et après — seules les métadonnées des commits changent.

Quand l'utiliser

  • Adoption de commits conventionnels dans un dépôt avec un historique non conforme existant
  • Le journal des commits est pollué par des messages WIP, fixup, merge ou insignifiants
  • Préparation d'un dépôt pour release-please (qui nécessite des commits conventionnels pour fonctionner)
  • Nettoyage avant l'open-sourcing ou le transfert d'un dépôt

Quand NE PAS l'utiliser

  • Le dépôt a déjà été publié avec des commits conventionnels (rien à réécrire)
  • Branches partagées avec des collaborateurs actifs qui n'ont pas été notifiés
  • Vous avez seulement besoin de corriger les quelques derniers commits (utilisez git rebase -i HEAD~N directement)
  • Le dépôt est un fork où l'amont contrôle le format des commits

Processus

Étape 1 : Évaluer le dépôt

# Compter le nombre total de commits
git rev-list --count HEAD

# Afficher un résumé des messages de commit
git log --oneline --no-merges

# Vérifier les collaborateurs (diversité des auteurs)
git shortlog -sne --no-merges

# Identifier la densité des commits de merge
git log --merges --oneline | wc -l

Point de décision : Si le nombre de commits < 50, utilisez le rebase interactif. Si >= 50, utilisez git-filter-repo.

Étape 2 : Classifier les commits existants

Analysez chaque commit en utilisant deux signaux : le texte du message et les fichiers modifiés. Lorsque le message est ambigu (par exemple, "update stuff"), la diff est la source de vérité.

Signal 1 : Contenu du message

Signal dans le messageType assigné
"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 : Fichiers modifiés (lorsque le message est ambigu)

git show --stat --format="" <hash>
Motif de fichierType assigné
Uniquement *.md, docs/**docs
Uniquement *test*, *spec*, __tests__/**test
Uniquement .github/workflows/**, .gitlab-ci.yml, Jenkinsfileci
Uniquement package.json, *.lock, requirements.txt, Cargo.tomlchore
Uniquement les fichiers de configuration (.eslintrc, tsconfig.json, .prettierrc)chore

Candidats à l'écrasement (squash)

SignalAction
Le message est "WIP", "wip", "temp", "save", "checkpoint", un seul mot, ou videécraser dans le commit significatif le plus proche
Le message est "fixup", "fix typo", "oops", "lint", "formatting"écraser dans le précédent
Commits de merge (provenant de merges non squash)candidat à l'écrasement — évaluer si le contenu est déjà sur main

Pour les commits ambigus, vérifiez la diff complète (git show <hash>) pour déterminer le type à partir du contenu.

Étape 3 : Rédiger des messages de qualité

Les messages réécrits doivent respecter les normes des messages de commit :

<type>[portée facultative]: <description impérative courte>

[corps facultatif expliquant POURQUOI, pas quoi]

Bonnes réécritures :

"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"

Mauvaises réécritures (ne faites pas ça) :

"stuff"                   →  "chore: stuff"          ← toujours insignifiant
"changes"                 →  "feat: changes"         ← pas de description
"fix"                     →  "fix: fix"              ← redondant

Lorsque le message original est insignifiant, lisez la diff pour rédiger une description appropriée. Si la diff est trop grande ou ambiguë, demandez à l'utilisateur à quoi le commit faisait référence.

Étape 4 : Présenter le plan de réécriture

Avant toute action destructive, montrez à l'utilisateur un plan complet :

PLAN DE RÉÉCRITURE
===================
Nombre total de commits : 47
Commits à réécrire : 32
Commits à écraser : 8
Commits déjà conformes : 7
Auteurs préservés : 3 (Alice <alice@co>, Bob <bob@co>, CI Bot <bot@co>)

CHANGEMENTS PROPOSÉS :
  abc1234  "initial setup"           →  "chore: initial project setup"
  def5678  "added login"             →  "feat: add user login"
  ghi9012  "wip"                     →  [ÉCRASER dans def5678]
  jkl3456  "fix bug"                 →  "fix: resolve login redirect loop"
  mno7890  "Update README.md"        →  "docs: update README with setup instructions"
  pqr1234  "feat: add search"        →  [CONSERVER — déjà conforme]
  ...

SÉCURITÉ :
  ✓ Branche de sauvegarde : backup/pre-rewrite-<timestamp>
  ⚠ Push forcé requis après la réécriture
  ⚠ Tous les collaborateurs doivent recloner ou réinitialiser après le push

INCHANGÉ :
  - Arborescence des fichiers (zéro modification de contenu)
  - Nom, email et date de l'auteur sur chaque commit
  - Ordre des commits et relations parent (sauf commits écrasés)

Attendez l'approbation explicite de l'utilisateur avant de procéder. Ne continuez pas sur un "ça a l'air bien" — exigez "oui" ou équivalent.

Étape 5 : Créer une sauvegarde

# Créer une branche de sauvegarde à HEAD actuel
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# Vérifier que la sauvegarde existe
git branch -l 'backup/*'

# Optionnel : pousser la sauvegarde sur le dépôt distant pour plus de sécurité
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

Étape 6 : Exécuter la réécriture

Option A : Rebase interactif (< 50 commits)

# Rebase depuis la racine
git rebase -i --root

Dans l'éditeur :

  • Marquez les candidats à l'écrasement avec squash ou fixup
  • Marquez les réécritures de messages avec reword
  • Préservez l'ordre des commits

Préservation des auteurs : Le rebase interactif préserve par défaut le nom, l'email et la date de l'auteur. N'utilisez PAS --reset-author. N'utilisez PAS --committer-date-is-author-date sauf demande explicite.

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

Créez un script de callback Python (rewrite-messages.py) qui mappe les anciens messages aux nouveaux :

import re

REWRITES = {
    b'ancien préfixe de hash': b'nouveau message',
    # ... généré à partir du plan
}

def rewrite(commit, metadata):
    # Rechercher par message ou hash original
    if commit.original_id in REWRITES:
        commit.message = REWRITES[commit.original_id]

Appliquez avec :

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

Préservation des auteurs : git-filter-repo préserve la paternité par défaut. N'utilisez PAS --mailmap ni les indicateurs de réécriture d'auteur sauf demande explicite de l'utilisateur.

Écrasement des commits

Pour les candidats à l'écrasement, combinez-les dans le commit significatif le plus proche. Le commit résultant :

  • Utilise le message de commit conventionnel du commit significatif
  • Préserve l'auteur original du commit significatif (pas la cible d'écrasement)
  • Combine toutes les modifications de la plage écrasée

Étape 7 : Vérifier

Exécutez toutes les vérifications. Chaque vérification doit réussir avant de procéder au push.

# 1. Examiner le journal réécrit
git log --oneline --no-merges

# 2. Valider que TOUS les messages correspondent aux commits conventionnels
FAILURES=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FAILURES" -gt 0 ]]; then
  echo "ÉCHEC : $FAILURES commits non conformes trouvés :"
  git log --format="%h %s" --no-merges | grep -vE "^[a-f0-9]+ (feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+"
  exit 1
fi
echo "SUCCÈS : tous les commits sont conformes"

# 3. Vérifier zéro modification de contenu (critique — prouve que seuls les messages ont changé)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "ÉCHEC : différences de contenu détectées — la réécriture a changé le code, pas seulement les messages"
  echo "$DIFF"
  exit 1
fi
echo "SUCCÈS : arborescence des fichiers identique"

# 4. Vérifier la préservation des auteurs
echo "=== Auteurs avant ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== Auteurs après ==="
git log --format="%an <%ae>" --no-merges | sort -u
# Ces deux listes doivent être identiques

Si une vérification échoue, NE FAITES PAS de push. Réinitialisez vers la sauvegarde et enquêtez :

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

Étape 8 : Pousser (avec confirmation)

Ceci est destructeur pour les collaborateurs distants. Confirmez avec l'utilisateur :

⚠ AVERTISSEMENT DE FORCE PUSH
  Branche : main
  Distant : origin
  Action : git push --force-with-lease origin main

  Après ce push, tous les collaborateurs doivent exécuter :
    git fetch origin
    git reset --hard origin/main

  Tapez "oui" pour continuer :
git push --force-with-lease origin main

Utilisez --force-with-lease (pas --force) pour éviter d'écraser les modifications distantes simultanées. Si --force-with-lease est rejeté, quelqu'un a poussé sur la branche depuis la réécriture — enquêtez avant de réessayer.

Nettoyage post-réécriture

Après un push réussi et la notification des collaborateurs :

# Vérifier que le distant correspond au local
git log --oneline -5
git log --oneline -5 origin/main
# Ces deux doivent être identiques

# Conserver la branche de sauvegarde pendant au moins 2 semaines, puis supprimer
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

Rationalisations

RationalisationRéalité
"L'ancien historique n'a pas d'importance"Release-please analyse les messages de commit pour déterminer les versions. Un historique non conforme produit des versions erronées ou aucune sortie. git bisect, git blame et git log --grep dépendent tous de messages significatifs.
"Je vais simplement repartir de zéro à partir de ce commit"Vous perdez le blame, la paternité et le contexte. Une branche --orphan détruit le récit de développement. La réécriture préserve tout sauf les mauvais messages.
"C'est trop risqué de réécrire"La branche de sauvegarde rend ce processus entièrement réversible. Vérifiez que la diff ne montre aucune modification de contenu. Si quelque chose tourne mal : git reset --hard backup/pre-rewrite-*.
"Je réécrirai plus tard quand ce sera important"Les réécritures d'historique deviennent plus difficiles avec le temps à mesure que plus de personnes clonent et bifurquent. Faites-le maintenant, avant que davantage de dépendances en aval n'existent.
"Le force push est dangereux"--force-with-lease protège contre l'écrasement du travail des autres. Combiné avec la branche de sauvegarde, c'est sûr.
"Les messages sont suffisamment proches""Suffisamment proche" signifie que release-please ne les analysera pas. Les commits conventionnels sont un format strict — soit un message est conforme, soit il ne l'est pas.
"Je peux simplement taguer les versions manuellement"Les tags manuels dérivent, sont oubliés et manquent d'intégration avec le changelog. L'objectif est l'automatisation — mais l'automatisation a besoin d'une entrée analysable.

Drapeaux rouges

  • Sauter la création de la branche de sauvegarde
  • Utiliser --force au lieu de --force-with-lease
  • Réécrire une branche sur laquelle d'autres travaillent activement sans notification
  • Des différences de contenu apparaissant dans la diff de l'étape 7 (signifie que la réécriture a changé le code, pas seulement les messages)
  • Les nombres d'auteurs changeant après la réécriture (signifie que la paternité n'a pas été préservée)
  • Procéder sans l'approbation de l'utilisateur du plan de réécriture
  • Messages réécrits encore insignifiants (par exemple, "chore: stuff")
  • Écraser des commits entre différents auteurs sans discussion
  • Ne pas conserver la branche de sauvegarde pendant au moins 2 semaines après la réécriture

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.

Skills associés