Communitygithub.com

lguidolin/commit-history-rewrite

當現有儲存庫的提交歷史混亂,需要符合 Conventional Commits 以便採用 release-please,或需要清理中間的 WIP/fixup/merge 提交時使用。

commit-history-rewrite 是什麼?

此技能提供一個結構化、安全的流程,將現有儲存庫的 Git 提交歷史重寫為 Conventional Commits 格式。它先分析所有提交,根據訊息內容和變更檔案進行分類,識別出需要壓縮(squash)的提交(例如 WIP、fixup、合併提交),並為最終訊息提出語意化的改寫。支援兩種執行策略:對於較少的提交(<50 個)使用互動式 rebase,對於大量提交則使用 git-filter-repo,並包含完整的自動化驗證(確保訊息格式規範、檔案變更為零、作者資訊保留)。此技能適用於 Claude Code、Cursor 及其他以 Git 為基礎的 AI 代理工具,特別是在導入 release-please 等自動化工具之前,必須先清理混亂的提交歷史。它強調安全機制:執行前建立備份分支、執行後驗證檔案內容未改變、僅在取得使用者明確的「是」或同等確認後才進行強制推送,並在最終步驟要求使用者再次確認。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/commit-history-rewrite

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Commit History Rewrite

重寫現有儲存庫的提交歷史,使其符合 Conventional Commits 規範。分析提交、依類型分類、識別需要壓縮的候選提交,並在完整的安全檢查機制下執行重寫。

概述

歷史即文件。 master 分支上的每則提交訊息,都是未來工程師(以及 AI 代理)用來了解做了什麼以及為什麼這麼做的永久記錄。像「fix」、「update」、「wip」這類訊息會破壞這份文件。Release-please 會解析提交訊息來決定版本號的變動——不符合規範的訊息會導致錯誤的發佈或根本無法發佈。

程式碼是負債。 Hyrum 定律同樣適用於 Git 歷史:當有太多工具依賴提交訊息(例如變更日誌、發佈自動化、git bisect、git blame),這些訊息的格式就成了一種契約。在採用自動化工具之前清理歷史,可以避免舊訊息污染所有下游工具。

此技能重寫的是訊息,而非程式碼。前後的檔案樹必須完全相同——只有提交的中繼資料會改變。

使用時機

  • 在已有不符合規範歷史的儲存庫中導入 Conventional Commits
  • 提交日誌被 WIP、fixup、合併或無意義的訊息污染
  • 準備讓儲存庫搭配 release-please(需要 Conventional Commits 才能運作)
  • 開源或轉移儲存庫前進行清理

請勿使用

  • 儲存庫已使用 Conventional Commits 發佈(無需重寫)
  • 共用分支上有活躍的合作者,且尚未通知他們
  • 只需要修正最後幾個提交(直接使用 git rebase -i HEAD~N)
  • 儲存庫是上游控制提交格式的分支(fork)

流程

步驟 1:評估儲存庫

# 計算總提交數
git rev-list --count HEAD

# 顯示提交訊息摘要
git log --oneline --no-merges

# 檢查合作者(作者多樣性)
git shortlog -sne --no-merges

# 識別合併提交密度
git log --merges --oneline | wc -l

決策點: 如果提交數 < 50,使用互動式 rebase。如果 >= 50,使用 git-filter-repo。

步驟 2:分類現有提交

使用兩種訊號分析每個提交:訊息文字與變更的檔案。當訊息不明確時(例如「update stuff」),diff 是真相的來源。

訊號 1:訊息內容

訊息中的訊號指派類型
「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

訊號 2:變更的檔案(當訊息不明確時)

git show --stat --format="" <hash>
檔案模式指派類型
僅 *.md、docs/**docs
僅 *test*、*spec*、__tests__/**test
僅 .github/workflows/**、.gitlab-ci.yml、Jenkinsfileci
僅 package.json、*.lock、requirements.txt、Cargo.tomlchore
僅設定檔(.eslintrc、tsconfig.json、.prettierrc)chore

壓縮候選提交

訊號動作
訊息為「WIP」、「wip」、「temp」、「save」、「checkpoint」、單字或空白壓入最近的語意化提交
訊息為「fixup」、「fix typo」、「oops」、「lint」、「formatting」壓入前一個提交
合併提交(來自非 squash 合併)壓入候選 — 評估內容是否已在 master 分支上

對於不明確的提交,檢查完整 diff(git show <hash>)以根據內容決定類型。

步驟 3:撰寫高品質訊息

重寫後的訊息必須遵循提交訊息標準:

<type>[optional scope]: <short imperative description>

[optional body explaining WHY, not what]

好的改寫範例:

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

壞的改寫範例(不要這樣做):

"stuff"                   →  "chore: stuff"          ← 仍然無意義
"changes"                 →  "feat: changes"         ← 沒有描述
"fix"                     →  "fix: fix"              ← 冗餘

當原始訊息無意義時,閱讀 diff 來撰寫合適的描述。如果 diff 太大或不明確,詢問使用者該提交的內容。

步驟 4:呈現重寫計畫

在任何破壞性操作之前,向使用者顯示完整的計畫:

重寫計畫
============
總提交數:47
需要重寫的提交:32
需要壓縮的提交:8
已符合規範的提交:7
保留的作者:3 位(Alice <alice@co>、Bob <bob@co>、CI Bot <bot@co>)

建議的變更:
  abc1234  "initial setup"           →  "chore: initial project setup"
  def5678  "added login"             →  "feat: add user login"
  ghi9012  "wip"                     →  [壓入 def5678]
  jkl3456  "fix bug"                 →  "fix: resolve login redirect loop"
  mno7890  "Update README.md"        →  "docs: update README with setup instructions"
  pqr1234  "feat: add search"        →  [保留 —— 已符合規範]
  ...

安全措施:
  ✓ 備份分支:backup/pre-rewrite-<timestamp>
  ⚠ 重寫後需要強制推送
  ⚠ 所有合作者必須在推送後重新複製或重置

不變的項目:
  - 檔案樹(零內容變更)
  - 每個提交的作者姓名、電子郵件與日期
  - 提交順序和父子關係(壓縮的提交除外)

在進行之前等待使用者明確同意。 不要因為「聽起來不錯」就繼續——需要使用者輸入「yes」或同等確認。

步驟 5:建立備份

# 在目前 HEAD 建立備份分支
git branch "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

# 確認備份存在
git branch -l 'backup/*'

# 選擇性地將備份推送到遠端以增加安全性
git push origin "backup/pre-rewrite-$(date +%Y%m%d-%H%M%S)"

步驟 6:執行重寫

選項 A:互動式 Rebase(< 50 個提交)

# 從根提交開始 rebase
git rebase -i --root

在編輯器中:

  • 將壓縮候選提交標記為 squash 或 fixup
  • 將需要改寫訊息的提交標記為 reword
  • 保留提交順序

作者保留: 互動式 rebase 預設會保留作者姓名、電子郵件和日期。除非使用者特別要求,否則不要使用 --reset-author。除非明確要求,否則不要使用 --committer-date-is-author-date。

選項 B:git-filter-repo(>= 50 個提交)

建立 Python 回呼腳本(rewrite-messages.py),將舊訊息對應到新訊息:

import re

REWRITES = {
    b'old hash prefix': b'new message',
    # ... 從計畫產生
}

def rewrite(commit, metadata):
    # 依原始訊息或 hash 查詢
    if commit.original_id in REWRITES:
        commit.message = REWRITES[commit.original_id]

使用以下指令套用:

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

作者保留: git-filter-repo 預設會保留作者身份。除非使用者明確要求,否則不要使用 --mailmap 或作者重寫旗標。

壓縮提交

對於壓縮候選提交,將其合併到最近的語意化提交中。結果提交:

  • 使用語意化提交的 Conventional Commit 訊息
  • 保留語意化提交的原始作者(而非壓縮目標)
  • 合併壓縮範圍內的所有變更

步驟 7:驗證

執行所有驗證檢查。每個檢查都必須通過才能繼續推送。

# 1. 檢視重寫後的日誌
git log --oneline --no-merges

# 2. 驗證所有訊息都符合 Conventional Commits 格式
FAILURES=$(git log --format="%s" --no-merges | grep -cvE "^(feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+")
if [[ "$FAILURES" -gt 0 ]]; then
  echo "失敗:發現 $FAILURES 個不符合規範的提交:"
  git log --format="%h %s" --no-merges | grep -vE "^[a-f0-9]+ (feat|fix|docs|chore|refactor|test|ci)(\(.+\))?!?: .+"
  exit 1
fi
echo "通過:所有提交均符合規範"

# 3. 驗證零內容變更(關鍵——這證明只有訊息改變了)
DIFF=$(git diff "backup/pre-rewrite-"*..HEAD --stat)
if [[ -n "$DIFF" ]]; then
  echo "失敗:檢測到內容差異——重寫改變了程式碼,而不只是訊息"
  echo "$DIFF"
  exit 1
fi
echo "通過:檔案樹完全相同"

# 4. 驗證作者保留
 echo "=== 重寫前的作者 ==="
git -C . log backup/pre-rewrite-* --format="%an <%ae>" --no-merges | sort -u
echo "=== 重寫後的作者 ==="
git log --format="%an <%ae>" --no-merges | sort -u
# 這兩個清單必須完全相同

如果任一檢查失敗,請勿推送。 重置到備份分支並調查:

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

步驟 8:推送(需經確認)

這對遠端協作者來說是破壞性操作。 與使用者確認:

⚠ 強制推送警告
  分支:main
  遠端:origin
  動作:git push --force-with-lease origin main

  推送後,所有協作者必須執行:
    git fetch origin
    git reset --hard origin/main

  請輸入「yes」以繼續:
git push --force-with-lease origin main

使用 --force-with-lease(而非 --force)以防止覆蓋並發的遠端變更。如果 --force-with-lease 被拒絕,表示有人在你重寫後推送到了該分支——在重試前先調查。

重寫後清理

在成功推送並通知協作者後:

# 確認遠端與本地一致
git log --oneline -5
git log --oneline -5 origin/main
# 兩者應相同

# 保留備份分支至少兩週,之後再刪除
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

理由說明

合理化現實
「舊的歷史不重要」Release-please 會解析提交訊息來決定版本。不符合規範的歷史會導致錯誤或無法發佈。git bisect、git blame 和 git log --grep 都依賴有意義的訊息。
「我從這個提交開始重新來過就好了」你會失去 blame、作者資訊和脈絡。--orphan 分支會破壞開發敘事。重寫會保留一切,除了壞訊息。
「重寫歷史太冒險了」備份分支讓這個過程完全可逆。驗證 diff 顯示零內容變更。如果出錯:git reset --hard backup/pre-rewrite-*。
「等以後需要時再重寫」歷史重寫會隨著時間推移,更多人複製和分支而變得越來越困難。立即執行,在下游相依性出現之前。
「強制推送很危險」--force-with-lease 可以防止覆蓋他人的工作。搭配備份分支,這是安全的。
「訊息差不多就行了」「差不多」代表 release-please 無法解析它們。Conventional Commits 是一種嚴格的格式——訊息要嘛符合,要嘛不符合。
「我手動標記版本發佈就好了」手動標記會偏移、被遺忘,而且缺少變更日誌整合。重點是全自動化——但自動化需要可解析的輸入。

紅旗標誌

  • 跳過建立備份分支
  • 使用 --force 而非 --force-with-lease
  • 在沒有通知的情況下重寫其他人正在積極開發的分支
  • 步驟 7 的 diff 中出現內容差異(表示重寫改變了程式碼,而不只是訊息)
  • 重寫後作者數量改變(表示作者身份未被保留)
  • 在未經使用者批准重寫計畫的情況下繼續操作
  • 重寫後的訊息仍然無意義(例如「chore: stuff」)
  • 未經討論就壓縮跨不同作者的提交
  • 重寫後未保留備份分支至少兩週

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.

相關技能