← 文章 / 编程开发
Lobsters 16小时前 · 2026-08-13 09:48:23 · 0 阅读

Jujutsu 中的 GitHub Stacks

本文是 Jujutsu Stacks 系列文章的一部分。系列中的其他文章:
  1. Jujutsu 中的 GitHub Stacks <== 你在这里
  2. 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。可以这样配置:

~/.config/jj/config.toml
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 命令行工具包含许多子命令,其中大部分用于管理或读取本地的栈跟踪(例如 initview)。在 jj 仓库中,你只需要使用那些用于管理 GitHub 上远程栈的子命令(例如 linkpush),具体如下:

创建一个栈

  1. 为栈中的每个提交创建一个书签:

    终端窗口
    # 使用我们的 revset 别名跳转到栈底
    jj edit -r "stack_bottom()"
    # 为提交命名
    jj b c <name>
    # 跳转到下一个提交
    jj next --edit
    # 为提交命名
    jj b c <name>
    # ... 对栈中的每个提交重复上述操作
  2. 将书签上传到 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

  1. 确认你正在编辑新提交:
    终端窗口
    jj edit -r abc
  2. 为当前新提交创建一个书签:
    终端窗口
    jj b c <name>
  3. 如果你修改了下栈中的任何提交,需要推送这些更新(因为 jj git push 会执行 --force-with-lease 推送,而 gh stack link 不会):
    终端窗口
    jj git push -r "stack()"
  4. 用所有栈书签重新执行 link:
    终端窗口
    gh stack link $(jj sb)
    下栈对应的已有 PR 会被跳过,新分支会获得一个新的草稿 PR,并被链接到栈中。

向栈的底部中间添加新提交

你无法向栈的中间添加提交(会报错:"Cannot update stack: new PRs must be added to the top of the existing stack"),所以只能删除栈后重建。假设新提交的变更 ID 为 abc

  1. 在 GitHub 网页端移除该栈: 截图:GitHub 栈界面,显示
  2. 为新提交创建一个书签:
    终端窗口
    jj b c -r abc <name>
  3. 因为你修改了 rebase 之后的上栈提交,需要重新推送所有内容:
    终端窗口
    jj git push -r "stack()"
  4. 用所有栈书签(包括新增的)重新运行 link:
    终端窗口
    jj top --editgh stack link $(jj sb)
    已有的 PR 会被跳过,新分支会创建一个新的 draft PR 并加入栈中

从栈中移除一个提交

你需要删除整个栈然后重建。假设要移除的提交其 change id 为 abc

  1. 前往 GitHub 网页界面,移除该栈: GitHub 栈界面截图,显示 “Unstack pull requests” 按钮
  2. 确保你处在栈顶:
    终端窗口
    jj top --edit
  3. 如果还没放弃该提交,先放弃它:
    终端窗口
    jj abandon -r abc
  4. 推送你的更新:
    终端窗口
    jj git push -r "stack()"
  5. 用所有栈书签重新运行 link:
    终端窗口
    jj top --editgh stack link $(jj sb)
    已有的 PR 会被跳过,栈会被重建
  6. 被移除的提交会在 GitHub 上留下一个孤立的 PR。你可以手动关闭它,或者删除远程分支。又或者,用 jj git push --deleted 推送所有已删除的书签。
注意:删除远程分支会关闭对应的 PR,所以如果你在重新运行 `gh stack link ...` **之前**执行 `jj git push --deleted`,被删除分支之上的那个 PR 也会被一起关掉。先链接再删除可以避免这个问题。

更新已有栈中的现有提交

  1. 重新推送你的栈:
    终端窗口
    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 状态。 🤷🏽‍♀️

更新

上面这些工作流几乎肯定还有改进空间,只是我没想到。如果你有更好的办法,联系我,我会更新本文。

感想与结语

我期待的未来改进:

  1. 在上述手动操作基础上构建的 GUI 工具,把繁琐步骤自动化掉。我个人用 超棒的 VisualJJ vscode 插件 来完成 80% 的 jj 命令,相信开发者们正在为它添加 GitHub stack 支持。
  2. gh stack 扩展推出一个 gh stack submit 子命令,把一切安排得明明白白,让上面的内容全部过时。
  3. 改进需要删除并重建堆栈的工作流程,例如在堆栈中间添加提交。

就目前而言,这些功能大体上都能正常工作,我对 GitHub 引入堆栈功能感到非常兴奋。我的初步印象是,这一功能可以立即投入使用,我感谢为此功能付出的开发者们。

话虽如此,我们得直面一个显而易见的问题:这是对堆叠式 PR 的一个缺乏雄心的实现。GitHub 的堆叠式 PR 完全建立在现有且存在缺陷的 GitHub PR 模型之上,没有改变任何基础:

恕我直言,你们参考了哪些先前的工作?为什么要求人们为堆栈中的每个更改创建分支?为什么开发者在迭代 PR 时必须创建新提交?对 interdiffs 的支持在哪里?变更 ID 又在哪里? -sunshowers 在 Hacker News 上

没有自动的变更 ID,取而代之的是手动分支。没有自动的 gh stack submit(如 graphitegit-branchless 的提交命令),你只能手动向 gh stack 提供分支列表。诸如此类。

简而言之,如果你对开发者体验不抱太高期望,最终结果会让你满意:在 GitHub 中实现可用的 PR 堆栈。

生产说明

AI 事实 每份含 1 篇文章 每份用量约 1 篇文章 每份含量 机器撰写文字 0% 机器占比*
  • 正文
手写完成。机器校对。人工修改。 0%
  • 代码与配置
0%
  • 创作
所有代码/配置均为手写。 0%
  • 验证
所有代码/配置均由 Claude Opus 5 以编程方式验证正确性与一致性。修复由人工完成。 100%
  • 人工审核
100%
  • 机器占比表示本文由机器撰写的比例。数值为估算,并非精确测量。此标签未经美国食品药品监督管理局评估。
原始来源: Lobsters

评论 (0)