关于 jujutsu
jujutsu 是面向编码 Agent 的 Jujutsu(jj)版本控制工作指南,介绍可变更提交、自动变基及 Git 兼容工作流。内容还针对非交互式 Agent 环境提供命令使用和操作核验建议。
主要功能
- 01可变更提交与自动变基
- 02使用 revset 选择提交
- 03提交整理与变更吸收
- 04Bookmark 与 Workspace 管理
- 05Git 仓库集成
- 06Agent 环境操作规范
适用场景
- 01在 jj 仓库中查看和修改提交历史
- 02将工作拆分为原子提交并整理
- 03在多 Workspace 间并行工作
- 04获取并推送 Git 远程变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ danverbraganza/ jujutsu-skill --skill jujutsu安装后在 Claude Code 中输入/jujutsu确认已加载。
详细介绍
Jujutsu (jj) 版本控制系统
此技能可帮助你使用 Jujutsu,这是一种兼容 Git、支持可变提交和自动变基的 VCS。
已使用 jj v0.37.0 测试——其他版本中的命令可能有所不同。
重要:自动化/Agent 环境
作为 Agent 运行时:
- 始终使用
--no-pager,防止命令打开交互式分页器(例如less),以免 Agent 卡住:
# Always use --no-pager on commands that produce output
jj --no-pager log # NOT: jj log
jj --no-pager diff # NOT: jj diff
jj --no-pager show <id> # NOT: jj show <id>
- 始终使用
-m标志内联提供消息,而不要依赖编辑器提示:
# Always use -m to avoid editor prompts
jj desc -m "message" # NOT: jj desc
jj squash -m "message" # NOT: jj squash (which opens editor)
基于编辑器的命令在非交互式环境中会失败。
- 在修改操作(
squash、abandon、rebase、restore)后,使用jj st验证操作成功。
核心概念
工作副本就是一个提交
在 jj 中,你的工作目录始终是一个提交(引用为 @)。运行任何 jj 命令时,变更都会自动创建快照。jj 没有暂存区。
无需运行 jj commit。
提交是可变的
重要:与 git 不同,jj 的提交创建后仍可自由修改。你可以更新描述、压缩变更、变基和吸收变更,所有这些操作都无需创建新提交。推荐的工作模式请参见下文的“基本工作流”。
变更 ID 与提交 ID
- 变更 ID:稳定标识符(例如
tqpwlqmp),即使提交被重写也会保留——引用提交时优先使用此 ID - 提交 ID:内容哈希(例如
3ccf7581),提交内容变更时也会改变
修订集表达式
jj 使用修订集表达式语言来选择命令所操作的提交。常见的修订集表达式:
@— 工作副本提交@-— 工作副本的父提交::@—@的所有祖先@::—@的所有后代trunk()..@— trunk 与@(你的分支)之间的提交bookmarks()— 所有带书签的提交
使用 -r 标志指定修订集表达式:jj log -r 'trunk()..@'
基本工作流
开始工作:先写描述,再编写代码
始终先创建提交消息,再编写代码:
使用 jj st 验证当前位于一个空白修订上。如果不是,请输入:
jj new
# First, describe what you intend to do
jj desc -m "Add user authentication to login endpoint"
# Then make your changes - they automatically become part of this commit
# ... edit files ...
# Check status
jj st
创建原子提交
每个提交应代表一项逻辑变更。提交消息使用以下格式:
Examples:
- "Add validation to user input forms"
- "Fix null pointer in payment processor"
- "Remove deprecated API endpoints"
- "Update dependencies to latest versions"
查看历史记录
# View recent commits
jj --no-pager log
# View with patches
jj --no-pager log -p
# View specific commit
jj --no-pager show <change-id>
# View diff of working copy (use --git for familiar +/- format)
jj --no-pager diff --git
重要:jj diff 输出格式:默认的 jj diff 输出采用并排行号格式(例如 26 26:),与 git 的 +/- 前缀格式看起来大不相同。这是正常且正确的——并非内容损坏或显示了过期内容。不过,为避免混淆,始终使用 jj diff --git 获取带有 +/- 行的标准统一差异格式。
在提交之间切换
# Create a new empty commit on top of current
jj new
# Create new commit with message
jj new -m "Commit message"
# Edit an existing commit (working copy becomes that commit)
jj edit <change-id>
# Edit the previous commit
jj prev -e
# Edit the next commit
jj next -e
优化提交
压缩变更
将当前提交中的变更移入其父提交:
# Squash all changes into parent
jj squash
注意:jj squash -i 会打开交互式界面,在 Agent 环境中会卡住。请避免使用。
拆分提交
警告:jj split 是交互式命令,在 Agent 环境中会卡住。要拆分提交,请使用 jj restore 移出变更,然后手动创建多个单独的提交。
吸收变更
自动将变更分配到最后修改了相应行的提交中:
# Absorb working copy changes into appropriate ancestor commits
jj absorb
放弃提交
彻底移除一个提交(其后代会变基到该提交的父提交上):
jj abandon <change-id>
撤销操作
反转最近一次 jj 操作:
jj undo
这会将仓库恢复到上一个命令执行前的状态。可用于从误执行 abandon、squash 或 rebase 等操作中恢复。
对提交进行变基
将提交移至其他父提交:
# Rebase current branch onto a destination
jj rebase -d <destination>
# Rebase a specific revision (without descendants) onto a destination
jj rebase -r <change-id> -d <destination>
# Rebase a revision and all its descendants
jj rebase -s <change-id> -d <destination>
# Rebase onto trunk (common: update your branch to latest main)
jj rebase -d main
恢复文件
丢弃特定文件的变更,或从另一个修订中恢复文件:
# Discard all uncommitted changes in working copy (restore from parent)
jj restore
# Discard changes to specific files
jj restore path/to/file.txt
# Restore files from a specific revision
jj restore --from <change-id> path/to/file.txt
使用书签(分支)
书签相当于 jj 中的 git 分支:
# Create a bookmark at current commit
jj bookmark create my-feature -r@
# Move bookmark to a different commit
jj bookmark move my-feature --to <change-id>
# List bookmarks
jj --no-pager bookmark list
# Delete a bookmark
jj bookmark delete my-feature
工作区
工作区是一个工作副本及其关联的仓库。一个仓库可以有多个工作区——每个工作区都有自己的工作目录和工作副本提交(@)——但它们共享相同的提交、操作和书签。这相当于 jj 中的 git worktree。
适用于在一个工作区中运行耗时的构建或测试,同时在另一个工作区中编辑。工作区是很少需要的功能;如需了解以下基础内容之外的信息,请查阅官方文档。
常用命令
# Create a new workspace (defaults: name = basename of path, parent = current @'s parent)
jj workspace add ../my-tests
jj workspace add --name tests -r <change-id> ../my-tests # explicit name and base
# Inspect
jj --no-pager workspace list
jj workspace root [--name <ws>]
# Remove (does NOT delete files on disk — rm the directory separately)
jj workspace forget [<ws>]
# Rename current workspace
jj workspace rename <new-name>
在 jj log 中,每个工作区的 @ 显示为 <workspace-name>@。
关键语义
- 默认隔离。
jj workspace add会为新工作区创建一个全新的空提交;工作区起初不会共享@,磁盘上的文件也不会在工作区之间实时镜像。 - 在命令边界传播。 每条 jj 命令都会为当前工作区的文件创建快照并读取操作日志,因此能看到其他工作区创建的提交和书签。这里没有文件系统监视器。
- 过时的工作副本。 如果另一个工作区改写了此工作区的
@(例如通过jj squash、rebase、abandon),jj 会拒绝在此处执行命令,直到你运行jj workspace update-stale。如果命令在更新过程中被中断,也需要用相同的方式恢复。 - 共享
@容易出问题。jj edit <id>允许两个工作区在不发出警告的情况下指向同一个变更。当其中一个工作区修改它时,另一个就会过时;如果过时的工作区中有尚未创建快照的编辑,update-stale会将它们保留为分歧提交(变更 ID 相同,在jj log中显示为xyz??),你必须解决这个问题。除非两个工作区都为只读,否则应避免共享@。
Agent 使用指南
- 始终为
jj workspace list传入--no-pager。 - 不要对另一个工作区已作为其
@的变更运行jj edit——这是意外产生分歧的主要原因。 - 不要在未运行
jj workspace forget <name>的情况下对工作区目录执行rm -rf。
Git 集成
使用现有 Git 仓库
# Clone a git repository
jj git clone <url>
# Initialize jj in an existing git repo
jj git init --colocate
获取远程更改
# Fetch all branches from the default remote
jj git fetch
# Fetch from a specific remote
jj git fetch --remote <remote-name>
# Fetch specific branches
jj git fetch -b <branch-name>
获取后,将你的工作变基到更新后的主干上:jj rebase -d main
在 jj 和 git 之间切换(仅适用于共置仓库)
本节仅适用于共置仓库(同时存在 .jj/ 和 .git/ 的仓库)。对于非共置仓库,请勿使用 git 命令,否则会破坏 jj 状态。
在共置仓库中,你可以谨慎地同时使用 jj 和 git 命令:
切换到 git 模式(例如用于合并工作流):
# First, ensure your jj working copy is clean
jj st
# Then checkout a branch with git
git checkout <branch-name>
切回 jj 模式:
# Use jj edit to resume working with jj
jj edit <change-id>
重要说明:
- 如果 jj 的工作副本与 git HEAD 不同,Git 可能会提示存在未提交的更改
- 切换到 git 前,务必确保你的工作已在 jj 中提交
- 执行 git 操作后,jj 会在下一条命令运行时检测并纳入这些更改
推送更改
当用户要求你推送更改时:
# Push a specific bookmark to the remote
jj git push -b <bookmark-name>
# Example: push the main bookmark
jj git push -b main
推送前,请确保:
- 你的书签指向正确的提交(书签不会像 git 分支那样自动前移)
- 提交经过了完善整理,且粒度清晰
- 用户已明确要求推送
重要:与 git 分支不同,创建新提交时 jj 书签不会自动移动。推送前必须手动更新书签:
# Move an existing bookmark to the current commit
jj bookmark move my-feature --to @
# Then push it
jj git push -b my-feature
如果你的更改没有对应的书签,请先创建一个:
# Create a bookmark at the current commit
jj bookmark create my-feature
# Then push it
jj git push -b my-feature
处理冲突
jj 允许提交包含冲突的内容——你可以稍后解决:
# View conflicts
jj st
Agent 冲突解决:不要使用 jj resolve(交互式)。应直接编辑冲突文件,移除冲突标记,然后运行 jj st 验证冲突已解决。
保持提交质量
重要:由于提交是可变的,请务必在完成工作前对其进行完善:
- 审查提交:
jj --no-pager show @或jj --no-pager diff --git - 是否是原子提交? 每个提交只包含一项逻辑变更
- 提交说明是否清晰? 使用句首字母大写格式的祈使动词短语,末尾不加句号,例如:"Add login endpoint"、"Fix null pointer in payment processor"、"Remove deprecated API endpoints"
- 是否包含无关变更? 使用
jj restore移出这些变更,然后创建单独的提交 - 这些变更是否应该放到其他位置? 使用
jj squash或jj absorb
快速参考
| 操作 | 命令 |
|---|---|
| 描述提交 | jj desc -m "message" |
| 查看状态 | jj st |
| 查看日志 | jj --no-pager log |
| 查看差异 | jj --no-pager diff --git |
| 新建提交 | jj new -m "message"(先使用 jj st;若 @ 为空则跳过) |
| 编辑提交 | jj edit <id> |
| 压缩到父提交 | jj squash |
| 自动分配 | jj absorb |
| 变基 | jj rebase -d <destination> |
| 放弃提交 | jj abandon <id> |
| 撤销上一次操作 | jj undo |
| 恢复文件 | jj restore [paths] |
| 创建书签 | jj bookmark create <name> |
| 获取远程内容 | jj git fetch |
| 推送书签 | jj git push -b <name> |
| 添加工作区 | jj workspace add <path> |
| 列出工作区 | jj --no-pager workspace list |
| 忘记工作区 | jj workspace forget [name] |
| 修复过时的工作副本 | jj workspace update-stale |
最佳实践总结
- 先写描述:编码前设置提交说明
- 每个提交只做一项变更:保持提交原子且聚焦
- 使用变更 ID:它们在重写后仍保持稳定
- 完善提交:利用可变性保持历史记录整洁
- 拥抱工作流:没有暂存区,也不需要暂存——只需提交