Jujutsu 中的 GitHub Stacks
- Jujutsu 中的 GitHub Stacks <== 你在这里
- Jujutsu 中的 Stacks
GitHub 于 2026-07-30 将其 堆叠拉取请求(stacked pull request)功能 发布到公开预览版。该功能主要面向 git 用户,但也对其他兼容 git 的版本控制系统(如 Jujutsu (jj))提供了文档化的支持。
本文是在 jj 中使用 GitHub stacks 的入门指南。
前置条件
安装命令行工具
-
需要安装
git(安装说明)和jj(安装说明)命令行工具。 -
需要安装 GitHub 的
gh命令行工具(安装说明)。 -
通过
gh extension install github/gh-stack安装 stacks 扩展。
本文编写/测试时所使用的版本:
- jj v0.43.0
- git v2.52.0
- gh v2.96.0
- github/gh-stack v0.1.0
配置 jj
本文将复用上一篇文章 Jujutsu 中的 Stacks 里定义的一些 jj 别名和 revset 别名。
附赠:了解 jj 的 bookmark 别名
GitHub 的 Stacked PR 要求每个 PR 都有一个 jj
bookmark(git 分支),所以你会经常需要创建它们。未来的 UI
工具大概率会自动帮你处理分支名,但眼下,先学会用
jj 别名来便捷地管理 bookmark:
| 命令 | 说明 |
|---|---|
jj b l |
列出所有 bookmark |
jj b s <name> -r <commit>
|
创建(或移动)bookmark 到指定 commit |
jj b a |
将最近的 bookmark 前进到当前 commit(类似
jj tug)
|
jj b --help |
查看所有别名命令的文档 |
💡 顺便一提:你知道吗,你可以通过自定义 revset
来改进默认的 jj bookmark advance(即
jj b a)行为。当你位于一个有代码的 commit
上方的全新空 commit 时,你可能希望把 bookmark
前进到下方那个有代码的 commit。可以这样配置:
1[revset-aliases."closest_pushable(to)"]2definition = 'heads(::to & mutable() & ~empty() & description(regex:".+"))'3doc = "Closest mutable, non-empty, described commits at or behind to"4
5[revsets]6bookmark-advance-to = "closest_pushable(@)"
jj 让你可以非常轻松地通过覆盖内置的
bookmark-advance-to revset
来定制任何你想要的行为。
手动工作流
gh stack 命令行工具包含许多子命令,其中大部分用于管理或读取本地的栈跟踪(例如 init、view)。在 jj 仓库中,你只需要使用那些用于管理 GitHub 上远程栈的子命令(例如 link、push),具体如下:
创建一个栈
-
为栈中的每个提交创建一个书签:
终端窗口 # 使用我们的 revset 别名跳转到栈底 jj edit -r "stack_bottom()" # 为提交命名 jj b c <name> # 跳转到下一个提交 jj next --edit # 为提交命名 jj b c <name> # ... 对栈中的每个提交重复上述操作 -
将书签上传到 GitHub,为每个书签创建草稿 PR,并将它们全部关联成一个栈:
使用我们的 stack 别名:
终端窗口 gh stack link $(jj sb -r "stack()")或者,使用 jj 的修订版本范围:
终端窗口 gh stack link $(jj sb -r commit1::commitN)又或者,由于
jj sb默认指向你的 substack,你可以跳转到 栈顶 (此时栈和 substack 是同一个东西),然后关联整个栈:终端窗口 jj top --edit gh stack link $(jj sb)最后这种方式可能是最容易记住的。
向栈顶部添加新提交
假设新提交的变更 ID 为 abc:
-
确认你正在编辑新提交:
终端窗口 jj edit -r abc -
为当前新提交创建一个书签:
终端窗口 jj b c <name> -
如果你修改了下栈中的任何提交,需要推送这些更新(因为
jj git push会执行--force-with-lease推送,而gh stack link不会):终端窗口 jj git push -r "stack()" -
用所有栈书签重新执行 link:
下栈对应的已有 PR 会被跳过,新分支会获得一个新的草稿 PR,并被链接到栈中。终端窗口 gh stack link $(jj sb)
向栈的底部或中间添加新提交
你无法向栈的中间添加提交(会报错:"Cannot update stack: new PRs must be added to the top of the existing stack"),所以只能删除栈后重建。假设新提交的变更 ID 为 abc:
-
在 GitHub 网页端移除该栈:
-
为新提交创建一个书签:
终端窗口 jj b c -r abc <name> -
因为你修改了 rebase 之后的上栈提交,需要重新推送所有内容:
终端窗口 jj git push -r "stack()" -
用所有栈书签(包括新增的)重新运行 link:
已有的 PR 会被跳过,新分支会创建一个新的 draft PR 并加入栈中终端窗口 jj top --editgh stack link $(jj sb)
从栈中移除一个提交
你需要删除整个栈然后重建。假设要移除的提交其 change id 为 abc:
-
前往 GitHub 网页界面,移除该栈:
-
确保你处在栈顶:
终端窗口 jj top --edit -
如果还没放弃该提交,先放弃它:
终端窗口 jj abandon -r abc -
推送你的更新:
终端窗口 jj git push -r "stack()" -
用所有栈书签重新运行 link:
已有的 PR 会被跳过,栈会被重建终端窗口 jj top --editgh stack link $(jj sb) -
被移除的提交会在 GitHub 上留下一个孤立的 PR。你可以手动关闭它,或者删除远程分支。又或者,用
jj git push --deleted推送所有已删除的书签。
更新已有栈中的现有提交
-
重新推送你的栈:
终端窗口 jj git push -r "stack()"
合并你的栈
理论上和拆栈一样可以从命令行操作,但太麻烦了:
gh stack link --open $(jj sb -r "stack()") # draft => publishedgh stack merge $(gh pr view "$(jj sb -r "stack()" | awk '{print $NF}')" --json number --jq .number) --yes --squash
所以直接在网页上操作就好了。反正你大概率也要在那边盯着 CI 状态。 🤷🏽♀️
更新
上面这些工作流几乎肯定还有改进空间,只是我没想到。如果你有更好的办法,联系我,我会更新本文。
感想与结语
我期待的未来改进:
- 在上述手动操作基础上构建的 GUI 工具,把繁琐步骤自动化掉。我个人用 超棒的 VisualJJ vscode 插件 来完成 80% 的 jj 命令,相信开发者们正在为它添加 GitHub stack 支持。
-
gh stack扩展推出一个gh stack submit子命令,把一切安排得明明白白,让上面的内容全部过时。 - 改进需要删除并重建堆栈的工作流程,例如在堆栈中间添加提交。
就目前而言,这些功能大体上都能正常工作,我对 GitHub 引入堆栈功能感到非常兴奋。我的初步印象是,这一功能可以立即投入使用,我感谢为此功能付出的开发者们。
话虽如此,我们得直面一个显而易见的问题:这是对堆叠式 PR 的一个缺乏雄心的实现。GitHub 的堆叠式 PR 完全建立在现有且存在缺陷的 GitHub PR 模型之上,没有改变任何基础:
恕我直言,你们参考了哪些先前的工作?为什么要求人们为堆栈中的每个更改创建分支?为什么开发者在迭代 PR 时必须创建新提交?对 interdiffs 的支持在哪里?变更 ID 又在哪里? -sunshowers 在 Hacker News 上
没有自动的变更 ID,取而代之的是手动分支。没有自动的 gh stack submit(如
graphite
或
git-branchless
的提交命令),你只能手动向 gh stack 提供分支列表。诸如此类。
简而言之,如果你对开发者体验不抱太高期望,最终结果会让你满意:在 GitHub 中实现可用的 PR 堆栈。
生产说明
AI 事实 每份含 1 篇文章 每份用量约 1 篇文章 每份含量 机器撰写文字 0% 机器占比*- 正文
- 代码与配置
- 创作
- 验证
- 人工审核
- 机器占比表示本文由机器撰写的比例。数值为估算,并非精确测量。此标签未经美国食品药品监督管理局评估。