使用 Graphite (gt) 处理堆叠式 PR——创建、导航和管理 PR 堆栈。
Graphite 技能
使用 Graphite (gt) 创建、导航和管理堆叠式拉取请求。
快速参考
| 我想... | 命令 |
|---|---|
| 创建新分支/PR | gt create branch-name -m "message" |
| 修改当前分支 | gt modify -m "message" |
| 向上导航堆栈 | gt up |
| 向下导航堆栈 | gt down |
| 跳转到堆栈顶部 | gt top |
| 跳转到堆栈底部 | gt bottom |
| 查看堆栈结构 | gt ls |
| 提交堆栈以供审查 | gt submit --no-interactive |
| 将堆栈变基到主干 | gt restack |
| 更改分支父级 | gt track --parent <branch> |
| 重命名当前分支 | gt rename <new-name> |
| 在堆栈中移动分支 | gt move |
什么是好的 PR?
按重要性大致降序排列:
- 原子性/封闭性 - 独立于其他更改;能通过 CI 并可以安全地单独部署
- 狭窄的语义范围 - 仅更改模块 X,或跨模块 X、Y、Z 进行相同更改
- 小差异 -(启发式)总差异行数少
不要担心创建太多拉取请求。 创建更多 PR 总是比创建更少 PR 更可取。
没有太小的更改: 小型 PR 能让中型/大型 PR 更清晰。
只要它们能独立通过构建,始终支持创建更多 PR。
分支命名约定
在堆栈中命名 PR 时,请遵循以下语法:
terse-stack-feature-name/terse-description-of-change
例如,一个包含 4 个 PR 的堆栈:
auth-bugfix/reorder-args
auth-bugfix/improve-logging
auth-bugfix/improve-documentation
auth-bugfix/handle-401-status-codes
创建堆栈
基本工作流程
- 对文件进行更改
- 暂存更改:
git add <files> - 创建分支:
gt create branch-name -m "commit message" - 对堆栈中的每个 PR 重复
- 提交:
gt submit --no-interactive
处理未跟踪的分支(常见于工作树)
在创建分支之前,检查当前分支是否被跟踪:
gt branch info
如果看到 "ERROR: Cannot perform this operation on untracked branch":
选项 A(推荐):临时跟踪,然后重新设置父级
- 跟踪当前分支:
gt track -p main - 使用
gt create正常创建堆栈 - 创建所有分支后,将第一个新分支的父级重新设置为 main:
gt checkout <first-branch-of-your-stack> gt track -p main gt restack
选项 B:暂存更改并从 main 开始
git stashgit checkout main && git pull- 创建新分支并取消暂存:
git checkout -b temp-working && git stash pop - 继续使用
gt track -p main和gt create
导航堆栈
# 向上移动一个分支(朝向堆栈顶部)
gt up
# 向下移动一个分支(朝向主干)
gt down
# 跳转到堆栈顶部
gt top
# 跳转到堆栈底部(主干之上的第一个分支)
gt bottom
# 查看完整的堆栈结构
gt ls
修改堆栈
修改当前分支
git add <files>
gt modify -m "updated commit message"
重新排序分支
使用 gt move 重新排序堆栈中的分支。这比尝试使用 gt create --insert 更简单。
重新设置堆栈父级
如果你在某个功能分支之上创建了堆栈,但希望它基于 main:
# 转到堆栈的第一个分支
gt checkout <first-branch>
# 将其父级更改为 main
gt track --parent main
# 变基整个堆栈
gt restack
重命名分支
gt rename new-branch-name
将提交重置为未暂存的更改
如果更改已提交,但你想以不同方式重新堆叠:
# 重置最后一次提交,保持更改未暂存
git reset HEAD^
# 重置多个提交(例如,最后 2 次提交)
git reset HEAD~2
# 查看差异以了解你正在处理的内容
git diff HEAD
提交之前
验证堆栈根植于 main
在运行 gt submit 之前,验证第一个 PR 的父级是 main:
gt ls
如果第一个分支的父级不是 main:
gt checkout <first-branch>
gt track -p main
gt restack
运行验证
创建每个 PR 后,运行适当的 lint、构建和测试:
- 参考项目的 CLAUDE.md 获取具体命令
- 如果验证失败,修复问题,暂存更改,然后使用
gt modify
提交和更新 PR
提交堆栈
gt submit --no-interactive
更新 PR 描述
提交后,使用 gh pr edit 设置正确的标题和描述。
重要: 永远不要使用 Bash heredoc 编写 PR 描述——shell 转义会破坏 Markdown 表格、代码块等。相反:
- 使用
Write工具创建/tmp/pr-body.md,包含完整的 Markdown 内容 - 使用
gh pr edit配合--body-file:
gh pr edit <PR_NUMBER> --title "stack-name: description" --body-file /tmp/pr-body.md
PR 描述必须包含:
- 堆栈上下文:这个堆栈的更大目标是什么?
- 什么?(对于小更改可选):非常简洁,关注是什么而不是为什么
- 为什么?:是什么促使了更改?为什么是这个解决方案?它如何融入堆栈?
示例(对于一个 3 个 PR 堆栈中添加警告功能的 PR):
## 堆栈上下文
此堆栈在用户绕过 GitHub 规则集时,在合并按钮上添加警告。
## 为什么?
能够绕过规则集的用户(通过组织管理员或团队成员身份)目前看不到任何提示
表明他们正在规避分支保护。此 PR 将绕过数据从服务器传递到
前端警告(PR 2)以显示它。
故障排除
| 问题 | 解决方案 |
|---|---|
| "Cannot perform this operation on untracked branch" | 先运行 gt track -p main |
| 堆栈父级指向错误的分支 | 使用 gt track -p main 然后 gt restack |
| 需要重新排序 PR | 使用 gt move |
| restack 期间冲突 | 解决冲突,然后 git rebase --continue |
| 想要拆分 PR | 重置提交(git reset HEAD^),选择性重新暂存,创建新分支 |
| 需要删除分支(非交互式) | gt delete <branch> -f -q |
gt restack 遇到无关冲突 |
改用有针对性的 git rebase <target>(见下文) |
| 变基在冲突中中断 | 检查文件是否已解决但未暂存,然后 git add + git rebase --continue |
高级:复杂堆栈中的精确变基
在深度嵌套且包含许多兄弟分支的堆栈中,gt restack 可能有问题:
- 它会变基所有需要变基的分支,而不仅仅是你的堆栈
- 可能在完全不相关的分支上遇到冲突
- 是全有或全无的——难以精确操作
何时使用 git rebase 而不是 gt restack
在以下情况下使用直接的 git rebase:
- 你只想更新堆栈中的特定分支
gt restack在无关分支上遇到冲突- 你需要在变基期间跳过过时的提交
针对性变基工作流程
# 1. 检出要变基的分支
git checkout my-feature-branch
# 2. 变基到目标(例如,更新后的父分支)
git rebase target-branch
# 3. 如果遇到冲突:
# - 解决文件中的冲突
# - 暂存它:git add <file>
# - 继续:git rebase --continue
# 4. 如果某个提交已过时且应跳过:
git rebase --skip
# 5. 变基后,使用 gt modify 同步 graphite 的跟踪
gt modify --no-edit
从中断的变基中恢复(上下文重置)
如果变基被中断(例如,Claude 会话上下文耗尽):
-
检查状态:
git status # 查找 "interactive rebase in progress" 和 "Unmerged paths" -
读取 "unmerged" 文件——它们可能已经解决(没有冲突标记)
-
如果已解决,只需暂存并继续:
git add <resolved-files> git rebase --continue -
如果仍有冲突标记,先解决它们,然后暂存并继续
从堆栈中删除分支
# 删除分支(非交互式,即使未合并)
gt delete branch-to-delete -f -q
# 同时删除所有子分支(上堆栈)
gt delete branch-to-delete -f -q --upstack
# 同时删除所有祖先分支(下堆栈)
gt delete branch-to-delete -f -q --downstack
标志:
-f/--force:即使未合并或关闭也删除-q/--quiet:隐含--no-interactive,最小化输出
删除中间分支后,子分支会自动变基到父分支。如果需要手动更新跟踪:
gt checkout child-branch
gt track --parent new-parent-branch






