Jujutsu 多远端(Multiple Remotes)工作流实战:fork 贡献与上游集成的配置指南
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
导读
在真实的开源协作中,一个 Jujutsu(jj)仓库往往同时面对两个远端:一个是你可以推送代码的origin(fork 或你自己的仓库),另一个是你定期同步或向其提交贡献的upstream(上游权威仓库)。如何在 jj 中正确配置 fetch、push 与 bookmark 跟踪,决定了你的main分支是否会被意外覆盖、trunk()是否稳定可依赖。本文以官方指南 multiple-remotes.md 为骨架,结合 jj 的源码实现与配置默认值,完整讲解两种主流多远端场景(GitHub 风格 fork 贡献、独立仓库集成上游变更)的配置命令、参数含义与底层原理,让你读完即可照抄配置、落地一套可复用的多远端工作流。
术语约定:origin 与 upstream 的角色划分
在开始配置前,先统一文档中的命名约定:
origin:你拥有写权限的远端,通常是推送变更的目标(如你的 fork 或个人仓库)。upstream:更知名、更权威的仓库,你通常没有写权限,只能从中 fetch。- 各仓库的主干分支默认假定为
main,因此对应的远端 bookmark 写作main@origin与main@upstream。
这一约定与 Git 生态中的常见习惯一致。在 jj 中,远端 bookmark 的记法是<bookmark>@<remote>,例如main@origin表示origin远端上的main。Jujutsu 官方术语中,"bookmark" 等价于 Git 的 branch,详见 bookmarks.md 与 glossary.md。
场景一:GitHub 风格 fork,向上游贡献代码
这是最典型的开源贡献场景:upstream是权威仓库,origin是你的 fork,你通过向origin推送代码并开启 Pull Request 来贡献给upstream。
你希望做的操作
- 从
upstreamfetch 最新变更; - 把
main推送到origin以保持 fork 同步; - 把
my-feature推送到origin,然后对upstream开 Pull Request。
推荐配置
官方指南给出的配置如下:
# 默认从两个远端都 fetch $ jj config set --repo git.fetch '["upstream", "origin"]' # 默认只推送到 fork $ jj config set --repo git.push origin # 跟踪两个远端的 main bookmark $ jj bookmark track main@upstream main@origin # upstream 定义主干分支 $ jj config set --repo 'revset-aliases."trunk()"' main@upstream逐项解读配置含义
1.git.fetch:默认 fetch 的远端列表
git.fetch是一个数组配置项,决定了不带--remote参数执行jj git fetch时默认拉取哪些远端。在源码 cli/src/commands/git/fetch.rs 的GitFetchArgs文档中明确写道:
If no remotes are specified, fetches the remotes specified by the
git.fetchsetting. If that is not configured and there are multiple remotes, the remote named "origin" will be used.
也就是说,如果未配置git.fetch且仓库有多个远端,jj 会退化为只 fetchorigin。因此在上游贡献场景中,显式配置'["upstream", "origin"]'是保证默认行为正确的关键。fetch 时若未指定分支,还会读取remotes.<name>.fetch-bookmarks/fetch-tags设置,未配置则回退到 Git 配置中的默认 refspec(见 fetch.rs)。
2.git.push:默认推送的远端
对应jj git push的--remote参数默认值。源码 cli/src/commands/git/push.rs 中说明:
This defaults to the
git.pushsetting. If that is not configured, and if there are multiple remotes, the remote named "origin" will be used.
与 Git 不同,jj 的推送目标不是从被跟踪的远端 bookmark 推导出来的,必须用--remote明确指定,且一次只能推送一个远端(见 push.rs)。这里把git.push设为origin,即可保证默认推送只到达你的 fork,不会误推upstream。
3.jj bookmark track main@upstream main@origin:跟踪远端 bookmark
跟踪远端 bookmark 后,jj 会将其导入为同名的本地 bookmark,并在后续 fetch 时把远端变化传播到本地 bookmark 上。源码 cli/src/commands/bookmark/track.rs 描述道:
A tracked remote bookmark will be imported as a local bookmark of the same name. Changes to it will propagate to the existing local bookmark on future pulls.
- 跟踪
main@upstream:每次从upstreamfetch 后,本地main都会跟着更新; - 跟踪
main@origin:执行jj git push(推送到origin)时,fork 的main分支会同步更新。
track命令的names参数接受BOOKMARK[@REMOTE]形式,BOOKMARK@REMOTE精确解析远端 bookmark;也可以只用BOOKMARK名称匹配所有远端。注意不能混用两种语法,且--remote与BOOKMARK@REMOTE符号互斥(见 track.rs)。
4.revset-aliases."trunk()":定义主干分支并使其不可变
jj 用trunk()这个 revset 别名代表主干分支,而主干分支属于"不可变提交"(immutable heads),默认不允许被重写(rebase、amend 等操作会受保护)。官方默认的trunk()实现在 cli/src/config/revsets.toml:
'trunk()' = ''' latest( remote_bookmarks(exact:"main", exact:"origin") | remote_bookmarks(exact:"master", exact:"origin") | remote_bookmarks(exact:"trunk", exact:"origin") | remote_bookmarks(exact:"main", exact:"upstream") | remote_bookmarks(exact:"master", exact:"upstream") | remote_bookmarks(exact:"trunk", exact:"upstream") | root() ) '''可以看到默认实现会在origin与upstream的main/master/trunkbookmark 中取latest。在 fork 贡献场景中,将其显式覆盖为main@upstream,意味着你的主干始终以权威仓库为准——main@upstream变为不可变提交,你的工作提交只会在其之上进行。immutable_heads()的默认构成(trunk() | tags() | untracked_remote_bookmarks() | untracked_remote_tags())见 revsets.toml。
场景二:独立仓库,定期集成上游变更
这是另一种常见形态:仓库最初从upstream克隆而来,但本地main分支积累了不属于上游的改动,且未来也不打算回馈给上游。
origin:你正在工作的仓库;upstream:你周期性集成其变更的远端。
你希望做的操作
- 从
originfetch 最新变更; - 把 bookmark 推送到
origin; - 把 Pull Request 合并进
main@origin; - 周期性从
main@upstreamfetch,并将其变更 merge、rebase 或 duplicate 进main@origin。
推荐配置
# 默认从 origin(或两个远端)fetch $ jj config set --repo git.fetch '["origin"]' # 或:jj config set --repo git.fetch '["upstream", "origin"]' # 默认只推送到 origin $ jj config set --repo git.push origin # 只跟踪 origin 的 bookmark $ jj bookmark track main --remote=origin $ jj bookmark untrack main --remote=upstream # origin 定义主干分支 $ jj config set --repo 'revset-aliases."trunk()"' main@origin与场景一的关键差异:不要跟踪main@upstream
本场景的核心原则是:只跟踪main@origin,绝不跟踪main@upstream。原因在于:
- 跟踪
main@origin后,本地main会在每次从originfetch 时更新,必要时也能推回origin; - 不跟踪
main@upstream,是为了防止 fetch 上游时本地main被上游的main覆盖或影响——你的main代表的是你自己的、含私有改动的主干,而不是上游主干。
jj bookmark untrack用于解除跟踪。源码 cli/src/commands/bookmark/untrack.rs 说明:未跟踪的远端 bookmark 只是一个指向"最近一次 fetch 到的远端状态"的指针,不会再被导入为本地 bookmark。当你想同时删除本地 bookmark 并解除远端跟踪时,应改用jj bookmark forget(见 untrack.rs)。
上游变更的集成则通过jj git fetch配合 merge/rebase 完成:从main@upstream拉取最新提交后,使用jj merge、jj rebase或jj duplicate将这些变更合入你自己的main@origin。由于main@upstream未被跟踪,拉取后它只是一个远端指针,不会自动触碰你的本地main。
这里使用--remote参数将跟踪/解除跟踪限定在单个远端,避免影响其他远端的同名 bookmark。track/untrack 的--remote接受 glob 模式匹配远端名,不指定时匹配所有远端(见 track.rs、untrack.rs)。
其他工作流与通用原则
把trunk()设为你的基准远端
trunk()应该设置为你通常在其上 rebase 的远端 bookmark。如果你总是以upstream为基准 rebase,就设main@upstream;如果你以自己仓库的origin为基准,就设main@origin。这直接决定你的工作提交栈的"地基"在哪里,也决定哪些提交被视为不可变而受到保护。
跟踪与否,决定了 bookmark 是否自动联动
跟踪远端 bookmarkmain@origin意味着它和本地main代表同一条分支:一方移动,另一方应随之移动。如果你希望二者自动同步,就跟踪它;如果不希望,就不要跟踪。这是上述两个场景设计差异的核心依据。
其他参考
- 若未配置
git.push,多个远端时默认仍推origin(见 push.rs 的DEFAULT_REMOTE常量); - 重命名远端时,jj 会自动尝试更新
trunk()别名。测试 test_git_remotes.rs 验证了重命名origin后trunk()仍能正确解析到新的main@<新远端名>;若别名指向的远端 bookmark 不存在,trunk()会临时退化为root()并给出警告与修复提示(见 test_git_remotes.rs); - 从 git-command-table.md 可以对照查看
jj git fetch、jj git push与 Git 对应命令的行为差异; - 如果你有不在上述范围内的多远端工作流,可以在项目仓库的讨论区提出,社区也在持续演进 bookmark 跟踪机制(仓库
docs/design/tracking-branches.md记录了相关设计)。
总结
多远端配置的本质,是回答三个问题:默认从哪 fetch、默认往哪 push、哪条远端 bookmark 是你的主干。Jujutsu 用git.fetch、git.push与revset-aliases."trunk()"三个配置项,配合jj bookmark track/untrack精确控制这些行为:
| 配置项 / 命令 | fork 贡献(场景一) | 独立集成(场景二) |
|---|---|---|
git.fetch | ["upstream", "origin"] | ["origin"](或两者) |
git.push | origin | origin |
| 跟踪 bookmark | main@upstream+main@origin | 仅main@origin |
trunk() | main@upstream | main@origin |
无论是向上游提交 PR,还是维护一条带私有改动的独立主干,上述配置都能让你在多远端环境下保持main分支与trunk()的行为符合预期。深入理解 bookmark 跟踪与远端指针的语义(见 bookmarks.md 与 tracking-branches.md),你还能在此基础上组合出更多适合自己的工作流。
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考