ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Jujutsu 核心概念与术语全解析:从匿名分支、变更 ID 到工作副本提交的底层模型

Jujutsu 核心概念与术语全解析:从匿名分支、变更 ID 到工作副本提交的底层模型 Jujutsu 核心概念与术语全解析从匿名分支、变更 ID 到工作副本提交的底层模型【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj本篇文章以 Jujutsujj仓库中的官方 术语表同版本亦维护于 cli/docs/glossary.md为骨架系统梳理 Jujutsu 的数据模型与核心术语。无论你是从 Git 迁移而来的新手还是希望深入理解jj提交图、变更change与视图view机制的老用户读完本文后你都能准确区分 Commit ID 与 Change ID、匿名分支与 bookmark、可见提交与隐藏提交并能结合底层源码理解jj log、jj rebase、jj abandon等命令背后的设计逻辑。一、提交模型Commit、Change 与 RewriteCommit提交快照加元数据在 Jujutsu 中一次提交commit是仓库在某个时间点的文件快照技术上是一个 tree object外加一组元数据。元数据包括作者author、提交者committer、时间戳以及指向其父提交的指针。通过父指针提交之间构成一个有向无环图DAG。注意一个关键设计尽管提交以“快照”形式存储使用中却常被当作“与父快照之间的差异”来对待。若提交有多个父提交则差异以父提交合并的结果为基准计算。例如jj diff展示某提交相对其父提交引入的改动jj rebase把改动应用到另一个基提交之上。在 commit.rs 的源码中Commit结构体由id: CommitId与data: Arcbackend::Commit组成其中backend::Commit包含parents、root_tree、change_id、author、committer、description等字段印证了“快照 元数据”的模型。术语表明确说明“revision” 是 “commit” 的同义词。这也是为什么jj log -r all()等命令中到处出现 “revision” 一词。Change变更随时间演化的提交“变更”change可以理解为一个提交随时间演化的身份。同一个变更被多次改写后会产生多个提交但这些提交共享同一个 Change ID。在数据模型中变更本身并不是一个独立对象——只有 Change ID 存在它是提交的一个属性。Rewrite改写换新提交、保留变更“改写”rewrite指创建同一提交的新版本新版本的内容、元数据含父指针或两者都可能不同。改写会生成一个新提交因而产生新的 Commit ID但Change ID 通常保持不变。典型改写场景包括修改提交描述jj describe变基jj rebase合并/拆分提交jj squash、jj split修改工作副本也会改写工作副本提交working-copy commit。这一“Commit ID 变、Change ID 不变”的机制正是 Jujutsu 激进重写提交历史却不会丢失“这条思路”的关键也是 rewrite.rs 等模块的核心职责。Root commit根提交全零的虚拟提交每个仓库根部都有一个虚拟的根提交Commit ID 全为0形如00000000...Change ID 全为z形如zzzzzzzz...在 revset 中可用函数root()引用。源码层面git_backend.rs 将根提交的root_commit_id取为 Git 对象哈希的null_ref()全零root_change_id取为 16 个全零字节——由于 Change ID 采用 z-k “反序十六进制”编码全零字节恰好渲染为全部z。而root()函数在 revset.rs 中被注册为内置 revset 函数最终展开为RevsetExpression::root()。注意区分Jujutsu 的“根提交”与 Git 的 “root commits” 完全不同。Git 的 root commit 指仓库中第一个提交没有父提交的提交即jj log -r root()显示的那些提交。二、标识符体系Commit ID、Change ID 与 Change offsetJujutsu 采用双重 ID 体系这是它与 Git 最根本的差异之一。Commit ID提交 ID唯一标识一个具体的提交对象使用 Git 后端时长度为20 字节且就是 Git 的 commit ID与 Git 完全互通jj log默认在行末以 12 位常规十六进制数字展示。在 backend.rs 中CommitId通过id_type!宏定义并标注为hex()正向十六进制编码文档注释明确“当提交被改写时其 CommitId 会改变。”Change ID变更 ID唯一标识一个变更跨提交演化稳定通常为16 字节、多为随机生成jj log默认在行首以 k-z 范围的 12 个字母展示这实际上是使用“z-k”代替“0-9a-f”的十六进制数字反向十六进制reverse hex。在源码 hex_util.rs 中定义了const REVERSE_HEX_CHARS: [u8; 16] bzyxwvutsrqponmlk即反向编码字符表encode_reverse_hex与decode_reverse_hex分别完成编码与解码见 hex_util.rs其单元测试hex_util.rs验证了z→0x0f、k→0xf0等映射关系。ChangeId在 backend.rs 中被定义为reverse_hex()编码并配套提供try_from_reverse_hex/reverse_hex方法backend.rs。Change offset变更偏移有时 Change ID 无法唯一确定一个提交例如提交处于隐藏状态Change ID 出现分叉divergent change。此时可以在 Change ID 后追加偏移量来指明具体提交最新提交的偏移为 0。例如最新提交可写作xyz/0前一个为xyz/1以此类推。这也解释了jj log中带/后缀的修订标识的来源。Author date 与 Committer date作者日期与提交者日期说明web 版术语表未收录这两条但 cli/docs/glossary.md 版本中有完整条目一并整理如下。Author date作者日期记录提交内容“最初撰写”的时间。提交首次创建时设置通常在一次提交被改写后保持不变。新提交的 author date 与 committer date 通常相同但经过jj rebase、jj describe、jj squash等历史编辑命令后二者会分离。使用 Git 后端时对应 Git 原生的 author date 字段。Committer date提交者日期记录该提交对象“被创建”的时间。新提交时设置每当jj改写提交变基、描述、合并等都会更新。使用 Git 后端时对应 Git 原生的 committer date 字段。在源码中backend.rs 定义了Timestamp毫秒精度 Unix 时间戳 时区偏移与Signaturecommit.rs 提供author()与committer()访问器。这一双时间戳设计让 Jujutsu 在“历史被反复改写”的同时仍能追溯原始创作时间。三、内容模型Tree 与 ConflictTree树对象Tree 对象表示仓库中某个目录的快照。它是递归定义的每个 tree 对象只包含其直接子文件与子目录子目录再以子树对象表示。这与 Git 的 tree 对象理念一致也是 tree.rs 与 tree_builder.rs 的实现基础。Conflict冲突冲突可能发生在多个层面文件冲突最常见也是其他 VCS 用户熟悉的类型。可以在jj status和jj log中看到行末的红色 “conflict” 标签。详细机制见 conflicts.md。Bookmark 冲突例如你在本地移动了一个 bookmark同时远端也移动了它jj fetch/jj git pull之后该 bookmark 即进入冲突状态。详见 bookmarks.md 的 “conflicts” 一节。变更分叉divergent change当某个变更在本地与远端分别被改写时该变更会进入冲突状态即下面要讲的 divergent change。在实现层面commit.rs 通过has_conflict()检查提交的树 ID 是否为未解析的合并!self.tree_ids().is_resolved()从源码上印证了“冲突是提交树层面的合并状态”这一事实。四、可见性与历史View、Visible/Hidden commits、Operation 与 Operation logView视图bookmark、匿名 head 与工作副本提交的快照视图是某一时刻bookmark 及其目标、匿名 head、工作副本提交的快照。匿名 head 决定了哪些提交可见。术语表用一个精妙的类比总结view 对象类似于 tree 对象——都是无历史的快照operation 对象类似于 commit 对象——在快照之上叠加元数据与历史。对应实现可参考 view.rs 与 op_store.rs。Visible commits可见提交与 Hidden commits隐藏提交可见提交你在jj log -r all()中看到的提交即从视图中的某个匿名 head 可达的提交可见提交的祖先隐式可见。直观理解可见提交是某个变更的“最新版本”。被放弃abandoned或被改写的提交会停止可见被标记为 “hidden”隐藏。隐藏提交不能再通过 Change ID 访问但仍可通过 Commit ID 访问这正是 Commit ID 的第二个用途。源码中 commit.rs 的is_hidden()通过查询该变更 ID 的目标集合并判断当前提交 ID 是否可见注释写道“A commit is hidden if its commit id is not in the change id index.”与术语表定义完全对应。Head头没有后代的提交Head 是“没有后代提交”的提交但“在什么范围内没有后代”取决于语境revset 函数heads(X)返回集合 X 内部没有后代的提交见 revsets.md 的函数一节视图记录在某个操作时刻可见的匿名 head没有 bookmark 指向的 head。术语表特别提醒这与 Git 的HEAD当前检出指针完全不同。Operation操作与 Operation log操作日志Operation某一时刻可见提交与 bookmark 的快照技术上即 view 对象外加元数据。元数据包括用户名、主机名、时间戳与指向其父操作的指针。Operation log由 operation 对象构成的 DAG正如提交构成 DAG即通常所说的“提交历史”。顺序发生的操作在图中形成一条线在 jj 看来并发发生的操作则造成分叉与合并。这是 Jujutsu 实现jj undo、jj op log、自动并发协调concurrency的基础jj operation log命令即遍历此 DAG见 cli/src/commands/operation/log.rs相关并发语义在 technical/concurrency.md 中有深入阐述。五、引用体系Bookmark、Branch 与 Anonymous branchBookmark书签命名指针Bookmark 是指向某个提交的命名指针类似 Git 的 branch更接近 Mercurial 的 bookmark。与 Git 最大的不同没有“当前 bookmark”的概念创建新提交时 bookmark不会随之移动。但若被指向的提交被改写bookmark 会自动跟随到新的提交。命令行入口见 cli/src/commands/bookmark/create、set、move、track、untrack、rename、delete、forget、list等子命令完整语义见 bookmarks.md。Branch分支口语化的“树的分支”在 jj 语境中“branch”通常指匿名分支或更口语化地指提交“树”提交图的一部分即便数学上不是树上的一个分支。另外谈到 Git 的分支、Git 远端上的分支时这些本地对应物就是 bookmark——在 colocated 工作区中每个本地 Git 分支对应一个 jj bookmark。Anonymous branch匿名分支匿名分支是一串提交不一定有 bookmark 指向它或它的任何后代。与 Git 不同Jujutsu 会保留匿名分支上的提交直到它们被显式放弃abandon。可见的匿名分支由视图跟踪视图存储这类分支的 head 列表。这是 jj 工作流的精髓你可以像在“隐形分支”上一样自由提交不必为每一条思路创建命名分支。Tracked bookmarks 与 tracking bookmarks被跟踪书签与跟踪书签远端 bookmark 可以通过jj bookmark track命令变为 “tracked”从而产生一个跟踪远端 bookmark 的本地 “tracking” bookmark。两个术语的精确定义与总结见 bookmarks.md 的 “terminology summary” 一节实现见 cli/src/commands/bookmark/track.rs 与 cli/src/commands/bookmark/untrack.rs。六、仓库与工作区Repository、Workspace、Working copy 与 RemoteRepository仓库基本就是.jj/目录下的所有内容即全部操作与提交的集合。Workspace工作区一个工作区 一个工作副本 关联的仓库。一个仓库可以对应多个工作区。每个工作区都有自己的.jj/目录但提交与操作存储初始工作区中其他工作区持有指向初始工作区的指针。这就是 Git 所说的 “worktree”。详见 working-copy.md 的 “workspaces” 一节命令入口见 cli/src/commands/workspace/add、forget、list、rename等。Working copy工作副本与 Working-copy commit工作副本提交工作副本你正在编辑的文件集合。几乎每个jj命令开始时都会自动快照工作副本若确有改动就会产生一个新的工作副本提交。反过来几乎每个jj命令结束时都会把工作副本自动更新到工作副本提交的状态。这就是 Git 所说的 “working tree”。工作副本提交对应工作副本当前状态的提交。每个工作区有一个工作副本提交当前的工作副本提交在操作日志中跟踪。这种“命令执行前后自动快照/恢复”的机制是jj无感管理工作副本状态的基础完整机制见 working-copy.md底层实现在 lib/src/local_working_copy.rs。Remote远端远端是对仓库副本的引用。最常见的是托管在互联网或其他网络上但本地远端同样可行。多个协作者协作时远端很有用由于 Jujutsu 与 Git 兼容所有主流 Git 托管平台如 GitHub、GitLab、Codeberg 等均可直接使用。远端相关命令见 cli/src/commands/git/remote/add、remove、rename、set-url、list。Colocated workspaces共存工作区当使用 Git 后端、且底层 Git 仓库的.git/目录是.jj/的同级目录时该工作区被称为 colocated共存。这种布局下绝大多数为 Git 设计的工具都能直接使用jj与git命令可以互换混用。详细说明见 git-compatibility.md 的 “Colocated Jujutsu/Git repos” 一节。七、存储层Backend后端Backend 是存储层的实现。目前唯一生产可用的内置提交后端是 Git 后端它把提交存储在 Git 仓库中lib/src/git_backend.rs。此外还有若干用于测试的后端如 simple backend见 lib/src/simple_backend.rsGoogle 也维护有云端后端。除了存储提交还存在存储其他信息的可插拔后端例如用于存储操作日志的 “operation store backend”相关 trait 定义于 lib/src/op_store.rs。Backendtrait 本身定义在 lib/src/backend.rs是读写提交、树、文件等的最底层接口。八、选择语言Revset 与 Divergent changeRevset修订集表达式Jujutsu 支持一种函数式语言来选择一组修订revision这类表达式被称为 “revset”术语 “revset” 也常被用来指代某个 revset 所选出的那组修订。语法与函数大全见 revsets.md 与 revsets.toml解析与求值实现位于 lib/src/revset_parser.rs 与 lib/src/revset.rs。例如本文反复提到的root()、heads(X)、all()都是 revset 函数根提交的root()实现见 revset.rs。Divergent change分叉变更当某个变更拥有多个可见提交时即为分叉变更。这类变更会在日志中以其 Change ID 后带 change offset 的形式展示。典型成因同一变更在本地与远端分别被改写类似 bookmark 冲突的“变更版”。处理分叉变更通常需要决定保留哪一版、丢弃哪一版这是 divergence.md 的专题内容。九、术语速查表术语一句话定义与 Git 的对应关系Commit某时间点文件快照tree 元数据 父指针近似 Git commitCommit ID具体提交对象的唯一 ID改写即变就是 Git commit IDGit 后端Change提交随时间演化的“思路”本身无对象无对应概念Git 没有Change ID变更的唯一 ID改写不变16 字节无对应概念Change offset追加在 Change ID 后消除歧义的偏移如xyz/0无对应概念Rewrite创建提交新版本新 Commit ID、旧 Change ID类似 rebase/amend 的合并效果Root commit全零提交 ID、全 z 变更 ID 的虚拟根不同于 Git 的 root commitTree递归定义的目录快照近似 Git treeConflict文件 / bookmark / 变更三个层面的冲突类似但能力更强Viewbookmark、匿名 head、工作副本提交的快照无直接对应Visible/Hidden commits可见 从匿名 head 可达隐藏 被放弃/改写无直接对应Git 依赖 reflog/GCHead无后代的提交注意不是 Git 的 HEAD概念不同Operationview 快照 元数据近似 Git 的 reflog 条目Operation logoperation 构成的 DAG近似 reflog 的图化Repository.jj/下的全部操作与提交近似.git/Workspace工作副本 仓库即 Git 的 worktreeWorking copy正在编辑的文件命令前后自动快照/恢复即 Git 的 working treeWorking-copy commit工作副本当前状态的提交近似未提交改动 暂存区Remote仓库副本的引用含本地远端同 Git remoteColocated workspace.git/与.jj/同级的布局无直接对应Backend存储层实现Git 后端为唯一生产后端无对应Git 内置存储Revset选择一组修订的函数式语言类似git rev-list表达式但更强Divergent change一个变更出现多个可见提交近似 Git 的 diverged 分支Bookmark命名指针创建提交时不自动移动近似 Git branch但语义不同Anonymous branch无 bookmark 指向的提交链默认保留Git 中易被 GC 清理结语用术语表读懂 jj 的设计哲学纵观整份术语表Jujutsu 的差异化设计可以浓缩为三组对比Commit ID 与 Change ID 的双 ID 体系让重写历史成为常态匿名分支与 View 模型让“多线并行”不再需要命名负担Operation log 的全量操作记录让撤销与并发协调变得可靠。掌握这些术语你就拥有了阅读jj log、jj op log、jj bookmark list输出的完整心智模型——后续深入 tutorial.md、conflicts.md 与 revsets.md 时也会顺畅许多。【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进