【jj】実践デモで学ぶJujutsu - 全操作を実行して理解する

PUBLISHED 2026-02-04

この記事では、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)
📖 status の読み方

  • @ = 現在の作業コピー(working copy)
  • @- = 親コミット
  • zrzsovuq = 変更ID(change ID)- jj独自の不変ID
  • bc6bd986 = コミット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との違い

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)同じ同じ
コミットIDa70be6436bf06be2
子孫のコミットIDbdcefe53defe516e

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!'
💡 squashの用途

作業中に細かくコミットしておき、最後にまとめて整理するのに便利です。

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
✂️ splitの用途

大きなコミットを論理的な単位に分割したいときに使います。レビューしやすい履歴を作るのに便利です。

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
...
🔀 rebaseの効果

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!'
💡 restoreの用途

特定のファイルだけ元に戻したいときに使います。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...
⏪ undoの強力さ

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
📋 jjのコンフリクトマーカー

  • %%%%%%%: 差分形式で変更を表示
  • +++++++: もう一方の変更内容

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 コマンド対応表

操作Gitjj
初期化git initjj git init
状態確認git statusjj status
履歴表示git logjj log
差分表示git diffjj diff
ステージングgit add不要
コミットgit commit -mjj describe -m / jj new -m
ブランチ作成git branchjj bookmark create
Rebasegit rebasejj rebase -d
Revertgit revertjj revert -r
Reset (soft)git reset --softjj restore
Reset (hard)git reset --hardjj abandon
Mergegit mergejj new rev1 rev2
操作履歴git reflogjj operation log
取り消し複雑jj undo

参考文献

まとめ

この記事で学んだこと
  • jj status/log の見方(@、変更ID、コミットID)
  • 自動追跡(git add 不要)
  • describe で後からメッセージを追加
  • edit で過去のコミットを直接編集(自動リベース)
  • squash でコミットを統合
  • split でコミットを分割
  • bookmark でブランチを管理
  • rebase で履歴を移動
  • revert で変更を打ち消し
  • restore でファイルを復元
  • abandon でコミットを破棄
  • undo で操作を取り消し
  • コンフリクト解決

jjは「とりあえずコミット、整理は後で」というワークフローを自然にサポートしています。実際に手を動かして試すことで、その便利さを実感できるはずです。

CATEGORY
円