jujutsu

几年前我就听说过 jujutsu 的大名,但是因为概念太复杂了,所以没有入坑。之后陆续一直看到有人推荐,尝鲜之心已经藏不住了;加上现在的 AI 搜索发达,降低了我的试错成本,于是尝试入坑。

jujutsu 是一个兼容 Git 的 VCS (Version Control System)。jujutsu 不是 git command wrapper 而是一个新的 VCS,比起 Git 有一些新奇的设计理念。简要介绍一下 jj 的设计理念:

安装与其他生态

我使用 cargo binstall 进行安装:cargo binstall --strategies crate-meta-data jj-cli。

其他生态:

避雷:

入门

编辑配置

假设本文的读者都是开发者 + Git user,事先编辑好配置文件也是一个不错的选择。jj config path --user 可以查看配置文件的路径;虽然官方推荐使用平台默认路径,但是我个人还是更喜欢把配置放在 ~/.jjconfig.toml,毕竟 windows 的 AppData\Roaming 实在是太邪恶了,我无法接受。

此处是我的配置,仅供参考。

基本概念

在一个 jj repo 里执行 jj(默认执行为 jj log),可以看到类似下面的东西:

sh
❯ jj
◉  mzqpoqyq <lxl66566@gmail.com> [2 minutes ago] 09ff4381
...

前面的字母 mzqpoqyq 表示变更 id,不会随着你的本地代码修改而变化;而右边的 09ff4381 代表 Git commit id,如果对 commit 进行了变更则会发生变化(例如 @ 工作区也是一个虚拟 commit,所以随着你的开发,它的 commit id 是会不断变化的)。

初步使用

sh
# --colocate 表示创建一个兼容 Git 的仓库。这个指令也可以在现有 git 仓库执行,以初始化 jujutsu。
# jujutsu 默认就是开启 --colocate:https://docs.jj-vcs.dev/latest/github/#updating-the-repository
jj git init --colocate

# 前文提到,jj 的开发模式是直接在提交树上进行开发。`jj new` 的意思是以 [args] 为父节点,创建一个虚拟 commit(`@`)进行开发
# `trunk()` 代表当前的主分支(main, master 或任何名称)。
jj new 'trunk()'

# 为当前的工作区 `@` 添加一个描述。(不进行 commit,多次添加描述会覆盖之前的描述)
jj desc -m '123'

# 相当于执行了 desc + new,将当前的更改保存到一个新的 commit
jj commit -m '123'

# 推送到远端。jj 的 push 默认就使用 --force-with-lease,并且智能识别你是否真正修改了内容,强行同步到远端。
# 同样的,jj 也有本地 bookmark 和远端 bookmark(例如 xxx@origin)的概念。上传之前,我们需要:
# 1. 创建或移动本地 bookmark 到你想要的位置
# 2. 如果没有设置本地 track 远端,需要设置一下 track
#    - `jj bookmark track branch_name --remote=origin`
# 3. 然后再进行推送
jj git push --tracked

# jj 的 push 默认是指定 --remote=origin。如果需要为非 origin 的上游进行 push 需要手动指定,否则报 Nothing changed.
jj git push --tracked --remote=xxx

# 移动最近的 bookmark 到 @,因为 bookmark 不会像 git branch 那样自己动。
jj bookmark advance

# 为 @ 添加 bookmark
jj bookmark set -r @ xxx

# 丢弃当前的更改,回到上一个 commit 的状态。相当于 `git reset --hard HEAD`。
jj abandon

# 移除 commit 节点
jj abandon xxx1 xxx2 xxx3

# 将 @ 的更改合并到上一个 commit,类似我的 snipets 里的 gfixup
jj squash

进阶

sh
# jj new 里可以指定任意数量的父级分支,相当于更强大的 git merge
jj new xx1 yy2 zz3

# 从某个提交里恢复文件,相当于 git checkout xxx -- filename
jj restore --from xxx filename

# 让某个 bookmark 不会被上传到 remote
jj bookmark forget xxx

# 类似 https://github.com/tummychow/git-absorb
# 并且由于 jj 对 conflict 的宽容性,它可以将语义模糊的冲突先保留,等待日后解决。只会改变 mutable 的 commit。
jj absorb

# 仅将当前的编辑内容(@)rebase 到其他节点上。注意,-r @ 是必须的!
jj rebase -r @ -o xxx

# 如果不带上 -r @,则是向上追溯到分支起点,把整条 commit 链给 rebase 到目的节点上。
jj rebase -o xxx

# 基本等价于 git cherry-pick,将一个 commit 拉到 @ 之前
jj duplicate -r xxx --insert-before @

其他技巧

适配 Git 工作流

一般来说,使用 jujutsu 时仓库大部分时间都处于 Git 的 detached HEAD 状态,虚拟 commit @ 也不会被 Git 读取。如果你有一些强制性的、基于 Git 的本地工作流(很想吐槽我的公司),就会有点难受。

这样需要具体情况具体讨论。对我的情况而言,公司的工作流依赖 HEAD 位置,所以我可以将 Git HEAD 移动到我想要的地方而不修改工作区文件:

sh
git symbolic-ref HEAD "refs/heads/my_branch"

然后再执行工作流即可。jujutsu 不依赖 Git 的 symbolic HEAD,因此这个做法不会对 jj 产生影响。

一键清理已经合入 master 的分支

sh
jj bookmark list -r "::master ~ master" | cut -d: -f1 | xargs jj bookmark delete

清理空提交

sh
jj abandon -r "mine() & empty() & mutable()"

虽然我们可以用这个命令清理空提交,但其实很多空提交是 immutable 的。

总结

jujutsu 不适合什么样的用户:

  1. 需要使用一些特殊的、依赖 Git 的工作流;
  2. 不想完全抛弃 git,希望与 git 混合使用的用户[1]:
    • 依赖 commit hook 或私有 git hooks;
    • 依赖 git 暂存区的一些操作,例如在 vscode 按照代码行添加暂存区,用于快速提取所需的变更;
  3. 喜欢往工作区扔一些 untracked 的 cache 文件,并且在切分支的时候希望保留这些文件;
  4. 深度依赖 git 的成熟工具,例如 vscode Git Graph 的。

jujutsu 适合什么样的用户:

  1. 喜欢尝试新玩意并且愿意学习;
  2. 喜欢 Rust 并且觉得被 Rusty tools 包围了好幸福~~~的;

我用了一段时间,实在是不契合我的工作习惯,所以跑回 git 了。

external

  1. thoughtpolice/jjconfig.toml
  2. Jujutsu VCS - Getting Started Guide
  3. A Better Merge Workflow with Jujutsu

  1. jujutsu 跟 git 混用,也许可能导致你的 git 仓库历史损坏:我遇到过疑似 jujutsu 导致的 broken link 和 missing blob,虽然我没有任何证据可以证明这一点。 ↩︎