Claude Code 实战 04|把 Git 全流程交给 Agent:rebase 保补丁、抹历史密钥、worktree 灰度原创
# Claude Code 实战 04|把 Git 全流程交给 Agent:rebase 保补丁、抹历史密钥、worktree 灰度
Git 是所有开发流里最不容出错、也最容易被 AI 帮上大忙的一环。它记得住每个提交哈希、每条 reflog、每个远程的名字——这些恰恰是人脑最容易记岔的东西。但 Git 又是"一条
push --force写错就抹掉历史"的高危工具,所以把它交给 Agent,重点从来不是"会不会 rebase",而是怎么让每一步都可回退、force push 之前必有护栏。
# 1. 落后上游几十个提交,本地补丁怎么不丢
一个常见的开源协作场景:你 fork 了一个上游仓库,在本地分支上打了几个自定义补丁。过了阵子上游往前走了几十个提交,你要把补丁接到最新的上游之上,同时一个都不能丢。
手动做,你得记住:本地那几个提交的哈希是哪些、哪个远程叫 upstream 哪个叫 fork、rebase 之后要用什么姿势 push 才不会把别人的提交冲掉。任何一步记岔,轻则冲突一堆,重则一条 git push --force 把远端历史覆盖掉。
Claude Code 在这里的价值很实在:它把这些易错的"状态记忆"接管了。你说"把我的补丁 rebase 到最新上游、保留全部本地修改",它会先 git status 确认干净、git log 记下本地提交哈希、git remote -v 认清远程拓扑,再动手——每一步都留痕,出了岔子能顺着 reflog 回退。
# 2. 为什么"记状态"是 Git 自动化的胜负手
Git 的绝大多数事故,根因不是命令写错,而是对当前状态的判断错了:
- 以为工作区干净,其实有未提交改动,rebase 直接把它们卷进冲突;
- 以为某个提交还没合进上游,催着合并,结果上游早已独立修复;
- 以为
push --force推的是自己的分支,其实覆盖了同事刚推的提交。
人会累会忘,Agent 不会——只要你要求它"动手前先核对状态",它就能把 diff、哈希、远程、CI 状态一次性摊清楚再决策。这正是它比"凭记忆敲命令"稳的地方。下面四个高频场景,逐一走一遍。
# 3. 四个高频 Git 场景的标准姿势
# 3.1 rebase 保留本地补丁,安全 force push
# 准备:确认干净 + 记住本地补丁哈希 + 认清远程
git status # 必须 "working tree clean"
git log --oneline origin/main..HEAD # 本地领先上游的提交,就是要保留的补丁
git remote -v # 分清 upstream / origin / fork
# 执行:把本地补丁接到最新上游之上
git fetch upstream
git rebase upstream/main
# 验证:补丁数量对得上、内容没丢
git log --oneline upstream/main..HEAD # 提交数应与 rebase 前一致(哈希已变)
# 推送:用 --force-with-lease,不用 --force
git push --force-with-lease fork my-fix-branch
2
3
4
5
6
7
8
9
10
11
12
13
14
关键:force push 一律用 --force-with-lease 而非 --force。 前者会在"远端分支自你上次 fetch 后被别人动过"时拒绝推送,是防止覆盖他人提交的最后一道保险;后者不问青红皂白直接覆盖。
# 3.2 用 git-filter-repo 抹掉误提交的历史密钥
密钥一旦被 commit,就算下一个提交删掉它,它依然躺在历史里,git log -p 一翻就能翻出来。只删文件不够,必须重写历史:
# 用 git-filter-repo(比老的 filter-branch 快且安全)
pip install git-filter-repo
# 把某个文件从全部历史中彻底抹除
git filter-repo --path config/secrets.yaml --invert-paths
# 或按内容替换:把匹配到的密钥串替换成占位符
echo 'AKIA1234567890==>REDACTED' > replacements.txt
git filter-repo --replace-text replacements.txt
2
3
4
5
6
7
8
9
做完必须做两件事:① 到密钥的签发方吊销并轮换这个凭证——历史里可能已被别人克隆走,重写历史不等于对方看不到;② 因为历史被重写,需要 --force-with-lease 强推,并通知所有协作者重新 clone。
# 3.3 用 worktree 隔离大版本升级,做灰度
要试一个可能翻车的大改动(比如升级一个跨版本的依赖),别在主工作区里直接搞——用 git worktree 开一个并存的独立工作目录,同一个仓库、不同分支、互不干扰:
# 从主工作区旁边开一个隔离工作树
git worktree add ../repo-upgrade upgrade/major-bump
cd ../repo-upgrade
# 在这里放手折腾升级、跑测试;主工作区保持随时可用
# 验证通过后合回主线,再删掉工作树
git worktree remove ../repo-upgrade
2
3
4
5
6
7
好处:主分支始终是"随时能发布"的干净状态;升级实验彻底隔离,成了就合、砸了就删,不留污染。
# 3.4 cherry-pick 跟进 + 关闭"上游已独立修复"的重复 PR
给上游提了 PR 后,要定期核对它是不是已经被合并、或者被上游用另一个提交独立修复了。判断"合没合"不能只看状态字:
# 列出自己的 PR 及其真实合并状态
gh pr list --author @me --state all
gh pr view <PR号> --json state,mergedAt # state=CLOSED 且 mergedAt=null → 是被关,不是被合
# 判断上游是否已独立修复:diff 关键文件
git fetch upstream
git log --oneline -- path/to/changed_file # 看上游有没有等价提交
2
3
4
5
6
7
如果确认上游已独立修复,优雅的做法是:在 PR 里评论指向上游那个提交,然后 gh pr close,同时把本地对应的临时补丁 drop 掉——否则下次 rebase 会和上游的等价修复撞车。如果只是想把上游某个已合并的提交拿到自己分支,用 git cherry-pick <commit> 单独摘过来即可。
# 4. 把 Git 交给 Agent 最容易栽的三个坑
# 坑 1:CLOSED 当成了 Merged
- 症状:让 Agent 核对 PR 状态,它看到
state: CLOSED就报"已合并",实际是被拒绝关闭。 - 原因:GitHub 里
CLOSED同时涵盖"被合并"和"被拒绝关闭"两种,光看状态字分不出。 - 解药:严格以
mergedAt字段为准——mergedAt为 null 就是没合并,无论状态字是什么。把这条写进给 Agent 的判断规则。
# 坑 2:rebase / 重写历史后,虚拟环境或路径失效
- 症状:rebase 完直接跑命令,报
.venv/bin/activate: no such file or directory或依赖找不到。 - 原因:历史重写可能改动了目录结构或依赖清单,旧的环境状态对不上了。
- 解药:把"重写历史后立即重建/校验环境"当成流程的固定收尾步——该重装依赖就重装,别假设环境还在。
# 坑 3:用 --force 而不是 --force-with-lease
- 症状:force push 覆盖掉了协作者刚推上去的提交,对方的工作凭空消失。
- 原因:
--force无条件覆盖远端,完全不检查远端是否已被他人更新。 - 解药:一切 force push 默认
--force-with-lease;远端有变更时它会拒绝推送,逼你先 fetch 核对。把裸--force直接写进第一篇讲的deny列表更稳。
# 5. 可复用要点
- 动手前先核对状态:
git status/git log/git remote -v/ CI 状态先摊清楚,Git 事故绝大多数是状态判断错,不是命令写错。 - force push 只用
--force-with-lease:这是防止覆盖他人提交的底线;裸--force进 deny 列表。 - 删密钥必须重写历史 + 轮换凭证:
git-filter-repo抹历史,同时到签发方吊销——历史可能已被克隆,重写不等于对方失明。 - 大改动用 worktree 隔离:主工作区永远保持可发布,实验成了就合、砸了就删。
- PR 合并判断以
mergedAt为准:CLOSED ≠ Merged;上游已独立修复的补丁要及时 drop,避免 rebase 撞车。
# 6. Agent 可直接解析的元数据块
{
"_meta": {
"doc_version": "2026-07-28",
"article_id": "claude-code-04-git",
"profile_context": "any",
"estimated_setup_time": "25min"
},
"quick_start": {
"step_1": "动手前核对状态:git status(须 clean)+ git log --oneline upstream/main..HEAD + git remote -v",
"step_2": "rebase 保补丁:git fetch upstream && git rebase upstream/main,验证提交数一致",
"step_3": "推送用 git push --force-with-lease,禁止裸 --force"
},
"safety_rules": [
"一切 force push 必须使用 --force-with-lease,禁止裸 git push --force",
"PR 是否合并以 mergedAt 字段为准,state=CLOSED 且 mergedAt=null 视为未合并",
"删除历史中的密钥必须用 git-filter-repo 重写历史,并到签发方吊销/轮换该凭证",
"重写历史后必须重建/校验虚拟环境与依赖,不假设旧环境仍可用"
],
"verification": {
"check_1": "rebase 后 git log --oneline upstream/main..HEAD 的提交数与 rebase 前一致",
"check_2": "git-filter-repo 后 git log -p | grep <密钥片段> 无任何命中",
"check_3": "gh pr view <PR> --json state,mergedAt 返回的 mergedAt 决定合并判断"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
AI Agent 使用指南:
- 当用户说"把我的补丁同步到最新上游" → 按
quick_start三步执行,push 阶段强制--force-with-lease。 - 当用户说"这个密钥不小心提交了" → 执行
git-filter-repo重写历史(verification.check_2验证),并提醒用户吊销轮换凭证。 - 当用户说"我的 PR 合并了吗" → 以
mergedAt判断,若上游已独立修复则建议关闭并 drop 本地补丁。
上一篇:Claude Code 实战 03|让 Claude Code 驾驭 CLI 工具:安装、认证与稳定调用 下一篇:Claude Code 实战 05|会话即资产:transcript 考古与误删恢复
- 02
- MySQL 性能压测:Sysbench 1.0 实战 原创07-29
- 03
- MySQL Router 实现读写分离 原创07-29