核心概念与术语完全指南:Commit、Change、Operation、Bookmark 与冲突模型深度解读)
Jujutsujj核心概念与术语完全指南Commit、Change、Operation、Bookmark 与冲突模型深度解读【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj导读本文以 Jujutsujj官方文档 docs/glossary.md 为骨架系统梳理这款 Git 兼容版本控制系统的全部核心术语与底层数据模型。从 Commit/Change 的双 ID 机制、Operation log 的无锁并发到 Bookmark 的追踪与冲突语义、Visible/Hidden 提交的可见性规则本文既保证每个术语都有可直接落地的命令示例又深入到仓库源码如 lib/src/backend.rs、core/src/hex_util.rs验证其真实实现。读完本文你将能准确理解jj log输出的每一列、jj undo为什么能撤销任意操作、以及冲突为什么是 Jujutsu 的一等公民。一、版本历史的核心Commit 与 ChangeCommit快照 元数据Commit提交是仓库中某时间点文件内容的快照技术上对应一个 Tree 对象外加一组元数据。根据 lib/src/backend.rs 中Commit结构体的定义一个提交包含parents父提交指针列表提交可以有任意数量的父提交仅根提交没有父提交change_id变更 ID见下文description提交说明commit messageauthor/committer作者与提交者签名含各自的时间戳root_tree根目录对应的树对象冲突时是多值合并MergeTreeIdsecure_sig可选的加密签名。通过这些父指针所有提交构成一个有向无环图DAG即常说的 commit history。需要注意虽然提交以快照形式存储但实践中常被当作快照之间的差异来对待——即相对其父快照的差异若有多个父提交则差异是与父提交合并结果相比。例如jj diff显示某提交相对其父引入的差异jj rebase则把这些变更应用到另一个基提交上。revision 是 commit 的同义词两个词在文档中经常互换使用。Change 与 Change ID随重写保持稳定的身份Change变更是一个提交随时间演进的过程这一抽象概念。变更本身并不作为数据模型中的对象存在只有Change ID变更 ID存在它是提交的一个属性。这是理解 Jujutsu 的关键Commit ID 标识提交的内容版本Change ID 标识提交的身份。当你用jj rebase、jj describe、jj squash等命令重写一个提交时会产生新的提交、新的 Commit ID但 Change ID 一般保持不变。这正是变更change随重写延续这一说法的由来。Change ID 通常是16 字节多为随机生成。在jj log中默认以行首的12 个字母呈现且字母范围在k-z之间。这些字符其实是使用z-k作为数字的十六进制表示用 z-k 代替 0-9a-f。在源码 core/src/hex_util.rs 中可以看到两组字符表const FORWARD_HEX_CHARS: [u8; 16] b0123456789abcdef; const REVERSE_HEX_CHARS: [u8; 16] bzyxwvutsrqponmlk;encode_reverse_hex使用zyxwvutsrqponmlk这 16 个字符编码decode_reverse_hex则把k..z反向映射回数值见 core/src/hex_util.rs 的reverse_hex_value。由于jj生态大量使用前缀缩写显示 ID选择 k-z 字符集可以避免在输出文本中产生真实的英文单词如bad、dead等减少误读。Commit ID内容寻址的指纹Commit ID提交 ID是提交的唯一标识。使用 Git backend 时长为20 字节且就是 Git 的 commit ID。在jj log中默认以行尾的12 位十六进制数字0-9a-f呈现。因为重写会改变提交内容Commit ID 随之改变这正是它与 Change ID 的本质区别。Root commit一切提交的虚拟祖先Root commit根提交是每个仓库根部的一个虚拟提交其 Commit ID 全为000000000...Change ID 全为zzzzzzzzz...。在 revset 中可用root()函数引用。注意它与 Git 的 root commit 定义不同Git 的 root commits 是仓库的最初提交而 Jujutsu 的 root commit 是所有提交包括 Git 眼中的 root commits的共同祖先。Git 所谓 root commits 在jj log -r root()中才会显示见 docs/revsets.md 的示例。Author date 与 Committer date两个时间戳Author date作者日期记录提交的变更最初被创作的时间。首次创作时设置提交被重写时通常保持不变。新提交的 author date 与 committer date 通常相同但在jj rebase、jj describe、jj squash等历史编辑命令后会不同。使用 Git backend 时它存储在 Git 原生 author date 字段中。Committer date提交者日期记录该特定提交对象被创建的时间。新提交时设置每当jj重写提交rebase、describe、squash 等时更新。使用 Git backend 时它存储在 Git 原生 committer date 字段中。二、重写与可见性理解 Jujutsu 的演进模型Rewrite创建新版本而非修改原对象Rewrite重写一个提交意味着用不同的内容、元数据包括父指针或两者创建该提交的一个新版本。重写产生新提交因而新 Commit ID但 Change ID 通常保持不变。示例包括修改提交说明、rebase 等。修改工作副本也会重写 working-copy commit。由于对象不可变、重写产生新对象旧的提交对象并不会被物理删除——这引出了可见性概念。Visible commits 与 Hidden commitsabandoned commitsVisible commits可见提交是你在jj log -r all()中能看到的所有提交。它们是 View 中记录的某个 anonymous head 可达的提交可见提交的祖先隐式可见。直观地说可见提交是某个 Change 的最新版本。被 abandon 或被重写覆盖的提交会停止可见被标记为hidden隐藏/已废弃。这类提交仍然可以通过其Commit ID或Change ID Change offset组合访问。隐藏提交在 revset 中默认不参与搜索除非显式提及例如用完整 commit ID、nameremote符号或at_operation()函数见 docs/revsets.md 的 Hidden revisions 一节。Head 与 Anonymous branchHead头是没有后代的提交。具体语境不同含义略有差异revset 函数heads(X)返回集合X内部没有后代的提交见 docs/revsets.mdView记录在某个 Operation 时刻可见的 anonymous heads即没有 bookmark 指向的 head。这与 Git 的HEAD当前检出的引用完全是两回事切勿混淆。Anonymous branch匿名分支是一串不必然有 bookmark 指向它或其任何后代的提交链。与 Git 不同Jujutsu 会保留匿名分支上的提交直到被显式 abandon。可见的匿名分支由 View 跟踪View 存储这类分支的 heads 列表。这也意味着 Jujutsu 天然支持游离 HEADdetached HEAD状态。三、存储与历史Backend、Operation 与 ViewBackend可插拔的存储层Backend后端是存储层的实现。目前唯一生产可用的内置提交 backend 是Git backend——它把提交存储在 Git 仓库中这也是jj能与 Git 生态无缝协作的根本原因。仓库中还有若干用于测试的 backend如 lib/src/simple_backend.rsGoogle 内部也有基于云的后端。此外还有可插拔的、用于存储提交以外信息的 backend例如存储 operation log 的 operation store backend。在 lib/src/repo.rs 中可以看到 backend 工厂类型dyn Fn(UserSettings, Path) - ResultBoxdyn Backend, _即仓库通过用户设置与路径初始化并加载 backendStore内部持有Boxdyn Backend见 lib/src/store.rs。Repository.jj/之下的全部Repository仓库基本等于.jj/目录下的一切即 operations 与 commits 的完整集合。Operation 与 Operation log可撤销历史的基石Operation操作是某一时刻 可见提交 与 bookmark 状态的快照技术上是一个 View 对象外加元数据用户名、主机名、时间戳以及指向其父操作们的指针。Operation log操作日志就是由 operation 对象构成的 DAG正如提交构成 DAG 一样。顺序发生的操作在图中形成单线从jj视角并发发生的操作则造成 DAG 的分叉与合并。Operation log 带来两大能力详见 docs/operation-log.md撤销与恢复jj undo逐个撤销操作jj op revert回滚某个非最新操作jj op restore把整个仓库恢复到历史某刻。无锁并发并发运行多个jj命令不会损坏仓库即便在不同机器通过分布式文件系统访问同一仓库只要文件系统保证写可见性。命令启动时加载最新 operation看不到并发命令的写入冲突会在后续jj st/jj log中提示。引用操作时可用表示当前操作支持x-父操作与x子操作运算符。顶层选项--at-op/--at-operation可将仓库加载到特定操作时刻此时不会自动快照工作副本。View仓库可见状态的快照View视图是 bookmarks 及其目标、anonymous heads、各 workspace 的 working-copy commits 的快照。anonymous heads 定义了哪些提交可见。View 对象类似 Tree 对象代表无历史的快照Operation 对象类似 Commit 对象为其添加元数据与历史。二者在数据模型上的对称关系正是 Jujutsu 设计优雅之处。Tree目录快照Tree树对象代表仓库中某个目录的快照。树对象递归定义每个树对象只包含其代表目录直接包含的文件与子目录不递归展开子目录内容。四、引用体系Bookmark、Branch、Remote 与追踪Bookmark命名指针Bookmark书签是命名指针指向某个提交。它类似 Git 的 branch更接近 Mercurial 的 bookmarks。核心语义差异没有当前 bookmark概念创建新提交时 bookmark不会随之移动bookmark会自动跟随被重写的提交沿 change ID 移动提交被 abandon 时指向它的 bookmark 会被删除见 docs/bookmarks.md 的 Bookmark updates 一节。常用命令jj bookmark list列出书签jj bookmark subcommandcreate/move/delete/track/untrack 等管理书签jj b是jj bookmark的别名jj b c NAME -r即jj bookmark create NAME -r。bookmark 名可直接作为修订参数如jj new main。Git 交互时 bookmark 映射到 Git branchjj git push --bookmark foo把foobookmark 推到远端foobranch在 colocated workspace 中jj git import自动把 Git 分支导入为 bookmark。Branch指什么在jj语境中branch分支通常指 anonymous branch或非正式地指提交树即提交图的一个分支。文档有时也讨论 Git 的分支与远端分支——本地它们对应 bookmark。在 colocated workspace 中每个本地 Git 分支对应一个jjbookmark。Remote 与 tracked bookmarksRemote远端是对仓库副本的引用。最常见是互联网/网络上的托管远端但本地远端同样可行。由于 Jujutsu 兼容 Git可以使用 Git 的所有流行托管服务GitHub、GitLab、Codeberg 等。Jujutsu 记录每个 bookmark 在各远端的最后已知位置类似 Git 的 remote-tracking branches每次jj git fetch/jj git push更新。可用bookmark nameremote name引用远端位置如jj new mainorigin。术语辨析详见 docs/bookmarks.md 的 Terminology summaryRemote bookmark远端上的 bookmark ref。jj只在与远端通信时知道其真实状态但保存其最后位置jj show nameremote可见完全类比 Git 的 remote-tracking branchesTracked (remote) bookmark被关联到同名本地 bookmark 的远端 bookmark。fetch 时远端变化会传播到本地 bookmark不存在则创建Tracking (local) bookmark被jj尽力与某远端 bookmark 保持同步的本地 bookmark。一个本地 bookmark 可同时追踪 0 或多个远端上的同名 bookmark——这不同于 Git 单个 upstream 的限制。手动追踪示例来自 docs/bookmarks.mdjj bookmark list --all # 列出所有本地与远端书签 jj bookmark track my-feature --remoteorigin # 追踪远端书签 jj git fetch --remote origin # 之后 fetch 会自动导入该书签 jj new my-feature # 基于该书签创建新修订取消追踪用jj bookmark untrack name --remoteremote列追踪书签用jj bookmark list --tracked或-t。自动追踪jj git clone会自动把默认远端书签如mainorigin设为 trackedpush 时新建的远端书签也会被标记为 tracked。其余远端书签默认不追踪可通过配置remotes.name.auto-track-bookmarks *改变默认行为见 docs/config.md。推送安全检查见 docs/bookmarks.md 的 Pushing bookmarks: Safety checksjj git push移动/创建/删除远端书签前会检查① 远端书签实际状态与jj记录一致否则拒绝需先 fetch 解决冲突类似git push --force-with-lease② 本地书签不得处于冲突状态③ 若远端书签已存在它必须被 tracked。Bookmark 冲突当本地与远端同时移动了书签pull 后书签进入冲突状态。jj status会提示冲突书签与解决指引jj log在潜在目标提交上用main??后缀显示。此时jj new main会因 revset 解析到多个修订而报错。解决方式本地书签冲突用jj bookmark move或先jj new main合并、jj rebase重排远端书签冲突用jj git fetch拉取解决。五、工作区Workspace、Working copy 与 ColocationWorkspace多工作副本Workspace工作区 一个 working copy 关联的 repository。一个仓库可有多个 workspace每个 workspace 有.jj/目录但提交与操作存储在初始 workspace其他 workspace 只保存指向它的指针Git 称之为 worktree。jj workspace add新增工作副本jj workspace root --name ws打印指定 workspace 根路径默认当前jj workspace list列出所有 workspace 及其根路径jj workspace forget让仓库忘记某 workspace文件可另行删除。多 workspace 场景一个跑长时测试、另一个继续开发。若某 workspace 的提交被另一 workspace 重写前者工作副本会变 stale用jj workspace update-stale更新详见 docs/working-copy.md。Working copy 与 Working-copy commitWorking copy工作副本是 working-copy commit 的文件落地处也是创建新提交时的文件读取处。与多数 VCS 不同自动提交几乎每个jj命令都会在开头对工作副本做快照若有改动则生成新的 working-copy commit替换旧的隐式跟踪新增文件默认自动跟踪jj st就会提交它们删除文件则隐式取消跟踪。snapshot.auto-track配置控制哪些路径被自动跟踪语法见 docs/filesets.md非默认值时可用jj file track/jj file untrack手动管理。忽略文件用.gitignore尚无.jjignore被忽略文件永不被自动跟踪但已跟踪文件即使匹配忽略规则仍保持跟踪。Working-copy commit工作副本提交是对应工作副本当前状态的提交每个 workspace 一个当前工作副本提交记录在 operation log 中。Colocated workspacesjj 与 git 的混居当使用 Git backend 且底层 Git 仓库的.git/目录与.jj/是同级目录时称为colocated同址workspace。这是jj git init/jj git clone的默认形态详见 docs/git-compatibility.mdjj与git命令可随意混用每个jj命令自动导入/导出 Git refs。jj git init --git-repopath可基于已有 Git 仓库创建jj git clone url克隆远端仓库。禁用同址可用--no-colocate标志或配置git.colocate false。同址也有代价交错使用jj/git易造成分支冲突或 divergent change大量分支时每命令的自动jj git import会变慢可用jj util gc缓解Git 工具难以理解 jj 记录的冲突见 docs/git-compatibility.md 的详细清单。jj git colocation status/enable/disable可随时查询与切换。六、冲突Jujutsu 的一等公民Conflict可记录、可重写、可推迟解决Conflict冲突在 jj 中可以出现在多处文件冲突最常见。当jj无法合并对同一文件的不同修改时发生如两人改同一段代码后jj new合并或jj rebase变基。与其他 VCS 不同Jujutsu 可以把冲突状态记录进提交rebase 产生冲突时冲突被记录在 rebased 提交里操作照常成功你可随时解决。冲突可被继续 rebase、合并或回退。提交中存储的是冲突的逻辑表示而非冲突标记因此变基冲突不会产生嵌套冲突标记实现见 docs/technical/conflicts.md。在jj status与jj log中可见红色 conflict 标签。Bookmark 冲突见上文Bookmark 冲突。Divergent change见下文。冲突标记在jj new/jj edit检出冲突提交时写入工作副本也出现在 diff 输出中如jj show。工作副本中的冲突标记会在下次快照时被解析、恢复为逻辑冲突状态因此你可以只解决一部分、分多次完成见 docs/working-copy.md 的 Conflicts 一节。冲突标记样式由ui.conflict-marker-style配置控制默认 diff 风格、snapshot快照风格、git的 Git diff3 风格多边冲突时 git 风格回退到快照风格。具体示例见 docs/conflicts.md。Divergent change 与 Change offsetDivergent change发散变更是拥有多于一个可见提交的 change。例如本地与远端各自重写了同一 changepull 后该 change 处于冲突状态。jj log中显示 divergent 标签。当 change ID 不能唯一确定提交时隐藏提交、发散变更可在 change ID 后加change offset偏移xyz/0表示 change ID 为xyz的最新提交xyz/1是它之前的一个依此类推。这也是访问隐藏提交的主要手段。冲突解决的典型工作流检出冲突提交、解决、squash 回原提交来自 docs/working-copy.mdjj new conflicted-commit # 在其上创建 working-copy commit冲突写入工作副本 # 编辑文件替换冲突标记为最终文本 jj diff # 检查冲突解决结果 jj squash # 把解决结果移入原冲突提交jj resolve可用外部合并工具解决双侧基准的冲突jj restore可选用冲突的某一侧。七、选择与查询Revset 与 RevisionRevset是 Jujutsu 用于选择一组修订的函数式语言思想源自 Mercurial。表达式由符号、运算符与函数构成。多数jj命令接受 revsetjj edit revset等命令要求其解析为恰好一个提交。完整语法见 docs/revsets.md这里给出与术语相关的要点Revisioncommit 的同义词符号当前 workspace 的 working-copy commitworkspace name指其他 workspace 的 working-copy commitnameremote指远端 bookmarks/tags完整/唯一前缀的 commit ID、change ID 均可作符号符号解析优先级为 tag 名 bookmark 名 commit/change ID可用commit_id(abc)等函数强制常用函数与术语对应heads(x)集合内无后代的提交、visible_heads()、root()、divergent()发散变更、conflicts()含冲突文件的提交、mine()、description()、author()/committer()、at_operation()等内置别名trunk()默认远端默认书签的 head、immutable_heads()/immutable()/mutable()、visible()/hidden()、builtin_log()等定义在 cli/src/config/revsets.toml可在[revset-aliases]中覆盖。实用示例jj log -r - # 工作副本提交的父提交类似 git log -1 HEAD jj log -r :: # 工作副本的全部祖先类似 git log jj log -r remote_bookmarks().. # 不在任何远端书签上的提交 jj log -r author(*martinvonz*) description(*reset*) # 组合条件八、术语速查表术语一句话定义与 Git 的对应关系Commit文件快照 元数据通过父指针构成 DAGGit commitChange ID提交身份的稳定标识k-z 反十六进制无直接对应近似 commit 的演进身份Commit ID提交内容版本的唯一标识20 字节Git commit IDRewrite创建提交的新版本Change ID 不变amend/rebase/cherry-pickVisible/Hidden commits从 view 的 anonymous heads 可达与否reachable commitsBookmark命名指针自动跟随重写Git branchAnonymous branch无书签指向的提交链detached HEADOperation log操作对象的 DAG支持撤销与并发reflog 的加强版Viewbookmarks、heads、working-copy commits 快照无直接对应Conflict可记录的合并冲突状态逻辑表示merge conflict但 jj 可提交存储Divergent change一个 change 有多个可见提交无直接对应Workspaceworking copy 关联 repoGit worktreeColocated workspace.git/与.jj/同级的 Git 后端仓库普通 Git 仓库Revset选择修订集合的函数式语言部分类似 rev-list 表达式结语Jujutsu 的术语体系并非为标新立异而是其数据模型自洽性的直接体现Change ID 让身份穿越重写Operation log 让历史穿越并发View 让可见性穿越操作Conflict 让合并状态穿越命令。理解这套词汇你就能读懂jj log的每一列输出、预测jj undo的边界、以及设计出符合 jj 哲学的自动化脚本。建议配合 docs/revsets.md、docs/bookmarks.md、docs/conflicts.md 与 docs/working-copy.md 深入实践。【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考