【jj】実践デモで学ぶJujutsu - 全操作を実行して理解する
この記事では、jj(Jujutsu)の操作を実際に実行しながら学びます。各コマンドの出力結果を確認することで、jjの動作を深く理解できます。
環境準備
インストール確認
jj --version
jj 0.37.0-11c3dbe17ac0a2172755789613fa8f04a50118a0
macOSは brew install jj、Windowsは winget install jj または GitHub Releases からダウンロードできます。
デモ用リポジトリの作成
mkdir /tmp/jj-demo && cd /tmp/jj-demo
jj git init
Initialized repo in "."
基本操作:status と log の見方
jj status
jj status
The working copy has no changes.
Working copy (@) : zrzsovuq bc6bd986 (empty) (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
@= 現在の作業コピー(working copy)@-= 親コミットzrzsovuq= 変更ID(change ID)- jj独自の不変IDbc6bd986= コミットID - 内容が変わると変化
jj log
jj log
@ zrzsovuq (no email set) 2026-02-04 18:05:14 bc6bd986
│ (empty) (no description set)
◆ zzzzzzzz root() 00000000
ログの記号
| 記号 | 意味 |
|---|---|
@ | 現在の作業位置 |
○ | 通常のコミット |
◆ | 不変コミット(root) |
ファイル作成と自動追跡
ファイルを作成
echo "# JJ Demo Project" > README.md
echo "Hello, Jujutsu!" > hello.txt
変更を確認
jj status
Working copy changes:
A README.md
A hello.txt
Working copy (@) : zrzsovuq f98064ed (no description set)
Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set)
git add なしで、ファイルが自動的に追跡されています。コミットIDが bc6bd986 から f98064ed に変化していますが、変更ID zrzsovuq は同じままです。
jj diff で差分確認
jj diff
Added regular file README.md:
1: # JJ Demo Project
Added regular file hello.txt:
1: Hello, Jujutsu!
describe:コミットメッセージの追加
jj describe -m "Add initial project files"
Working copy (@) now at: zrzsovuq 7a169380 Add initial project files
Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
Gitでは commit 時にメッセージを書きますが、jjでは後から describe で追加できます。「とりあえず作業、メッセージは後で」が可能です。
new:新しいコミットを開始
jj new -m "Add feature file"
Working copy (@) now at: ttpvyonx 250423a2 (empty) Add feature file
Parent commit (@-) : zrzsovuq 7a169380 Add initial project files
jj log
@ ttpvyonx demo@example.com 2026-02-04 18:06:11 250423a2
│ (empty) Add feature file
○ zrzsovuq (no email set) 2026-02-04 18:05:52 7a169380
│ Add initial project files
◆ zzzzzzzz root() 00000000
ファイルを追加
echo "def greet():" > feature.py
echo " return 'Hello from feature!'" >> feature.py
jj status
Working copy changes:
A feature.py
Working copy (@) : ttpvyonx a70be643 Add feature file
Parent commit (@-): zrzsovuq 7a169380 Add initial project files
edit:過去のコミットを直接編集
jjの強力な機能の一つが、過去のコミットを直接編集できることです。
準備:もう1つコミットを追加
jj new -m "Add tests"
echo "def test_greet():" > test_feature.py
echo " assert greet() == 'Hello from feature!'" >> test_feature.py
jj log
@ mrlrrlnv demo@example.com 2026-02-04 18:06:47 bdcefe53
│ Add tests
○ ttpvyonx demo@example.com 2026-02-04 18:06:27 a70be643
│ Add feature file
○ zrzsovuq (no email set) 2026-02-04 18:05:52 7a169380
│ Add initial project files
◆ zzzzzzzz root() 00000000
過去のコミット(ttpvyonx)を編集
jj edit ttpvyonx
Working copy (@) now at: ttpvyonx a70be643 Add feature file
Parent commit (@-) : zrzsovuq 7a169380 Add initial project files
Added 0 files, modified 0 files, removed 1 files
test_feature.py が消えました。これは ttpvyonx の時点ではまだ存在しないファイルだからです。
ファイルを追加編集
echo "" >> feature.py
echo "def goodbye():" >> feature.py
echo " return 'Goodbye!'" >> feature.py
jj status
Working copy changes:
A feature.py
Working copy (@) : ttpvyonx 6bf06be2 Add feature file
...
Rebased 1 descendant commits onto updated working copy
自動リベース発生! 子孫コミット(Add tests)が自動的にリベースされました。これがjjの大きな特徴です。
jj log
○ mrlrrlnv demo@example.com 2026-02-04 18:07:17 defe516e
│ Add tests
@ ttpvyonx demo@example.com 2026-02-04 18:07:17 6bf06be2
│ Add feature file
○ zrzsovuq (no email set) 2026-02-04 18:05:52 7a169380
│ Add initial project files
◆ zzzzzzzz root() 00000000
ID の変化を確認
| 項目 | 編集前 | 編集後 |
|---|---|---|
| 変更ID(ttpvyonx) | 同じ | 同じ |
| コミットID | a70be643 | 6bf06be2 |
| 子孫のコミットID | bdcefe53 | defe516e |
squash:コミットの統合
jj new mrlrrlnv -m "Continue development"
jj squash --from mrlrrlnv --into ttpvyonx -m "Add feature with tests"
Rebased 1 descendant commits
Working copy (@) now at: txvpwvkx 5664223f (empty) Continue development
Parent commit (@-) : ttpvyonx c2b71ef9 Add feature with tests
jj log
@ txvpwvkx demo@example.com 2026-02-04 18:07:53 5664223f
│ (empty) Continue development
○ ttpvyonx demo@example.com 2026-02-04 18:07:53 c2b71ef9
│ Add feature with tests
○ zrzsovuq (no email set) 2026-02-04 18:05:52 7a169380
│ Add initial project files
◆ zzzzzzzz root() 00000000
統合されたコミットの内容を確認
jj show ttpvyonx
Added regular file feature.py:
1: def greet():
2: return 'Hello from feature!'
3:
4: def goodbye():
5: return 'Goodbye!'
Added regular file test_feature.py:
1: def test_greet():
2: assert greet() == 'Hello from feature!'
作業中に細かくコミットしておき、最後にまとめて整理するのに便利です。
split:コミットの分割
複数のファイルを1つのコミットに追加
echo "# Configuration" > config.py
echo "DEBUG = True" >> config.py
echo "# Utilities" > utils.py
echo "def helper(): pass" >> utils.py
ファイルごとにコミットを分割
jj split config.py -m "Add configuration"
Selected changes : txvpwvkx b8dccbcc Add configuration
Remaining changes: ypmxrtzy 18555845 Continue development
Working copy (@) now at: ypmxrtzy 18555845 Continue development
Parent commit (@-) : txvpwvkx b8dccbcc Add configuration
jj log
@ ypmxrtzy demo@example.com 2026-02-04 18:09:21 18555845
│ Continue development
○ txvpwvkx demo@example.com 2026-02-04 18:09:21 b8dccbcc
│ Add configuration
○ ttpvyonx demo@example.com 2026-02-04 18:07:53 main c2b71ef9
│ Add feature with tests
○ zrzsovuq (no email set) 2026-02-04 18:05:52 7a169380
│ Add initial project files
◆ zzzzzzzz root() 00000000
大きなコミットを論理的な単位に分割したいときに使います。レビューしやすい履歴を作るのに便利です。
bookmark:ブランチの作成
jj bookmark create main -r ttpvyonx
jj bookmark create feature-branch -r @
jj bookmark list
feature-branch: ypmxrtzy 18555845 Continue development
main: ttpvyonx c2b71ef9 Add feature with tests
rebase:履歴の移動
並列開発の準備
jj new ttpvyonx -m "Parallel feature A"
echo "Feature A code" > feature_a.py
jj new ttpvyonx -m "Parallel feature B"
echo "Feature B code" > feature_b.py
jj log
@ zpqswouk demo@example.com 2026-02-04 18:09:59 425fcf7e
│ Parallel feature B
│ ○ ppustqrz demo@example.com 2026-02-04 18:09:49 c2e83d04
├─╯ Parallel feature A
│ ○ ypmxrtzy demo@example.com 2026-02-04 18:09:21 18555845
│ │ Continue development
│ ○ txvpwvkx demo@example.com 2026-02-04 18:09:21 b8dccbcc
├─╯ Add configuration
○ ttpvyonx demo@example.com 2026-02-04 18:07:53 main c2b71ef9
│ Add feature with tests
...
マージコミットを作成
jj new ppustqrz zpqswouk -m "Merge features A and B"
Working copy (@) now at: wlxomonr dbb61a6f (empty) Merge features A and B
Parent commit (@-) : ppustqrz c2e83d04 Parallel feature A
Parent commit (@-) : zpqswouk 425fcf7e Parallel feature B
jj log
@ wlxomonr demo@example.com 2026-02-04 18:10:05 dbb61a6f
├─╮ (empty) Merge features A and B
│ ○ zpqswouk demo@example.com 2026-02-04 18:09:59 425fcf7e
│ │ Parallel feature B
○ │ ppustqrz demo@example.com 2026-02-04 18:09:49 c2e83d04
├─╯ Parallel feature A
...
rebase でコミットを移動
jj rebase -s ypmxrtzy -d wlxomonr
Rebased 1 commits to destination
jj log --limit 5
○ ypmxrtzy demo@example.com 2026-02-04 18:12:19 fd0c33be
│ Continue development
@ wlxomonr demo@example.com 2026-02-04 18:10:05 dbb61a6f
├─╮ (empty) Merge features A and B
...
ypmxrtzy(Continue development)がマージコミットの後に移動しました。コミットIDは変化しましたが、変更IDは同じです。
revert:変更の打ち消し
jj revert -r ppustqrz --insert-after ypmxrtzy
Reverted 1 commits as follows:
kuszwrpx 6796c016 Revert "Parallel feature A"
jj show kuszwrpx
Revert "Parallel feature A"
This reverts commit c2e83d042fb8215dad9fad98b710b0f0ab1a9748.
Removed regular file feature_a.py:
1 : Feature A code
restore:ファイルの復元
jj new -m "Test restore"
echo "MODIFIED CONTENT" > feature.py
jj diff
Modified regular file feature.py:
1 : def greet():
...
1: MODIFIED CONTENT
変更を取り消す
jj restore feature.py
cat feature.py
def greet():
return 'Hello from feature!'
def goodbye():
return 'Goodbye!'
特定のファイルだけ元に戻したいときに使います。git checkout — file に相当します。
abandon:コミットの破棄
jj abandon @
Abandoned 1 commits:
wqwwrmxn 5392b3e1 (empty) Test restore
Working copy (@) now at: kzwrzkmn 15a7c2c4 (empty) (no description set)
Parent commit (@-) : wlxomonr dbb61a6f (empty) Merge features A and B
abandonしても、新しい空のコミットが自動的に作成されます。これにより、作業コピーが常に保護されます。
undo:操作の取り消し
jj operation log --limit 5
@ dfa208a0b743 ... create bookmark feature-branch ...
○ ee4a50e33301 ... create bookmark main ...
○ de3c4041c01d ... squash commits ...
...
jj undo
Restored to operation: ee4a50e33301 (2026-02-04 18:08:16) create bookmark main...
jjでは全ての操作が記録されているため、いつでも過去の状態に戻せます。これはGitの reflog よりも直感的です。
コンフリクト解決
コンフリクトを発生させる
jj new ttpvyonx -m "Change A"
echo "VERSION = 'A'" >> feature.py
jj new ttpvyonx -m "Change B"
echo "VERSION = 'B'" >> feature.py
jj new rtxquzsk rylkuzvn -m "Merge A and B"
Working copy (@) now at: yoosttos 1da08d28 (conflict) (empty) Merge A and B
...
Warning: There are unresolved conflicts at these paths:
feature.py 2-sided conflict
コンフリクトマーカーを確認
cat feature.py
def greet():
return 'Hello from feature!'
def goodbye():
return 'Goodbye!'
<<<<<<< conflict 1 of 1
%%%%%%% diff from: ttpvyonx c2b71ef9 "Add feature with tests"
\\\\\\\ to: rtxquzsk d247e3e8 "Change A"
+VERSION = 'A'
+++++++ rylkuzvn fbab81ff "Change B"
VERSION = 'B'
>>>>>>> conflict 1 of 1 ends
%%%%%%%: 差分形式で変更を表示+++++++: もう一方の変更内容
Gitとは異なる形式ですが、両方の変更が明確にわかります。
コンフリクトを解決
ファイルを編集して、マーカーを削除し、適切な内容に修正します。
def greet():
return 'Hello from feature!'
def goodbye():
return 'Goodbye!'
VERSION = 'A+B merged'
jj status
Working copy changes:
M feature.py
Working copy (@) : yoosttos bb1126f8 Merge A and B
...
コンフリクトマーカー (conflict) が消えました。
Git vs jj コマンド対応表
| 操作 | Git | jj |
|---|---|---|
| 初期化 | git init | jj git init |
| 状態確認 | git status | jj status |
| 履歴表示 | git log | jj log |
| 差分表示 | git diff | jj diff |
| ステージング | git add | 不要 |
| コミット | git commit -m | jj describe -m / jj new -m |
| ブランチ作成 | git branch | jj bookmark create |
| Rebase | git rebase | jj rebase -d |
| Revert | git revert | jj revert -r |
| Reset (soft) | git reset --soft | jj restore |
| Reset (hard) | git reset --hard | jj abandon |
| Merge | git merge | jj new rev1 rev2 |
| 操作履歴 | git reflog | jj operation log |
| 取り消し | 複雑 | jj undo |
参考文献
まとめ
- jj status/log の見方(@、変更ID、コミットID)
- 自動追跡(git add 不要)
- describe で後からメッセージを追加
- edit で過去のコミットを直接編集(自動リベース)
- squash でコミットを統合
- split でコミットを分割
- bookmark でブランチを管理
- rebase で履歴を移動
- revert で変更を打ち消し
- restore でファイルを復元
- abandon でコミットを破棄
- undo で操作を取り消し
- コンフリクト解決
jjは「とりあえずコミット、整理は後で」というワークフローを自然にサポートしています。実際に手を動かして試すことで、その便利さを実感できるはずです。