jj-history-investigation について
jj-history-investigationは、複雑なJujutsuリポジトリを扱う開発者向けのスキルです。jjコマンドを使った履歴調査、コミットの分割と整理、不変性への対処、コンフリクト解決を扱います。
主な機能
- 01コミット履歴の検索と確認
- 02annotateによる行変更の追跡
- 03コミットを目的別に分割
- 04保護に配慮した不変履歴の書き換え
- 05冗長なコミットの整理とコンフリクト解決
活用シーン
- 01変更が導入された時期と箇所の特定
- 02大きなコミットをレビュー向けに分割
- 03リベースや分割後の履歴整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ edmundmiller/ dotfiles --skill jj-history-investigationインストール後、Claude Code で/jj-history-investigationを入力して読み込まれたか確認します。
詳細
Jujutsu (jj) の履歴調査と操作
概要
複雑な jj リポジトリを扱う際、過去のコミットを調査したり、単一の大きな変更を目的別のコミットに分割したり、乱雑な履歴を整理したりする必要が生じることがあります。このスキルでは、以下の効率的な手法について説明します。
- コミット履歴の調査:変更がいつ、なぜ行われたかを特定します
- jj annotate の使用:特定の行が誰によって、いつ追加されたかを追跡します
- コミットの分割:大きなコミットを、レビューしやすい目的別のコミットに分割します
- 不変性への対処:共有履歴を書き換える際に保護を解除します
- 冗長な変更の整理:重複したコミットや空のコミットを削除します
- 競合の解決:リベース後のマージ競合を修正します
主なコマンド
| コマンド | 用途 | 例 |
|---|---|---|
jj log | コミット履歴を表示します | jj log -r 'ancestors(@, 10)' |
jj show | コミットの詳細を表示します | jj show abc123 |
jj file annotate | 行ごとの変更を追跡します | jj file annotate src/main.tex |
jj edit | コミットの編集を開始します | jj edit abc123 --ignore-immutable |
jj split | コミットを複数に分割します | jj split --ignore-immutable path/to/file |
jj abandon | 履歴からコミットを削除します | jj abandon abc123 |
jj rebase | コミットを新しいベースに移動します | jj rebase -b main -d new-base |
調査手法
1. 変更が行われた時期の特定
ファイルが変更されたことは分かっていても、その時期が分からない場合は、次の方法を使います。
最近のコミットメッセージを確認します。
# Search for commits mentioning "copyright"
jj log -r 'description(copyright)' --no-graph
# Output:
# trsozpwy feat(frontmatter): Add post-defense required sections
# (shows commits with "copyright" in the message)
コミットの詳細を表示します。
# Show full commit with statistics
jj show trsozpwy --stat
# Show just the files changed
jj show trsozpwy --summary
特定のファイルの履歴を確認します。
# See who/when each line was added
jj file annotate src/main.tex | grep "copyrightpage"
# Output:
# trsozpwy edmund.m 2025-11-09 00:13:05 49: \copyrightpage{2025}
2. コミットの系譜を理解する
あるコミットを土台にした変更を確認します。
# View descendants of commit abc123
jj log -r 'abc123::@' --limit 20
# Check ancestors of current commit
jj log -r 'ancestors(@, 10)'
ブランチ間の関係を特定します。
# See where main and feature branches diverged
jj log -r 'ancestors(main) & ancestors(feature)'
# Check if commit is ancestor of main
jj log -r 'abc123::main'
3. 特定のコミット情報を取得する
コミットのメタデータを取得します。
# Show commit ID, author, date
jj show abc123 | head -10
# Get just the change ID
jj log -r abc123 --no-graph -T 'change_id'
ファイルの変更を確認します。
# See what changed in a commit
jj diff -r abc123
# See specific file diff
jj diff -r abc123 src/main.tex
コミットの分割
分割するタイミング
個別にレビューするべき複数の論理的な変更が含まれている場合は、コミットを分割します。
- ✅ 適した例:「フロントエンド+バックエンド+テスト+ドキュメントの追加」
- ✅ 複数の目的が混在:著作権ページ+謝辞+履歴書を1つのコミットにまとめている
- ❌ すでに目的が明確:「README のタイプミスを修正」
基本的な分割の手順
1. コミットが不変かどうかを確認します。
jj edit abc123
# If error: "Commit abc123 is immutable"
# Then use --ignore-immutable flag
2. コミットを編集します。
jj edit abc123 --ignore-immutable
# Working copy now at abc123
# File system shows state at that commit
3. ファイルまたはディレクトリごとに分割します。
# Split out specific files
jj split --ignore-immutable src/main.tex
# jj will prompt for commit messages for:
# - Selected changes (what you specified)
# - Remaining changes (everything else)
4. 段階的に分割します。
# After first split, you're at "remaining changes"
# Split again to extract more pieces
jj split --ignore-immutable src/chapters/appendix.tex
5. コミットメッセージを更新します。
# Update the commit you just split
jj describe @- -m "feat(frontmatter): Enable copyright page"
# Continue until all pieces are separated
高度な分割手法
分割と TODO の更新を同じコミットで行います。
# 1. Split out the main change
jj split --ignore-immutable src/main.tex
# 2. In the split commit, also update TODO.org
# Edit TODO.org to mark item as DONE
jj describe -m "feat(frontmatter): Enable copyright page
Enable copyright on page ii as recommended by UTD.
Mark as complete in TODO.org."
複数のファイルを1つのコミットに分割します。
# Save final state
cp src/main.tex /tmp/main.tex.final
# Restore to parent
jj restore --from @- src/main.tex
# Apply changes incrementally
# Make change 1, commit
# Make change 2, commit
# etc.
不変性への対処
コミットが不変になる理由
次の場合、コミットは不変になります。
- 子孫コミット(そのコミットを土台にした他のコミット)がある
- 共有履歴に含まれている(リモートにプッシュされている)
- 設定で明示的に指定されている
不変性の解除
安全に解除できる場合:
- ✅ ローカルのみの履歴(プッシュされていない)
- ✅ すべての子孫コミットを自分が管理している
- ✅ 他の共同作業者に影響しない
解除方法:
# Edit immutable commit
jj edit abc123 --ignore-immutable
# Split immutable commit
jj split --ignore-immutable src/main.tex
# Note: This will rewrite ALL descendant commits
解除するとどうなるか:
# Before split:
# abc123 (monolithic commit)
# ↓
# def456 (descendant 1)
# ↓
# ghi789 (descendant 2)
# After split:
# abc123 (piece 1)
# ↓
# xyz111 (piece 2)
# ↓
# def456' (rewritten descendant 1)
# ↓
# ghi789' (rewritten descendant 2)
履歴の整理
冗長なコミットの特定
空のコミットを探します。
# Show commit stats
jj show abc123 --stat
# If output shows "0 files changed", it's empty
競合するコミットを探します。
# Check for conflicts
jj log -r 'all()' | grep conflict
# Output:
# × abc123 (conflict) feat: Add something
コミットの破棄
冗長なコミットを削除します。
# Abandon single commit
jj abandon abc123
# Abandon multiple commits
jj abandon abc123 def456 ghi789
# What happens:
# - Commit is removed from history
# - Descendants are rebased onto parent
# - Changes are preserved in descendants
空のコミットを自動的に破棄します。
# After rebasing, jj may create empty commits
# Abandon them to clean up
jj log -r 'all()' --summary | grep "(empty)"
jj abandon empty-commit-id
分割後の整理
一般的なパターン:
# 1. Split monolithic commit
jj edit old-commit --ignore-immutable
jj split src/file1.txt
jj split src/file2.txt
# (repeat for each logical piece)
# 2. Check for redundant descendants
jj log -r 'old-commit::@' --limit 30
# 3. Abandon redundant commits
jj abandon redundant-commit-1 redundant-commit-2
# 4. Verify history
jj log -r 'old-commit::@' --no-graph
競合の解決
リベースまたは分割の後
1. 競合を特定します。
jj status
# Output:
# Warning: There are unresolved conflicts at these paths:
# TODO.org 2-sided conflict
2. 解決用のコミットを作成します。
# Create new commit on top of conflict
jj new conflicted-commit-id
3. 競合マーカーを確認します。
cat TODO.org
# Output:
# <<<<<<< Conflict 1 of 1
# +++++++ Contents of side #1
# ** DONE Add something
# %%%%%%% Changes from base to side #2
# ** TODO Add something
# >>>>>>> Conflict 1 of 1 ends
4. 解決方法を選びます。
# Edit file to pick side #1, side #2, or merge both
# Remove conflict markers
# Or use editor with conflict resolution
jj resolve TODO.org
5. 解決をスカッシュします。
# Merge resolution into parent
jj squash
# This resolves the conflict in the parent commit
履歴の検証
大規模な変更後
1. コミットの系譜を確認します:
# View commits from base to current
jj log -r 'base-commit::@' --limit 50
# Check all commits are present
jj log -r 'commit1 | commit2 | commit3' --no-graph
2. 各コミットの変更内容が絞られていることを確認します:
# For each commit in your split:
jj show commit-id --stat
# Should show:
# - Small number of files changed
# - Logical grouping of changes
# - Clear commit message
3. 競合がないか確認します:
# List all commits with conflicts
jj log -r 'all()' | grep conflict
# Should be empty after cleanup
4. ブックマークを確認します:
# Check where main/feature branches point
jj log -r 'bookmarks()'
# Ensure they're at correct positions
履歴の健全性チェックリスト
分割/リベース/整理の後:
-
jj statusに競合マーカーがない -
jj logに(conflict)ラベルがない - 空の
(empty)コミットがない(意図的な場合を除く) - コミットメッセージが明確で内容を適切に表している
- 各コミットの変更内容が絞られている
- ブックマーク(main、feature など)が正しい位置を指している
- TODO.org などのファイルに一貫性がある
実例
シナリオ: 履歴にあるコミット trsozpwy に、6 種類の異なる変更が混在しています。レビューに向けて分割する必要があります。
調査フェーズ:
# 1. Find the commit
jj log -r 'description(post-defense)' --no-graph
# Found: trsozpwy
# 2. Check what's in it
jj show trsozpwy --stat
# Shows: 10 files changed
# 3. Check if immutable
jj edit trsozpwy
# Error: Commit is immutable (16 descendants)
# 4. Verify we can override
jj log -r 'trsozpwy::@' --limit 20
# Shows descendant commits, all owned by us
分割フェーズ:
# 1. Override immutability
jj edit trsozpwy --ignore-immutable
# 2. Split out Python scripts
jj split --ignore-immutable src/figures/modules/
jj describe @- -m "feat(ch3): Add core overlap Venn diagram scripts"
# 3. Split out appendix fix
jj split --ignore-immutable src/chapters/appendix-*.tex
jj describe @- -m "fix(appendix): Remove 'A' label from solo appendix"
# 4. Split frontmatter pieces incrementally
# (Copyright page)
# Edit src/main.tex to add only copyright
jj commit -m "feat(frontmatter): Enable copyright page (2025)"
# (Acknowledgments)
# Edit src/main.tex to add acknowledgments
jj commit -m "feat(frontmatter): Add acknowledgments section"
# (And so on for biographical sketch, CV)
整理フェーズ:
# 1. Check for redundant descendants
jj log -r 'trsozpwy::@' | grep "Mark post-defense"
# Found: kqkm (redundant - duplicates our split work)
# 2. Abandon redundant commits
jj abandon kqkm lkto qqkq srls
# 3. Rebase main branch onto splits
jj rebase -b main -d last-split-commit
# 4. Resolve conflicts
jj new conflicted-commit
# Edit TODO.org to pick correct side
jj squash
検証フェーズ:
# 1. Check final structure
jj log -r 'first-split::@' --limit 30
# 2. Verify each split commit
jj show trso --stat # Core overlap scripts
jj show nlnr --stat # Appendix fix
jj show zspm --stat # Copyright page
jj show mrpk --stat # Acknowledgments
jj show wqrn --stat # Biographical sketch
jj show ynom --stat # CV
# 3. Confirm no conflicts
jj status
# (should be clean)
# 4. Check bookmarks
jj log -r 'bookmarks()'
# main and Cleanup should be on correct commits
結果: 1 つのモノリシックなコミットを、明確な意図と適切な TODO.org の更新を含む、変更内容を絞った 6 つのコミットに分割できました。
ヒントとコツ
調査を効率化する
よく使うクエリにはエイリアスを使用します:
# In ~/.jjconfig.toml
[aliases]
recent = ["log", "-r", "ancestors(@, 20)", "--no-graph"]
conflicts = ["log", "-r", "conflicts()"]
empty = ["log", "-r", "empty()"]
コミットメッセージを効率よく検索します:
# Case-insensitive search
jj log -r 'description("(?i)copyright")'
# Multiple terms
jj log -r 'description(frontmatter) & description(copyright)'
よくある落とし穴を避ける
- 調査せずに分割しないでください:コミットの内容を把握するため、まず必ず
jj showを実行します - 分割前に状態を保存します:履歴を大幅に書き換える前に
cp important-file /tmp/を実行します - 段階的に分割します:すべてを一度に分割しようとせず、論理的なまとまりごとに進めます
- TODO をすぐに更新します:作業を行うコミットと同じコミットで TODO 項目に印を付けます
- 各手順の後に検証します:分割/リベース操作中は、
jj logをこまめに確認します
分割しない場合
- コミットがすでにプッシュされ、他の人と共有されている
- 変更が真に不可分である(例:「誤字とそのテストを修正」)
- 分割すると履歴が理解しにくくなる
- コミットが古く、ほとんど参照されない
参考資料
関連スキル
jj:jj-workflow- jj の基本操作とワークフローjj:commit-messages- 適切なコミットメッセージの書き方jj:commit-curation- 共有に向けたコミットの整理