using-jj
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesejj Workflow
jj 工作流
Philosophy
理念
- Commits are cheap, descriptions are mandatory. The working copy is always a commit. Never leave it as "(no description set)".
- Experiment freely, the oplog is your safety net. Every mutation is recorded. and
jj undomake anything reversible.jj op restore - Conflicts are state, not emergencies. jj stores conflicts in commits as structured data. Rebase succeeds even with conflicts. Resolve when ready.
- Change IDs are your handle on work. Commit hashes change on rewrite; change IDs don't. Use change IDs to refer to work across rebases and squashes.
- Bookmarks exist for GitHub, not for you. Work with anonymous changes. Add bookmarks only when you need to push.
- Keep the stack shallow. Squash early. Don't let history grow 10 commits deep before curating.
- Use over manual squash routing. When fixing across a stack, let jj figure out where each hunk belongs.
absorb - Colocated = invisible to the team. Teammates see standard git. They don't know you use jj.
- 提交成本低,描述是必须项。工作副本始终是一个提交。绝不要让它处于"(no description set)"状态。
- 自由实验,oplog是你的安全网。每一次变更操作都会被记录。和
jj undo可撤销任何操作。jj op restore - 冲突是状态,而非紧急事件。jj将冲突作为结构化数据存储在提交中。即使存在冲突,变基也能成功。可在方便时再解决冲突。
- 变更ID是你处理工作的标识。提交哈希会在重写时改变,但变更ID不会。跨变基和压缩操作引用工作时,请使用变更ID。
- 书签是为GitHub准备的,而非你自己。使用匿名变更进行工作。仅当需要推送时再添加书签。
- 保持提交栈简洁。尽早压缩提交。不要在整理前让提交历史增长到10个提交的深度。
- 使用而非手动压缩路由。当在提交栈中进行修复时,让jj自动判断每个代码块所属的提交。
absorb - 本地操作对团队不可见。你的同事看到的是标准git操作,他们不会知道你在使用jj。
CRITICAL: AI-Specific Rules
关键:AI专属规则
Always use flag to prevent jj from opening an editor:
-mbash
undefined始终使用参数,避免jj打开编辑器:
-mbash
undefinedWRONG - opens editor, blocks AI
错误示例 - 会打开编辑器,阻塞AI
jj new
jj describe
jj commit
jj squash
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"
**Never use these interactive commands** (no non-interactive mode):
- `jj split` / `jj split -i`
- `jj squash -i`
- `jj diffedit`jj new -m "message"
jj describe -m "message"
jj commit -m "message"
jj squash -m "message"
**绝不要使用这些交互式命令**(无非交互模式):
- `jj split` / `jj split -i`
- `jj squash -i`
- `jj diffedit`Core Concepts
核心概念
Working Copy = Commit
工作副本 = 提交
There is no staging area. Every file edit is automatically tracked in (the current change). No needed.
@git add- = your current change (working copy commit)
@ - = parent of current change
@- - = grandparent
@--
暂存区不存在。所有文件编辑都会自动被(当前变更)追踪。无需执行。
@git add- = 当前变更(工作副本提交)
@ - = 当前变更的父提交
@- - = 当前变更的祖父提交
@--
Change IDs vs Commit IDs
变更ID vs 提交ID
Every change has two identifiers:
- Change ID (e.g., ) — stable across rewrites. Use this to refer to work.
kpqxywon - Commit ID (e.g., ) — changes when content is rewritten.
a1b2c3d4
When you squash, rebase, or amend, the change ID stays the same. This means you can bookmark a change ID mentally and it always resolves, unlike git commit hashes.
每个变更都有两个标识符:
- 变更ID(例如:)—— 重写后保持稳定。使用它来引用工作。
kpqxywon - 提交ID(例如:)—— 内容重写时会改变。
a1b2c3d4
当你压缩、变基或修订提交时,变更ID保持不变。这意味着你可以在脑海中标记一个变更ID,它始终能被正确解析,不像git提交哈希。
Accessing Previous Versions (xyz/n
syntax)
xyz/n访问历史版本(xyz/n
语法)
xyz/nEvery rewrite of a change is recorded. Access previous versions with :
<change-id>/n- — latest (current) version (same as
xyz/0)xyz - — previous version
xyz/1 - — two versions ago
xyz/2
This is useful for restoring a change to its earlier state:
bash
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每个变更的重写记录都会被保存。使用访问历史版本:
<change-id>/n- —— 最新(当前)版本(与
xyz/0相同)xyz - —— 上一个版本
xyz/1 - —— 前两个版本
xyz/2
这可用于将变更恢复到早期状态:
bash
jj restore --from xyz/1 --to xyz # 将xyz恢复到上一个版本的内容
jj diff --from xyz/1 --to xyz # 查看版本间的变更Conflicts Are Just State
冲突只是一种状态
When a rebase produces conflicts, jj records the conflict in the commit and succeeds. No "rebase in progress" blocking state. No ceremony.
--continue- Descendants of conflicted commits work normally
- Resolve conflicts whenever convenient — check out the commit, fix files, done
- marks conflicted commits so you can spot them
jj log
当变基产生冲突时,jj会将冲突作为结构化数据存储在提交中,变基仍能成功。不存在“变基进行中”的阻塞状态,也无需执行操作。
--continue- 冲突提交的后代可正常工作
- 可在方便时解决冲突 —— 切换到该提交,修复文件即可
- 会标记冲突提交,方便你识别
jj log
Workflows
工作流
The Squash Workflow (Recommended)
压缩工作流(推荐)
bash
jj describe -m "feat: what I'm building" # State intent on current change
jj new -m "wip" # New empty change on topbash
jj describe -m "feat: 我正在开发的功能" # 在当前变更上声明意图
jj new -m "wip" # 在当前变更之上创建新的空变更... make changes ...
... 进行代码修改 ...
jj squash -m "feat: done" # Squash into parent
undefinedjj squash -m "feat: 功能完成" # 合并到父提交
undefinedThe Commit Workflow (Simpler)
提交工作流(更简单)
bash
undefinedbash
undefined... make changes ...
... 进行代码修改 ...
jj commit -m "feat: what I did" # Describe + create new change in one step
jj commit -m "feat: 我完成的工作" # 一步完成描述并创建新变更
... keep working ...
... 继续工作 ...
`jj commit` is equivalent to `jj describe -m "..." && jj new`.
`jj commit`等价于`jj describe -m "..." && jj new`。The Edit Workflow (Mid-Stack Fixes)
编辑工作流(提交栈中间的修复)
Need to fix something in an older change? No stash/rebase-i dance:
bash
jj edit <change-id> # Switch working copy to that change需要在较早的变更中修复问题?无需执行暂存/变基交互操作:
bash
jj edit <change-id> # 将工作副本切换到该变更... make your fix ...
... 进行修复 ...
jj new -m "back to work" # Return to tip (descendants auto-rebased)
All descendants of the edited change are automatically rebased.jj new -m "回到工作" # 返回提交栈顶端(后代会自动变基)
被编辑变更的所有后代都会自动变基。Parallel Experiments
并行实验
bash
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 loserbash
jj new main -m "方案A" # 从main分支创建新变更
jj new main -m "方案B" # 从main分支创建另一个新变更(不是从A分支)
jj diff --from <A-id> --to <B-id> # 对比两种方案
jj edit <winner-id> # 继续使用胜出的方案
jj abandon <loser-id> # 丢弃失败的方案Absorb: Smart Squash Routing
Absorb:智能压缩路由
When you have a stack of changes and make fixes in , automatically distributes each hunk to the ancestor where those lines were last modified.
@jj absorbbash
undefined当你有一个提交栈,并且在中进行修复时,会自动将每个代码块分配到最后修改这些代码行的祖先提交中。
@jj absorbbash
undefinedYou're at the top of a 3-commit stack, fixing bugs across all of them
你处于3个提交的栈顶,正在修复所有提交中的bug
jj absorb # Each fix goes to the right commit automatically
Use `jj absorb` when fixing across a stack. Use `jj squash` when you know exactly where changes should go.jj absorb # 每个修复会自动分配到对应的提交
当在提交栈中跨提交修复时,使用`jj absorb`。当你明确知道变更应归属的提交时,使用`jj squash`。Bookmarks & Pushing
书签与推送
Bookmarks are jj's equivalent of git branches, but they don't auto-advance. You must move them explicitly.
书签是jj中对应git分支的功能,但它们不会自动推进。你必须显式移动它们。
Push to main
推送到main分支
bash
jj bookmark set master -r @- # Point bookmark at your commit (not empty @)
jj git pushbash
jj bookmark set master -r @- # 将书签指向你的提交(不是空的@)
jj git pushFeature branches
功能分支
bash
undefinedbash
undefinedCreate and push
创建并推送
jj bookmark create feature-x -r @-
jj git push
jj bookmark create feature-x -r @-
jj git push
Update after more work
后续工作后更新
jj bookmark set feature-x -r @-
jj git push
undefinedjj bookmark set feature-x -r @-
jj git push
undefinedAddressing PR feedback
处理PR反馈
bash
jj new feature-x- -m "address review feedback"bash
jj new feature-x- -m "处理评审反馈"... make changes ...
... 进行修改 ...
jj squash -m "feat: updated per review"
jj bookmark set feature-x -r @-
jj git push
undefinedjj squash -m "feat: 根据评审更新内容"
jj bookmark set feature-x -r @-
jj git push
undefinedRevsets
Revsets(版本集)
Revsets are a functional language for selecting commits. Beyond and :
@@-| Expression | Meaning |
|---|---|
| Current working copy |
| Parent |
| Grandparent |
| Children of x |
| All descendants of x |
| All ancestors of x |
| The trunk/main commit |
| All bookmarked commits |
| Empty commits |
| Divergent changes |
| Remote tags |
| Commits with matching diff |
| Filter by description |
| Filter by author |
Useful examples:
bash
jj log -r 'trunk()..@' # Everything between main and here
jj log -r '::@ & ~::trunk()' # My branch only
jj log -r 'author("trevor")' # My commitsRevsets是一种用于选择提交的函数式语言。除了和之外:
@@-| 表达式 | 含义 |
|---|---|
| 当前工作副本 |
| 父提交 |
| 祖父提交 |
| x的子提交 |
| x的所有后代提交 |
| x的所有祖先提交 |
| 主干/main提交 |
| 所有带书签的提交 |
| 空提交 |
| 分歧变更 |
| 远程标签 |
| 包含匹配差异的提交 |
| 根据描述筛选提交 |
| 根据作者筛选提交 |
实用示例:
bash
jj log -r 'trunk()..@' # 查看main分支到当前提交之间的所有内容
jj log -r '::@ & ~::trunk()' # 仅查看我的分支内容
jj log -r 'author("trevor")' # 查看我的提交Syncing with Remote
与远程仓库同步
bash
jj git fetch # Pull from remote
jj rebase -d master@origin # Rebase onto updated mainbash
jj git fetch # 从远程拉取
jj rebase -d master@origin # 变基到更新后的main分支Temporarily Disabling Immutable Commits
临时禁用不可变提交
When you need to rewrite a commit protected by (e.g., squashing into a remote bookmark):
immutable_heads()bash
undefined当你需要重写被保护的提交时(例如,合并到远程书签):
immutable_heads()bash
undefinedDisable protection (quote the key — parentheses are invalid TOML bare keys)
禁用保护(对键名加引号——括号在TOML裸键中无效)
jj config set --repo 'revset-aliases."immutable_heads()"' 'none()'
jj config set --repo 'revset-aliases."immutable_heads()"' 'none()'
Do your rewrite
执行重写操作
jj squash -m "updated message"
jj squash -m "更新后的提交信息"
ALWAYS restore protection immediately after
完成后立即恢复保护
jj config set --repo 'revset-aliases."immutable_heads()"' 'builtin_immutable_heads() | remote_bookmarks()'
**Important:** The `NAME` argument requires shell quoting around the TOML key because `immutable_heads()` contains parentheses. Use single quotes around the full dotted key with inner double quotes: `'revset-aliases."immutable_heads()"'`.
**CRITICAL: Always ask the user before disabling immutable protection.** Rewriting remote bookmarks means force-pushing, which rewrites shared history. Confirm with the user before proceeding — never silently disable immutability.jj config set --repo 'revset-aliases."immutable_heads()"' 'builtin_immutable_heads() | remote_bookmarks()'
**重要提示**:`NAME`参数需要对TOML键名进行shell转义,因为`immutable_heads()`包含括号。请使用单引号包裹整个带点的键名,内部用双引号:`'revset-aliases."immutable_heads()"'`。
**关键注意事项**:在禁用不可变保护前,务必先询问用户。重写远程书签意味着强制推送,这会修改共享的提交历史。在执行前请与用户确认——绝不要静默禁用不可变保护。Recovery
恢复操作
The operation log records every mutation. Nothing is ever truly lost.
bash
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操作日志记录了每一次变更操作。没有任何内容会真正丢失。
bash
jj op log # 查看所有操作
jj undo # 撤销上一次操作
jj op restore <id> # 跳转到任意历史状态
jj evolog # 查看当前变更的演进历史
jj evolog -r <change-id> # 查看任意变更的演进历史Recommended Config
推荐配置
User config lives at :
~/.config/jj/config.tomltoml
[remotes.origin]
auto-track-bookmarks = "*"
[revset-aliases]用户配置文件位于:
~/.config/jj/config.tomltoml
[remotes.origin]
auto-track-bookmarks = "*"
[revset-aliases]Prevent rewriting pushed commits
防止重写已推送的提交
'immutable_heads()' = 'builtin_immutable_heads() | remote_bookmarks()'
'immutable_heads()' = 'builtin_immutable_heads() | remote_bookmarks()'
Shorthand for trunk
主干的简写
'trunk()' = 'master@origin'
undefined'trunk()' = 'master@origin'
undefinedBail Out
紧急退出
bash
rm -rf .jj # Delete jj state, keep git unchangedbash
rm -rf .jj # 删除jj状态,保留git内容不变