关于 jj-history-investigation
jj-history-investigation 面向处理复杂 Jujutsu 仓库的开发者,涵盖历史调查、拆分与清理提交、处理不可变性以及解决冲突,并使用 jj 命令完成这些操作。
主要功能
- 01搜索并检查提交历史
- 02用 annotate 追踪行变更
- 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
拆分提交
何时拆分
当一个提交包含多个应分别审查的逻辑更改时,应拆分该提交:
- ✅ 适合拆分:“添加前端 + 后端 + 测试 + 文档”
- ✅ 涉及多种关注点:在一个提交中包含版权页 + 致谢 + 简历
- ❌ 已经聚焦:“修复 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."
将多个文件拆分到一个提交中:
# 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 个单体提交拆分为 6 个聚焦明确的提交,每个提交意图清晰,并正确更新了 TODO.org。
提示与技巧
加快调查速度
为常用查询使用别名:
# 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- 整理提交以便共享
评论
支持 Markdown