Jujutsu 项目路线图深度解读:8 大技术方向与源码级现状
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
本文以 Jujutsu(
jj)官方路线图文档 web/docs/src/content/docs/roadmap.md 为主体,逐一剖析项目规划中的 8 个相互独立的技术目标:文件复制/重命名追踪、Forge 集成、Git 子模块支持、面向 UI 的 Rust API 重构、RPC API、开源云端仓库(服务端与守护进程)、虚拟文件系统(VFS)与大文件支持。文章不仅完整保留路线图原文的目标描述,还结合jj当前仓库中的设计文档、核心源码与测试用例,给出每项规划的动机、已落地的实现证据与仍待解决的设计问题,帮助读者准确判断 Jujutsu 的能力边界与发展方向。
路线图总览:一份没有时间表的愿望清单
Jujutsu 官方路线图开宗明义:它记录的是项目一些希望达成的目标,且这些目标大多相互独立,可以单独推进。文档同时特别说明,绝大多数贡献者是在业余时间参与 Jujutsu 开发,因此无法为任何目标附加预计完成日期。
这一点对读者非常重要:路线图中的每一项都代表"项目希望做什么",而非"项目已经做了什么"。要判断某项功能的真实落地程度,必须回到仓库源码中去验证——这正是本文在每一节中都会做的事。
路线图列出的 8 个目标分别是:
- 文件复制(copy)与重命名(rename)追踪
- Forge(代码托管平台)集成
- Git 子模块(submodule)支持
- 面向 UI 的更好 Rust API
- RPC API
- 开源云端仓库(服务端与守护进程)
- 虚拟文件系统(VFS)
- 更好的大文件支持
下文按路线图原文顺序逐一展开。
1. 支持文件复制与重命名追踪
目标与动机
路线图提出,Jujutsu 希望以"把决策权交给 commit backend(提交后端)"的方式支持复制追踪——即由后端决定是记录复制还是检测复制。这样做的好处是双重的:
- 兼容现有 Git 仓库:Git 本身不记录复制/重命名,而是在比较两个树时即时检测(on the fly);
- 适配超大仓库:在非常大的仓库中,即时检测的开销可能高到不可接受,此时一个能显式记录并重新提供复制信息的后端就更有优势。
该项工作的完整设计思路记录在 docs/design/copy-tracking.md 中,这也是路线图所链接的设计文档。
设计要点:把复制历史做成对象
设计文档给出的核心思路是:Jujutsu 采用与 Git 类似的基于快照(snapshot)的模型,因此复制信息也必须融入快照模型中。具体方案是更新 tree 对象,使其携带文件"过去的名字"。例如,foo在某次提交中被重命名为bar,之后又被重命名为baz,那么baz的记录中就会包含它曾叫过bar和foo。
为了支持"多文件合并为一个文件"的场景(例如合并提交中,两侧把不同源文件复制/重命名到同一个目标文件),文件过去名字的列表实际是一个DAG(有向无环图)。为了避免在树条目中存储全部历史路径,设计将复制历史单独写成对象,树条目通过 ID 引用它——这与 commit ID 引用提交 DAG 节点的方式类似。
设计文档给出了数据结构草案(对应lib中的实现,见下文):
// Current `TreeValue::File` variant: File { id: FileId, executable: bool }, // New `TreeValue::File` variant: File { id: FileId, executable: bool, copy_id: CopyId }, // A CopyId is a hash of this struct: struct CopyHistory { path: RepoPath, parents: Vec<CopyId> }设计文档还讨论了两个值得注意的取舍:
- 是否给复制历史节点加盐(salt):如果只用文件名作为 ID 输入,会得到确定性的树 ID;如果加入盐,则可以表达"文件被彻底重写"(即使没有发生复制,也得到新的 copy ID)。设计者认为这在逻辑上说得通,但当时不确定实际价值有多大;
- 符号链接与目录的追踪:符号链接的历史对 blame 意义不大,但可用于检测目录重命名(当目录中所有文件和符号链接都被重命名时);目录本身的复制追踪则因复杂度被暂缓。
仓库中的实现证据
从源码看,这一目标在当前仓库中已有相当实质性的落地:
- lib/src/backend.rs 定义了
CopyId类型,并有CopyId::placeholder()占位实现; - lib/src/backend.rs 定义了
CopyRecord、CopyHistory(含current_path、parents、salt字段)与RelatedCopy; - lib/src/backend.rs 中
TreeValue::File变体已实际包含copy_id字段,同时TreeValue还新增了GitSubmodule(CommitId)变体; - lib/src/copies.rs 实现了
CopiesTreeDiffStream,为 diff 流添加复制/重命名信息,并区分CopyOperation::Copy与CopyOperation::Rename(判断依据是源路径在目标树中是否仍存在); - lib/src/copies.rs 实现了
CopyGraph(IndexMap<CopyId, CopyHistory>)、祖先/后代收集与is_ancestor判定; - lib/src/copies.rs 实现了
CopyHistoryDiffStream,即"考虑复制历史"的 diff 流,并明确说明优先使用MergedTree::diff_stream_with_copy_history()(见 lib/src/merged_tree.rs); - backend trait 中新增了
get_related_copies()与get_copy_records()两个查询方法(lib/src/backend.rs),这正是设计文档中"让云后端用数据库索引加速复制查询"的抽象接口。
有意思的是,各后端的支持程度并不一致:
- Simple 后端(lib/src/simple_backend.rs)与Secret 后端(lib/src/secret_backend.rs)的
get_related_copies目前都是空实现; - Git 后端(lib/src/git_backend.rs)的
write_copy与get_related_copies明确返回BackendError::Unsupported,错误信息为"The Git backend doesn't support tracked copies yet"——这与路线图"Git 不记录复制、只做即时检测"的描述完全吻合:在 Git 后端上,复制/重命名仍走检测路线。
此外,CLI 侧还有一个调试命令jj debug copy-detection,用于打印某个修订相对其父提交检测到的文件复制信息(cli/src/commands/debug/copy_detection.rs),可以看作是复制检测能力的验证工具。对应的测试在 cli/tests/test_copy_detection.rs 中。
设计文档中的复杂场景示例
设计文档用大量命令历史示例验证了数据模型的表达能力,这里选取两个最能说明问题的场景:
场景一:发散复制与重命名(divergent copy and rename)
M rename foo->baz, create bar || || L copy foo->bar, create baz ||/ K add foo从L到M的 diff 中,foo、bar、baz的 copy 图互相关联。算法最终呈现四条记录:baz被删除、bar被创建、foo被重命名为baz、bar被合并进baz——这需要最短路径匹配与"已被占用目标不重复使用"的约束。
场景二:收敛重命名(convergent renames)
$ jj log C rename bar->baz || || B rename foo->baz ||/ A add foo, add bar $ jj new B C合并提交中baz的 copy 图应同时继承自foo与bar,形成 copy 图中的 merge,其树结构可表示为5:baz->{3:baz->1:foo, 4:baz->2:bar}。这正是"复制历史是 DAG 而非链表"的直接原因。
设计文档还讨论了大量开放决策:是否把变更跨复制传播(最终结论是"由jj resolve询问用户是否传播",而非像 Mercurial 那样自动传播)、Git 表示方式(是否在 Git 对象之外额外存储重命名、克隆后是否后台做复制索引)等。作为参照,文档引用了实测数据:git log --summary --find-copies-harder在 git.git 仓库约需 165 秒,在 Nixpkgs 仓库约需 13 小时——这正是超大仓库中即时检测不可行的论据。
2. Forge 集成
目标与动机
路线图希望让用户更方便地与各种流行的代码托管平台(Forge,如 GitHub、GitLab)协作,方式是提供类似jj github submit与jj gitlab submit的命令。对于流行的 Forge,这些支持可能默认编译进标准jj二进制。
仓库中的现状:Gerrit 集成的参照
当前仓库中已经存在一条完整的 Forge 集成实现——Gerrit。jj gerrit upload命令(cli/src/commands/gerrit/upload.rs)已经具备相当完整的上传流程:
- 将一组修订上传到 Gerrit 进行代码评审,每个修订(及所有可变祖先)会创建一个独立的 "change";
- 若某修订已存在对应 change(含相同
Change-Id),则更新已有 change 的内容; - 没有
Change-Idtrailer 的提交会基于 Jujutsu change ID 生成一个,但该 trailer 只添加到被上传的提交上,因此上传后的 commit ID 可能与本地不同,可能引发 divergence;文档建议通过jj rebase --skip-emptied ...解决; - 也可以在配置中通过
[templates] commit_trailers使用format_gerrit_change_id_trailer(self)在本地自动添加Change-Id(完整配置示例见 cli/src/commands/gerrit/upload.rs)。
从jj gerrit upload的成熟度可以推断:GitHub/GitLab 的submit类命令在架构上并非无从下手,它们大概率会复用同样的"上传修订集 → 管理远端 change"模式,只是尚未列入实现计划。与之配套的测试位于 cli/tests/test_gerrit_upload.rs,文档可参考 docs/gerrit.md。
3. 子模块(Submodule)支持
目标与动机
路线图指出,Git 子模块在大型 Git 仓库中使用得足够频繁,因此 Jujutsu 大概率需要支持它们,但UX(用户体验)方面仍存在大问题。
这一目标的完整设计文档是 docs/design/git-submodules.md(以及存储方案的讨论 docs/design/git-submodule-storage.md)。该文档明确这是一份"理想化的、描述 jj 未来将如何支持子模块"的文档,且仍在持续完善中。
设计文档要点
设计文档首先回顾了 Git 子模块的机制:子模块是嵌入超级项目(superproject)的完整仓库,拥有自己的 index、对象库与引用库;超级项目提交中通过两个地方记录子模块信息:
- 树中的
gitlink条目,其值是被引用的子模块提交 ID; - 顶层
.gitmodules文件(Git config 语法),核心字段为submodule.<name>.path与submodule.<name>.url。
工作区中,Git 通过.git条目识别子模块,其现代推荐形式是 "absorbed form"(.git文件指向<superproject-git-directory>/modules/<submodule-name>)。
设计文档给出了分阶段路线(作为指南而非严格顺序):
- Phase 1:只读子模块——支持全新克隆子模块、抓取新的子模块提交、查看子模块历史与分支、在工作区填充子模块内容、将超级项目 gitlink 更新/解决到已有子模块提交;
- Phase 2:快照新变更——允许把工作区新内容记录为子模块提交、修改子模块分支、推送到子模块远端;
- Phase 3:合并/变基/冲突——以内容感知方式(区别于 Git 只比较 gitlink 提交 ID)合并/变基超级项目提交,并让冲突解决变得合理;
- Phase ?:理想世界——重写子模块提交时正确更新后代与超级项目 gitlink、子模块冲突自动解析到"正确"提交、嵌套子模块同样易用、操作日志记录子模块变更等。
值得注意的非目标:不支持非 Git 的"子模块"(如原生 jj 子模块或其他 VCS)、不支持非 Git 后端(如 Google 内部后端)、也不改变 Git 本身对子模块的实现。
仓库中的现状
从源码看,子模块支持处于早期但方向明确的阶段:
- lib/src/backend.rs 中
TreeValue已包含GitSubmodule(CommitId)变体,说明数据模型层面已经预留了子模块的位置; - lib/src/default_submodule_store.rs 与 lib/src/submodule_store.rs 中已有
SubmoduleStoretrait 的雏形(当前仅有name()方法); - 配置层面存在
git.submodule-...相关选项,测试见 lib/tests/test_git.rs。
这与设计文档"Phase 1 起步"的状态相符:存储与只读管线正在搭建,而快照、合并等更高阶段仍是开放的 UX 设计问题。
4. 更好的 Rust API(面向 UI 工具)
目标与动机
路线图指出,像gg这样的 UI 工具目前不得不从jj-cli复制相当多的逻辑。Jujutsu 需要把这些代码从 CLI 中解耦出来(例如返回状态对象而非打印消息),并移入jj-lib。
这是一个架构层面的重构目标:jj-lib应当成为可复用的库,而 CLI 只是它的一个消费者。这与第 5 节的 RPC API 目标互为表里——前者解决"Rust 内嵌复用",后者解决"跨语言/跨构建复用"。
仓库中的现状
从仓库结构看,jj-lib已经是一个非常完整的库,包含事务、操作、修订集、树合并、工作副本等大量核心逻辑(见 lib/src/lib.rs 及其下属模块)。不过,面向 UI 的"状态对象化"改造仍在进行中:CLI 层的命令输出仍大量直接使用Ui的 stdout/stderr(例如前文 cli/src/commands/gerrit/upload.rs 与 cli/src/commands/debug/copy_detection.rs 中的writeln!(ui.stdout(), ...)模式)。由此可以推断,将"命令结果"抽象为可编程状态对象、并下沉到jj-lib,是一项尚未完成的重构工作。
5. RPC API
目标与动机
路线图阐述了两个使用 Rust API 的问题:
- 后端耦合:用 Rust API 编写的工具只能与它们编译时链接的后端一起工作。例如,常规
gg构建无法处理 Google 仓库,因为它没有加载这些仓库所需的后端; - 跨语言壁垒:VS Code 这类非 Rust 编写的工具很难直接使用 Rust API。
因此 Jujutsu 希望提供一种 RPC API:工具通过运行类似jj api的命令获得一个地址来通信。RPC API 的抽象层级大概率高于 Rust API。
路线图还暗示,RPC API 的守护进程可能与第 6 节的云端仓库守护进程是同一个进程——这两项规划在架构上是耦合的。
仓库中的现状
从源码搜索来看,当前仓库中尚不存在jj api命令:在 cli/src/commands 目录下没有对应的 api 模块。这属于路线图中典型的"已立项、未动工"目标。不过其依赖基础(操作日志、仓库加载、对象存储的抽象层)在jj-lib中均已具备,因此一旦启动,主要是协议设计与 CLI 暴露层的工作。
6. 开源云端仓库(服务端与守护进程)
目标与动机
这一节首次透露了 Google 内部的 Jujutsu 架构:一个由数据库支撑的内部 Jujutsu 服务器,把提交和仓库(操作日志)存储在云端(即数据库中),而工作副本仍保存在本地。
为了降低延迟,Google 内部还有一个本地守护进程负责:
- 缓存读写;
- **预取(prefetch)**客户端接下来可能请求的对象;
- 通过乐观地应答写请求来降低写入延迟(因此守护进程必须知道服务器的哈希方案,才能返回正确的 ID)。
路线图表示:项目(而非 Google 本身)希望为所有用户提供类似的体验,因此计划创建类似的服务器与守护进程;守护进程可能与 RPC API 是同一个进程。
仓库中的现状与推论
当前开源仓库中没有云端服务器的实现,但存在若干与之配套的设计线索:
copy-tracking设计文档中专门有一节 "Representation in cloud repo (e.g. Google)"(见 docs/design/copy-tracking.md),讨论在同步到更新主线分支时,如何利用 copy ID 判断文件是否被重命名,以及为何需要后端提供"按 copy ID 拉取完整复制图"的方法;- 该节还提醒了一个安全细节:服务器可能只想为公共/不可变提交构建索引,否则用户可以通过大量创建复制来"投毒"索引,使未来查询变慢;
- backend trait 中的
get_related_copies/get_copy_records抽象(lib/src/backend.rs)正是为"用数据库索引实现这些查询的云后端"预留的接口。
这些证据支持一个判断:云端仓库方向的后端接口抽象已经就位,但开源实现尚未开始。
7. 虚拟文件系统(VFS)
目标与动机
对于非常大的项目和/或大文件,更新工作副本可能非常昂贵。VFS 的构想是:
- 更新工作副本到另一个提交时,只需告诉 VFS 以该提交为基准,目标提交中的大文件直到用户通过文件系统真正请求时才下载;
- VFS 通过跟踪相对基准提交的所有变更,让快照工作副本变得廉价。
路线图还特别指出,VFS 对jj run也非常有益——因为可以廉价地为命令创建临时工作副本。
仓库中的现状:jj run已落地,VFS 尚未
jj run命令本身已经完整实现(详见 cli/src/commands/run.rs),其设计文档 docs/design/run.md 在"Context and Scope"一节中明确写道:"目前没有任何开源 Jujutsu 后端(Git、Simple)拥有支持它的花哨虚拟文件系统,所以我们无法应用这项优化。我们只能将命令运行在常规本地磁盘工作副本中……一旦我们有了基于虚拟文件系统的工作副本实现,我们就能做到同样的事。"
从jj run的实现细节可以清晰看到"临时工作副本"机制的现实形态:
- 每个并行任务使用
.jj/run/default/N/下的一个固定工作副本槽位(WorkspacePool,见 cli/src/commands/run.rs),槽位通过.jj/run/default/N.lock锁文件做进程间互斥; - 工作副本在多次
jj run调用之间持久复用,以便复用构建产物(如 cargo 的target/目录),--clean参数可强制清空; - 命令运行后会快照工作副本,将 diff 应用回原提交,默认对后代执行 rebase 传播,
--restore-descendants与--ignore-changes可改变这一行为; - 子进程会获得
JJ_WORKSPACE_ROOT、JJ_CHANGE_ID、JJ_COMMIT_ID三个环境变量(cli/src/commands/run.rs)。
也就是说,jj run已经在本地磁盘工作副本上实现了路线图中"为命令廉价创建临时工作副本"的雏形,而 VFS 则是让这一能力更廉价、让大仓库工作副本更新更快的未来优化。相关测试见 cli/tests/test_run_command.rs。
8. 更好的大文件支持
目标与动机
最后一项规划相对开放:项目讨论过使用**内容定义分块(CDC,content-defined chunking)**来降低大文件的存储与传输成本,未来云端服务器上可能采用与 XetHub 存储类似的模型来存储文件。
仓库中的现状
从源码看,当前仓库对"大文件"的应对仍以工作副本快照阶段的保护性限制为主。例如jj run的快照选项中硬编码了max_new_file_size: 64_000_u64(即 64 MB,见 cli/src/commands/run.rs),用于限制新文件的追踪大小;配置侧存在snapshot.max-new-file-size选项(测试样例见 cli/tests/sample-configs/valid/snapshot.max-new-file-size_numeric.toml)。而 CDC 分块存储、与 XetHub 类似的去重模型,仍停留在"讨论过"的阶段。
总结:从路线图看 Jujutsu 的能力边界
综合 8 项目标与仓库源码证据,可以对 Jujutsu 的发展状态做如下收敛判断:
| 路线图目标 | 当前状态(基于仓库证据) |
|---|---|
| 复制/重命名追踪 | 已实质落地:CopyId/CopyHistory/CopyRecord进入数据模型,TreeValue::File携带copy_id,CopiesTreeDiffStream与CopyHistoryDiffStream已实现;Git 后端明确暂不支持追踪(返回Unsupported),Simple/Secret 后端为空实现 |
| Forge 集成 | 部分落地:Gerrit 集成(jj gerrit upload)完整可用;GitHub/GitLab 的submit命令尚无实现 |
| 子模块支持 | 早期阶段:GitSubmodule(CommitId)已进入TreeValue,SubmoduleStoretrait 仅有雏形;分阶段设计文档在案 |
| 更好的 Rust API | 进行中:jj-lib架构完备,但命令输出仍以 CLI 打印为主,状态对象化重构未完成 |
| RPC API | 未动工:无jj api命令,属已立项规划 |
| 开源云端仓库 | 接口就绪、实现未开始:backend trait 已为云后端预留查询抽象 |
| VFS | 未动工:jj run已用本地磁盘临时工作副本实现等价场景的雏形 |
| 大文件支持 | 早期讨论:仅有快照阶段的文件大小保护限制,CDC/去重存储尚未实现 |
这份路线图的价值在于它清晰地划定了 Jujutsu 的"下一步"。对于使用者和潜在贡献者,最值得关注的是:复制/重命名追踪已具备可验证的模型与实现雏形(docs/design/copy-tracking.md 与 lib/src/copies.rs 是最好的阅读起点);而子模块、RPC API 与云端仓库则仍在等待设计与实现者。由于路线图明确不承诺时间表,任何功能的实际可用状态都应以当前仓库源码为准——本文给出的源码路径即为读者自查的索引。
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考