Communitygithub.com

lguidolin/commit-history-rewrite

用于现有仓库提交历史混乱,需要规范化以符合约定式提交(conventional commits)以便采用 release-please,或者需要清理中间 WIP/fixup/merge 提交的场景。

commit-history-rewrite 是什么?

该技能用于重写现有 Git 仓库的提交历史,使其符合约定式提交规范。它会分析每个提交,根据消息内容和文件变更将其分类为 `feat`、`fix`、`refactor`、`chore`、`test`、`docs`、`ci` 等类型,识别需要压缩(squash)的候选提交(如 WIP、fixup、合并提交等),并生成有意义的规范提交消息。整个过程包含完整的安全门:自动计算提交数量并选择最佳重写方式(少于 50 个提交使用交互式 rebase,否则使用 `git-filter-repo`),在任意破坏性操作前先展示完整计划并等待用户明确批准,创建备份分支,重写后验证所有消息是否符合规范、文件树是否完全一致以及作者信息是否保留,最终使用 `--force-with-lease` 推送。支持在 Claude Code、Cursor 等 AI Agent 中作为技能直接调用,适用于准备采用 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 的新对话。

文档

提交历史重写

重写现有仓库的提交历史,使其符合约定式提交规范。分析提交,按类型分类,识别压缩候选,并在完全安全门控下执行重写。

概述

历史即文档。 main 分支上的每一条提交消息都是一份永久记录,未来工程师(以及 AI 智能体)会用它来理解什么发生了变化以及为什么发生。像 "fix"、"update"、"wip" 这样的消息会破坏这份文档。Release-please 通过解析提交消息来确定版本号——不符合规范的消息会导致错误的版本或不产生任何发布。

代码是负债。 Hyrum 定律同样适用于 Git 历史:当有足够多的工具依赖提交消息(变更日志、发布自动化、git bisect、git blame)时,这些消息的格式就成了一种契约。在采用自动化之前清理历史,可以防止旧消息污染所有下游工具。

本技能重写的是消息,而非代码。重写前后的文件树必须完全一致——只有提交元数据会发生变化。

何时使用

  • 在一个存在非规范历史的仓库中采用约定式提交
  • 提交日志被 WIP、fixup、合并或无意义消息污染
  • 为启用 release-please 做准备(它需要约定式提交才能正常工作)
  • 在开源或转移仓库之前进行清理

何时不使用

  • 仓库已经使用了约定式提交(无需重写)
  • 共享分支上有活跃协作者且尚未通知他们
  • 只需要修复最后几个提交(直接使用 git rebase -i HEAD~N)
  • 仓库是上游控制提交格式的分支

流程

第 1 步:评估仓库

# 统计总提交数
git rev-list --count HEAD

# 显示提交消息摘要
git log --oneline --no-merges

# 检查协作者(作者多样性)
git shortlog -sne --no-merges

# 确定合并提交密度
git log --merges --oneline | wc -l

决策点: 如果提交数 < 50,使用交互式变基。如果 >= 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="" <哈希值>
文件模式分配的类型
仅 *.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"压缩到前一个提交中
合并提交(来自非压缩合并)压缩候选——评估变更是否已在 main 分支上

对于模糊的提交,查看完整 diff(git show <哈希值>)以根据内容确定类型。

第 3 步:编写高质量消息

重写的消息必须遵循提交消息标准:

<类型>[可选范围]: <简短祈使句描述>

[可选正文,解释为什么,而非是什么]

好的重写:

"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-<时间戳>
  ⚠ 重写后需要强制推送
  ⚠ 所有协作者在推送后必须重新克隆或重置

未更改的内容:
  - 文件树(零内容变更)
  - 每个提交的作者、邮箱和日期
  - 提交顺序和父子关系(除压缩的提交外)

在得到用户明确批准后再进行。 不要因为“听起来不错”就继续——需要明确的“是”或等同的确认。

第 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:交互式变基(提交数 < 50)

# 从根开始变基
git rebase -i --root

在编辑器中:

  • 将压缩候选标记为 squash 或 fixup
  • 将需要重写消息的提交标记为 reword
  • 保持提交顺序

作者保留: 交互式变基默认保留作者姓名、邮箱和日期。不要使用 --reset-author。除非用户特别要求,否则不要使用 --committer-date-is-author-date。

选项 B:git-filter-repo(提交数 >= 50)

创建一个 Python 回调脚本(rewrite-messages.py),将旧消息映射到新消息:

import re

REWRITES = {
    b'旧哈希前缀': b'新消息',
    # ... 根据计划生成
}

def rewrite(commit, metadata):
    # 根据原始消息或哈希查找
    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 或作者重写标志。

压缩提交

对于压缩候选,将它们合并到最近的有意义的提交中。最终提交:

  • 使用有意义提交的约定式提交消息
  • 保留有意义提交的原始作者(而非压缩目标)
  • 合并压缩范围内的所有变更

第 7 步:验证

运行所有验证检查。每一项检查都必须通过才能继续推送。

# 1. 查看重写后的日志
git log --oneline --no-merges

# 2. 验证所有消息是否符合约定式提交
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
# 这些应该完全相同

# 至少保留备份分支 2 周,之后删除
# git branch -D backup/pre-rewrite-*
# git push origin --delete backup/pre-rewrite-*

常见疑虑与事实

常见疑虑事实
"旧历史不重要"Release-please 解析提交消息以确定版本。非规范的历史会导致错误的版本或不产生发布。git bisect、git blame 和 git log --grep 都依赖于有意义的消息。
"我从这个提交开始重新开始"你会丢失出处、作者信息和上下文。--orphan 分支会破坏开发叙事。重写能保留除坏消息之外的所有内容。
"重写风险太大"备份分支使这个过程完全可逆。验证 diff 显示内容零变更。如果出问题:git reset --hard backup/pre-rewrite-*。
"等真正需要的时候再重写"随着时间的推移,历史重写会越来越难,因为更多人克隆并拉出了分支。立即执行,在产生更多下游依赖之前。
"强制推送很危险"--force-with-lease 保护你不会覆盖他人的工作。结合备份分支,这是安全的。
"消息差不多就行了""差不多"意味着 release-please 无法解析它们。约定式提交是严格的格式——要么消息符合规范,要么不符合。
"我可以手动标记版本"手动标记会漂移、被遗忘,并且缺少变更日志集成。整个意义在于自动化——但自动化需要可解析的输入。

危险信号

  • 跳过创建备份分支
  • 使用 --force 而不是 --force-with-lease
  • 重写其他人正在积极工作的分支且未通知
  • 第 7 步的 diff 中显示内容差异(意味着重写更改了代码,而不仅仅是消息)
  • 重写后作者数量发生变化(意味着作者身份未被保留)
  • 未获得用户批准重写计划就继续执行
  • 重写后的消息仍然无意义(例如 "chore: stuff")
  • 跨不同作者压缩提交前未进行讨论
  • 重写后未保留备份分支至少 2 周

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.

相关技能