Jujutsu (jj) Git 兼容性全解:Git 后端、Colocated 工作区与格式映射机制
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
Jujutsu(jj)提供了两种存储提交的后端,其中一种直接建立在标准 Git 仓库之上,意味着你可以与 Git 用户协作,而对方甚至察觉不到你没有使用git命令行。本篇以官方文档 git-compatibility.md 为主体,结合当前仓库源码,系统讲解 jj 对 Git 各项特性的支持矩阵、三类建仓流程、Colocated(同置)工作区的工作原理与转换方法,以及提交元数据在 Git 对象格式中的映射细节;读完之后,你能够独立在 jj 与 Git 之间切换协作,并理解二者数据互通的底层机制。
双后端架构与 jj git 命令族
Jujutsu 有两个提交存储后端:默认的后端使用 jj 自有的存储格式,而 Git 后端则直接复用真实的 Git 仓库作为对象库,这正是 Git 兼容性的基础。通过jj git init或jj git clone创建的仓库都采用 Git 后端,仓库中会同时存在.jj与.git两个目录。
所有 Git 交互都收敛在jj git命令族之下。从 git 命令入口 的GitCommand枚举可以看到完整的子命令清单:clone、colocation、export、fetch、import、init、push、remote与root。官方建议通过jj help git查看该命令族的帮助,用jj help git push(或更简短的jj git push -h)查看具体子命令的帮助。
支持的 Git 特性清单
以下列表描述了 Jujutsu 与各项 Git 特性的兼容程度(更细的工作流差异对比见 Git 对比文档):
| Git 特性 | 兼容程度 | 说明 |
|---|---|---|
| 配置(Configuration) | 部分支持 | 仅读取 Git 的两类配置:remote 配置([remote "<name>"],在未通过 CLI 显式指定分支时,简单的 fetch refspec 会被遵守,底层调用git完成远端操作);core.excludesFile |
| 认证(Authentication) | 支持 | 远端操作底层使用git,因此凭证机制完全一致 |
| 分支(Branches) | 支持 | 详见 bookmarks 文档 及本文“分支”一节 |
| 标签(Tags) | 部分支持 | 可按名称检出带标注/轻量标签指向的提交;可创建轻量标签,但不能创建带标注标签 |
.gitignore | 支持 | 支持.gitignore、.git/info/exclude以及core.excludesFile。由于几乎所有jj命令都会对工作副本做快照,新加入忽略规则的文件需要运行jj file untrack才能从工作副本提交中排除,建议尽早配置忽略规则。实现是 jj 的原生实现,与git行为不一致时应反馈为 bug |
.gitattributes | 不支持 | 上游已立项(至少支持eol属性) |
| Hooks | 不支持 | 上游有专门跟进 pre-commit 集成的议题 |
| 合并提交 | 支持 | 包括八爪鱼合并(2 个以上父提交) |
| 游离 HEAD(Detached HEAD) | 支持 | jj 天然支持匿名分支,这就是自然状态 |
| 孤儿分支(Orphan branch) | 支持 | jj 有一个虚拟根提交,充当 Git 所谓“根提交”的父提交 |
| 暂存区(Staging area) | 忽略 | 暂存区会被忽略,例如jj diff展示的是 Git HEAD 到工作副本的 diff;无暂存区也能满足相应使用场景(见 Git 对比文档的索引一节) |
| 垃圾回收(GC) | 支持 | 在 Git 仓库里运行git gc应该是安全的(未做完整测试,建议先备份整个工作区);jj 自身的数据结构目前还没有垃圾回收与重打包 |
| 裸仓库(Bare repositories) | 支持 | 用jj git init --git-repo=<path>可以创建由裸 Git 仓库支撑的仓库 |
| 子模块(Submodules) | 不支持 | 不会出现在工作副本中,但也不会丢失 |
| Partial clones | 不支持 | — |
| 浅克隆(Shallow clones) | 部分支持 | 浅克隆的边界提交以虚拟根提交为父;加深或完全去浅(unshallow)尚不支持,会造成问题 |
| git-worktree | 不支持 | jj 有原生的多工作副本支持(单仓库多 working copy),见jj workspace命令族 |
| Sparse checkouts | 不支持 | jj 有原生的 sparse checkout 支持,见jj sparse命令 |
| 签名提交(Signed commits) | 支持 | 可通过配置自动签名(见 config.md 的 commit signing 一节),或使用jj sign命令 |
| Git LFS | 不支持 | 上游已立项跟进 |
三种建仓方式
创建空仓库
jj git init <name>这会创建一个 colocated 的 Jujutsu 工作区:目录下同时生成.jj与.git。
由现有 Git 仓库创建
jj git init --git-repo=<path to Git repo> <name>该仓库的行为类似 Git worktree:工作副本文件与工作副本提交的记录相互独立,但提交在两个仓库中都可以访问。之后需要双向同步:
jj git import:把 Git 仓库中的变更导入 Jujutsu 仓库;jj git export:把 Jujutsu 仓库中的变更导出回 Git 仓库。
克隆远端 Git 仓库
jj git clone <URL> [<destination>] # 例如 jj git clone https://github.com/octocat/Hello-World默认远端名为origin,可通过--remote <remote name>指定其他名字。
Colocated Jujutsu/Git 工作区
Colocated 工作区是一种 Jujutsu/Git 混合工作区,也是jj git init和jj git clone的默认形态。此时 Git 仓库与 Jujutsu 工作区共享同一个工作副本,Jujutsu 会在每一条jj命令执行时自动完成与 Git 仓库的 import/export。
这种模式在构建工具等外部工具“假设 Git 仓库必须存在”的场景下非常方便。
在 colocated 工作区里可以任意顺序混合使用jj与git命令,但更易于掌握节奏的做法是:主要用只读git命令观察仓库,用jj发起修改。原因是 jj 没有“当前跟踪分支”的概念,jj命令通常会把 Git 仓库置于“detached HEAD”状态;在执行会修改仓库的 Git 命令前,可能需要先用git switch告诉 Git 当前分支应该是什么。
混合使用的回退手段:jj undo与jj op restore可以撤销修改性git命令的结果;在jj op log中,git 引起的变更会显示为一条 “import git refs” 操作。
关闭 colocation 有两种方式:jj git init/jj git clone加--no-colocate参数,或设置配置git.colocate = false。从 CLI 内置配置默认值 可以看到当前colocate的默认值是true。关闭后,仓库数据仍大多存储为 Git 格式,但 Git 仓库会隐藏在.jj目录的一个子目录中;除非显式执行jj git import和jj git export,那个 Git 仓库要么没有任何分支(连 main 都没有),要么分支与 jj 的 bookmarks 不同步。
为什么有时要禁用 colocation
官方文档明确列出了 colocated 模式的几个缺点:
- 交叉命令引发冲突状态:
jj与git命令交错执行会增加分支冲突或“冲突(即分叉)change id”出现的概率——这些状态不会丢数据,但会造成困扰。且这种交错可能不知不觉发生,例如某些 IDE 会在后台自动定时执行git fetch。 - 大规模 ref 下变慢:分支或其他 ref 数量非常多的 colocated 工作区中,由于每条命令都会自动执行
jj git import,命令可能明显变慢。可以不定期运行jj util gc缓解(该命令包含对 Git refs 的打包)。 - Git 工具读不懂含冲突文件的提交:
jj在工作副本中用冲突标记渲染这些文件,但仓库内部存储的是不可读形式,Git 工具经常看到的是后者。 - 冲突分支在 Git 侧位置不一致:当 jj 分支处于冲突状态时,Git 仓库中该分支的位置与冲突位置中的某一个不一致,其在 git 中的状态会被标记为属于名为 “git” 的远端,例如
branch@git。 - 忽略 Git 的中间状态:Jujutsu 会忽略 Git 的暂存区,也不理解 Git 表示的合并冲突、未完成的
git rebase状态以及其他较少见的仓库状态。 - 并发鲁棒性较弱:若通过 NFS 或 Dropbox 等共享仓库,colocated 工作区对并发问题的抗性更差;这类用法目前尚未得到充分测试。
- 偶发的指针错位 bug:
jj与修改性git命令交错时可能仍有 bug,通常表现为分支指针落到了错误位置。维护方正在处理已知问题,欢迎报告新发现。
在 colocated 与非 colocated 之间转换
一个由 Git 仓库支撑的 Jujutsu 工作区内部含有完整的 Git 仓库,可以用jj git colocation命令组在两种形态间转换。
# 查看当前 colocation 状态 jj git colocation status # 转换为 colocated 工作区 jj git colocation enable # 转换为非 colocated 工作区 jj git colocation disable这些子命令的实现见 colocation.rs。从 status 子命令源码 可以看出,它会通过is_colocated_git_workspace判断当前形态,并打印工作区名字和“Last imported/exported Git HEAD”——即最后一次导入/导出时 Git HEAD 的位置(非 colocated 工作区中该值通常缺席,但会打印实际状态以便排障);随后给出下一步建议,若工作区由外部 Git 仓库支撑则提示“无法启用 colocation”。
enable 的实现 自动化的就是下面这套手动流程:
# 让 Git 忽略 .jj 目录 echo '/*' > .jj/.gitignore # 移动 Git 仓库 mv .jj/repo/store/git .git # 告诉 jj 去哪里找它(Windows 上不要用这一行!见下文) echo -n '../../../.git' > .jj/repo/store/git_target # 将 Git 仓库设为 non-bare 并设置 HEAD git config --unset core.bare # 促使 jj 更新 .git/HEAD 指向工作副本提交的父提交 jj new && jj undoWindows 注意:Windows 上
echo会追加行尾换行,导致jj抱怨git_target的内容。应改用:Set-Content -Path .jj/repo/store/git_target -Value ../../../.git -NoNewLine
对照 disable 的实现 可以看到反向操作:把.git移回.jj/repo/store/git、将其设为 bare、把git_target内容重置为git、删除.jj/.gitignore,并移除 git HEAD 引用。源码中maybe_add_gitignore辅助函数(git/mod.rs)则负责在 colocated 工作区写入内容为/*的.jj/.gitignore,防止 Git 跟踪 jj 的仓库数据。相关行为有专门测试覆盖,见 test_git_colocation.rs、test_git_init.rs 与 test_git_clone.rs。
分支映射
原文档中该节尚标注为 TODO。就当前仓库而言,jj 的“bookmarks”在功能上对应 Git 的分支概念,二者的互操作细节见 bookmarks 文档;在 Git 对象层面,与远端标签相关的引用存放在refs/jj/remote-tags/命名空间下(见 git.rs)。
格式映射细节
这一节描述 jj 数据与 Git 对象格式之间的具体对应关系,是理解“Git 工具看到什么”的关键。
- 路径编码:路径默认按 UTF-8 处理,官方目前不打算支持其他编码的路径。
- 防 GC 保护:由
jj创建的提交会带一个以refs/jj/开头的 ref 以防被垃圾回收。从 git_backend.rs 源码看,该命名空间具体是refs/jj/keep/,jj 会在导入/导出时为活跃提交重建这些 ref,并清理指向已不可达提交的旧 ref。 - Git 无法承载的元数据:Change ID、冲突信息等无法表示在 Git 提交中的元数据,存储在 Git 仓库之外(当前位于
.jj/store/extra/)。 - 冲突提交的表现:含冲突的提交无法在 Git 中直接表示。它们在 Git 提交中表现为一组名为
.jjconflict-base-*/与.jjconflict-side-*/的“根目录”。注意这种表示的目的仅是防止相关 tree 被 GC,权威信息仍在上文所述的 Git 仓库外存储中。只要用jj命令操作就不会察觉这些路径;但若用git switch检出其中一个含冲突的提交,工作副本里会看到这些目录,随后运行jj status生成的快照会包含它们,看起来像替换了仓库中所有其他路径——此时通常需要用jj abandon回到未解决冲突的状态。 - Change ID 提交头:Change ID 以反转的十六进制编码存入 git 提交头
change-id(常量定义见 git_backend.rs)。这是一个非标准头,并非所有 git 工具都会保留它:git commit --amend会保留,而 rebase 不会;GitHub 等主要托管平台通常会保留。该头自0.30.0版本起由jj在创建提交时默认写入,可通过配置git.write-change-id-header关闭;从 内置配置默认值 与 配置解析 看,其默认值为true。 - 验证头是否存在:
git show与git log都不打印这个头。用 git 自身验证时可用:
git cat-file -p <commit ref>输出中若带有change-id <十六进制>一行,即表明头存在。该行为的往返正确性在单元测试中有断言,见 test_git.rs 中 “change-id header did not roundtrip” 相关用例。
小结与适用前提
- 与 Git 用户协作的入口是
jj git命令族:init(空仓库 /--git-repo基于既有仓库)、clone(远端 URL)、fetch/push(远端同步)、import/export(非 colocated 模式下手工同步)、remote(远端管理)、root/colocation(仓库位置与形态管理)。 - 默认形态是 colocated:共享工作副本、每条
jj命令自动双向同步,适合希望工具链“无感”的场景;对分支/冲突敏感或 ref 数量极多的场景,可考虑--no-colocate/git.colocate = false,并用jj git import/jj git export显式同步。 - Git 侧无法看到 jj 的 Change ID(除非提交头被保留)与冲突的内部表示,跨工具操作含冲突的提交时要格外小心。
- 以上行为均以当前仓库文档与源码为准;特性支持矩阵中的“部分支持/不支持”条目(如
.gitattributes、hooks、LFS、子模块)会随版本演进,升级后建议以仓库内文档与 CHANGELOG 复核。
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考