[柔術] Jujutsu - Git互換のVCSを使ってみる [呪術]

[柔術] Jujutsu - Git互換のVCSを使ってみる [呪術]

2026.02.19

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 addgit 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 addgit 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 addgit 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 logjj 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ツールがあります。

tij.png

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も試してみてください。

References

この記事をシェアする

FacebookHatena blogX

関連記事