--- name: linearize-commits description: "线性化整理 git 提交历史——合并细碎的修正提交、控制颗粒度以支持按提交粒度回退、保留原始开发者的作者归属、不跳过项目 git hooks、把每条提交信息规范为 `type(scope): 中文描述`;整理后可选并入指定分支(未指定时默认为主干)。当用户要求「合并到主干」「合并到某个分支」「线性化合并」「rebase 到 main/master」「整理/压缩/squash 提交历史」「分支合并前清理历史」时必须加载;纯整理不带合并时同样加载——如「整理一下这个分支的提交」「重写某个 hash 之后的历史」「squash 最近几个提交」「整理已经推送的分支(可 force-push)」;即使用户只说「把这个分支合到 main / 合回主干」也应加载,因为历史整理是合并前的必要步骤。普通单次提交、只读 log 查询不触发。" --- # 线性化提交整理 目标:把 worktree 或功能分支的提交历史整理成一串**线性、颗粒度合适、信息规范**的提交。整理本身就是完整交付;如用户同时要求合并,再以 fast-forward 方式并入目标分支(未指定时为主干)。 适用形态: - **整理并合并**:整理分支历史后并入目标分支——目标分支由用户指定,未指定时默认为主干 - **纯整理**:只重写分支历史,不合并——包括整理某个 hash 之后的提交、整理已推送的分支(整理后需 force-push) 这类工作的价值在于:主干历史是团队理解演进过程、二分定位问题、**异常时按提交粒度回退**的事实源。颗粒度合适的直接含义是——任意一条提交出问题时,单独 revert 它就能安全回退,不牵连无关改动。整理过的历史应当读起来像一份精心撰写的变更叙述,而不是开发过程的实况录像。所以整理时的每一个判断——哪些提交合并、合并到哪、信息怎么写——都要站在「半年后读这段历史或紧急回退的人」的立场上做。 四条不可妥协的原则贯穿全流程: 1. **不丢作者**:合并提交必须保留原始开发者的归属(见「保留作者归属」),整理人是编辑不是作者。 2. **不跳检查**:任何提交都不允许 `--no-verify` 绕过项目 git hooks;hook 失败就修原因,不绕过。 3. **不动代码**:整理只改历史结构,最终 tree 必须与整理前逐字节一致(见「验证」)。 4. **执行不外包**:盘点、分组判断、rebase / 重写、tree 验证必须全部由当前会话亲自执行,不得委派子代理。历史整理的正确性依赖对全部上下文(每个 diff、hook 行为、冲突语义、归属安排)的连续把握,委派产生的上下文断层会直接导致功能偏移或归属丢失;子代理的「完成」回报也无法替代亲自跑出的 `git diff` 空结果。 ## 前置检查 1. `git status` 确认工作区干净;有未提交改动先请用户处理,不要擅自 stash 后继续。 2. 确认整理范围的下界(base):用户给了 hash 就用它(只重写该 hash 之后的历史);没给、且整理目标相对某个目标分支时,用 `git merge-base HEAD <目标分支>`。目标分支由用户指定;未指定时默认为主干,主干名从 `git symbolic-ref refs/remotes/origin/HEAD` 或 `main` / `master` / `trunk` 中辨认,不确定就问用户。 3. 确认整理对象:当前分支,还是某个 worktree / 其他分支。跨 worktree 时先 `git worktree list` 定位,可以直接在目标 worktree 目录里操作,或 `git checkout` 对应分支,不要在错误的 checkout 上动手。 4. 创建备份引用并告知用户:`git branch backup/<分支名>-pre-linearize <当前HEAD>`(分支名冲突时追加日期后缀)。这是整理过程中的回退点,验证通过后按「验证」一节清理。 5. **已推送的分支单独确认**:分支已 push 过时,整理后的 force-push 会重写远程历史——必须先确认该分支是本人独占(无他人基于它开发),并得到用户对 force-push 的明确同意;推送时用 `--force-with-lease`(远端若被他人更新会拒绝),不用裸 `--force`。共享分支禁止重写,直接报告冲突并停止。 ## 工作流程 ### 1. 盘点 ``` # base 为用户指定的 hash,或与主干的 merge-base git log --reverse --format='%h %s' ..HEAD git show --stat <每个提交> # 了解每个提交实际改了什么 ``` 只看 message 会误判——「fix typo」可能混着逻辑改动,「wip」可能是半个功能。逐一看 diff 统计,必要时看完整 diff。 ### 2. 分组与方案 按「独立意图」把提交分组(准则见下节),产出目标提交序列: ``` 原历史(8) -> 目标历史(3) a1b2c3d feat: add auth ┐ e4f5g6h fix typo ├> feat(auth): 实现登录认证 i7j8k9l fix lint ┘ ... ``` **历史重写不可逆,方案必须先展示给用户确认再执行。** 方案里写清每条目标提交的 message 和它吞掉了哪些原提交,让用户能一眼发现分错组的地方。 ### 3. 执行重写 两种路径,按场景选: - 分组边界与原提交对齐(只是 squash / reword / 重排)→ `GIT_SEQUENCE_EDITOR` 脚本化的 `git rebase -i `,保留原 diff 边界,最快最稳。 - 需要跨提交重新切分(一个提交拆两个、两个提交各取一半)→ `git reset --soft ` 后按分组逐批 `git add`(必要时 `git add -p`)重新提交。慢但边界完全可控。 Windows 下写 `GIT_SEQUENCE_EDITOR` 时注意用能在非交互 shell 里跑的脚本(如 `node -e "..."` 或一个小 ps1/sh),不要依赖交互编辑器。rebase 冲突时不要猜,停下看冲突内容,按目标分组的语义解决。 **任何 `git commit` 都不得加 `--no-verify`。** 项目的 pre-commit / commit-msg hooks(lint、类型检查、信息格式校验)是质量门的一部分,整理出的每条提交都必须真实通过它们。hook 失败时修复导致失败的内容再提交;如果整理前旧历史本身就通不过当前 hook(例如中途改过 lint 规则),把这一情况报告给用户,由用户决定,而不是自行绕过。 ### 保留作者归属 整理提交是在编辑别人的工作成果,归属信息不能丢: - rebase / squash 时 git 默认保留 author,只更新 committer——这是正确行为,不要用 `--reset-author` 或 `--amend --author=...` 改掉原作者。 - squash 的多个提交若**作者不同**,选主体意图的作者作为 author,其余作者以 `Co-authored-by: 名字 <邮箱>` trailer 追加到提交信息末尾;作者完全并列、无法分出主体时,优先考虑不合并这组提交。 - 走 reset 路径重新创建提交时,每条必须用 `--author="原名字 <原邮箱>"` 恢复原作者,日期可用 `--date` 保留原提交时间。 - worktree 场景下分支可能来自多位开发者,分组盘点时顺手记录每条提交的作者,方案里标注每条目标提交的作者与 Co-authored-by 安排,随方案一起给用户确认。 ### 4. 验证 **这是硬性验收点,不允许跳过:** ``` git diff <备份引用> HEAD # 必须为空——整理只动历史,不动最终代码 ``` 再逐文件核验一次(`git diff --stat` 为空即文件级一致),并跑项目的最小验证(类型检查或受影响测试),确认整理后的分支在功能上与整理前等价。 **一致性确认后删除备份引用**:`git branch -D backup/<分支名>-pre-linearize`。核验通过意味着新历史与原历史逐文件一致,备份已完成使命,不留在分支列表里制造噪音。最终报告里仍附上原 HEAD 的 SHA(reflog 中仍可追溯)。若 tree 不一致,立即用备份引用恢复(`git reset --hard <备份引用>`)并排查,此时备份绝不能删。 ### 5. 并入目标分支(可选,仅在用户要求时) ``` git rebase <目标分支> # 接到目标分支最新,保持线性 git checkout <目标分支> && git merge --ff-only ``` 目标分支由用户指定,未指定时为主干。`--ff-only` 保证不产生 merge commit。**用户只要求「整理」时到第 4 步就停**,合并与推送都留给用户。分支已推送过、用户要求同步远端时,确认前置检查第 5 条后用 `git push --force-with-lease`;用户没要求推送就不推。 ## 颗粒度准则(本 skill 的核心判断) **一个提交 = 一个可独立理解、可独立 revert 的意图。** 应合并的(太细的典型): - fixup 类:修 typo、修 lint、修 CI、补漏提交的文件、「address review comments」——它们是上一意图的补丁,不是独立意图 - WIP / checkpoint /「先提交一下」类 - 同一意图的连续小步:刚加了函数马上修它的 bug,bug 从未存在于任何值得停留的历史点上 不应合并的(防止太粗): - 不同模块或不同意图的改动,即使时间上相邻 - 「功能 + 配套重构」「实现 + 测试」是否合并取决于 revert 语义:回滚功能时测试必然一起回滚,可合并;重构独立成立,分开 - 作者不同且贡献并列的提交——合并会模糊归属(见「保留作者归属」) - 判断测试:能否不用「和 / 并且 / 顺便」描述这条提交?**线上出问题时单独 revert 它是否合理、会不会牵连无关改动?** 两问有一问答不上来,说明分组错了 太粗的处理:单个提交混了多个意图时,走第 3 步的 reset 路径拆分,而不是为了「保持提交数少」而容忍。 ## 提交信息格式 ``` type(scope): 中文描述 ``` - **type**:`feat` / `fix` / `refactor` / `perf` / `docs` / `test` / `chore` / `build` / `ci` / `style` - **scope**:模块名,小写英文,取自目录或模块名(如 `auth`、`api-service`、`telemetry`)。一条提交只取最主要的一个 scope,不堆叠;纯仓库级改动可用 `repo`、`deps` - **描述**:中文,动词开头(增加 / 支持 / 修复 / 移除 / 抽出 / 统一……),说清**做了什么**而不是怎么做的;不加句号;不超过 50 字 示例(取自真实项目历史): ``` feat(telemetry): 增加端到端反代可观测性 feat(codex): 支持额度重置积分的查询、消耗与账号入口 fix(test): 测试隔离预检对 worktree junction 透明 fix(model-block): 移除隐式账号模型封禁排程 feat(ui): 用 data-state 隐藏未激活的供应商标签页 ``` 如果项目已有自己的提交信息约定(看主干近期历史),**项目约定优先于本格式**,格式服务于一致性。 ## 安全红线 - 已推送且可能共享的分支:force-push 前必须得到用户明确确认,且只用 `--force-with-lease` - 备份引用在验证通过前必须一直有效;验证通过、文件一致性确认后按流程删除备份,不遗留也不提前删 - 只重写 base 之后的本分支历史,绝不动已并入目标分支或主干的公共历史 - 任何提交不得 `--no-verify`;不得用 `--amend --reset-author`、`--author` 覆盖原作者 - rebase / 重写 / 验证不委派子代理,由当前会话亲自执行并亲自核对结果 - 用户没要求合并时,整理完就停——不擅自执行 merge 或 push