![[柔術] Jujutsu - Git互換のVCSを使ってみる [呪術]](https://devio2024-media.developers.io/image/upload/f_auto,q_auto,w_3840/v1771462870/user-gen-eyecatch/h2cozajrerj8gp2gsexv.png)
[柔術] Jujutsu - Git互換のVCSを使ってみる [呪術]
Introduction
開発現場ではGitが事実上の標準となっており、多くの開発者にとって日常的なツールになっています。
非常に優れたVCSですが、不満がないわけではありません。
例えば↓など。
git add忘れ- rebase地獄。よくわからなくなって何度も --abort
- 作業途中で緊急タスク→とりあえず
git stashしてブランチ作って云々 - コンフリクトが発生すると解消するまで進めない
Gitの知識が当たりまえ(AIに任せてるが)になってるし、
基本のステージング〜コミット〜プッシュフローは強力ですが、
そもそも覚えることも多く、ちょっとしたミスで事故ることもあったりします。
そこで今回紹介するJujutsuです。
Jujutsu(jj)?
Jujutsu(jj)は、GoogleのMartin von Zweigbergk氏が開発した次世代のVCSです。
※現状、普通に検索すると呪術廻戦が多くヒットするため、jj-vcs/jjなどで検索してください
jjは「開発者が理解すべき概念の数を減らす」ことを設計思想としています。
Gitでは ワーキングツリー/ステージング/コミットそれぞれの状態を意識する必要がありますが、
jjではワーキングコピー自体が常にコミットとなります。
ファイルを編集し、次にjjコマンドを実行したタイミングで自動的にchangeに反映されるため、git add も git stash も不要です。
また、jjはGit互換です。
内部でGitリポジトリをバックエンドとして使用するので既存のGitリポジトリにコマンド1つで導入できます。
他のメンバーがjjの存在を知らなくても、自分のローカル作業はJujutsuで行うことが可能です。
本稿では、jjのセットアップから基本的な使い方について紹介します。
Environment
- MacBook Pro (14-inch, M3, 2023)
- OS: macOS 15.7
- jj: 0.38.0
Setup
macならHomebrewで簡単にインストールできます。
% brew install jj
・・・
% jj --version
jj 0.38.0
Gitと同様に、ユーザー名とメールアドレスを設定しておきましょう。
jj config set --user user.name "Your Name"
jj config set --user user.email "your@email.com"
設定は ~/.jjconfig.toml に保存されます。
既存GitリポジトリへJujutsuを導入する(colocateモード)
現在、既存のGitリポジトリでjjを使うcolocateモードが主な使用方法となっています。
この場合、colocateモードを使います。
cd /path/to/your-git-repo
jj git init --colocate
これだけで .git と .jj が共存し、git コマンドも jj コマンドもどちらも使える状態になります。
(VS CodeのGit連携もそのまま動作する)
Jujutsuが不要となったら.jj ディレクトリを削除するだけで元のGitリポジトリに戻れます。
% rm -rf .jj
なお、新しくリポジトリを作る場合は以下。
% mkdir my-project && cd my-project
% jj git init
Difference Between Git and Jujutsu
Jujutsuの使い方はJujutsu docsをみればわかります。
ここではいくつかのGitとJujutsuの違いについて解説します。
| concept | Git | Jujutsu |
|---|---|---|
| ステージング | git add が必要 |
なし(自動) |
| ワーキングコピー | ダーティな状態がある | 常にコミット |
| 識別子 | commit hash | Change ID |
| コンフリクト | 即座に解消が必要 | 後回しにできる |
| undo | git reflog |
jj undoで簡単 |
| リベース | 手動 | 自動 |
| ブランチ | 名前付きが基本 | 匿名が基本(bookmark) |
| stash | git stash で一時退避 |
不要 |
大きな違いとしては、jjにはステージングエリアがありません。
Gitでは「ファイル編集 → git add → git commit」という3ステップが必要ですが、
jjではファイルを編集した時点で自動的に現在のchangeに記録されます。
git add に相当する操作が存在しません。
Status
Git/jjのstatusとかlogをみる方法。
(初期化直後の状況)
# Gitの場合
% git status
% git log --oneline --graph
# Jujutsuの場合
% jj st # ステータス表示
The working copy has no changes.
Working copy (@) : wulzrslt fca394db (empty) (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
% jj log # ログ表示
@ wulzrslt hoge@classmethod.jp 2026-02-18 12:14:18 fca394db
│ (empty) (no description set)
◆ zzzzzzzz root() 00000000
Commit(Change作成)フロー
まずコード変更を適当にします。
% vim src/main.rs
この時点でjj stすると、
すでに自動的にchangeに記録されています。
% jj st
Working copy changes:
A src/main.rs
Working copy (@) : wulzrslt 60baeac0 (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
ファイルを修正したらそのワーキングコピーに対してメッセージを設定します。
% jj describe -m "Add main function"
jj describeは現在のワーキングコピーのコミットメッセージを設定・変更するコマンドです。
(Gitでいうと「git commit --amend -m "msg"」相当)
jjではワーキングコピー自体が常にコミットなので、
「メッセージを付ける=今のコミットを説明する」という感じです。
メッセージを設定したら、jj newで新しい空のchangeを作成して次の作業へ行きます。
jj new を実行した時点で新しい作業が開始されます。
% jj new
# `jj describe` + `jj new` の代わりに `jj commit -m "message"` でもOK
過去のコミットの修正
以下の状態とします。(jj newした直後)
% jj st
The working copy has no changes.
Working copy (@) : smvrxozl 77a9fd13 (empty) (no description set)
Parent commit (@-): wulzrslt eb57e123 Add main function
ここで過去コミット(wulzrslt)を書き換えます。
editで前のコミットに移動してファイルを修正。
% jj edit wulzrslt
Working copy (@) now at: wulzrslt eb57e123 Add main function
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
describeでメッセージつけてjj newですれば過去コミット修正完了です。
% jj describe -m "Add main function & println"
Working copy (@) now at: wulzrslt d3761b58 Add main function & println
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
% jj new
Working copy (@) now at: yvzmutyr 210954a0 (empty) (no description set)
Parent commit (@-) : wulzrslt d3761b58 Add main function & println
jjはwulzrsltの内容が変わったことを検知し、その子孫(smvrxozl)を自動リベースします。
smvrxozlは変更なしでdescriptionもなかったため、自動で破棄されました。
Gitの場合、過去コミットを変えたら手動で git rebase --continueします。
もしコンフリクトがあればrebaseが止まります。
jjの場合、コンフリクトがあってもリベース自体は完了します(コンフリクト状態でコミットに記録される)。
Gitでコンフリクト発生したらすぐ直さなければいけませんが、
jjの場合はコンフリクトが記録されるだけで、修正するタイミングは自由です。
操作の取り消し
jjは操作の取り消しが非常に楽です。
Gitだとreflogから履歴を探してreset --hardとかする必要がありますが、
jjならundoコマンド実行するだけです。
# 直前の操作を取り消し
% jj undo
redoコマンドはありませんが、以下のようにすれば任意の時点に戻せます。
# 操作履歴を見る
jj op log
# 特定の操作時点に戻す
jj op restore <Operation ID>
実例:コミットを入れ替える
実際にJujutsuでコミットの順序を入れ替えてみましょう。
Gitでは git rebase -i でエディタを開いて行を入れ替える操作ですが、Jujutsuではもっと直感的にできます。
準備:サンプルリポジトリを作成
% mkdir jj-reorder-demo && cd jj-reorder-demo
% jj git init
Initialized repo in "."
3つのchangeを作成します。
# Change 1: READMEを追加
% echo "# My Project" > README.md
% jj commit -m "Add README"
# Change 2: main.rsを追加
% echo 'fn main() { println!("Hello"); }' > main.rs
% jj commit -m "Add main.rs"
# Change 3: テストを追加
% echo '#[test] fn it_works() { assert!(true); }' > test.rs
% jj commit -m "Add tests"
ログを確認。
% jj log
@ txszzynm 2026-02-19 17:58:54 cfd62f33
│ (empty) (no description set)
○ uoluosyv 2026-02-19 17:58:54 55464835
│ Add tests
○ zwuuskou 2026-02-19 17:57:09 fdefccea
│ Add main.rs
○ souuksxy 2026-02-19 17:56:55 3d8ee9e1
│ Add README
◆ zzzzzzzz root() 00000000
現在の順番は:Add README → Add main.rs → Add tests です。
コミットを入れ替える
「Add tests」を「Add main.rs」の「前」に移動しましょう。
Gitなら git rebase -i HEAD~3 でエディタを開いて行を入れ替えますが、
Jujutsuでは jj rebase コマンドで完了します。
# "Add tests"(uoluosyv)を "Add main.rs"(zwuuskou)の前に挿入
% jj rebase -r uoluosyv --insert-before zwuuskou
Rebased 1 commits to destination
Rebased 2 descendant commits
-r は移動対象のchange、--insert-before は「指定したchangeの前に挿入」です。
もう一度ログを確認。
% jj log
@ txszzynm 2026-02-19 18:00:19 4a441f48
│ (empty) (no description set)
○ zwuuskou 2026-02-19 18:00:19 9b8cc7c1
│ Add main.rs
○ uoluosyv 2026-02-19 18:00:19 b80013f7
│ Add tests
○ souuksxy 2026-02-19 17:56:55 3d8ee9e1
│ Add README
◆ zzzzzzzz root() 00000000
コミットの順番が入れ替わりました(Add README → Add tests → Add main.rs)。Jujutsuが
子孫のchangeを自動的にリベースしてくれるので、手動で rebase --continue する必要がありません。
もし間違えたら
間違えたらundoするだけ。
% jj undo
Restored to operation: 206b291a9959 (2026-02-19 17:58:54) commit 6479a91b...
簡単に元に戻ります。Gitの rebase -i で間違えた場合と比べると、
安心感が高いです。
Jujutsu from Git Perspective
現在、Jujutsuは内部でGitを使用しています。
jjの操作がGit内部でどう扱われるかを見てみましょう。
ファイル編集
echo "# My Project" > README.md
↑のようにファイルを作成します。
この時点で jj st を実行すると、コマンド実行前にワーキングコピーのスナップショット(コミットオブジェクト化)が行われます。
# jj側
$ jj log
@ tvptzzuk ... 72e10386
│ (no description set) # ← descriptionは空だがコミットは存在する
◆ zzzzzzzz root()
# Git側
$ git log --oneline --all
* 72e1038 # ← jjが自動生成したGitコミット(メッセージ空)
* dab4864 # ← 初期の空コミット
Gitでは git add → git commit の2ステップが必要ですが、
Jujutsuでは次にjjコマンドを実行したタイミングで、ワーキングコピーの変更を検知しスナップショットします。
このコミットオブジェクトはjj管理の参照(refs/jj/...)として保持されるため、
git log --all で確認できます。
jj st の前後で git status を実行すると、
スナップショットによりGit側の内部状態が変化していることが確認できます。
| タイミング | git status の表示 |
|---|---|
jj st 実行前 |
Untracked files: README.md |
jj st 実行後 |
Changes not staged for commit: new file: README.md |
これはjjのスナップショットがGitのindex・参照の状態に影響を与えるためです。
jj st は見た目はステータス確認コマンドですが、内部ではワーキングコピーのスナップショットが先に実行されています。
(※ これは jj st に限らず、jj log や jj diff など全てのjjコマンドで同様です)
コミットメッセージの設定
jj describeコマンドは現在のワーキングコピー(@)にメッセージを設定します。
$ jj describe -m "Add README"
# jj側
$ jj log
@ tvptzzuk ... 75bebcd
│ Add README # ← メッセージが設定された
◆ zzzzzzzz root()
# Git側
$ git log --oneline --all
* 75bebcd Add README # ← 新しいハッシュ(メッセージ付き)
* 72e1038 # ← 古いコミット(まだ残っている)
* dab4864
jj describe はメッセージを上書きしてるように見えますが、Git側では新しいコミットオブジェクトが作成されます。Change IDは変わりませんが、Gitのcommit hashは変わります。
(古いコミットオブジェクトはGit内部に一定期間残りますが、jjからは見えない)
次の作業を開始する
jj newコマンドは新しい空のchangeを作成し、ワーキングコピー(@)を移動します。
$ jj new
# jj側
$ jj log
@ xyzqwert ... 356e916
│ (empty) (no description set) # ← 新しい空のchange(@の位置)
○ tvptzzuk ... 75bebcd
│ Add README
◆ zzzzzzzz root()
# Git側
$ git log --oneline --all
* 356e916 # ← 空の新しいコミット
* 75bebcd Add README
jj newは、前のchangeを確定して次へ進む処理です。
colocateモードの場合、Git側でも空のコミットオブジェクトとして確認できます。
(jj describe + jj new の代わりに jj commit -m "message" でも同じ)
過去のchangeに移動する
jj editは、指定したchangeをワーキングコピー(@)にして、
そのchangeの内容を編集できる状態にします。
# "Add README" のchangeに移動
$ jj edit tvptzzuk
# ワーキングディレクトリ
$ ls
# main.rs が消えている(Add READMEの時点に戻った)
README.md
# Git側
$ git log --oneline --all
# 以前の@のコミット(そのまま残っている)
* 726b5bc
* 4a386a3 Add main.rs
* 75bebcd Add README
jj edit はワーキングディレクトリの内容を指定したchangeの状態に戻します。
Gitの git checkout に近いですが、jjはブランチを切り替えるのではなく
「どのchangeを編集中か(@の位置)」を切り替えます。
(Git側では過去のコミットが消えるわけではなく、全て保持されたまま)
過去changeの編集&自動リベース
前述の「過去のコミットの修正」と同じ操作を、Git側から見てみます。
"Add README"のchangeを編集中にREADMEを更新すると、Git側では以下のようになります。
# Git側:新旧両方のコミットが存在
$ git log --oneline --all --graph
* b014c89 Add main.rs # ← 新しいハッシュ(リベース後)
* 5b872a7 Add README # ← 内容更新済み
* 4a386a3 Add main.rs # ← 古い方(リベース前)
* 75bebcd Add README # ← 古い方
jjの自動リベースは、Git側では対象と全子孫のコミットオブジェクトが
新しいハッシュで再作成されることで実現されています。
古いコミットオブジェクトはGit内部に残りますが、jjからは最新のものだけが見えます。
まとめ
| jj操作 | Git側で起きること | commit hashの変化 |
|---|---|---|
| ファイル編集 + jjコマンド実行 | スナップショットでコミットオブジェクトが生成 | 新規作成 |
jj describe |
新しいメッセージ付きコミットで置換 | 変わる |
jj new |
空のコミットが追加 | 新規作成 |
jj edit |
ワーキングディレクトリが対象の状態に戻る | 変わらない |
| 過去changeの編集 | 対象+全子孫がリベース(新コミットで再作成) | 全て変わる |
jj commit |
describe + new の一括実行 | 新規作成 |
JujutsuのChange ID(tvptzzuk など)はこれらの操作で一切変わりません。
Gitのcommit hashが変わる一方で、Change IDは安定した識別子として機能します。
Github Cooperation(bookmark / clone / fetch / push)
Jujutsuでは、Gitのbranchに相当する概念をbookmarkと呼びます。
Gitではブランチが作業の中心ですが、jjでは基本的にchangeを直接操作するため、
ブランチ(bookmark)は「リモートとの同期ポイント」として役割を果たします。
jjのchangeは明示せず作業できるため、bookmarkはGitHubへのpushなど
外部と連携するときに必要になるものです。
| 概念 | Git | Jujutsu |
|---|---|---|
| 作業の単位 | ブランチ上のコミット | change(明示しなくてOK) |
| 名前付き参照 | branch(必須) | bookmark(必要な時だけ) |
| リモート追跡 | origin/main |
main@origin |
clone
jj git clone でGitリポジトリをjjリポジトリとしてcloneできます。
$ jj git clone https://github.com/user/repo.git my-repo
Fetching into new repo in "/path/to/my-repo"
bookmark: main@origin [new] tracked
bookmark: dev@origin [new] tracked
リモートのブランチが自動的にbookmarkとして追跡(tracked)されます。
既存リポジトリへの導入(colocate)
既存のGitリポジトリでjjを使う場合、colocateモードを使います。
$ cd /path/to/existing-git-repo
$ jj git init --colocate
.git と .jj が共存し、git コマンドも jj コマンドも使えます。
変更をpushする
変更をGitHubにpushするには、bookmarkを作成してpushします。
# 1. ファイルを編集
$ echo "new feature" > feature.md
# 2. descriptionを設定
$ jj describe -m "Add new feature"
# 3. bookmarkを作成(現在のchangeに紐づける)
$ jj bookmark create my-feature -r @
# 4. pushする
$ jj git push --bookmark my-feature
Changes to push to origin:
Add bookmark my-feature to xxxxxxxx
push後、新しいchangeで再度pushする方法です。
jj bookmark set を使ってbookmarkを更新してからpushします。
# 新しいchangeで作業
$ jj new
$ echo "update" >> feature.md
$ jj describe -m "Update feature"
# bookmarkを現在のchangeに移動
$ jj bookmark set my-feature -r @
# pushする(forward move)
$ jj git push --bookmark my-feature
Changes to push to origin:
Move forward bookmark my-feature from bbee3c58be98 to ca8ed46d35f5
fetch
リモートの変更を取り込むには jj git fetch を使います。
$ jj git fetch
bookmark: main@origin [updated] tracked
Gitの git fetch に相当し、リモートの情報を取り込んで追跡中のbookmarkを更新します。ただしローカルの作業changeへの統合(merge)は自動では行われません。
bookmarkの削除(= Gitブランチの削除)
jjのbookmark削除は、Git/GitHub側ではブランチの削除に相当します。
# ローカルのbookmarkを削除
$ jj bookmark delete my-feature
# リモートからも削除(GitHub上のブランチが消える)
$ jj git push --deleted
Changes to push to origin:
Delete bookmark my-feature from ca8ed46d35f5
Summary
Jujutsu(jj)はGitの「惜しいところ」を根本から見直した次世代バージョン管理システムです。
- ステージングエリアを廃止し、ファイル編集が即座にchangeに反映される
- stash不要で、
jj editでいつでも過去のchangeに移動して編集可能 jj undoで全ての操作を安全に取り消せる- コンフリクトを後回しにでき、作業を中断せずに進められる
- 自動リベースで子孫のchangeが自動的に追従する
- Git完全互換で、既存のGitリポジトリにリスクなく導入可能
一言でまとめると、「Gitでやりたかったことが、もっと少ないコマンドで、安全にできる」ツールです。
colocateモードなら .jj を消すだけで元に戻せるので、気軽に試してみてください。
Appendix:Jujutsu用のTUIツール
ここまでみてきて、結局また別のコマンド覚えてコンソールで入力するのめんどくさい、
とおもった方へ。
TijというJujutsu用のTUIツールがあります。

GitでいうtigのJujutsu版で、ターミナル上でJujutsuの操作をシンプルに行えます。
以下のようにCargoかHomebrewでインストールできます。
※Rust 1.85以上とjjが必要
% cargo install tij
or
% brew tap nakamura-shuta/tij && brew install tij
jjリポジトリ内で起動します。
% cd /path/to/jj-repo
% tij
tijはvimライクなキーバインドで操作します。
| キー | 操作 |
|---|---|
j/k |
カーソル移動(上下) |
Enter |
diff表示 |
d |
description編集(コミットメッセージ) |
e |
change編集(jj edit相当) |
c |
新しいchange作成(jj new相当) |
S |
squash(親changeに統合) |
R |
rebase(changeの移動) |
u |
undo |
r |
revsetフィルタ |
/ |
テキスト検索 |
? |
ヘルプ |
q |
終了 |
ログを眺めながら簡単に操作できるtijも試してみてください。







