Jujutsu 设计文档蓝图(Design Doc Blueprint):为 jj 新特性撰写技术提案的完整指南
2026/9/11 13:00:35 网站建设 项目流程

Jujutsu 设计文档蓝图(Design Doc Blueprint):为 jj 新特性撰写技术提案的完整指南

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

Jujutsu(jj)在推进大型技术决策时依赖一套正式的设计文档(Design Doc)流程:任何涉及新组件或重大改动的特性,都必须先通过评审才能合入代码。本文以仓库中的 docs/design_doc_blueprint.md 模板为骨架,结合 docs/design_docs.md 的流程说明与 docs/design/ 目录下 9 篇真实设计文档(如jj runjj converge、Sparse Patterns v2、Copy Tracking 等),系统讲解如何为 Jujutsu 撰写一份结构完整、论据扎实、可评审通过的设计提案。读完本文,你将掌握 jj 设计文档的全部章节含义、每个章节应填入什么内容、以及如何引用仓库源码与既有设计作为证据支撑。

Jujutsu 设计文档机制概述

设计文档是 Jujutsu 社区驱动技术决策的核心工具。根据 docs/design_docs.md,该机制与 Rust RFC 流程有相似之处,但主要面向技术问题本身,并兼顾所有利益相关方的技术与社区关切:

  • 在大型项目或新组件上,设计文档用于驱动技术决策,是讨论提案的地方;
  • 流程非常严格:设计文档必须先获得批准,相关功能的 PR 才会被接受
  • 如果你想为 Jujutsu 构建原生后端(native backend)或服务端组件,就必须走完这个流程。

标准流程四步走

  1. docs/design/目录下新建一个 Markdown 文档,以你要改进的功能或项目命名(例如docs/design/run.mddocs/design/jj-converge-command.mddocs/design/sparse-v2.md);
  2. 描述当前世界的状态以及你想要改进的内容;
  3. 等待维护者(Maintainers)与利益相关方(Stakeholders)出现并参与评审;
  4. 以常规代码评审的方式反复迭代,直到所有人都接受这个变更。

设计文档的最终产出是仓库根目录下的 docs/design_doc_blueprint.md,即本文接下来要逐节拆解的"蓝图"模板。

蓝图模板逐节拆解

以下每个小节都对应蓝图中的一个章节,并附上真实设计文档中的对应实例,帮助你理解"该写什么、怎么写"。

标题与作者信息(Title / Author)

每篇设计文档需要一个有辨识度的标题,并在标题下方注明作者及可联系邮箱。蓝图要求:

# Title > A cool name for your Project Author: [Your-Name](mailto:your-name@reachable.com)

实际文档中的做法可以参看 docs/design/run.md 的开头:

# Introducing JJ run Authors: [Philip Metzger](mailto:philipmetzger@bluewin.ch), [Martin von Zweigberk](mailto:martinvonz@google.com), ...

多个作者时依次列出即可。jj-converge-command.md还额外在标题下方提供了Summary摘要段,用 2~3 句话交代"这份文档提出什么命令、解决什么问题",方便评审者快速判断主题相关性。部分文档还会标注状态(如 docs/design/managed-config.md 的Status: Pending implementation)或初版日期(如run.md标注Initial Version, 10.12.2022)。

Summary(摘要)

蓝图要求用 3~10 句话概括你的项目 / 重新设计 / 组件,以及它解决的问题。摘要应当做到:

  • 一句话点明提案是什么(例如jj run是"在多个 revision 上运行用户提供的命令或脚本,以无缝集成构建系统、linter 和 formatter");
  • 说明它解决的核心痛点;
  • 给出后续详情的锚点链接。

run.md的摘要是一个很好的范本:它先声明"本文档设计 jj 的新run命令",紧接着列出典型用途(build systems、linters、formatters),并链接到文内的 Use-Cases 小节。摘要不是执行摘要的缩写,而是让评审者 30 秒内判断"这份提案是否与我相关"的门面

State of the Feature as of$VERSION(当前状态,可选)

如果该功能已有现状,用这一节说明"截至某个版本,现状是什么、短板在哪里"。如果没有可对照的现状,则整节省略。

例如 docs/design/sparse-v2.md 的 "Current State (as of jj 0.13.0)" 明确写道:稀疏模式(Sparse Patterns)本质上是无顺序的字符串前缀列表

path/one path/to/dir/two

文件集合由"匹配任意前缀"决定,状态存放在未纳入 Op Store 版本管理的工作副本状态文件中。由于所有路径都是裸字符串、没有转义或更高层格式,现行设计很难新增"排除规则"或"路径重映射"等特性——这正是 Sparse Patterns v2 要解决的问题。

另一个范例是 docs/design/tracking-branches.md 的 "Current data model (as of jj 0.8.0)":它用数据模型图列出branchestagsgit_refsgit_head的现有结构,并点出现有模型的两个缺陷(jj branch forget与 colocated 工作区的jj op revert会导致远端分支与 git refs 失步;@git伪跟踪分支需要特判)。

写作要点:写明版本号(如as of jj 0.13.0),用可验证的结构化描述(数据结构、命令行为)代替模糊吐槽,为后面的 Goals 提供"要改什么"的依据。

Prior work(既有工作,可选)

如果该特性在其他地方已经存在,用这一节记录它的做法与做出的取舍;如果没有先例,则改用文末的 Related Work 节。

run.md的 Preface 是典型示例,它列举了五种先例并逐一给出简短结论:

  • git test(git-branchless 的一部分):与jj run提案最接近;
  • hg run(Google 内部 Mercurial 扩展):与jj run类似,但依赖 CitC 虚拟文件系统做惰性应用,而当前 jj 的开源后端(Git、Simple)都没有支持它的虚拟文件系统,因此暂时只能在普通本地磁盘工作副本中运行命令——这是一个非常关键的取舍陈述;
  • hg fix(Google 开源 Mercurial 扩展):更专注于在缺乏完整工作目录上下文时重写文件内容;
  • git rebase -x:在 rebase 过程中机会式地运行命令;
  • git bisect run:运行命令定位引入 bug 的提交。

写作要点:先例的价值在于证明"你研究过别人怎么做的",并显式记录"为什么不能直接照搬",这往往决定了方案的技术路线(如run.md因缺少虚拟文件系统而放弃hg run的优化路径)。

Goals and non-goals(目标与非目标)

这是设计文档最重要的章节之一。蓝图要求:直接列出项目目标,以及明确"不值得做"的特性run.md提供了一个教科书级的示例:

Goals(目标)

  • 命令可应用到任意 revision(已发布或未发布);
  • 可并行运行命令,同时保持良好的控制台输出;
  • 命令可在任意提交(包括工作副本提交)中工作;
  • 存在某种方式发出硬失败信号;
  • jj testjj fixjj format建立足够的基础设施;
  • 主要目标是"足够好",因为未来随时可以扩展功能。

Non-Goals(非目标)

  • 不应把jj test/jj format/jj fix的用例塞进jj run(只建基础,不包办);
  • 命令不应过于聪明,过多的 workflow 假设会让用户困惑;
  • 避免对输出做智能缓存(用户输入命令不可预测);
  • 不做细粒度的面向用户的配置(属于无谓的复杂度);
  • 不提供fix子命令(会过度切割设计空间)。

写作要点:Non-goals 的价值不亚于 Goals。它防止评审时出现"为什么不顺便支持 X"的无休止蔓延,也是后续版本迭代的边界声明。注意run.md的非目标里把"智能缓存"和"过于聪明"各写了两遍——这恰好说明非目标之间允许语义重叠,评审讨论中反复出现的顾虑值得被显式记录。

Overview(概览)与 Detailed Design(详细设计)

Overview 是对项目及其带来的改进的详细综述,其中必须包含Detailed Design 小节:蓝图明确要求"在这里描述所有新接口与交互,以及它如何融入现有代码和行为,这是所有与系统交互的细节的安放之处"。

这通常是设计文档篇幅最大的部分,不同文档的组织方式差异很大,以下是三种有代表性的写法:

1. 数据模型驱动(docs/design/sparse-v2.md):先给出完整 Rust 结构体定义,再讲 CLI 语法。Sparse Patterns v2 用WorkingCopyPatterns对象取代裸字符串列表,并给出SparsePatternsPathTypeDir/Files/Exact)与include布尔字段:

pub enum SparsePatternsPathType { Dir, // Everything under <path>/... Files, // Files under <path>/* Exact, // <path> exactly } pub struct SparsePatternsPath { path_type: SparsePatternsPathType, include: bool, // True if included, false if excluded. path: RepoPathBuf, }

同时给出 CLI 的紧凑语法与等价命令:

(include|exclude):(dir|files|exact):<path>
  • jj sparse set --add foo/bar等价于jj sparse set --add include:dir:foo/bar
  • jj sparse set --add exclude:dir:foo/bar新增一条Dir类型、include = false的规则
  • 文件是否被包含,由逆序第一条匹配规则决定

2. 算法与示例驱动(docs/design/jj-converge-command.md):jj converge文档用大量 ASCII 提交图 + 推导公式讲解分歧合并算法。它提出一个MergedState数据结构,每个字段按P + (B/0 - P) + (B/1 - P)的方式合并:

struct MergedState { author: Merge<Signature>, description: Merge<String>, parents: Merge<Vec<CommitId>>, tree: Merge<MergedTree>, }

并推导出"解决方案父提交"的求解公式(示例 3):parents = P⁻ + (B/0⁻ - P⁻) + (B/1⁻ - P⁻),当结果平凡可解(trivially resolves)时直接采用,否则提示用户选择。它还提出新的try_resolve_deduplicating_same_diffs方法来解决"截断演化图"场景——这正是 Sparse Patterns 文档之外的另一种 Detailed Design 写法:先讲清楚算法与期望行为,再讲数据结构

3. 流程与状态机驱动(docs/design/tracking-branches.md):用 ASCII 流程图描述 import/export 数据流,用 Rust 伪代码描述状态决策:

fn default_state_for_newly_imported_branch(config, remote) { if remote == "git" { State::Tracked } else if matches_auto_track_bookmarks { State::Tracked } else { State::New } }

写作要点:无论采用哪种组织方式,Detailed Design 都必须回答"接口长什么样、边界行为是什么、与现有代码如何交互"。真实文档通常会包含命令行为示例表(如 tracking-branches.md 的 fetch/import、push、export、undo fetch 各种分支场景逐一列出预期行为),因为评审者对边界情况的关注远多于对主路径的关注

Alternatives considered(备选方案,可选)

记录其他备选方案及它们为何不可行。这一节的存在能大幅减少评审中的重复争论。

优秀的备选方案分析需要逐条给出否决理由。docs/design/copy-tracking.md 是范例:它分析了三种备选模型并各给出硬伤:

  • 像 Git 一样即时检测拷贝:Git 不记录拷贝信息,而是在比较两棵树时推断。它很难扩展到超大仓库——例如把本地提交 rebase 到领先 100 万提交的上游时,想找出本地文件中哪些在上游被拷贝过,靠比较新旧 base 树代价极高;
  • 在树中记录逻辑文件标识符(BitKeeper 模型):难以扩展支持拷贝(只支持重命名);在 Git 后端也难以合成文件 ID;
  • 把拷贝信息放进 FileId(Mercurial 模型):Mercurial 把拷贝信息存在文件内容的元数据段,只记录最近一次拷贝,文件被修改后 ID 会变,需要沿文件历史回溯——与快照模型融合得不如提案优雅。

jj-converge-command.md的备选方案更简短但同样有效:"自动解决分歧"(应在引入第二个可见提交时就避免,需单独调研)、"两两解决分歧"(提案的算法本就可处理任意数量分歧提交)、"只考虑演化分叉点与可见提交"(示例 6 已证明会导致次优启发式结果)。

写作要点:每个备选方案至少要写"它是什么 + 一个具体的、可论证的失败原因"。切忌只列名字不给理由。

Issues addressed(解决的 Issue,可选)

列出该设计所解决的问题清单。许多设计文档将 issue 编号直接嵌入正文:例如run.md提到 [pre-commit 相关的 GitHub discussion] 与 [git-hook 模型的 Discord 讨论](对应#405号 issue)、jj op log的整合等待#963号 issue;tracking-branches.md的 Objective 直接引用#1136(多 Git remote 场景下本地分支交互不佳);sparse-v2.md引用#1896(更灵活匹配规则)与#2288(客户端路径重映射)。

写作要点:用 issue 链接把设计文档与社区讨论历史绑定,评审者可以回溯问题提出的原始语境。若设计同时解决多个 issue,可列成清单。

Related Work(相关工作,可选)

如果其他 VCS 中存在与你的提案有相似之处的特性,放在这里。蓝图给了一个极佳的例子:"Jujutsu 稀疏工作区与 Perforce 客户端工作区(client workspaces)"

sparse-v2.md的附录扩展了这一思路:Perforce client maps 与整个WorkingCopyPatterns概念非常相似(设计目标就是达到类似功能),Josh Project 则用与稀疏模式相似的方式实现部分 Git 克隆。copy-tracking.md则在正文中详细对比了 Git / Mercurial / BitKeeper 三种模型(见前节),并把 Mercurial 的hg run、git-branchless 的git test等列为run.md的相关工作。

写作要点:Related Work 与 Prior work 的区别在于——前者强调"功能相似的其他系统实现",后者强调"同一项目内或直接前身的工作"。两者都可能需要,也可以相互引用。

Future Possibilities(未来可能性)

记录讨论期间"可以加入但暂定超出范围"的事情。这是防止有价值想法在评审中被丢弃的安全网。run.md的 Future possibilities 包括:

  • 在内存中重写文件(一个巧妙的优化);
  • 暴露内部状态以实现更精确的资源约束;
  • 虚拟文件系统的集成选项(用于缓存所需工作副本);
  • Jujutsu 全局的缓存工作副本概念(物化代价高);
  • 定制化失败消息(对机器人有用,可类比 Bazel 的select(..., message = ...));
  • jj run异步化:派生main进程、立即返回用户、增量更新jj st的输出。

git-submodules.md则用 "Phase ?: An ideal world" 记录了理想世界的成果(如重写子模块提交时正确重写后代并更新超级项目的 gitlink、操作日志捕获子模块变更等)。

写作要点:每一项未来可能性都应是一句话可说明的独立想法,方便后续有人单独立项时直接引用。

从蓝图到落地:如何用真实设计文档对照自检

在提交设计文档前,可以用仓库中已落地的设计文档做"对拍检查"。以下是三个高价值对照点:

1. 命令设计类提案对照jj run

run.md的 "Command Options" 一节给出了完整的命令选项设计(可参看jj run最终实现于 cli/src/commands/run.rs):

  • --command:第一个参数(命令名)的显式写法;
  • -x:为 Git 兼容保留(可别名到其他命令);
  • -j, --jobs:并行度;
  • -k, --keep-going:失败后继续(可别名到其他命令);
  • --show:展示受影响 revision 的 diff;
  • --dry-run:只记录所有预期的文件与参数,不实际执行;
  • --rebase/--reparent:将受影响 revision 的父提交改为新变更;
  • --clean:移除既有工作区并清除被忽略文件;
  • --readonly:忽略多次 run 调用之间的变更;
  • --error-strategy=continue|stop|fatal:对应 "Dealing with failure" 一节的三种失败策略。

注意该文档明确写出"默认情况下jj run作用于@(当前工作副本)",并逐一说明了与jj logjj diffjj stjj op logjj undo等其他命令的整合方式——命令设计文档必须交代与其他命令的交互面

2. 数据模型类提案对照 Sparse Patterns v2

sparse-v2.md展示了"旧格式兼容 + 新格式升级"的完整迁移路径:View 对象中wc_commit_ids: HashMap<WorkspaceNameBuf, CommitId>演变为带wc_patterns_idWorkingCopyInfo结构,且老 View 在读取时会自动补上当前工作副本模式(迁移期至少 6 个月)。它还给出了**规则规范化(Canonicalization)**的严格定义——4 组功能等价的规则集应被统一重写为最小规范形式,要求"每条规则都影响功能(无冗余规则)"且"按字典序排序,但/排在所有字符之前(便于构建路径前缀树)"。

3. 安全类提案对照 Secure Config

docs/design/secure-config.md 提供了另一种详实度标准:它先建立威胁模型(从"无知识攻击者"到"极高级重放攻击"共 4 个攻击向量,逐一说明防御所需条件),再给出详细设计。其中"zip 文件问题"(zip 文件问题:攻击者打包仓库发送给受害者,受害者解压后运行jj fix即执行[fix.tools.foo] command = ["malicious", "command"])已成为 jj 配置安全讨论中的标志性概念,并直接催生了 docs/design/managed-config.md 的仓库托管配置(repo-managed configuration)设计——后者用TrustLevel枚举(UNSET/IGNORED/TRUSTED/NOTIFY/REVIEW)把"是否信任仓库提供的配置"的选择权交给用户。

这些文档之间的引用关系本身就是设计流程的体现:一份新设计文档可以引用并扩展旧设计(managed-config.md引用secure-config.mdmetadata.binpb机制),评审者因此能沿着文档链理解设计演进。

设计文档中的源码级细节:以 Merge 算法为例

设计文档的 Detailed Design 经常直接引用核心数据结构的源码语义,评审者据此判断提案与现有抽象是否兼容。jj-converge-command.md中的Merge<T>SameChange::Acceptresolve_trivial都是对 jj 核心冲突代数(conflict algebra)的引用,其真实实现位于 core/src/merge.rs:

  • 第 109~122 行定义了SameChange枚举:Keep(保留同变更冲突不解决)与Accept(将同变更冲突视为一侧未变更,即A+(A-B)=A,与 Git、Mercurial 的三方合并行为一致,而与 Darcs 不同;副作用是多次三方合并的结果可能依赖合并顺序);
  • 第 124 行起的trivial_merge函数实现了"平凡合并":要求输入项数为奇数,并针对最常见的 3 方合并([add0, remove, add1])做短路优化——当add0 == add1SameChange::Accept时直接返回add0

jj-converge设计文档正是建立在这些语义之上:try_resolve_deduplicating_same_diffs被描述为"与resolve_trivial相似,但把多个相同的(X - Y)项只计数一次",评审者可以对照core/src/merge.rs中的trivial_merge验证这一描述是否成立。在 Detailed Design 中引用这类核心抽象时,应说明它们与现有语义的关系(继承、扩展或修改),这是评审能否通过的关键

编写设计文档的实践建议

综合蓝图模板与仓库中的真实文档,可以总结出以下经过验证的写作纪律:

  1. 先写 Summary 与 Goals/Non-Goals,再写 Detailed Design。这两节是评审者最先读的部分,也是争论最集中的部分;
  2. 每个"应该"都要有场景支撑run.md的所有设计决策(临时工作副本、保留 ignored 文件以支持增量构建、失败策略三选一)都能追溯到 Use-Cases 或明确的问题陈述;
  3. 边界情况优先于主路径jj-converge-command.md花大量篇幅处理"候选父提交是分歧提交的后代""演化历史超过 50 个节点"等边缘情况,并明确"error out"兜底;
  4. 用可验证的格式呈现行为。命令选项表、ASCII 提交图、Rust 结构体、伪代码函数,这些格式在 jj 设计文档中被反复使用,因为它们在评审中可以逐行讨论;
  5. 显式记录决策与遗留问题run.md的 "Open Points"(命令是否应工作副本后端相关?如何管理进程?配置选项是用户级还是仓库级?)、jj-converge-command.md的 "Open questions"(是否会出现 committer 分歧?改动 committer 是否安全?)都坦率地列出了未决问题——设计文档不是"假装一切已定"的文件;
  6. 链接要指向仓库内的相对路径。蓝图模板与各设计文档之间的交叉引用(如git-submodules.md指向 docs/design/git-submodule-storage.md、design_docs.md指向 docs/design_doc_blueprint.md)都应使用可从仓库根目录解析的相对路径,确保评审者在 Web 界面上可以点击跳转;
  7. 如果特性涉及配置或命令,对照配置样例验证。仓库中 cli/src/config/ 目录下的 TOML 样例(如merge_tools.tomlrevsets.toml)与 cli/tests/sample-configs/ 中的配置测试,可以作为设计文档中配置语法表述的实证来源。

结语

Jujutsu 的设计文档蓝图是一份"最少骨架、最大自由"的模板:它不规定你必须写多少页,但规定了必须回答的问题——现状是什么、目标与非目标是什么、接口长什么样、为什么不是别的方案、遗留了什么。仓库中docs/design/下的 9 篇设计文档从命令设计、数据模型重构到安全模型,展示了这份蓝图在不同主题下的完整实践。当你准备为 jj 提交新特性时,按蓝图搭建结构、以真实设计文档为参照、用源码语义支撑细节,你的提案就具备了进入评审流程所需的全部要素。

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

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

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

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

立即咨询