Claude Code 与 Git 的完美协作流程
不是夸它。是真的比我强。
我写了10年的 commit message,格式大概是这样:fix bug、update payment、change stuff。
让 Claude Code 接手之后,它写的是:feat(billing): add idempotency check for Stripe webhook to prevent duplicate subscription creation on retry。
我看着那行字沉默了几秒。然后把它加进了我的工作流。
一个人开发,Git 纪律最容易崩
Section titled “一个人开发,Git 纪律最容易崩”做出海独立开发,没有 code review,没有团队规范,没有人盯着你。
Git 的纪律是最先崩的那个东西。
我见过一个朋友的 git log:上午十点写了个功能,git commit -m "done"。下午改了三个文件解决一个 bug,git commit -m "fix"。晚上 push 上去,如果三个月后需要 revert 某一个特定的改动——根本不知道从哪下手。
这不只是习惯问题。是成本问题。
一个人开发本来就没有备份意识,commit 粒度太粗,出了问题回滚就是噩梦。我有一次上线后发现 Stripe Webhook 有问题,需要回滚到出问题之前的版本,结果那个 commit 里混入了另外五个功能的代码,根本没法单独 revert。
最后只能手动一行行改回去,花了两个小时。
把 Claude Code 接进 Git 工作流之后,这些问题基本都解决了。
工作流全貌:四个节点
Section titled “工作流全貌:四个节点”整套工作流就四个节点:开分支 → 写代码 → Claude 生成 commit → PR 描述自动化 。每个节点 Claude Code 都能参与,但参与深度不一样。
节点一:让它帮你规划分支策略
Section titled “节点一:让它帮你规划分支策略”我现在开始一个新功能之前,会先问它:
我要给 SaaS 订阅工具加一个功能:用户可以查看自己的账单历史,包括每次扣费记录和对应的发票 PDF 链接(来自 Stripe)。
帮我规划一下这个功能需要几个分支,每个分支负责什么,建议的合并顺序是什么。
它会给我类似这样的规划:
feature/billing-history-ui # 前端页面和组件feature/billing-history-api # API 路由,从 Stripe 拉取数据feature/billing-history-db # 数据库 schema 变更(如果需要本地缓存)合并顺序:db → api → ui,因为前端依赖 API,API 依赖 schema。
这个规划我以前都是靠感觉,有时候顺序搞反了,merge 的时候有依赖冲突,解冲突又花时间。
现在让它先规划,省掉的是后期的麻烦。
节点二:每次 commit 前,让它看 diff
Section titled “节点二:每次 commit 前,让它看 diff”这是整套工作流里投入产出比最高的一步。
git diff --staged把这个输出喂给 Claude Code:
这是我准备 commit 的改动(git diff —staged 的输出):
[粘贴 diff 内容]
帮我做两件事:
- 判断这次改动是否应该拆成多个 commit,如果是,告诉我怎么拆
- 按照 Conventional Commits 规范,给每个 commit 写一个 message
它会告诉你:这次改动混了两件事——修了一个 Prisma 查询的 bug 和加了一个新的 API 路由,建议拆成两个 commit。
然后给你两条 message:
fix(db): resolve N+1 query in subscription status fetch by adding include clause
feat(api): add GET /api/billing/invoices endpoint to retrieve Stripe invoice list大多数教程不告诉你的细节是: Conventional Commits 格式不只是为了好看,它可以被工具自动解析。feat: 开头的 commit 会被 changelog 生成工具自动归类为新功能,fix: 开头的会归类为 bug 修复。做出海产品,如果你以后要维护一个 CHANGELOG 给用户看——这套规范的价值就体现出来了。
拆 commit 的操作:
# 把已经 staged 的文件先 unstagegit restore --staged .
# 只 stage 第一批改动(Prisma 查询修复相关的文件)git add lib/db.ts app/api/subscription/route.ts
# commit 第一个git commit -m "fix(db): resolve N+1 query in subscription status fetch by adding include clause"
# 再 stage 第二批git add app/api/billing/invoices/route.ts
# commit 第二个git commit -m "feat(api): add GET /api/billing/invoices endpoint to retrieve Stripe invoice list"说人话就是:git restore --staged . 是把所有已经 stage 的文件退回工作区,但不丢失改动。然后你可以重新选择性地 stage。
节点三:让它帮你处理 merge conflict
Section titled “节点三:让它帮你处理 merge conflict”这是很多人避之不及的地方,但用 Claude Code 处理其实挺顺的。
遇到冲突时,把冲突文件的内容直接贴给它:
我在合并两个分支时遇到了冲突,冲突文件内容如下:
<<<<<<< HEAD> const subscription = await prisma.subscription.findFirst({> where: { userId, status: 'active' }> })> =======> const subscription = await prisma.subscription.findUnique({> where: { userId_status: { userId, status: 'active' } }> })> >>>>>>> feature/billing-history-api>HEAD 分支用的是 findFirst,feature 分支用的是 findUnique 加了复合索引。我们的 Prisma schema 里有没有定义 userId_status 这个复合索引?
它会帮你分析两种写法的区别,告诉你哪个更合适,给出合并后的正确代码。
细节:把 Prisma schema 文件也一起贴过去,让它能判断复合索引是否存在。如果 schema 里没有定义
@@unique([userId, status]),那findUnique用法会在 runtime 报错。它能帮你发现这个问题,人工合并的时候很容易忽略。
节点四:自动生成 PR 描述
Section titled “节点四:自动生成 PR 描述”我现在每次准备开 PR(对自己的 main 分支合并),会先跑这个命令:
git log main..HEAD --oneline把输出喂给 Claude Code:
这是这个 feature branch 相对于 main 的所有 commits:
a3f2d1c feat(api): add GET /api/billing/invoices endpoint b7e9c4a fix(db): resolve N+1 query in subscription status fetch c1d8f5b feat(ui): add BillingHistory page with invoice list and download links d4a2e7f chore: add Stripe invoice types to types/stripe.ts
帮我写一个 PR 描述,包括:这个 PR 做了什么、为什么要做、有哪些需要特别注意的地方(比如环境变量、数据库 migration、Stripe 配置)
它生成的 PR 描述我基本不用改,直接用。
这对独立开发者来说价值可能不那么明显——你自己合自己的,写不写 PR 描述都行。但我有一个习惯:每个 feature 的 PR 描述是我的开发日志。三个月后回来看,我能知道这段代码是为了解决什么问题、当时有什么注意事项。
这比 commit message 更有价值,因为它记录的是决策背景,不只是改了什么。
坑一:让它帮我写 commit,它把还没 stage 的文件也纳入分析了
有一次我 git diff --staged 之后把输出给它,它帮我写了三个 commit message。我看了觉得没问题,就按照它的建议 stage 文件、依次 commit。
结果发现第三个 commit 里包含了一个我本来不打算这次提交的文件——lib/analytics.ts,那是我在探索一个新功能,还没写完。
我盯着 git log 看了一会才意识到:git diff --staged 只显示已 stage 的文件,但我当时不小心 git add . 了整个目录,那个未完成的文件也被 stage 进去了。
Claude Code 只看了 diff,它不会告诉你”这个文件看起来还没写完”——它只知道你 stage 了什么。
解决方案: 在让 Claude Code 分析 diff 之前,先用 git status 确认 staged 区域只有你想要的文件。养成习惯:git add 不要用 .,用具体文件路径或者 git add -p 交互式 stage。
坑二:它写的 commit message 太长,被 GitHub 截断了
Conventional Commits 建议 subject 行不超过 72 个字符,但 Claude Code 偶尔会写出这样的 message:
feat(billing): implement Stripe invoice retrieval endpoint with pagination support and proper error handling for expired invoices这在 GitHub 的 commit 列表里会被截断,git log --oneline 也会显示不全。
我发现这个问题是在某次 code review 自己项目时,看到 commit 列表里一堆被截断的 message,找一个特定改动要看半天。
解决方案: 在 Prompt 里加一条约束:
commit message 的 subject 行必须在 72 个字符以内,超出的内容放到 body 里(空一行后写)
加了这条限制,它生成的 message 格式就规范了。
Git 纪律的本质是:让三个月后的你能看懂三个月前的你在干什么。
Claude Code 帮你写 commit 不是在偷懒,是在强制你把每次改动说清楚——这个压力以前只有 code review 才能带来,现在一个人开发也能有。
最后问你一个实际的问题: 你最近一次需要 git revert 或者回滚某个特定功能时,你花了多长时间找到正确的 commit?如果超过五分钟,你觉得问题出在 commit 粒度、commit message,还是分支策略?
fix bug
update payment
change stuff
feat(billing): add idempotency check for Stripe webhook to prevent duplicate subscription creation on retry
git commit -m "done"
git commit -m "fix"
feature/billing-history-ui # 前端页面和组件 feature/billing-history-api # API 路由,从 Stripe 拉取数据 feature/billing-history-db # 数据库 schema 变更(如果需要本地缓存)
git diff --staged
`fix(db): resolve N+1 query in subscription status fetch by adding include clause
feat(api): add GET /api/billing/invoices endpoint to retrieve Stripe invoice list
`
feat:
fix:
`# 把已经 staged 的文件先 unstage
git restore —staged .
只 stage 第一批改动(Prisma 查询修复相关的文件)
Section titled “只 stage 第一批改动(Prisma 查询修复相关的文件)”git add lib/db.ts app/api/subscription/route.ts
commit 第一个
Section titled “commit 第一个”git commit -m “fix(db): resolve N+1 query in subscription status fetch by adding include clause”
再 stage 第二批
Section titled “再 stage 第二批”git add app/api/billing/invoices/route.ts
commit 第二个
Section titled “commit 第二个”git commit -m “feat(api): add GET /api/billing/invoices endpoint to retrieve Stripe invoice list”
`
git restore --staged .
`<<<<<<< HEAD
const subscription = await prisma.subscription.findFirst({
where: { userId, status: ‘active’ }
})
Section titled “`<<<<<<< HEAD
const subscription = await prisma.subscription.findFirst({
where: { userId, status: ‘active’ }
})”const subscription = await prisma.subscription.findUnique({
where: { userId_status: { userId, status: ‘active’ } }
})
feature/billing-history-api
`
@@unique([userId, status])
findUnique
git log main..HEAD --oneline
git diff --staged
lib/analytics.ts
git diff --staged
git add .
git status
git add
.
git add -p
feat(billing): implement Stripe invoice retrieval endpoint with pagination support and proper error handling for expired invoices
git log --oneline
git revert