十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jujutsu(jj)架构深度解析:数据模型、双 crate 分层与存储无关 API 设计

Jujutsu(jj)架构深度解析:数据模型、双 crate 分层与存储无关 API 设计 Jujutsujj架构深度解析数据模型、双 crate 分层与存储无关 API 设计【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj导读本文围绕 Jujutsujj官方架构文档展开系统梳理其核心设计与 Git 相似但独立演化的提交数据模型、jj-lib与jj-cli双 crate 分层、可插拔的存储后端体系以及 revset 四阶段求值流水线。读者读完可完整掌握 jj 仓库的内部结构、关键类型Backend/Store/ReadonlyRepo/MutableRepo/Transaction等的职责划分并了解GitBackend、SimpleBackend、StackedTable等底层实现如何在源码中落地为后续阅读源码或二次开发打下基础。本文以 web/docs/src/content/docs/technical/architecture.md 为主干结合 lib/src 与 cli/src 目录下的源码进行印证与扩充。一、数据模型与 Git 同源但有自己的演进Jujutsu 的提交数据模型与 Git 的对象模型blob / tree / commit结构相似但不相同。两者的核心差异在于“提交Commit如何被标识”CommitId提交 ID由提交内容决定。提交被改写rewrite后其CommitId会随之变化。这与 Git 的 commit hash 思路一致。ChangeId变更 ID稳定标识符随提交一起“进化”而不随内容变化。一次逻辑改动无论被 rebase、squash 多少次其ChangeId保持不变。这是 jj “演化evolution”语义的基石——用户可以从一条变更change的历史中看到同一逻辑改动的所有版本。在 lib/src/backend.rs 中可以看到这两个 ID 类型的定义id_type!( /// Identifier for a [Commit] based on its content. When a commit is /// rewritten, its CommitId changes. pub CommitId { hex() } ); id_type!( /// Stable identifier for a [Commit]. Unlike the CommitId, the ChangeId /// follows the commit and is not updated when the commit is rewritten. pub ChangeId { reverse_hex() } );注意ChangeId使用reverse_hex()输出它采用z-k的“反向十六进制”字符集而非0-9a-f这样在日志输出中变更 ID 能使用 Git 不会出现的字符避免混淆见 lib/src/backend.rs。除提交外数据模型还定义了TreeId、FileId、SymlinkId、CopyId等对象 IDlib/src/backend.rs以及Signature作者/提交者与时间戳等元数据结构。整个模型使用纯 Rust 数据结构表达不绑定任何具体的磁盘格式——这正是下一节“存储无关 API”的基础。二、库与 UI 分离两个 Rust crate 的职责边界jj可执行文件由两个 Rust crate组成见 cli/Cargo.toml 与 lib/Cargo.tomlcrate目录职责jj-liblib/src仓库状态、提交/树/文件读写、revset 求值、事务、并发控制等核心逻辑jj-clicli/src命令行参数解析、用户交互、模板渲染、输出格式化核心约束jj-lib当前只被 CLI 使用但设计上应当也能被 GUI / TUI / 多用户服务器复用。因此库 crate不得直接与终端交互所有输入输出由 CLI crate 完成库 crate不能读取用户主目录的配置也不能读取用户特定的环境变量因为服务器场景没有这些少量例外存在例如仓库格式自动升级时打印的提示消息。在jj-lib的模块清单lib/src/lib.rs中可以看到这种边界git、git_backend等底层存储模块位于库侧而cli_util、ui、diff_util等所有与用户打交道的逻辑都在 CLI 侧。库侧代码还开启了#![forbid(unsafe_code)]lib/src/lib.rs保证核心逻辑全部为安全 Rust。从源码结构看库 crate 的 API 在“好用”上投入了大量设计但像集合类型选用、导出符号范围这类“细节”尚未刻意打磨——这提示读者在引入jj-lib作为依赖时API 仍可能小幅演进。三、存储无关 API为“换存储”而生贯穿 jj 架构的一条总原则是数据存在哪里应当易于改变。默认落在本地磁盘但也应能搬到云端Jujutsu 诞生于 Google天然面向这一目标。为此架构把仓库的每一类数据都抽象为独立的后端数据由谁存储磁盘位置默认提交、树、文件等提交后端commit backend.jj/repo/store/操作operation与视图view操作后端operation backend.jj/repo/op_store/操作日志头部op headsop heads 后端.jj/repo/op_heads/提交索引索引后端index backend.jj/repo/index/工作副本工作副本后端working copy backend.jj/下的工作副本状态每个后端的选择在仓库创建时写入对应目录下的type文件.jj/repo/store/type.jj/repo/index/type.jj/repo/op_store/type.jj/repo/op_heads/type这些接口都用普通 Rust 数据类型定义不绑定特定格式因此替换实现时无需改动上层逻辑。工作副本目前还没有独立的 trait但其接口面很小需要时可以很容易地抽象出来。ReadonlyRepo::init()lib/src/repo.rs就是这套多后端装配过程的代码证据初始化时依次创建store/、op_store/、op_heads/、index/、submodule_store/目录并把各后端的名字分别写入各自的type文件最后组装成RepoLoader。这也印证了“存储无关 API”并非文档中的理想而是落地的实现事实。四、库 crate 设计核心类型全景架构文档给出了一张类型关系图见 web/docs/src/content/docs/technical/types.svg由 Excalidraw 绘制可在 Excalidraw 中右键 “Copy to Clipboard as SVG” 获取可编辑副本。核心脉络是Workspace ──→ WorkingCopy ──→ TreeState │ └──→ RepoLoader ──→ ReadonlyRepo ──→ MutableRepo经 Transaction 获取下面逐一说明每个类型的职责与源码对应关系。4.1 Backend提交后端的统一接口Backendtraitlib/src/backend.rs是每个提交后端都必须实现的接口包括name()后端唯一名称写入.jj/repo/store/typecommit_id_length()/change_id_length()ID 长度root_commit_id()/root_change_id()根提交唯一没有父提交的虚拟提交的 IDempty_tree_id()空树 IDread_file/write_file、read_symlink/write_symlink、read_tree/write_tree、read_commit/write_commit对象级读写concurrency()后端能良好支撑的并发请求数估计本地后端可设为 1云后端可设为 100 量级同时保证至少返回 1。write_commit还接受可选的sign_with签名函数供支持加密签名的后端将签名结果存回secure_sig字段。由于还存在非提交类后端文档指出Backend这个名字迟早应改名为CommitBackend——这是当前实现的一个已知命名欠账。4.2 GitBackend以 Git 仓库为存储的提交后端GitBackendlib/src/git_backend.rs把提交、树、文件直接存进一个 Git 仓库使用gitoxide读写提交与 refs见 lib/src/git_backend.rs 的use gix::...导入。三个关键设计点防 GC 引用为防止 Git GC 删除仍被操作日志可达的提交GitBackend会在refs/jj/keep/命名空间下为操作日志中的每个提交保存一个 ref。对应常量NO_GC_REF_NAMESPACE: str refs/jj/keep/定义在 lib/src/git_backend.rs。非 Git 元数据外置Jujutsu 模型有而 Git 模型没有的提交数据——变更 IDchange ID与前驱predecessors列表——存放在.jj/repo/store/extra/的一个StackedTable中。对没有该表数据的提交即由git直接创建的提交前驱取空列表变更 ID 取“位反转的提交 ID”。相关常量CHANGE_ID_COMMIT_HEADER: str change-id等见 lib/src/git_backend.rs。ID 冲突即报错因为直接使用 Git 对象 ID 作为提交 ID两个仅变更 ID 不同的提交会得到相同提交 ID写入第二个时会报错。4.3 SimpleBackend概念验证后端SimpleBackendlib/src/simple_backend.rs只是一个概念验证实现对象按哈希寻址每个对象一个文件。文档明确说明其定位并非生产级而是用来验证后端接口设计的完整性。4.4 Store对 Backend 的包装与缓存Storelib/src/store.rs包装Backend对外返回更方便使用的包装类型包装对象持有Store自身的引用因此可以写出commit.parents()这样无需显式传入 store 的调用提供提交与树的缓存commit_cache容量 100与tree_cache容量 1000因为树对象多于提交且常被多个提交共享见 lib/src/store.rs。4.5 ReadonlyRepo某个操作下的仓库快照ReadonlyRepolib/src/repo.rs表示仓库在某个特定操作operation时的状态持有该操作关联的视图view对象。结构体字段包括loaderRepoLoader、operation、只读索引index、变更 ID 索引change_id_index以及视图view。仓库本身不知道工作副本在磁盘的哪里它只通过视图对象知道每个 workspace 当前应处于哪个工作副本提交。4.6 MutableRepo可修改的仓库MutableRepolib/src/repo.rs是ReadonlyRepo的可变版本持有对基础ReadonlyRepo的引用但拥有自己的视图对象副本允许调用方修改。4.7 Transaction把变更发布到操作日志Transactionlib/src/transaction.rs持有MutableRepo与即将写入操作日志的元数据mut_repo事务范围内的内存变更parent_ops父操作列表op_metadata操作元数据end_time可选的结束时间。提交事务时MutableRepo变成操作日志中磁盘上的视图对象Transaction对象本身变成操作对象内存中Transaction::commit()返回一个新的ReadonlyRepo。源码注释lib/src/transaction.rs将其类比为事务之于仓库就像提交之于内容、树之于内容快照——三者同构地表达了“变更”与“变更后的状态”。Transaction::commit()可能返回TransactionCommitError涵盖索引、索引存储、op heads 存储、op 存储四类错误lib/src/transaction.rs。4.8 RepoLoader指向.jj/repo/的指针RepoLoaderlib/src/repo.rs表示一个操作未指定的仓库可视为指向.jj/repo/目录的指针。给定操作 ID它可以创建对应的ReadonlyRepo。4.9 TreeState工作副本的文件状态机TreeStatelib/src/local_working_copy.rs表示工作副本中文件的状态为每个被跟踪文件记录mtime 与 size知道工作副本当前对应的TreeIdsnapshot()利用记录的 mtime/size 检测工作副本变化若有变化返回新的TreeIdcheckout()把磁盘文件更新到请求的TreeId支持稀疏检出sparse checkout事实上所有工作副本都是稀疏的只是大多数情况下跟踪整个仓库。源码中可以看到其内部还维护sparse_patterns路径前缀列表、own_mtime、watchman_clock配置 Watchman 文件监控时的时钟值等字段默认稀疏模式为vec![RepoPathBuf::root()]即跟踪整个仓库见 lib/src/local_working_copy.rs。4.10 WorkingCopy 与 WorkspaceWorkingCopytraitlib/src/working_copy.rs在TreeState之上还知道自己的WorkspaceName与最近更新时的操作。Workspacelib/src/workspace.rs表示仓库 工作副本的组合类似 Git 的 worktree 概念。文档指出一个重要的“愿望与现实的偏差”当前操作下的仓库视图决定每个 workspace应该处于哪个工作副本提交WorkingCopy决定工作副本实际是什么。当工作副本提交被其他 workspace 改变或更新进程崩溃时工作副本会变 stale过期。4.11 git 模块高于 GitBackend 的互操作层git模块lib/src/git.rs提供与 Git 仓库互操作的功能层次高于GitBackendGitBackend受Backendtrait 约束只能做对象读写git模块专门针对 Git 后端仓库负责从 Git 仓库导入 refs、向 Git 仓库导出 refs以及向远端推送 / 从远端拉取。4.12 Revsets四阶段求值流水线用户提供的 revset 表达式字符串要经过四阶段求值lib/src/revset.rs 与 lib/src/revset_parser.rs解析把表达式解析成RevsetExpression接近 AST解析符号把tags()这类符号/函数解析为具体提交此阶段后仍是RevsetExpression但不再含CommitRef变体解析可见性解析visible_heads()与all()产出ResolvedExpression求值把ResolvedExpression求值为Revset。关键点在于第 4 步由Index::evaluate_revset()执行允许Revset实现利用特定索引实现如 lib/src/default_index 目录下的默认索引的特性而前三步与索引实现无关因此可以被任何索引后端复用。4.13 StackedTable自研的磁盘键值格式StackedTable实际是ReadonlyTable与MutableTable见 lib/src/stacked_table.rs 与 lib/src/stacked_table.rs是一种简单的磁盘键值格式键定长、值变长按键排序采用自研格式的原因需要无锁并发详见 web/docs/src/content/docs/technical/concurrency.md而现有键值库无法满足文件格式一张查找表按序排列的键列表每个键后跟着对应值在拼接值区中的偏移 拼接的值区父表链查表时当前表找不到就查父表表从不原地更新——若新条目数少于父表条目数的一半则新建一张指向父表的新表否则把父表条目与新条目复制进一张以祖父表为父的新表递归执行保证父表规模至少是子表的 2 倍摊还后插入与查找均为O(log N)尚无不可达表的垃圾回收表以哈希命名另用目录保存指向当前叶表的指针与操作日志的存储方式相同见 web/docs/src/content/docs/technical/concurrency.md#storage。TableStorelib/src/stacked_table.rs还提供gc()lib/src/stacked_table.rs用于在保持叶表的前提下清理旧表。五、CLI crate 设计要点5.1 模板Templates模板概念源自 Mercurial但语法不同顶层表达式本身就是模板表达式而非像 Mercurial 那样是一个字符串没有字符串插值Mercurial 中可写Commit ID: {node}jj 不支持这种写法。模板的解析与渲染实现在 CLI 侧涉及 cli/src/template_parser.rs基于 pest 语法语法文件 cli/src/template.pest与 cli/src/commit_templater.rs 等文件。5.2 差异编辑Diff-editingjj diffedit等命令的差异编辑机制很巧妙创建两个极其稀疏的工作副本只包含希望用户编辑的文件让用户编辑差异的右侧直接对该工作副本做 snapshot得到新树。这样就把“编辑差异”问题转化为普通的文件编辑 快照问题复用了TreeState::snapshot()的既有能力。实现位于 CLI 的 diff 编辑流程中依赖库侧的TreeState快照机制。六、从架构到实践如何继续深入仓库如果你希望验证或深入学习上述设计建议按以下路径阅读源码对象模型lib/src/backend.rsCommitId/ChangeId定义与Backendtrait多后端装配lib/src/repo.rsReadonlyRepo::init()展示各后端type文件的写入Git 互操作lib/src/git_backend.rs防 GC refs、extra/元数据表与 lib/src/git.rs事务与并发lib/src/transaction.rs 与 web/docs/src/content/docs/technical/concurrency.md工作副本lib/src/local_working_copy.rsTreeState的 snapshot/checkout/稀疏模式revset 求值lib/src/revset.rs 与 lib/src/default_index/revset_engine.rs磁盘格式lib/src/stacked_table.rsReadonlyTable/MutableTable/TableStore。另外docs/technical/architecture.md 是与本文同源的仓库根目录版本cli/docs/technical/architecture.md 为旧版镜像三者主题一致可交叉对照。结语Jujutsu 的架构本质上是**“把 Git 的好用之处保留下来把 Git 的模型扩展成可演化、可替换存储的形态”**通过CommitId/ChangeId双 ID 模型支撑演化语义通过Backend/OpStore/IndexStore等后端抽象实现存储无关通过事务 操作日志实现并发安全再以jj-lib/jj-cli分离保证核心逻辑可被 GUI/TUI/服务器复用。理解这张架构蓝图是读懂 jj 源码、乃至贡献代码的第一把钥匙。【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表