使用 Jujutsu (jj) 与 GitHub/GitLab 协作:从提交栈到 Pull Request 的完整实战指南
2026/9/10 9:08:52 网站建设 项目流程

使用 Jujutsu (jj) 与 GitHub/GitLab 协作:从提交栈到 Pull Request 的完整实战指南

【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj

Jujutsu(简称 jj)是一款 Git 兼容的版本控制系统,它改变了"先有分支、再在其上提交"的传统心智模型——你可以先自由地堆叠提交,直到需要推送时再创建 bookmark。本文以 web/docs/src/content/docs/github.md(及其完整版 docs/github.md)为主线,结合 cli/src/commands/git/push.rs 等源码实现,完整讲解如何在 GitHub / GitLab 项目上使用 jj 完成从本地开发、推送、评审响应到多远端协作的全流程。读完本文,你将掌握两种 bookmark 推送工作流、同步远端更新的标准做法、三种响应评审意见的方式,以及多 remote、GitHub CLI 与 Git push option 的实战配置。

前置基础:bookmark 与 Git 分支的映射

在进入 GitHub/GitLab 工作流之前,需要先理解 jj 的 bookmark 概念。正如 docs/bookmarks.md 所述,bookmark 是指向某个修订(revision)的命名指针,等价于 Git 中的分支。它与 Git 分支的区别在于:

  • bookmark 可以在不影响目标修订身份的前提下自由移动;
  • 当修订被重写(例如jj rebase)时,bookmark 会自动跟随移动;
  • jj 中没有"当前检出的分支"(active/current/checked-out bookmark)的概念。

当与 Git 仓库交互时,jj 会将 bookmark 映射为 Git 分支:jj git push --bookmark foo会把foobookmark 推送到远端的foo分支;反过来,在 colocated 工作区中,你在 Git 仓库里创建的bar分支经过自动jj git import后也会变成barbookmark。远端 bookmark 的最后已知位置会以<bookmark>@<remote>的形式记录(如main@origin),这与 Git 的 remote-tracking 分支类似。

基本工作流:先堆叠提交,再创建 bookmark

jujutsu 的核心建议是:先创建一叠提交(stack),只有需要推送时才创建 bookmark。这对应两种主流程:让 jj 自动生成 bookmark 名,或显式命名 bookmark。

方式一:使用自动生成的 bookmark 名

# 基于默认 bookmark 开始一个新的提交(working copy) $ jj new main # 重构一些文件,添加描述并开始新的提交 $ jj commit -m 'refactor(foo): restructure foo()' # 添加一个功能,添加描述并开始新的提交 $ jj commit -m 'feat(bar): add support for bar' # 让 jj 自动生成 bookmark 名并推送到 GitHub。 # 注意:我们推送的是 working-copy 提交的 *父提交*(@-), # 因为 working-copy 提交本身是空的。 $ jj git push --change @- # 即 -c @-

这里的关键参数是--change(简称-c),它会为指定提交自动生成一个 bookmark 名并推送。从 cli/src/commands/git/push.rs 的参数定义可以看到,生成的 bookmark 会自动被跟踪(tracked),其名称默认由模板"push-" ++ change_id.short()生成,例如push-mwmpwkwknuz。如果你希望生成的名称带有个人前缀,可以在配置中覆盖模板(详见后文"自动生成的 bookmark 名"小节)。

方式二:使用命名 bookmark

# 基于默认 bookmark 开始一个新的提交 $ jj new main # 重构一些文件,添加描述并开始新的提交 $ jj commit -m 'refactor(foo): restructure foo()' # 添加一个功能,添加描述并开始新的提交 $ jj commit -m 'feat(bar): add support for bar' # 创建一个 bookmark,指向 working-copy 提交的 *父提交*, # 因为 working copy 本身是空的。此时 bar 包含前面两个提交。 $ jj bookmark create bar -r @- # 设置该 bookmark 在远端被跟踪 $ jj bookmark track bar # 推送到 GitHub(只推送 bar) $ jj git push

需要特别提醒的是:虽然你可以像 Git 那样提前创建 bookmark 并在其上继续提交,但jj 不会像 Git 那样自动移动 bookmark。每创建一个新提交,你都必须手动把 bookmark 移到新位置,否则 bookmark 会停留在旧提交上。

同步远端更新:jj git fetch+jj rebase

截至本文写作时,jj 尚没有与git pull等价的单一命令(项目跟踪的同步问题见 issue #1039)。更新你的提交需要两步:

# 第一步:抓取远端所有变化 $ jj git fetch # 第二步:把你的提交变基到主 bookmark 之上 $ jj rebase -o main

jj rebase -o main的默认行为等价于-b @,即只变基当前工作区涉及的提交。如果你有多个未合入的分支,需要为每个分支再执行一次jj rebase -b <bookmark> -o main,或一次传入多个-b参数。

从源码角度看,jj git fetch的远端选择逻辑位于 cli/src/commands/git/fetch.rs:默认读取git.fetch配置,未配置且只有一个 remote 时使用该唯一 remote(会打印提示),否则回退到名为origin的 remote(DEFAULT_REMOTE)。抓取时默认遵循remotes.<name>.fetch-bookmarks/fetch-tags配置,未配置则读取 Git 自身的默认 refspec。

在 Git colocated 工作区中协作

jj git init默认创建的是colocated 工作区.jj.git目录并存,二者共享同一个工作副本。这一点可以从 cli/src/config/misc.toml 中的默认配置git.colocate = true得到印证。在这种模式下,Git 会处于 detached HEAD 状态——这对习惯命名分支的 Git 用户来说很反常,因为 jj 没有"当前分支"的概念。

colocated 工作区的关键特性是自动同步:每个jj命令都会自动执行 Git 与 Jujutsu 视图之间的导入导出。例如,jj commit会更新 Git 仓库的 HEAD,这让你可以渐进式迁移现有 Git 仓库。典型流程:

$ nvim docs/tutorial.md $ # 继续做一些工作 $ jj commit -m "Update tutorial" # 在 working-copy 提交的父提交上创建 bookmark $ jj bookmark create doc-update -r @- $ jj bookmark track doc-update $ jj git push

关于 colocated 模式的更多细节(如何禁用、优缺点、如何用jj git colocation命令转换),可参见 docs/git-compatibility.md。另外请注意,docs/config.md 说明git.colocate是布尔开关,设为false即可让jj git init/jj git clone默认创建非 colocated 工作区。

在纯 Jujutsu 仓库中工作

如果你不需要与他人混用 Git 命令,纯 Jujutsu 仓库的工作流更简洁:因为 jj 可以为任意修订生成 bookmark,无需显式命名,直接用--change即可:

$ # 完成你的工作 $ jj commit $ # 推送修订 "mw",让 jj 自动创建一个名为 "push-mwmpwkwknuz" 的 bookmark $ jj git push --change mw

注意这里--change接受的是修订参数(change ID 前缀或 revset),它会为该修订创建 bookmark 并推送。

定制自动生成的 bookmark 名

如果你希望推送后自动创建的 bookmark 名更具可读性,可以覆盖templates.git_push_bookmark模板(默认值为"push-" ++ change_id.short())。例如,模拟常见的"用户名前缀"约定:

[templates] git_push_bookmark = '"martinvonz/push-" ++ change_id.short()'

该模板必须包含change_id之类的表达式以保证名称唯一且稳定。完整说明见 docs/config.md。

处理评审意见的三种方式

响应 GitHub/GitLab 上的评审意见时,不同项目有不同偏好:许多项目(典型的 GitHub 工作流)偏好向 bookmark 追加新提交;而另一些项目(如 Jujutsu 与 LLVM)偏好重写提交、保持提交历史干净,然后强制推送。

方式一:追加新提交(GitHub 风格)

# 在上面的 your-feature bookmark 之上创建新提交 $ jj new your-feature # 根据意见修改代码,然后查看改动 $ jj diff # 为修复添加描述,并创建新的 working copy $ jj commit -m 'address pr comments' # 把 bookmark 移到新提交 $ jj bookmark move your-feature --to @- # 推送到远端 $ jj git push

方式二:不新建提交,直接描述并移动 bookmark

上面的流程会创建一个新提交。如果不希望产生新提交,可以这样做——但需要注意,此后所有编辑仍会被追加(amend)到当前提交,因此强烈建议在下面的示例执行完后执行一次jj new

$ jj new your-feature # 根据意见修改代码,然后查看改动 $ jj diff # 直接修改当前提交的描述 $ jj describe -m 'address pr comments' # 把 bookmark 移到当前提交 $ jj bookmark move your-feature --to @ # 推送到远端 $ jj git push

方式三:重写提交,保持历史干净

如果项目要求提交干净,可以先跳到你需要修改的那个提交,改完再 squash 进父提交:

# 在 your-feature 的倒数第二个提交之上创建新提交, # 因为评审者要求在那里修改。(注意尾部连字符不是笔误!) $ jj new your-feature- # 根据意见修改代码,然后查看改动 $ jj diff # 把改动 squash 进父提交 $ jj squash # 推送更新后的 bookmark。jj 会自动将其变为 force push $ jj git push --bookmark your-feature

your-feature后面的连字符来自 revset 语法:<rev>-表示"该修订的父提交"。

推送时的自动安全检查

无论采用哪种方式,jj git push都会在真正移动/创建/删除远端引用前做一系列安全检查(实现于 cli/src/commands/git/push.rs 的CommitsValidator):默认拒绝推送空描述缺少作者/提交者信息包含冲突以及属于git.private-commits集合的提交。可用--allow-empty-description--allow-conflicts--allow-private显式放行。此外,jj git push的更新语义类似git push --force-with-lease:只有当远端当前状态与 jj 上次抓取的状态一致时才会更新远端引用(见 push.rs 的命令文档)。推送前还可以用--dry-run预览将发生的变更。

与其他贡献者的 bookmark 协作

默认情况下,jj git clone只导入远端的默认 bookmark(通常是mainmaster),而jj git fetch不会把新的远端 bookmark 导入为本地 bookmark。因此,如果你想检出并测试其他贡献者的 bookmark,需要显式指定:

$ jj new <bookmark>@<remote>

如果你希望把包括非活跃 bookmark 在内的所有远端 bookmark 都自动导入并跟踪,可以在配置文件中设置:

[remotes.origin] auto-track-bookmarks = "*"

设置后即可直接用jj new <bookmark>而无需写@<remote>后缀。auto-track-bookmarks的值是一个字符串模式(string pattern),同时作用于本地新建与远端抓取的新 bookmark。该配置的详细说明与使用场景见 docs/config.md。

常见的用法是按前缀过滤:多人共用同一个远端时,每个人只跟踪自己前缀的 bookmark(如alice/*),从而避免跟踪他人全部 bookmark、也避免误推送本地专用 bookmark;GitHub fork 场景则可对 origin 跟踪全部、对 upstream 只跟踪main。另外还有只作用于本地创建的auto-track-created-bookmarks选项。如果你希望贡献者的 bookmark 在jj git fetch时就被抓取,也可以结合remotes.<name>.fetch-bookmarks配置(见 docs/config.md)。

让 GitHub CLI 在 jj 仓库中正常工作

在非 colocated 的 jj 仓库中,gh(GitHub CLI)会无法找到正确的 Git 仓库路径(相关讨论见 issue #1008)。解决方法是指定$GIT_DIR环境变量指向 jj 内部的 Git 仓库:

$ GIT_DIR=.jj/repo/store/git gh issue list # 等价写法,jj 会自动解析仓库根路径 $ GIT_DIR=$(jj git root) gh issue list

如果不想每次手动设置,可以借助 direnv:在仓库根目录创建.envrc文件,加入一行:

export GIT_DIR=$PWD/.jj/repo/store/git

然后运行direnv allow批准它生效。此后即使工作区不是 colocated,gh issue list等命令也能正常运行。注意jj git root也是获取仓库根目录的便捷命令(实现见 cli/src/commands/git/root.rs)。

实用的 revset 查询

配合jj log -r使用 revset,可以快速筛选出值得推送或需要关注的提交:

# 列出所有本地 bookmark 上、但既不在主 bookmark 也不在任何远端上的修订 $ jj log -r 'bookmarks() & ~(main | remote_bookmarks())' # 列出你创作的、位于 bookmark 上且尚未出现在任何远端的修订 $ jj log -r 'mine() & bookmarks() & ~remote_bookmarks()' # 列出你创作或提交过的所有远端 bookmark $ jj log -r 'remote_bookmarks() & (mine() | committer(your@email.com))' # 列出当前 working copy 的所有祖先中、尚未出现在任何远端的修订 $ jj log -r 'remote_bookmarks()..@'

这些表达式可帮助你快速判断"还有哪些提交需要推送"。revset 的完整语法参见 docs/revsets.md。

合并冲突的处理

jj 把冲突视为仓库中的一等公民,并将其建模在提交树中(详见 docs/conflicts.md 与 docs/tutorial.md)。在推送遇到冲突提交时,jj git push的默认安全检查会拒绝推送(除非显式加--allow-conflicts),因此通常的流程是先用jj resolvejj squash等手段解决冲突后再推送。完整的冲突处理教程请回顾 tutorial。

同时使用多个 remote:upstream 与 fork

为共享仓库做贡献时,常见做法是同时配置多个远端:"upstream" 指向变更最终会通过 Pull Request 合入的上游仓库,"origin" 指向你的私有 fork。

# 以 "upstream" 为远端名克隆上游仓库 $ jj git clone --remote upstream https://github.com/upstream-org/repo $ cd repo # 添加你自己的 fork 作为 "origin" $ jj git remote add origin git@github.com:your-org/your-repo-fork

克隆后会自动设置对上游主 bookmark 的跟踪,通常是main@upstreammaster@upstream。接下来你大概率会从 upstream 抓取向 origin 推送,这可以通过配置默认远端来完成(例如写在仓库级配置.jj/repo/config.toml,或用jj config set --repo设置):

[git] fetch = "upstream" push = "origin"

git.fetchgit.push的默认值都是"origin"。源码中对应的默认值见 cli/src/commands/git/fetch.rs 与 cli/src/commands/git/push.rs。关于git.fetch/git.push的完整配置说明(包括命令行设置方式、glob 与正则模式),见 docs/config.md。

如果你在多台电脑上工作,还可以让jj默认从多个远端抓取,以通过 origin 同步你自己的 bookmark:

[git] fetch = ["upstream", "origin"] push = "origin"

注意:git.fetch支持字符串或数组(以及 glob / 正则模式),而git.push目前只能是单个远端名,这是一个已知限制,未来可能放开。

推送选项(Push Options):对接 GitLab CI 与 Merge Request

jj git push支持通过-o/--option向服务端传递 Git 的"推送选项"(push options),这些选项会原样转发给远端并被托管平台解释(如果平台支持的话)。参数定义与底层传递分别见 cli/src/commands/git/push.rs 和 lib/src/git.rs 中的GitPushOptions结构——它最终被拼装进git push --push-option子进程参数(见 push_refs 的实现)。

用法要点:

  • 语法:jj git push -o <push_option>jj git push --option <push_option>
  • 可重复:jj git push -o foo -o bar=val可发送多个选项
  • 引号:如果选项值包含空格,请用双引号包裹

推送选项的支持情况取决于服务端:GitLab 支持用推送选项控制 CI 与 Merge Request,其他平台可能不支持。以下均为 GitLab 的常用示例:

跳过本次推送触发的 CI:

jj git push -o ci.skip

为创建的流水线传递 CI 变量:

jj git push -o 'ci.variable=MAX_RETRIES=10' -o 'ci.variable=MAX_TIME=600'

推送时直接创建带元数据的 Merge Request:

jj git push \ -o merge_request.create \ -o merge_request.target=main \ -o 'merge_request.title=Add feature X' \ -o 'merge_request.description=Implements X with tests' \ -o merge_request.draft

流水线成功后自动合入并删除源分支:

jj git push \ -o merge_request.merge_when_pipeline_succeeds \ -o merge_request.remove_source_branch

添加和移除标签:

jj git push \ -o 'merge_request.label=label1' \ -o 'merge_request.label=label2' \ -o 'merge_request.unlabel=label3'

指派与取消指派用户:

jj git push \ -o 'merge_request.assign=user1' \ -o 'merge_request.assign=user2' \ -o 'merge_request.unassign=user3'

完整的选项列表与行为请以你的托管平台文档为准。

小结:一套工作流覆盖 GitHub 与 GitLab

至此,你已经掌握了 jj 在 GitHub/GitLab 协作中的完整工具链:先用jj new+jj commit自由堆叠提交;推送时用--change自动生成 bookmark 或jj bookmark create/track命名 bookmark;同步用jj git fetch+jj rebase;响应评审用追加提交、原地描述或重写 squash 三种方式;多 remote 用git.fetch/git.push配置;GitLab 场景还可以用-o推送选项直接驱动 CI 与 Merge Request。这些能力的底层行为都能在 cli/src/commands/git/push.rs 与 cli/src/commands/git/fetch.rs 中找到对应实现,配置默认值则集中在 cli/src/config/misc.toml。如果你想验证这些命令在真实仓库中的行为,仓库的 cli/tests/test_git_push.rs 与 cli/tests/test_git_fetch.rs 是很好的参考。

【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询