是一种新的 version control system
Resources
https://steveklabnik.github.io/jujutsu-tutorial/
看看这个
Jujutsu (jj) VCS workflows and the convenience of its “operation log” | Krist…
jj init — Sympolymathesy, by Chris Krycho
Introduction - Jujutsu for everyone
https://github.com/jj-for-everyone/jj-for-everyone.github.io
Philosophy
Jujutsu(jj)是一套“以重写为常态”的现代版本控制系统。它的核心理念可以概括为以下几点:
-
把“变更”而非“提交”作为一等公民
jj为每个逻辑变更分配稳定的“变更ID”(change ID),即使你重写、rebase 或拆分合并提交,变更ID仍保持不变。提交ID可以变化,但变更的身份不变,从而让代码评审、堆叠式开发、跨分支搬运更顺畅。
insight: 于是我需要学习的就是 如何操控这个 change DAG
insight: change ID 只由dag的结构确定? -
安全的重写与全局撤销
jj鼓励你随时重写历史,因为它有“操作日志”(operation log)记录所有仓库操作。
你可以对任意一步执行撤销或回滚,而不仅仅是某个分支的 reflog。
这让大胆的历史整理变成低风险的日常操作。 -
冲突是数据,不只是工作副本里的标记
jj把冲突以结构化数据存入提交中,你可以在有冲突的状态下继续工作、推迟解决、或分步解决。这样冲突不再是阻塞工作流的临时脏状态,而是可追踪、可审计的历史事实。 -
简化心智模型:单父工作副本、没有暂存区
工作副本总是对应一个父提交,当前更改就是“这一提交相对父提交的差异”。jj不设“暂存区”(index)与“stash”这类额外概念,降低理解和操作负担。 -
用可查询的“集合思维”来操作历史
jj内置类似 Mercurial 的 revset 查询语言,先选中一组提交(如 ancestors(), descendants(), authored-by() 等),再对集合做操作。
相比逐命令、逐状态的操作方式,这种“先选集合、后变换”的思路更一致、更强大。 -
本地优先与可互操作
绝大多数操作都是本地、可撤销的;网络复制(push/pull)是显式行为。
jj默认能与Git互操作(常用的是把对象存进Git仓库),既能融入现有生态,又不受其UI与概念束缚。 -
鼓励堆叠式开发与演化式历史
jj把一串相关变更当作“补丁栈”来管理,重排、折叠、拆分都很轻松;这契合现代代码评审与持续集成的习惯:先做小步提交,再按需要演化历史,使其更清晰。
insight: 极大地简化了 git 的分支模型! -
一致性与可恢复性优先
设计上倾向于“有记录的状态”而非“临时隐性状态”,并尽量让每一步都能回退;这样既适合新手,也让资深用户在复杂重写中有安全网。
简言之,Jujutsu的哲学是:把版本库视为可查询、可塑的DAG图,把“变更”当作稳定的业务对象,以强大的撤销机制和结构化冲突支持,鼓励你随时整理历史,用更低的心智负担实现更高质量的提交流。
若你熟悉Git/Mercurial,它像是二者优点的融合:保留Git的生态与性能,借鉴Mercurial的查询与演化理念,并以更一致的模型和更安全的重写体验为核心。
Design
Working copy
working copy 是当前工作副本提交的文件写入的working copy 是可以与之交互。
它也是读取文件以创建新提交的地方(尽管创建新提交有很多其他方法)。
一个 workspace 对应一个 working copy
changeset
change 的本质是什么?
revision
revision 类似与 git 中的 commit
在 jujutsu(常简称 jj)里,revision 指的是仓库中某个「变更集合」在某一时刻的确定状态。你可以把它理解为:
- 类似 Git 的提交(commit),但 jujutsu 的 revision 更强调基于有向无环图(DAG)的变更关系与可重写性。
- 每个 revision 都有一个唯一的哈希 ID,记录了该变更的父子关系、作者、时间、消息,以及所包含的文件内容快照或对父修订的差异。
- revision 之间通过父子链构成历史;分支、标签等只是指向某个 revision 的引用。
- 工作副本对应一个「当前」revision;你进行变更并提交,就会产生新的 revision。
与 Git 的关键差异与特性:
- 可重写的有序变更集:jj 强调在本地自由重写历史(比如分割、合并、重排 commit),以保持清晰的变更序列,然后再与远端同步。
- 操作以变更为中心:许多命令直接针对 revision 进行选择、编辑、合并,而不是仅以分支为主。
- 并行历史更自然:多父修订、可视化与选择器语法使在复杂分叉/合并场景下操作更灵活。
常用关联概念和命令示例:
- 创建新 revision:jj commit(或 jj new,然后 jj describe)
- 查看历史:jj log(支持选择器,比如 jj log -r @..@-)
- 重写 revision:jj rebase、jj squash、jj split、jj amend
- 引用 revision:用哈希前缀、符号 @(当前)、@-(上一个)、@—(再上一个),或选择器表达式
一句话概括:revision 是 jujutsu 中表示一次代码变更及其位置的基本单位,是你在仓库历史里操作和引用的核心对象。
Bookmarks
书签是针对修订版本(类似于 Git 中的分支)的命名指针。
你可以移动它们而不影响目标修订版本的标识。
当修订版本被重写时(例如通过 jj rebase ),书签会自动移动。
你可以将书签的名称传递给需要修订版本作为参数的命令。
例如, jj new main 将在 main 书签之上创建一个新的修订版本。
使用 jj bookmark list 来列出书签,使用 jj bookmark <subcommand> 来创建、移动或删除书签。
目前还没有活跃/当前/已检出的书签的概念。
jj 是如何进行 git 的 remote 管理的?
它是基于 bookmark 的!!!
Chat: 对比一下 jujutsu 的 bookmark 和 git 的 branch ARCHIVE
Conflicts in jj
这是 Jujutsu 默认采用“side #1 = diff + side #2 = snapshot”布局的原因与好处:
- 让差异一目了然:%%%%%%% Changes from base to side #1 直接告诉你“把 base 改成 side #1 需要做哪些修改”,无需手动对比两份文本,就能看到自己这边到底删改了什么。
- 保留完整参照:+++++++ Contents of side #2 把另一侧的内容原样提供出来,作为基准快照;你只需把 diff 套在这份快照上,就能得到合并结
果。这样避免了来回翻找另一侧改了哪些行 - 多边冲突也可读:若有三个及以上分支参与,Jujutsu 会继续在后面列出 side #3 的 diff(或 snapshot),仍旧遵循“快照 + 针对 base 的差
异”结构。这比传统把每个 side 全量展示更易于逐个应用修改。 - 减少手工比对开销:文档中特别强调,这种 diff+snapshot 组合能省去肉眼逐行比对的功夫,尤其在多侧冲突中,每个 diff 都是相对同一个
base 的补丁,按顺序应用即可。
因此,默认样式兼顾“看差异”与“保留原文”,既方便理解自己的改动,也方便把改动与另一侧结合起来。如果更偏好全量对比或 Git 传统风格,可以
再通过 ui.conflict-marker-style 切换。
TODO 自动冲突解决是如何实现的?
jj 把冲突记录为 “第一棵树 + 一系列 (加,减) 差分” 表达式,例如 A+(C-B),这种一等公民的冲突模型让未决冲突可以保存在提交里,并按需递归到子目录或文件粒度再求解。
当冲突嵌套出现时,系统通过 Merge::flatten() / Merge::simplify() 把表达式展开并消去可以抵消的项,确保多次 rebase/backout 不会堆叠出复杂的递归冲突。
<~/Dev/jj/lib/src/merge.rs> -561
合并逻辑使用通用的 Merge<T> 容器保存交替的删除/添加项,再配合 SameChange 策略在双方做出相同修改时进行自动化裁决,行为与 Git/Mercurial 的“三方同改视为已解决”规则一致。
</Dev/jj/lib/src/merge.rs> -147/Dev/jj/lib/src/merge.rs> -196
<
目录与文件合并由 merge_trees 管道驱动:
它按路径递归,针对文件级冲突在配置的 hunk 粒度上生成 MergedTreeValue,再尝试执行上述“同改”或其他平凡化处理,保留未解决的分支供后续人工 diffedit。
<~/Dev/jj/lib/src/tree_merge.rs> -168
TODO 冲突显示的具体方式
<~/Dev/jj/lib/src/conflicts.rs> -659
在默认的 “diff” 样式里,不管冲突有多少个 side,整段标记仍然只会出现一个 snapshot,其余 side 都以 diff 的形式出现。具体流程(lib/src/conflicts.rs:605-659)如下:
- 遍历冲突里的每个 “base → side” 组合时,会先把当前 side 和对应 base 做一次行级 diff(ContentDiff::by_line)。
- 如果此时还没有写出过 snapshot,就额外看看“下一 side”跟同一个 base 的 diff;谁的 diff 更短,就把谁定成 snapshot:
- 若当前 side 的 diff 更小,就直接把当前 side 以 diff 形式输出;
- 若下一 side 更小,当前 side 会改写成 snapshot,而下一 side 才输出 diff。
- 一旦 snapshot 已经出现,后续所有 side 都固定以 diff 呈现。
- 万一所有 side 都走 diff 分支(即前面没有触发 snapshot),循环结束后会强制用最后一个 side 作为 snapshot,以保证标记结构完整。
因此,多 side 冲突时的总体效果是:在所有 side 中挑出“最适合作为参考”的那一个(通常是 diff 最短的那一侧)当作唯一的 snapshot,其余每个 side 都展示为“从 base
到该 side 的 diff”。这样即便有三方乃至更多侧,也仍旧保持“先选一个基准快照,再依次套 diff”的阅读体验。
operation
jj 记录每次修改仓库的操作在”操作日志”中。
你可以通过 jj op log 查看日志。
每个操作对象包含操作结束时仓库状态的快照。
我们称这个快照为”视图”对象。
视图包含每个书签、标签(在 Git 支持的仓库中)以及 Git 引用指向的位置的信息,还包括仓库中的所有头部以及每个工作区中的当前工作副本提交。
操作对象还(除了视图之外)包含指向其之前立即操作的指针,以及关于操作元数据,如时间戳、用户名、主机名、描述。
CLI reference
general options
—no-pager 给ai用必须加这个选项
jj-edit
类似于 checkout
默认不能 edit immutable 的 rev
被推到主分支的rev一般认为是不可修改的
需要使用 —ignore-immutable 来强制 edit
其他分支默认就是可以修改的,这还挺好
然后默认是 force-push 的,这倒是挺惊奇的
jj-new
我们可以以相当多的花样创建变更
功能上包含了 git 的 merge
jj-rebase
修改一个 revision 的 parent 集合
将修订移动到不同的父节点
该命令在保留每个修订的更改(diff)的同时,将修订移动到不同的父节点。
有三种方式指定要重定位(rebase)的修订范围:
- —source/-s:重定位某个修订及其所有后代
- —branch/-b:相对于目标位置,重定位整个“分支”(分支头到其祖先之间的链)
- —revisions/-r:仅重定位指定的修订,不包含它们的后代
如果不提供上述选项,默认等同于 -b @(重定位当前分支)。
有三种方式指定选中的 revsets 应被放到哪里,
这三种 flag 的理解方式是在各种命令中共享的,
我们用对应的 flags 字母来表示这个参数对应的 revsets。
--destination/ =-d=:选中revsets的父亲为 d--insert-after/ =-A=:选中revsets的父亲为 A,儿子为原 A 的儿子--insert-before/ =-B=:选中revsets的父亲为 B 的父亲,儿子为 B
如果某个工作副本对应的修订在该操作中被废弃(变更集合并成空了),系统会为其创建一个新的空修订。
这是通用规则,并非此命令特有。
-
--skip-emptiedIf true, when rebasing would produce an empty commit, the commit is
abandoned. It will not be abandoned if it was already empty before the
rebase. Will never skip merge commits with multiple non-empty parents -
--keep-divergentKeep divergent commits while rebasing
这里的 divergent commit 说的就是 change ID 后面有两个 ?? 的提交
jj-show
貌似功能被 diff 完全包含?
自由度很低,还不支持 template
jj-diff
和 git 的 diff 有什么区别?
jj diff 默认使用 :color-words 内联高亮,把同一行里的修改直接着色展示,效果接近 git diff —color-words;
示例和对比在 docs/tutorial.md:125–docs/tutorial.md:136,文中也提醒为了获得 Git 式输出可以加 —git。
Insight: 这里的内联高亮貌似有一些判断逻辑,而且中文适配不佳,它对词的边界的判断似乎是只有空格和一些符号
这一默认格式可通过 [ui] diff-formatter 配置切换成 :git、:summary、:stat 等内置格式,或改用外部工具;
详见 docs/config.md:298–docs/config.md:304。
Git 需要单独加选项才能做类似切换(如 —stat、—name-only),而 jj 把它设成全局默认。
内联格式还提供 max-inline-alternation、context 等专门选项控制多少交替删除/添加时退回到传统逐行 diff,以及上下文长度;
参见 docs/config.md:306–docs/config.md:335。
Git 的对应能力主要依赖 —word-diff 与 —word-diff-regex 等命令行参数,默认没有自动阈值。
两者都能显示 Git 风格 diff:jj diff —git 或在配置里设 :git 时,输出与 git diff 的统一格式相同;
区别只在于 jj 需要显式请求这种模式,因为默认聚焦在更易读的内联 word diff。
jj 的 diff 是基于什么算法实现的?
- jj 的 diff 流水线会先把输入切分成“词”区间;算法把 0x80 以上的字节都视为单个词,从而把中文等多字节 UTF-8 字符作为不可拆分的 token,有利于保持中文行内差异的
可读性。citelib/src/diff.rs:42-69 - 核心的 ContentDiff 支持对任意数量的输入做比较:它把每个版本都 token 化、为每个词生成哈希,然后构建直方图以找出稀有词作为锚点;接着在左右两侧的锚点序列上
求最长公共子序列(LCS),从而定位未修改区间,剩余部分再递归细分。这个过程与 Git 的 histogram diff 思路一致,只是实现于 Rust 并支持多输入。citelib/src/
diff.rs:306-547lib/src/diff.rs:600-739 - 对比策略通过 CompareBytes 接口抽象,可切换为严格比对、忽略全部空白或归一化空白,从而在相同的框架内处理 —ignore-whitespace 等需求。citelib/src/
diff.rs:111-183
jj-log
默认显示的可变rev和一些用来表示上下文的额外rev
用 jj log -r :: 来显示所有rev
“elided revisions” 通常不是我期望的
jj log -p 可以显示 patch
我希望我在 majutsu-section-toggle 下面显示的信息里可以放上 patch (diff)
如何读懂 graph
这东西要单独学习说实话有点离谱了
规律:每一条横线都必须得能看出方向
它使用两种方式来暗示方向
╰─ / ─╮ 就表示一定是从左到右的
╭─ / ─╯ 就表示一定是从右到左的
每一条横线都至少要有一个这种符号
还有一种情况是 ╭─┴─╮ 两边往中间走,从中间出
jj-evolog
这其实是非常有意义的功能!相当于有自动保存的功能
会有很多 snapshot working copy
jj-split
它使用的是自带的一个 TUI
选中的 section 会被转移到 parent rev
有两种风格,一种是 2 panel 一种是 3 panel
jj-bookmark
- create [c]
- delete [d]
- forget [f]
- list [l]
- move [m]
- rename [r]
- set [s]
- track [t]
- untrack [很抱歉你不能用 u 来简写 untrack]
jj-absorb
Jujutsu Megamerges and jj absorb — Sympolymathesy, by Chris Krycho
这个命令非常好用!
在处理一些 lint 之类的完全不需要存在的 commit 的时候,
可以在保留更改的同时让更改自动被前面的 revision 吸收
jj-duplicate
jj duplicate creates new commits with the same content as existing ones.
Core behavior:
- 如果没有
-d-A-B的 flag,
它的默认行为是,每个选中的 rev 在它的parents后面复制一遍(共享parents),
如果一个 rev 的某个 parent rev 也是选中的 rev,会接到 parent rev 新生成的 rev。 - 如果有 flag ,每个 flag 的地位是等同的,我们会在每个 flag 表示的位置创建一个 dag,
这个 dag 是选中 revset 的导出 dag(保持了点与点之间的连通关系)。
Defaults:
- [REVSETS] defaults to @ (the checked-out commit).
- Duplicates keep the original description;
override via templates.duplicate_description.
jj-resolve
需要的功能是不是和 split 类似?
更多细节可以看这里
merge tools - Settings - Jujutsu docs
怎么是一个冲突一个冲突解决的?
meld 是这样的
jj-next / jj-prev
定位作用:
jj next [OFFSET] 将工作副本推进到当前父提交之后的第 OFFSET 个后代(默认 1),新建一个空的工作副本提交;
jj prev [OFFSET] 则把工作副本拉回到父提交之前的第 OFFSET 个祖先(默认 1)。
这些语义和示意图写在 jj/cli/tests/[email protected]:1911 与 :2227。
编辑模式:加 —edit 会直接改目标提交而不是新建提交;—no-edit 强制相反。
说明见 jj/cli/tests/[email protected]:1952-1958 与 :2268-2273。
默认是否编辑可由配置 ui.movement.edit 控制,命令行标志优先。
冲突导航:自 0.19.0(2024-07-03)起新增 —conflict
jj next --conflict 跳到最近的有冲突的后代,
jj prev --conflict 跳到最近的有冲突的祖先,
便于逐个处理冲突(jj/CHANGELOG.md:2276-2277)。
兼容合并提交:
自 0.17.0(2024-05-01)起,工作副本是 merge 时也能用 prev/next(jj/CHANGELOG.md:2504)。
编辑推断:
自 0.15.0(2024-03-06)起,如果你已经在编辑一个有子节点的提交,prev/next 会自动推断 —edit,避免再生成新提交(jj/CHANGELOG.md:2715-2716)。
歧义提示:
自 0.14.0(2024-02-07)起,目标不唯一时会提示让你选,而不是直接失败(jj/CHANGELOG.md:2880-2881)。
最初上线:
这两个命令是在 0.9.0(2023-09-06)首次加入,用于线性地在历史上前进/后退(jj/CHANGELOG.md:3305-3307)。
jj-run
jj-vcs/jj#1869 FR: Implement JJ `run`
</home/disk/Dev/jj/docs/design/run.md>
jj run 是 Jujutsu 里用来批量在多个修订上执行命令的通用“命令运行器”。
它的定位是帮助开发者把构建系统、lint/formatter、脚本化重写等任务直接纳入版本控制流程,而不必手动在每个提交里重复执行。
运行机制大体如下:
- 核心概念:用户提供一段命令(或脚本)和一组 revset,jj run 会在每个目标修订对应的临时工作区里依次或并行地执行这段命令。
目的是无缝集成到 CI、预提交、批量重写等场景。 - 典型用途:批量跑 pre-commit、cargo clippy、cargo fmt;对一串本地或远程改动做大规模替换;在多个提交上触发 bazel、ninja 等构建或测试。
后续还计划在此基础上演化出更专门的 jj test、jj format、jj fix 等命令。 - 实现思路:在 .jj/ 下为每个并行任务准备临时工作区(会复用以减少开销),默认保留缓存文件以支持增量构建,可选清理。
命令在这些临时工作区运行,不会干扰用户当前工作副本;
若进程修改了内容,可按选项选择“合并回去”或忽略。 - 调度与容错:支持拓扑顺序执行、限制并行度(-j/—jobs),并提供出错策略(继续、停止、立即中断)。
执行失败时不会擅自改动原提交,以免留下半成品。 - 常用选项:—command(或 -x)指定要运行的指令,-r/—revset 选范围,-k/—keep-going 控制失败策略,—clean 清理临时工作区,—rebase/—reparent 决定如何把子提交的修改合并回去,—dry-run 做演练等。
总之,jj run 面向的是“在版本历史的多个节点上批量执行同一条命令”,让构建、检查、修复等工作流程更自动化、更贴合 Jujutsu 的 revset 模型。
jj-fix
jj-workspace
我觉得这东西应该是非常常用的功能!
工作区允许你添加附加的工作副本,这些副本与同一个仓库关联。常见用法是,你可以在一个工作区运行缓慢的构建或测试,同时继续在另一个工作区编写代码。
和 git 的 worktree 有什么区别?
- jj workspace 会在提交图里保留一个对应的工作副本提交(形如 name@),任何地方改写都会在历史中留下记录;Git worktree 没有专门的“工作副本提交”,只是各自维护 HEAD 和索引。
- jj 的操作与 jj op 的操作日志紧密结合,冲突、撤销、jj workspace update-stale 都是对历史友好的;Git worktree 更像独立检出,主要依赖分支引用,不追踪操作历史。
- jj 默认给每个 workspace 保存自己的稀疏模式;Git 的 sparse checkout 也能 per-worktree 配置,但需要手动维护。
- jj 内建 stale 检测:如果某个 workspace 的 name@ 被外部改写就会提示更新;Git worktree 不会提醒你底层提交被改写了,它只会继续指向当前分支状态。
- 创建或删除 jj workspace 本身会记录为一次操作,可参与撤销/重做;Git worktree 的 add/remove 仅影响磁盘和引用,不写入历史日志。
jj-sparse
管理工作副本提交中哪些路径存在于工作副本中
- 每个 jj workspace 都维护一套自己的稀疏模式(sparse patterns),决定哪些文件会实际出现在该工作区的磁盘上。
- 稀疏模式是由包含/排除规则组成的列表(支持路径、通配符等),通过 jj sparse set/add/remove 等命令维护;在 .jj/workspaces/<workspace>/sparse_patterns 中可以
看到记录。 - 创建新工作区时,—sparse-patterns 决定如何初始化:copy 复制当前工作区的模式;full 拉取仓库全部文件;empty 则先不包含任何文件,需要你再手动添加。
- jj 的所有命令都会 respecting 稀疏模式:只更新、检测并在记录快照时考虑被包含的文件;其他文件既不会下载也不会触碰。
- 稀疏模式属于工作区本地状态,不会写入提交历史,因此不同 workspace 可以针对同一提交维持不同的“聚焦范围”。
jj sparse edit
这里使用的文件后缀是 jjsparse
jj-bisect
只有 —command 的功能,可能不太能需要集成到 majutsu 中
jj-git
- clone Create a new repo backed by a clone of a Git repo
- colocation Manage Jujutsu repository colocation with Git
- export Update the underlying Git repo with changes made in the repo
- fetch Fetch from a Git remote
- import Update repo with changes made in the underlying Git repo
- init Create a new Git backed repo
- push Push to a Git remote
- remote Manage Git remotes
- root Show the underlying Git directory of a repository using the Git backend
jj-file
- annotate
- chmod
- list
- show
- track
- untrack
要取消跟踪某个路径,将其添加到你的 .gitignore 再运行jj file untrack <path>
jj-metaedit
我打算完全重新设置我的邮箱,以后统一改到谷歌邮箱了
这个批量操作实在是太爽了
我现在就用了 --update-author
可以和 --ignore-immutable 配合
jj-tag
v0.35.0 添加了 set 和 delete
基本就是处于可用状态了
暂时还不支持 annotated tag
不对,jj-git-push 还没有支持 push tags
我们还需要用 git push —tags 来推送 tag 更新
jj-simplify-parents
第一次用到这个东西,的确有存在的必要
jj Config
How do core Jujutsu developers configure Jujutsu? · jj-vcs/jj · Discussion #5…
jj Editing diffs
我还没搞清楚和 merge-editor 之间的关系
DSL languages
revset
Revset language - Jujutsu docs
fileset
Fileset language - Jujutsu docs
这里涉及到 pest 这个东西
实际的元字符可以直接从语法文件看出来:=:= 用来指明 glob:, root: 这一类模
式前缀;=~=, &, | 分别表示取反、交集、并集;=(= ) 和 , 用在函数调用与分组;
' 和 " 是字符串字面量的定界符。出现这些字符就会触发语法解析,所以文件
名中若包含它们,必须写成字符串(如 “foo:bar”、‘notes(1).md’)。
citelib/src/fileset.pest:48lib/src/fileset.pest:50lib/src/fileset.pest:57
另一方面,bare_string 规则只允许字母、数字、空格以及 + - . @ _ * ? [ ]
/ \ 这类有限的 ASCII 字符,所以像 # $ % ^ = ; < > 等其它 ASCII 标点目
前也被当成“元字符”——它们虽然没有特殊语义,但同样需要通过加引号或使用
file:"path#name" 这类函数式写法来转义。citelib/src/fileset.pest:30
需要注意:Glob 的元字符(*, ?, [ ], { },以及在 Unix 上的 \)只在 glob
:、prefix-glob: 模式里被视为通配用途,文件集语法本身并不把它们当成需要
额外引用的“元字符”;文档里提到“glob 字符不是元字符”说的就是这个区别。
citedocs/filesets.md:19lib/src/fileset.rs:289
- 什么时候可以不用写引号
中间没有特殊字符的时候,这里的特殊字符包含
空格 ~ & | ()
template
Templating language - Jujutsu docs
使用 git 的理由(不使用 jujutsu 的理由)
一个重要的理由:git-submodule
现在的 jj 的子模块支持还非常非常有限
另一个理由:git-lfs
Jujutsu 目前对 Git LFS 不友好的根本原因是两者的工作方式不兼容:
- Git LFS 依赖 Git 工作区里的 clean/smudge 过滤器和一些钩子(如 pre-push)来把占位符文件和真实大文件互换,并在 push/fetch 时自动上传下载二进制内容。
- Jujutsu 虽然可以把 Git 当作存储后端,但它不会像 git checkout 那样触发这些过滤器,也不会自动调用 git lfs fetch/push。结果是在 Jujutsu 工作区里看到的只是 LFS
的文本指针文件,而不是大文件本身。 - 同理,从 Jujutsu 工作区写回库时也不会自动把新大文件交给 git-lfs 处理,从而导致 push 时缺少实际的 LFS 对象。
在官方实现里还没有补上这层集成,所以整体体验就显得“不友好”。暂时的做法通常是:在需要 LFS 的仓库里继续用 Git 工作区处理大文件相关的操作,或在 Jujutsu 之外手动
运行 git lfs 命令。
Insight
.jj 目录中有什么?
.jj/ 是 Jujutsu 在每个仓库下的内部数据库,按照不同“后端”分类保存所有必要的元数据。主要内容如下:
- repo/store/:
存放提交、树、文件等对象本身。store/type 声明使用的提交后端(GitBackend 或 SimpleBackend)。
Git 后端还会在 store/extra/ 里保存 Git 模型没有的额外信息,例如 change-id 和 predecessors 表,并在 refs/jj/keep/ 中挂 refs 防止 GC 删除。
<~/Dev/jj/docs/technical/architecture.md> - repo/op_store/:
记录 operation log,即每次修改仓库(log、rebase、new、snapshot working copy 等)后的“操作对象”和对应的 view(包含 bookmark 位置、工作区的当前提交等)。
jj evolog、jj undo、jj op log 等命令都依赖这里的数据。
</Dev/jj/docs/operation-log.md> </Dev/jj/docs/technical/architecture.md> - repo/op_heads/:
保存 operation log 的最新 head(可能不止一个,用于无锁并发),以便下次命令能从最新操作开始。<~/Dev/jj/docs/technical/architecture.md> - repo/index/:
commit 索引后端,用于加速 revset 查询、ancestor 计算等(例如 jj log、jj describe)。
同样有 index/type 指示具体实现。
<~/Dev/jj/docs/technical/architecture.md> - workspaces/(或与 workspace 相关的元数据):
跟踪各工作区的 working copy 状态、snapshot 自动保存等;具体格式由工作区后端决定,但路径也在 .jj/ 之下。
<~/Dev/jj/docs/operation-log.md> - 其它辅助文件:
例如 config.toml(仓库级配置)、git/(如果是 Git-backed repo,会有嵌套 Git 仓库存放真实对象)、缓存与锁文件等。
简而言之,.jj/ 承担了 Git 的 .git/ 类似的职责:对象存储、操作日志、索引、工作区快照、配置等全部集中在这里。
只要 .jj/ 完整保留,Jujutsu 就能还原所有 change、operation、evolog 信息。
把 JJ 用于笔记的焦点管理的思考
理论上一个commit用来表示一个动机是最好的
它可以把一些零散的块合并在一起,这种表示形式是一般表示形式得不到的
其实这个想法并不太行
因为我用的是 org-roam 数据库的状态如果不和当前笔记库匹配的话会出很多问题
虽然可以直接重建数据库来覆盖就是了,这样就有点麻烦。。。
实际上不仅仅是重建数据库的问题啊
还有 org-element-use-cache 这玩意的问题
如果设成 t,checkout 之后直接给你死循环卡住
如果什么时候卡住了,就设成 nil 重新跑一遍,之后再调回来就没问题了
倒可以在 majutsu module 里配置一下?暂时不管这个问题
我如果想要解决这个问题,我需要在edit之后手动刷新一下 cache 就好了
这应该是比较重要的功能,在操作之后对操作涉及到的文件做一下回调,然后在回调里写一下刷新 org 文件的 org-element cache 即可
(with-current-buffer (find-file-noselect "/path/to/file.org")
(org-element-cache-reset))在这个基础上,还有 org-roam 层面的问题,需要运行一下 org-roam-db-sync
Chat: jj 可能很适合用来作为记录个人笔记的综合日志? ARCHIVE
JJ with Emacs
On Jujutsu and Magit · Antoine Martin
对于 magit 高手而言 jj 的优势可能只有冲突处理?
期待 jj 有类似于 magit 的在 emacs 中的完美集成
不用期待了,我出手了
vc-jj
我发现一个比较崩溃的事情,这小东西的功能主要集中在 C-x v(这是 vc 默认使用的前缀)
然后主要提供的是一些基础的功能
jj-mode
完全的初级阶段,无法使用的状态
它的作用只有好看的持久化 status
jujutsushi
相当简陋,可以当他不存在
jujutsu.el
⚠️ Pre-alpha - Experimental ⚠️
上次提交还在十个月前
不依赖 magit
而是用自研的轻量虚拟 DOM(nx.el)驱动的 UI 架构
Chat: 不推荐 jujutsu.el ARCHIVE
Community-built tools
lazyjj
貌似和 lazygit 不是一个开发者
jjui
它竟然能有 1k star
Issues
-
关于冲突解决的元数据问题
jj-vcs/jj#8196 FR: See file content of either side of a conflict
这相当有道理
jj-vcs/jj#5307 FR: Add a language for selecting a tree
大概是想要通过这个来实现 -
关于 diff/merge tool 的配置设计
jj-vcs/jj#1285 Redesigning config for diff and merge editors -
刚刚提出的 jj-grep FR
jj-vcs/jj#7940 FR: `git grep` like command -
jj-diff 对空格的内联高亮行为
jj-vcs/jj#7809 FR: Possibility to revert color only for whitespace-only diff-… -
缓慢的 checkout
jj-vcs/jj#7685 Poor UX when operations are performing slowly in large repo
PRs
jj-vcs/jj#7754 docs: Add Jujutsu for Git experts by jennings
Contribute
Guidelines and “How to…?” - Jujutsu docs
这个贡献文档非常符合 jj 的风格啊😲
jj Dev Notes
这是我的第一个项目笔记试点
Inbox
这里有本地包导入的方式
</home/disk/Dev/jj/Cargo.toml>
这个就是 split 里的视图
</home/disk/Dev/jj/Cargo.toml>
怎么还能依赖 nix 的
</home/disk/Dev/jj/Cargo.toml>
依赖
</home/disk/Dev/jj/Cargo.toml>
cargo insta 是什么?
</home/disk/Dev/jj/docs/cli-reference.md>
Arch Linux - cargo-insta 1.43.2-1 (x86_64)
Cargo Insta | Insta Snapshots
不懂,运行会直接卡住
jj 如何 apply patch?
补丁应用流程
-
交互式“apply patch”发生在内置 diff/merge 工具里:edit_diff_builtin() 先按照 matcher 生成 scm_record::File 列表,让用户挑选要保留的 hunk,
结束后把结果连同左右树交给 apply_diff_builtin()
</home/disk/Dev/jj/cli/src/merge_tools/builtin.rs> -
apply_diff_builtin() 的策略是“先回滚,再套用选择”:它先用左树内容覆盖所有候选路径,确保后续操作基于基准版本,然后调用 apply_changes()
只把被勾选的片段写回。写文件时直接调用 store.write_file() 生成新的 TreeValue::File,若原文件是冲突(merge value),则通过
conflicts::update_from_content() 把用户编辑过的标记文件重新解析成冲突结构。
</home/disk/Dev/jj/cli/src/merge_tools/builtin.rs> -
apply_changes() 逐文件判断勾选的是模式变更、文本 hunk 还是二进制替换:文本会把 scm_record 收集的行重新串成内容写入对象存储,二进制/清空文
件走对应分支,未勾选则保持左树版本。这一层也负责根据勾选情况删除文件或只更新可执行位。
</home/disk/Dev/jj/cli/src/merge_tools/builtin.rs>
生成 Git Patch
- CLI 的 DiffFormatArgs 定义了 —git 和 —tool,而 —tool=:git 只是 —git 的别名:解析阶段会把 —git 或 —tool=:git 映射成 DiffFormat::Git。因此任何走 DiffFormatArgs 的命令(diff/show/log/evolog/…)都能输出 Git patch,不限于 jj diff。cli/src/diff_util.rs:101-268cli/src/diff_util.rs:328-366
- 如果不想每次打旗子,可以把 ui.diff-formatter = “:git” 写进配置;default_diff_format() 会读取这个设置,把默认长格式换成 Git diff。cli/src/diff_util.rs:368-389
- 一旦选择了 Git 格式,DiffRenderer::show_diff() 会切到 show_git_diff(),它为每个文件写出 diff —git, index, ---
+, @@ 等标准头,并用 diff.git.context—context 控制上下文行数,生成完全兼容 git apply 的补丁。这个渲染器由 jj diff 等命令通过 workspace_command.diff_renderer_for() 统一创建。cli/src/commands/diff.rs:203-231cli/src/diff_util.rs:419-551cli/src/diff_util.rs:1551-1725
changelog
v0.35.0
</home/disk/Dev/jj/CHANGELOG.md>