Git互換の次世代バージョン管理システムJujutsuを試す

Jujutsuとは?
Rust製の Git 互換 VCS「Jujutsu(jj)」は、既存の Git リポジトリをそのまま扱えるため、今のホスティングやワークフローを崩さず導入できます。
変更は自動でコミットされ、さらにリポジトリ全体の操作を記録する「オペレーションログ」により、任意のタイミングで undo/redo が可能で作業を安全に巻き戻せます。
ステージングエリアや固定ブランチを排した設計で
jj split
やjj squash
による履歴整形がワンコマンドで行え、レビュー前に“完璧な履歴”を簡単に作れます。
インストール
早速インストール&セットアップを行っていきたいと思います。
Installation and setup - Jujutsu docs
Macでの動作確認を行なっている為、Homebrew でインストールします。
$ brew install jj
# ....
$ jj --version
jj 0.28.2
実際の使い方に関しては公式ドキュメントにもある👇のドキュメントを参考に進めていきたいと思います。

リポジトリ作成
早速適当なリポジトリを作成してみたいと思います。
$ mkdir jj-example
$ cd jj-example
$ jj git init
Initialized repo in "."
この時点で .jj
ディレクトリが作成され中身は以下の様な構成になっています。
$ erd -H -y inverted -L 3 ".jj"
136.0 KiB .jj
124.0 KiB ├─ repo
84.0 KiB │ ├─ store
68.0 KiB │ │ ├─ git
8.0 KiB │ │ ├─ extra
4.0 KiB │ │ ├─ git_target
4.0 KiB │ │ └─ type
20.0 KiB │ ├─ index
8.0 KiB │ │ ├─ segments
8.0 KiB │ │ ├─ operations
4.0 KiB │ │ └─ type
12.0 KiB │ ├─ op_store
4.0 KiB │ │ ├─ views
4.0 KiB │ │ ├─ type
4.0 KiB │ │ └─ operations
4.0 KiB │ ├─ submodule_store
4.0 KiB │ │ └─ type
4.0 KiB │ └─ op_heads
4.0 KiB │ ├─ type
- │ └─ heads
12.0 KiB └─ working_copy
4.0 KiB ├─ tree_state
4.0 KiB ├─ type
4.0 KiB └─ checkout
.jj/repo/store/git
内にまるッとgitの構成ファイルが含まれているようでした 👀
$ ls .jj/repo/store/git
config description HEAD hooks info objects refs

実際にリポジトリ上で作業してみる
まずは git status
に相当する jj st
を作成したばかりのリポジトリで実行してみます。
$ jj st
The working copy has no changes.
Working copy (@) : unkvksyx ff0e2d03 (empty) (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
見慣れない感じですが、何も差分がない状態なのは分かります。
早速node.jsの簡単なアプリを作っていきたいと思います。
$ node -v
v22.9.0
$ npm -v
11.3.0
$ npm init -y
Wrote to /Users/..../jj-example/package.json:
{
"name": "jj-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs"
}
この時点で jj st
を実施してみます。
$ ls
package.json
$ jj st
Working copy changes:
A package.json
Working copy (@) : unkvksyx 11c38355 (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
Working copy
の (empty)
がきえてハッシュ値が変化してます ff0e2d03
→ 11c38355
ここで jj log
で履歴を見てみます。
$ jj log
@ unkvksyx (no email set) 2025-05-xx xx:xx:xx 11c38355
│ (no description set)
◆ zzzzzzzz root() 00000000
先ほどの変化したハッシュ値 11c38355
が最新になっています。
ここで package.json
を修正して jj st
を実施してみます。
$ jj st
Working copy changes:
A package.json
Working copy (@) : unkvksyx 147002bb (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
先ほどとハッシュ値が変化しました 147002bb
jj log
を実施すると
$ jj log
@ unkvksyx (no email set) 2025-05-xx xx:xx:xx 147002bb
│ (no description set)
◆ zzzzzzzz root() 00000000
最新のハッシュ値が 11c38355
から 147002bb
になっています。
この様に変更がある度にコミットが上書きされ新しいハッシュ値になっている挙動の様です。
ちなみに置き換え前のコミットは jj evolog
で確認する事ができます。
$ jj evolog
@ unkvksyx (no email set) 2025-05-xx xx:xx:xx 147002bb
│ (no description set)
○ unkvksyx hidden (no email set) 2025-05-xx xx:xx:xx 11c38355
│ (no description set)
○ unkvksyx hidden (no email set) 2025-05-xx xx:xx:xx ff0e2d03
(empty) (no description set)

コミットに分かりやすい説明文をつける
最新のコミット値 147002bb
に対して jj describe
で説明文を付けてみます。
$ jj st
Working copy changes:
A package.json
Working copy (@) : unkvksyx 147002bb (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
$ jj describe -m "hello world"
# ...
$ jj st
Working copy changes:
A package.json
Working copy (@) : unkvksyx 5d847a45 hello world
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
(no description set)
が hello world
に変わっています。
ここで jj evolog
を実施すると
$ jj evolog
@ unkvksyx (no email set) 2025-05-xx xx:xx:xx 5d847a45
│ hello world
○ unkvksyx hidden (no email set) 2025-05-xx xx:xx:xx 147002bb
│ (no description set)
○ unkvksyx hidden (no email set) 2025-05-xx xx:xx:xx 11c38355
│ (no description set)
○ unkvksyx hidden (no email set) 2025-05-xx xx:xx:xx ff0e2d03
(empty) (no description set)
新しいコミットが追加されてます。

新しい履歴を始める
jj new
コマンドで新しい履歴を始めることが出来ます。
$ jj new
Working copy (@) now at: uopsnzms fb4ee9a9 (empty) (no description set)
Parent commit (@-) : unkvksyx 5d847a45 hello world
$ jj log
@ uopsnzms (no email set) 2025-05-xx xx:xx:xx fb4ee9a9
│ (empty) (no description set)
○ unkvksyx (no email set) 2025-05-xx xx:xx:xx 5d847a45
│ hello world
◆ zzzzzzzz root() 00000000
新しく uopsnzms
が作成され、そこにコミットが積まれていく様です。
何か編集して jj evolog
を確認すると uopsnzms
にコミットが積まれているのが分かります。
$ jj evolog
@ uopsnzms (no email set) 2025-05-xx xx:xx:xx 1ecef4e8
│ (no description set)
○ uopsnzms hidden (no email set) 2025-05-xx xx:xx:xx fb4ee9a9
(empty) (no description set)

undoで修正を元に戻す
ここで package.json
を修正します。修正diffは以下になります。
$ jj diff
Modified regular file package.json:
1 1: {
2 2: "name": "jj-example",
3 3: "version": "1.0.0",
4 4: "description": "jj-example",
5 5: "main": "index.js",
6 6: "scripts": {
7 7: "test": "echo \"Error: no test specified\" && exit 1"
ここで jj undo
して修正を元に戻してみます。
$ jj undo
Working copy (@) now at: uopsnzms fb4ee9a9 (empty) (no description set)
Parent commit (@-) : unkvksyx 5d847a45 hello world
Added 0 files, modified 1 files, removed 0 files
$ jj st
The working copy has no changes.
Working copy (@) : uopsnzms fb4ee9a9 (empty) (no description set)
Parent commit (@-): unkvksyx 5d847a45 hello world
変更が無くなって先ほど jj new
した直後に戻っています。