关于 git-commit
用于将工作区变更整理为 Conventional Commits,或改写近期不符合规范的提交标题。它将创建提交、推送和重写历史分为不同流程,并设置相应的安全检查。
主要功能
- 01按逻辑单元整理变更
- 02使用 Conventional Commits 格式
- 03工作区干净时检查是否需改写
- 04检查敏感文件并展示提交计划
- 05通过安全检查后推送或重写历史
适用场景
- 01将不同变更分开提交
- 02规范近期提交标题
- 03以非强制方式推送提交
- 04变更前检查文件和提交信息
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ chann/ skills --skill git-commit安装后在 Claude Code 中输入/git-commit确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlevals/evals.jsonscripts/rewrite_msg.py详细介绍
Git 提交技能
概述
将工作树中的更改归并为 Conventional Commits 1.0.0 单元(每个逻辑单元对应一个提交),或就地重写近期不符合规范的提交主题。
核心原则: 根据用户意图选择正确的工作流——创建新提交还是重写历史;除非用户明确同意(例如使用 /git-commit-rewrite 的 force 关键字),否则绝不使用破坏性标志绕过安全措施。
开始时声明: “我正在使用 git-commit 技能来 <提交 / 推送 / 重写> 这些更改。”
命令
| 命令 | 技能 | 操作 | 作用对象 |
|---|---|---|---|
/git-commit | git-commit | 将工作树中的更改归并为 Conventional Commits | 未提交的更改(已暂存 + 未暂存) |
/git-commit-push | git-commit-push | 同上,然后执行 git push(不使用 --force) | 未提交的更改 + 远程仓库 |
/git-commit-rewrite | git-commit-rewrite | 重写近期不符合规范的提交主题 | 现有本地历史 |
选择正确的命令
开始任何操作前先检查:
git status --short # Are there working-tree changes?
git log --oneline -10 # Are recent messages already Conventional?
| 情况 | 命令 |
|---|---|
| 工作树有更改,历史记录正常 | /git-commit |
| 工作树有更改 + 想要推送 | /git-commit-push |
| 工作树干净,近期历史记录杂乱(“Added X”“WIP”“Fixed bug”) | /git-commit-rewrite |
| 两者兼有:工作树有未提交更改,且历史记录杂乱 | 先提交(/git-commit),然后单独执行 /git-commit-rewrite |
自动路由: 如果用户直接运行 /git-commit,但工作树干净,则检查近期历史记录。如果近期提交不符合规范,告知用户并询问:“没有可提交的工作更改。近期历史记录中有 N 个不符合规范的主题——你想使用 /git-commit-rewrite 重写它们吗?”
Conventional Commits 格式
<type>[optional scope][!]: <description>
[optional body]
[optional footer(s)]
允许的类型
| 类型 | 含义 |
|---|---|
feat | 新功能(MINOR semver 版本提升) |
fix | 修复错误(PATCH semver 版本提升) |
docs | 仅限文档更改 |
style | 格式 / 空白字符;不更改逻辑 |
refactor | 既不修复错误也不添加功能的代码更改 |
perf | 性能改进 |
test | 添加或修复测试 |
build | 构建系统或外部依赖项 |
ci | CI 配置文件和脚本 |
chore | 其他不修改 src 或测试的更改 |
revert | 撤销先前的提交 |
破坏性更改——在类型 / 作用域后添加 !(例如 feat(api)!: drop v1 endpoints),和 / 或包含 BREAKING CHANGE: ... 页脚。
主题规则
- 使用祈使语气:“add”,而不是“added” / “adds”
- 描述的首词使用小写
- 末尾不加句号
- 主题行尽量控制在 ≤72 个字符
- 作用域可选,但当其能明确限定模块 / 区域时,建议使用
示例
feat(parser): add ability to parse arrays
fix(ui): correct button alignment
docs: update README with usage instructions
refactor(auth): extract token validation
chore: update dependencies
feat!: send email on registration
BREAKING CHANGE: email service is now required at boot
工作流:/git-commit(默认)
步骤 1:检查工作树
git status --short
git diff
git diff --cached
如果没有更改 → 告知用户,执行自动路由检查(见上文),然后停止。
第 2 步:将更改分组为逻辑单元
阅读每个修改文件的差异,并按意图分组。每组对应 一个 Conventional Commit。忽略现有暂存状态——从头合并并重新分组已暂存和未暂存的更改。
分组规则:
- 将
feat与fix分开——绝不合并 - 将
docs与代码更改分开(除非文档是同一项更改中的文档字符串) - 新功能或修复的测试可以与对应提交合并;无关测试则可以单独提交
- 将
build/ci/ 依赖更新与功能工作分开 - 将
refactor与feat分开 - 无关模块中的修复 → 按模块拆分提交
- 功能文件中的琐碎空白更改可以合并到功能提交中
不要拆分过细(与功能一并提交一行无关紧要的清理也可以),也不要合并过度(feat 和 fix 必须分开)。
机密信息检查——标记以下任一情况,默认排除,除非用户明确确认:.env*、credentials.*、*_rsa、*.pem、*.key、*.p12、路径中包含单词 secret 的文件。不要仅因 .env.example 与 .env* 匹配而将其标记;它是公开模板。其他所有机密路径规则仍然适用,包括敏感的父目录或其他匹配的文件名模式。提醒用户仍被怀疑含有机密信息的文件;这些文件不应提交。
第 3 步:展示提交计划
创建任何提交之前,先打印计划并让用户提出异议:
Plan: 3 commits
1. feat(auth): add OAuth login flow
- src/auth/oauth.ts
- src/auth/index.ts
2. fix(api): handle null response in user fetch
- src/api/users.ts
3. docs: update README with auth setup
- README.md
第 4 步:逐个创建提交
按计划顺序处理每个单元:
git add path/one path/two # explicit paths only
git commit -m "$(cat <<'EOF'
type(scope): description
optional body explaining the why
EOF
)"
规则:
- 绝不使用
git add .或git add -A——始终指定明确的路径 - 绝不使用
--no-verify、--no-gpg-sign、--amend(除非用户明确要求) - 绝不添加表情符号或
Co-Authored-By: Claude,除非用户要求 - 多行消息使用 HEREDOC,确保引号处理正确
- 每次提交后运行
git status --short,确认只有预期文件已提交
如果预提交钩子失败:不要使用 --no-verify 重试。调查并修复根本问题(或报告问题),然后创建新的提交。
第 5 步:最终摘要
git log --oneline -<N>
(其中 N 为已创建的提交数),并将其展示给用户。
示例
User: 변경사항 의미 단위로 커밋해줘
git status --short →
M src/auth/oauth.ts
M src/auth/index.ts
M src/api/users.ts
M README.md
?? src/api/.env
→ Detect .env (secret) — exclude, warn
→ Group:
1. feat(auth): add OAuth login flow (oauth.ts, auth/index.ts)
2. fix(api): handle null response in user fetch (users.ts)
3. docs: update README with auth setup (README.md)
→ Show plan, wait for confirmation
→ For each: git add <paths> && git commit -m "..."
→ git log --oneline -3
工作流:/git-commit-push
运行上述默认工作流。每次提交成功后:
git push
如果当前分支未配置上游分支:
git push -u origin "$(git branch --show-current)"
绝不使用 --force 或 --force-with-lease。如果推送被拒绝(非快进),请停止并报告——不要自动解决。如果推送因任何原因失败,请说明错误并让用户决定。
示例
User: /git-commit-push
(Same workflow as default, then:)
→ git push
error: failed to push some refs (non-fast-forward)
→ STOP. Report:
"Push rejected: branch is behind origin. Run `git pull --rebase`
first, then re-push manually. I will not auto-resolve."
工作流:/git-commit-rewrite
重写近期历史中不符合 Conventional Commit 规范的提交消息。此操作具有破坏性——会更改提交 SHA。默认策略会拒绝修改任何已存在于远程上的提交。
强制模式(force 关键字)
如果调用中包含独立的令牌 force 或 --force(不区分大小写)——例如 /git-commit-rewrite force、“force rewrite these commits”、“force로 다시 써줘”——则以强制模式运行。该关键字表示用户已明确预先同意执行破坏性的原地重写,因此跳过所有交互式检查并直接应用。(触发条件是英文令牌 force/--force;仅出现韩语词语 강제로 不会触发强制模式。)
- 跳过安全检查 A 和 B(工作区有未提交更改 / HEAD 处于分离状态)——不要提前停止。Git 仍会执行自身规则:如果工作区确实有未提交更改,
git filter-branch会拒绝操作(“Cannot rewrite branches: You have unstaged changes”)。如果 Git 报错,原样显示错误——不要绕过。 - 跳过第 2 步的已推送提交菜单——即使提交已存在于远程,也自动选择原地重写。不要提示用户。
- 跳过第 5 步的等待——仍要展示旧消息 → 新消息的计划,以确保操作透明,然后立即应用,无需等待。
- **推送:**重写后,如果有任何被重写的提交已存在于远程,则最后执行
git push --force(不带其他参数)。这是唯一允许使用裸--force的场景,而且仅限用户使用该关键字选择加入强制模式时。如果没有任何提交已推送到远程,则重写仅保留在本地——不要推送。 - 清理步骤不变——保留
refs/original/备份引用并报告它(第 8 步)。
不包含 force 关键字时,遵循下方完整的谨慎工作流。
第 1 步:确定重写范围
默认基准:
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)
if [ -n "$upstream" ]; then
base=$(git merge-base HEAD "$upstream")
elif git rev-parse --verify main >/dev/null 2>&1; then
base=$(git merge-base HEAD main)
elif git rev-parse --verify master >/dev/null 2>&1; then
base=$(git merge-base HEAD master)
else
echo "Could not determine a base. Ask the user for an explicit range." >&2
exit 1
fi
如果用户指定数量(例如“重写最近 5 个”),则改用 HEAD~5 作为基准。绝不要使用 --root。
第 2 步:安全检查
强制模式会跳过整个步骤(参见上方“强制模式”)——直接进入第 3 步。
原地重写前,必须通过以下三项检查:
检查 A — 工作区干净:
[ -z "$(git status --porcelain)" ]
失败 → 告知用户先提交或暂存更改,然后停止。
检查 B — HEAD 已关联到分支:
git symbolic-ref -q HEAD >/dev/null
失败 → 告知用户切换到某个分支,然后停止。
检查 C — 范围内没有提交已存在于远程:
for sha in $(git rev-list "$base..HEAD"); do
if [ -n "$(git branch -r --contains "$sha")" ]; then
pushed_commits+=("$sha")
fi
done
如果 pushed_commits 非空:**不要默默拒绝操作。**向用户显示以下菜单:
Found N commits in <base>..HEAD whose subjects are non-Conformant.
M of them are already pushed to a remote:
abc1234 origin/main "Added User-Agent Parser"
def5678 origin/main "Fixed bug"
...
Rewriting published history breaks pulls for collaborators. Options:
1. Cancel — keep history as-is
2. Rewrite locally + force-push (destructive; coordinate with team first)
3. Cherry-pick onto a NEW branch with rewritten messages (safe; original branch untouched)
Which option? (default: 1)
- 选项 1 → 正常退出
- 选项 2 → 继续前,要求用户明确输入类似“yes force push”的短语;重写后执行
git push --force-with-lease(绝不要使用--force) - 选项 3 → 参见下方“基于分支的重写”
第 3 步:识别不符合规范的提交
对于 <base>..HEAD 中的每个提交,检查其主题行是否符合:
^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?: .+
跳过已符合规范的提交。跳过合并提交(使用 git rev-list --no-merges)。
第 4 步:生成新消息
对于每个不符合规范的提交:
- 检查:
git show --stat <sha>;必要时查看差异内容 - 根据更改内容,从上方表格中选择一个
type - 选择可选的
scope(模块 / 文件区域)——不确定时省略 - 编写新的主题行:使用祈使语气、小写、不超过 72 个字符、末尾不加句号
- **逐字保留原提交正文。**只重写主题行。如果原正文为空,则保持为空。
典型转换示例请参见下方“常见不符合规范模式的映射”部分。
第 5 步:展示旧消息 → 新消息的计划
Rewrite plan: 3 commits
abc1234 added login feature
→ feat(auth): add login feature
def5678 Fixed bug
→ fix(api): handle null response in user fetch
ghi9012 WIP
→ chore: work-in-progress checkpoint
等待用户明确确认后再应用。(强制模式会展示此计划,但不会等待——而是立即应用。)
第 6 步:应用重写(原地)
在 /tmp/cc-rewrite-map.tsv 中构建映射文件。每个不符合规范的提交占一行:
<full-40-char-sha>\t<new full message including preserved body>
新的完整消息为 <new subject>\n\n<original body>(如果没有正文,则仅为 <new subject>)。在 TSV 中,消息内的换行符以 \n 转义;rewrite_msg.py 会将其还原。
然后运行(使用脚本的绝对路径):
script="$HOME/.claude/skills/git-commit/scripts/rewrite_msg.py"
# Plugin install path may differ; resolve via:
# script=$(find ~/.claude -name rewrite_msg.py -path '*git-commit*' | head -1)
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f \
--msg-filter "python3 '$script'" \
"$base..HEAD"
脚本读取 $GIT_COMMIT,在 /tmp/cc-rewrite-map.tsv 中查找该提交,并输出新消息。不在映射文件中的提交将原样保留。
如果可以使用 git filter-repo(优先于已弃用的 filter-branch),请改用它。但随附的映射流程目前依赖 filter-branch;只有在用户安装了 filter-repo 并且更倾向于使用它时,才进行切换。
第 7 步:基于分支进行重写(第 2 步中的选项 3)
当提交已推送且用户选择选项 3 时,请改为执行以下操作,而不是原地重写:
# 1. Note current branch
src=$(git branch --show-current)
# 2. Create a fresh branch off the upstream/base
git checkout -b "${src}-cc" "$base"
# 3. Cherry-pick each commit, rewriting message as you go
for sha in $(git rev-list --reverse --no-merges "$base..$src"); do
new_msg=$(lookup_in_map "$sha") # from Step 4 plan; or compose interactively
git cherry-pick "$sha"
git commit --amend -m "$new_msg" # only the cherry-pick we just made
done
# 4. Verify, then push the new branch
git log --oneline "$base..HEAD"
git push -u origin "${src}-cc"
原始分支不会受到影响。用户可以从 ${src}-cc 创建 PR,并在准备好后弃用原始分支。无需强制推送,不会造成破坏。
第 8 步:重写后的清理
原地重写后(第 6 步):
- 运行
git log --oneline "$base..HEAD"并显示结果。 - 告知用户备份引用已保存在
refs/original/refs/heads/<branch>,并说明确认无误后如何将其删除:
不要自动删除。git update-ref -d refs/original/refs/heads/<branch> - 删除临时文件:
rm -f /tmp/cc-rewrite-map.tsv
基于分支重写后(第 7 步):
- 告知用户如果改变主意,如何切换回去:
git checkout <original-branch> git branch -D <branch>-cc
示例:混乱的历史记录
User: 커밋 메시지 다시 써줘
(User shares git log:)
96b6be8 gnhf #43: Added User-Agent Parser ...
eda73c5 gnhf #42: Added JSON Schema Generator ...
e943794 gnhf #41: Added IPv4 Subnet Calculator ...
0a7e01e gnhf #40: Added Markdown TOC Generator ...
Step 1 (range):
upstream = origin/gnhf/prd-chann-tools-2026-8f2964
base = $(git merge-base HEAD origin/main)
Step 2 (safety):
✓ working tree clean
✓ HEAD attached
✗ all 4 commits exist on origin/<branch>
→ Show menu (Cancel / Force-push / New branch). User picks 3.
Step 3 (regex): all 4 fail the regex (start with "gnhf #N:")
Step 4 (mapping):
"gnhf #43: Added User-Agent Parser..." → feat(api-network): add user-agent parser
"gnhf #42: Added JSON Schema Generator" → feat(data-format): add JSON schema generator
"gnhf #41: Added IPv4 Subnet Calculator" → feat(api-network): add IPv4 subnet calculator
"gnhf #40: Added Markdown TOC Generator" → feat(text): add Markdown TOC generator
Drop the "gnhf #N" prefix (ticket-tracker noise; belongs in the body or a footer).
To preserve the ticket reference, add a footer:
Refs: gnhf#43
Step 5: show old → new plan, wait for confirmation.
Step 7 (branch-based):
git checkout -b feature-tools-cc origin/main
for sha in (cherry-pick each, --amend with new message)
git push -u origin feature-tools-cc
常见的不符合规范模式映射
在第 4 步生成新主题时,使用以下映射。
| 原始模式 | 可能的 Conventional 改写 |
|---|---|
Added X / Add X | feat: add X(或 feat(scope): add X) |
Fixed X / Fix X / Bugfix: X | fix: <imperative> |
Updated X(外观调整/依赖升级) | chore: update X 或 build(deps): bump X |
Updated X(实际重构) | refactor: <imperative> |
Cleanup / Clean up X | refactor: <imperative> 或 chore: <imperative> |
Renamed X to Y | refactor: rename X to Y |
Removed X / Delete X | refactor: remove X(如果有破坏性变更,则用 feat!:) |
WIP / wip / tmp | chore: work-in-progress checkpoint |
Initial commit | chore: initial commit |
Merge branch 'X' | 跳过(已排除合并提交) |
[TICKET-123] Added X | feat: add X + 页脚 Refs: TICKET-123 |
proj #N: Added X | feat(scope): add X + 页脚 Refs: proj#N |
Tests for X | test: add tests for X |
Docs: <anything> | docs: <imperative> |
Refactor X(已接近祈使形式) | refactor: <imperative form> |
经验法则:
- 过去时 → 祈使形式(“Added” → “add”,“Fixed” → “fix”)
- 首字母大写 → 小写
- 删除末尾句点
- 将工单跟踪器前缀移至
Refs:页脚(不要丢失关联信息) - 如果类型确实难以在
chore、refactor和style之间判断,工具/配置相关的优先用chore,代码结构变更用refactor,只有空白/格式调整才用style
快速参考
遇到问题时,请询问:
| 症状 | 操作 |
|---|---|
| 没有未提交的更改 + 历史记录混乱 | 建议使用 /git-commit-rewrite |
| 有未提交的更改 + 历史记录混乱 | 先提交,再使用 /git-commit-rewrite |
| 已推送的历史记录 + 想要修复 | 使用第 7 步(基于分支重写) |
| 提交前钩子失败 | 修复根本问题;绝不要使用 --no-verify |
| 推送被拒绝(非快进) | 停止并显示错误;未经明确同意,绝不要使用 --force |
git filter-branch 提示已弃用 | 已设置 FILTER_BRANCH_SQUELCH_WARNING=1;忽略即可 |
常见错误
在一个提交中合并 feat 和 fix
- 问题:
feat(auth): add OAuth and fix null response混合了不同类型 - 修复: 拆分为两个提交,一个 feat,一个 fix
使用过去时的主题
- 问题: “added login flow”、“fixed bug”
- 修复: 使用祈使语气——“add login flow”、“handle null response”
使用 git add . 暂存所有内容
- 问题: 意外暂存机密或无关更改
- 修复: 每组更改都始终使用
git add <explicit-paths>
使用 --no-verify 绕过钩子
- 问题: 掩盖真实问题;提交在本地“能用”,但在 CI 中失败
- 修复: 调查钩子失败的原因;修复根本原因;创建新的提交
默默拒绝改写已推送的历史
- 问题: 只说“我不能”,不提供后续方案
- 修复: 在步骤 2 中提供 3 个选项(取消 / 强制推送 / 基于分支)
改写时丢弃工单引用
- 问题: “gnhf #43: Added X” → “feat: add X” 会丢失
gnhf#43关联 - 修复: 将工单移到
Refs: gnhf#43页脚;不要丢失它
改写合并提交
- 问题: 对合并提交使用 Filter-branch 会扰乱父提交关联
- 修复: 始终使用
git rev-list --no-merges
危险信号
绝不:
- 使用
git add .或git add -A(使用明确指定的路径) - 未经用户明确同意使用
--no-verify、--no-gpg-sign、--amend - 使用
--force推送——除非处于/git-commit-rewrite强制模式(force关键字即为明确同意);其他情况下,只有在获得明确同意后才使用--force-with-lease - 未经用户明确例外授权,提交疑似
.env*文件(精确文件名为.env.example的文件除外)、credentials.*、*_rsa、*.pem、*.key或*.p12 - 在一个提交中合并
feat和fix - 未先展示 3 个选项就改写已推送的提交——强制模式除外,其中
force关键字即为预先同意 - 使用
git filter-branch --root - 改写时丢弃工单引用(将它们移至
Refs:页脚)
始终:
- 创建提交前展示提交计划
- 每次提交后运行
git status --short以进行验证 - 改写时逐字保留原始提交正文
- 列出要改写的提交时使用
--no-merges - 询问已推送提交时,默认选择“取消”
- 遵循仓库现有的范围命名惯例(先运行
git log -20 --oneline)
行为说明
遵循仓库现有风格。 先检查 git log -20 --oneline。如果仓库使用 Conventional Commits,就遵循其范围命名惯例。如果现有提交信息风格混杂,则默认使用小写描述和简洁范围。
对话说明与提交信息的语言。 对话更新使用用户提示词的语言。提交信息使用 git log 中占主导地位的语言(无法确定时用英语)。
谨慎使用范围。 范围必须对应实际存在的模块或区域。如果不确定,就省略范围,不要凭空编造。
不要在正文中过度解释。 正文应补充主题无法表达的背景(原因、棘手的权衡、相关问题)。如果主题已能清楚说明,就不要写正文。
拒绝操作,不要绕过检查。 如果安全检查失败(未经同意的已推送提交、工作区不干净、HEAD 分离、疑似机密),请报告并停止。不要使用破坏性标志绕过检查。例外: /git-commit-rewrite 强制模式,用户通过 force 关键字明确选择启用(参见强制模式)。
信任用户明确授权的例外。 “强制推送”、“我知道它已推送,还是要改写”、“这次把 .env 包含进去” → 可以继续,但先用一行说明风险。对于 /git-commit-rewrite,force 关键字是这种授权的正式形式(参见强制模式)。
集成
配合使用:
- code-review — 提交前运行
/code-review,作为最终质量检查 - git-merge-to-main / git-merge-to-dev — 提交后集成分支
- 任何插件提交钩子(commitlint、husky)——修复违规,不要使用
--no-verify
调用方式: 仅由用户手动调用。绝不在其他技能的工作流中自动运行。