jj-workflow について
jjを使う開発者向けに、変更の説明、ブランチ、リベース、競合処理、復旧の手順をまとめたガイドです。AI支援作業向けの非対話コマンド規則も示し、colocated利用ならチームにはGitとして見える運用を説明します。
主な機能
- 01非対話コマンドで変更を記述
- 02書き換え後も安定する変更IDの利用
- 03競合をコミット状態として管理
- 04並行する実験の作成と比較
- 05jj absorbによる修正の振り分け
- 06操作ログからの状態復旧
活用シーン
- 01浅い変更スタックの維持
- 02古い変更の修正と後続変更のリベース
- 03同じ基点から複数の実装を試行
- 04操作の取り消しやリポジトリ状態の復元
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trevors/ dot-claude --skill jj-workflowインストール後、Claude Code で/jj-workflowを入力して読み込まれたか確認します。
同梱ファイルREFERENCE.md
詳細
jj ワークフロー
基本方針
- コミットは簡単に作れますが、説明は必須です。 作業コピーは常にコミットです。「(no description set)」のままにしないでください。
- 自由に試してみましょう。oplog がセーフティネットになります。 すべての変更が記録されます。
jj undoとjj op restoreを使えば、どの操作も元に戻せます。 - コンフリクトは状態であり、緊急事態ではありません。 jj はコンフリクトを構造化データとしてコミットに保存します。コンフリクトがあってもリベースは成功します。準備ができたら解決してください。
- 変更 ID は作業を特定するためのものです。 書き換えるとコミットハッシュは変わりますが、変更 ID は変わりません。リベースやスカッシュをまたいで作業を参照するには、変更 ID を使ってください。
- ブックマークは GitHub 用であり、自分のためのものではありません。 匿名の変更で作業してください。プッシュが必要になったときだけブックマークを追加します。
- スタックは浅く保ちましょう。 早めにスカッシュしてください。整理せずに履歴を 10 コミット分も深くしないでください。
- 手動でスカッシュ先を指定するより
absorbを使いましょう。 スタック全体にまたがって修正するときは、各ハンクの適切な配置先を jj に判断させてください。 - Colocated ならチームには見えません。 チームメイトには標準的な git として見えます。あなたが jj を使っていることはわかりません。
重要:AI 固有のルール
jj がエディターを開かないように、必ず -m フラグを使ってください。
# WRONG - opens editor, blocks AI
jj new
jj describe
jj commit
jj squash
# CORRECT - non-interactive
jj new -m "message"
jj describe -m "message"
jj commit -m "message"
jj squash -m "message"
次の対話型コマンドは使わないでください(非対話モードがありません)。
jj split/jj split -ijj squash -ijj diffedit
基本概念
作業コピー = コミット
ステージング領域はありません。ファイルを編集すると、すべて自動的に @(現在の変更)で追跡されます。git add は不要です。
@= 現在の変更(作業コピーのコミット)@-= 現在の変更の親@--= 祖父母
変更 ID とコミット ID
各変更には 2 つの識別子があります。
- 変更 ID(例:
kpqxywon)— 書き換え後も安定しています。作業を参照するにはこちらを使ってください。 - コミット ID(例:
a1b2c3d4)— 内容を書き換えると変わります。
スカッシュ、リベース、修正を行っても、変更 ID は変わりません。つまり、変更 ID を覚えておけば常にその変更を特定でき、git のコミットハッシュのように変わることはありません。
以前のバージョンにアクセスする(xyz/n 構文)
変更を書き換えるたびに、その変更が記録されます。<change-id>/n を使って以前のバージョンにアクセスできます。
xyz/0— 最新(現在)のバージョン(xyzと同じ)xyz/1— 1 つ前のバージョンxyz/2— 2 つ前のバージョン
これは、変更を以前の状態に戻すときに便利です。
jj restore --from xyz/1 --to xyz # Revert xyz to its previous contents
jj diff --from xyz/1 --to xyz # See what changed between versions
コンフリクトは単なる状態です
リベースでコンフリクトが発生すると、jj はそのコンフリクトをコミットに記録して、処理を成功させます。「リベース進行中」のブロック状態も、--continue を実行する手順もありません。
- コンフリクトのあるコミットの子孫は通常どおり作業できます
- 都合のよいときにコンフリクトを解決できます。コミットをチェックアウトしてファイルを修正すれば完了です
jj logではコンフリクトのあるコミットに印が付くため、見つけやすくなります
ワークフロー
スカッシュワークフロー(推奨)
jj describe -m "feat: what I'm building" # State intent on current change
jj new -m "wip" # New empty change on top
# ... make changes ...
jj squash -m "feat: done" # Squash into parent
コミットワークフロー(よりシンプル)
# ... make changes ...
jj commit -m "feat: what I did" # Describe + create new change in one step
# ... keep working ...
jj commit は jj describe -m "..." && jj new と同等です。
編集ワークフロー(スタック途中の修正)
古い変更を修正する必要がありますか?stash や rebase-i の手順は不要です。
jj edit <change-id> # Switch working copy to that change
# ... make your fix ...
jj new -m "back to work" # Return to tip (descendants auto-rebased)
編集した変更のすべての子孫は、自動的にリベースされます。
並行して試す
jj new main -m "approach A" # Branch from main
jj new main -m "approach B" # Another branch from main (not from A)
jj diff --from <A-id> --to <B-id> # Compare approaches
jj edit <winner-id> # Continue with the winner
jj abandon <loser-id> # Discard the loser
Absorb:スマートなスカッシュ先の振り分け
変更のスタックがある状態で @ を修正すると、jj absorb は各ハンクを、その行が最後に変更された祖先へ自動的に振り分けます。
# You're at the top of a 3-commit stack, fixing bugs across all of them
jj absorb # Each fix goes to the right commit automatically
スタック全体にまたがる修正には jj absorb を使ってください。変更の移動先が明確な場合は jj squash を使います。
ブックマークとプッシュ
ブックマークは jj における git ブランチに相当しますが、自動では進みません。明示的に移動する必要があります。
main へのプッシュ
jj bookmark set master -r @- # Point bookmark at your commit (not empty @)
jj git push
フィーチャーブランチ
# Create and push
jj bookmark create feature-x -r @-
jj git push
# Update after more work
jj bookmark set feature-x -r @-
jj git push
PR のフィードバックへの対応
jj new feature-x- -m "address review feedback"
# ... make changes ...
jj squash -m "feat: updated per review"
jj bookmark set feature-x -r @-
jj git push
Revsets
Revset は、コミットを選択するための関数型言語です。@ と @- 以外にも、次の表現があります。
| 表現 | 意味 |
|---|---|
@ | 現在の作業コピー |
@- | 親 |
@-- | 祖父母 |
x+ | x の子 |
x:: | x のすべての子孫 |
::x | x のすべての祖先 |
trunk() | trunk/main コミット |
bookmarks() | ブックマークされたすべてのコミット |
empty() | 空のコミット |
divergent() | 分岐した変更 |
remote_tags() | リモートタグ |
diff_lines("text") | 差分が一致するコミット |
description("text") | 説明で絞り込む |
author("name") | 作成者で絞り込む |
便利な例:
jj log -r 'trunk()..@' # Everything between main and here
jj log -r '::@ & ~::trunk()' # My branch only
jj log -r 'author("trevor")' # My commits
リモートとの同期
jj git fetch # Pull from remote
jj rebase -d master@origin # Rebase onto updated main
イミュータブルなコミットを一時的に無効化する
immutable_heads() で保護されたコミットを書き換える必要がある場合(例:リモートブックマークにスカッシュする場合):
# Disable protection (quote the key — parentheses are invalid TOML bare keys)
jj config set --repo 'revset-aliases."immutable_heads()"' 'none()'
# Do your rewrite
jj squash -m "updated message"
# ALWAYS restore protection immediately after
jj config set --repo 'revset-aliases."immutable_heads()"' 'builtin_immutable_heads() | remote_bookmarks()'
重要: immutable_heads() には括弧が含まれるため、NAME 引数では TOML キーをシェルクォートで囲む必要があります。内側を二重引用符にして、ドット区切りのキー全体を一重引用符で囲んでください:'revset-aliases."immutable_heads()"'。
重大:イミュータブル保護を無効化する前に、必ずユーザーに確認してください。 リモートブックマークを書き換えるには強制プッシュが必要で、共有履歴が書き換えられます。続行する前にユーザーに確認してください。イミュータブル性を黙って無効化してはいけません。
復旧
操作ログには、すべての変更が記録されます。完全に失われるものはありません。
jj op log # See all operations
jj undo # Undo last operation
jj op restore <id> # Jump to any past state
jj evolog # See how current change evolved
jj evolog -r <change-id> # See how any change evolved
推奨設定
ユーザー設定は ~/.config/jj/config.toml にあります:
[remotes.origin]
auto-track-bookmarks = "*"
[revset-aliases]
# Prevent rewriting pushed commits
'immutable_heads()' = 'builtin_immutable_heads() | remote_bookmarks()'
# Shorthand for trunk
'trunk()' = 'master@origin'
中止する
rm -rf .jj # Delete jj state, keep git unchanged