Jujutsu (jj) Git 命令对照表:从 Git 平滑迁移的完整命令速查与源码级实战解析
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
Jujutsu(jj)是一个与 Git 兼容的版本控制系统,其绝大多数命令都沿用了 Git 用户熟悉的语义,但底层模型更简洁、更强大。本文以官方文档中的 Git 命令对照表(web/docs/src/content/docs/git-command-table.yml)为骨架,逐项给出 Git 与jj的等价命令,并结合仓库源码说明每个命令的实际行为与适用场景。读完本文,你将掌握从git迁移到jj的完整命令映射,能够直接用jj完成日常开发中的仓库管理、差异查看、历史改写、分支/标签操作与撤销恢复。
一、对照表的使用前提
在开始逐条对照之前,需要先理解 Jujutsu 与 Git 在模型上的一个关键差异:Git 的大多数操作只作用于"当前分支/工作副本",而jj的所有命令都可以作用于任意 commit(revision)。对照表为了简洁省略了这一点,但它是理解jj能力的基础。
例如,Git 中只能git commit --amend修改当前提交,而 Jujutsu 中:
jj squash -r <revision>会将指定 revision 的 diff 移入其 parent——即使该 revision 并不是工作副本提交。这种"任意 revision 可寻址、可改写"的设计,正是jj历史改写如此轻松的根本原因。
本文的对照表数据以仓库中的 YAML 文件为唯一事实来源(web 站点版与 mkdocs 版各有一份,内容基本一致,本文合并了两者并采用当前源码确认的命令形式):
- 渲染入口:web/docs/src/content/docs/git-command-table.md 通过
::yaml-table{file="git-command-table.yml"}指令引用数据文件; - 数据文件:web/docs/src/content/docs/git-command-table.yml(mkdocs 版为 docs/git-command-table.yml);
- 渲染插件:web/docs/plugins/remark-yaml-table.mjs 将 YAML 数组解析为 Markdown 表格,每个对象一行、键名作为列头。
二、Git → jj 完整命令对照总表
下表覆盖官方文档的全部条目(使用场景、Git 命令、jj 命令、备注)。@表示当前工作副本提交,@-表示其父提交;revset 语法详见 docs/revsets.md。
| 使用场景 | Git 命令 | jj 命令 | 备注 |
|---|---|---|---|
| 创建新仓库 | git init | jj git init [--no-colocate] | 默认创建与.git共存的 colocated 仓库 |
| 克隆已有仓库 | git clone <source> <destination> [--origin <remote name>] | jj git clone <source> <destination> [--remote <remote name>] | 暂不支持克隆非 Git 仓库 |
| 从远程拉取所有分支 | git fetch [<remote>] | jj git fetch [--remote <remote>] | 暂不支持从非 Git 仓库拉取 |
| 推送所有分支到远程 | git push --all [<remote>] | jj git push --all [--remote <remote>] | 只影响被跟踪的书签(bookmark) |
| 推送单个分支到远程 | git push <remote> <bookmark name> | jj git push --bookmark <bookmark name> [--remote <remote>] | 暂不支持推送到非 Git 仓库 |
| 添加远程目标 | git remote add <remote> <url> | jj git remote add <remote> <url> | — |
| 查看状态摘要 | git status | jj st | — |
| 查看当前变更的差异 | git diff HEAD | jj diff | — |
| 查看某个变更的差异 | git diff <revision>^ <revision> | jj diff -r <revision> | — |
| 查看某变更到当前变更的差异 | git diff <revision> | jj diff --from <revision> | — |
| 查看变更 A 到变更 B 的差异 | git diff A B | jj diff --from A --to B | — |
| 查看 A..B 范围内所有变更 | git diff A...B | jj diff -r A..B | revset 范围语法 |
| 查看变更描述与差异 | git show <revision> | jj show <revision> | — |
| 添加文件到当前变更 | touch filename; git add filename | touch filename | jj 自动快照工作副本 |
| 从当前变更移除文件 | git rm filename | rm filename | — |
| 取消跟踪但仍保留在磁盘 | git rm --cached filename | jj file untrack filename | 文件名必须匹配 ignore 模式才能保持未跟踪,见工作副本文档 |
| 修改当前变更中的文件 | echo stuff >> filename | echo stuff >> filename | — |
| 完成当前变更并开始新变更 | git commit -a | jj commit | — |
| 查看当前提交祖先的紧凑日志 | git log --oneline --graph | jj log -r ::@ | ::@表示 @ 的所有祖先 |
| 查看所有可达提交的紧凑日志 | git log --oneline --graph --all | jj log -r 'all()'或jj log -r :: | Git 会显示 jj 保留的隐藏提交,可用--exclude refs/jj/* --all排除 |
| 查看 main 分支之外的提交 | git log --oneline --graph --branches --not upstream/main | jj log | — |
| 查找变更行包含 "stuff" 的提交(pickaxe) | git log -G stuff | jj log -r 'diff_lines(regex:stuff)' | 旧版diff_contains()已废弃,见下文源码说明 |
| 列出工作副本中已版本化的文件 | git ls-files --cached | jj file list | — |
| 在版本化文件中搜索 | git grep foo | jj file search --pattern=foo或rg --no-require-git foo | — |
| 放弃当前变更并开始新变更 | git reset --hard(不可撤销) | jj abandon | — |
| 将当前变更清空 | git reset --hard | jj restore | — |
| 放弃工作副本的父提交但保留其差异 | git reset --soft HEAD~ | jj squash --from @- | — |
| 丢弃部分文件的工作副本改动 | git restore <paths>...或git checkout HEAD -- <paths>... | jj restore <paths>... | — |
| 修改当前变更描述 | Git 不支持 | jj describe | — |
| 修改上一个变更的描述 | git commit --amend --only | jj describe @- | — |
| 修改任意变更的描述 | git commit --fixup=reword:X; git rebase --autosquash X^ | jj describe X | — |
| 暂时收起当前变更 | git stash | jj new @- | 旧工作副本提交保留为兄弟提交,可用jj edit X恢复 |
| 基于 main 书签开始新变更 | git switch -c topic main或git checkout -b topic main | jj new main | 无需先 stash 或 commit |
| 将分支 A 合并进当前变更 | git merge A | jj new @ A | 创建合并提交 |
| 检出命名 revision 以查看源码 | git checkout v1.0.1 | jj new v1.0.1 | 在其上创建新空变更 |
| 将书签 A 移到书签 B 上 | git rebase B A | jj rebase -b A -o B | — |
| 将变更 A 及其后代移到 B 上 | git rebase --onto B A^ <some descendant bookmark> | jj rebase -s A -o B | — |
| 将变更顺序 A-B-C-D 重排为 A-C-B-D | git rebase -i A | jj rebase -r C --before B | — |
| 一次重排多个提交 | git rebase -i | jj arrange | 交互式重排 |
| 将当前变更的差异并入父变更 | git commit --amend -a | jj squash | — |
| 交互式将部分差异并入父变更 | git add -p; git commit --amend | jj squash -i | — |
| 将工作副本差异并入某个祖先 | git commit --fixup=X; git rebase --autosquash X^ | jj squash --into X | — |
| 交互式把任意变更的部分差异移到另一变更 | Git 不支持 | jj squash -i --from X --into Y | — |
| 交互式把工作副本变更一分为二 | git commit -p | jj split | — |
| 交互式把任意变更一分为二 | Git 不支持(可用git rebase -i的 edit 模拟) | jj split -r <revision> | — |
| 交互式编辑某变更的差异 | Git 不支持(可用 edit 模拟) | jj diffedit -r <revision> | — |
| 解决冲突并继续中断的操作 | echo resolved > filename; git add filename; git rebase/merge/cherry-pick --continue | echo resolved > filename; jj squash | 操作不会中断,无需 continue |
| 在另一提交之上创建提交副本 | git co <destination>; git cherry-pick <source> | jj duplicate <source> -o <destination> | — |
| 查找工作副本根(或判断是否在仓库内) | git rev-parse --show-toplevel | jj workspace root | — |
| 列出书签/分支 | git branch | jj bookmark list或jj b l | — |
| 创建书签/分支 | git branch <name> <revision> | jj bookmark create <name> -r <revision> | — |
| 向前移动书签/分支 | git branch -f <name> <revision> | jj bookmark move <name> --to <revision>或jj b m <name> -t <revision> | — |
| 向后或侧移书签/分支 | git branch -f <name> <revision> | jj bookmark move <name> --to <revision> --allow-backwards | — |
| 删除书签/分支 | git branch --delete <name> | jj bookmark delete <name> | — |
| 列出标签 | git tag -l | jj tag list | — |
| 创建标签 | git tag <name> <revision> | jj tag set <name> -r <revision> | — |
| 删除标签 | git tag -d <name> | jj tag delete <name> | — |
| 列出包含某 revision 的标签 | git tag --contains <revision> | jj tag list -r '<revision>::' | ::后缀运算符选择全部后代 |
| 列出已并入某 revision 的标签 | git tag --merged <revision> | jj tag list -r '::<revision>' | ::前缀运算符选择全部祖先 |
| 查看仓库操作日志 | Git 不支持 | jj op log | — |
| 撤销上一次操作 | Git 不支持 | jj undo | 另有对应的jj redo |
| 回滚某个历史操作 | Git 不支持 | jj op revert | — |
| 创建抵消某提交的提交 | git revert <revision> | jj revert -r <revision> -B @ | — |
| 查看每行最后修改的 revision 与作者 | git blame <file> | jj file annotate <path> | — |
三、仓库创建、克隆与远程协作
3.1 初始化与克隆
jj git init默认会创建一个与.git目录共存的colocated仓库,让你可以随时用 Git 命令访问同一份仓库内容;若想生成纯jj仓库,可加--no-colocate。
克隆时,jj git clone <source> <destination> [--remote <remote name>]与 Git 的参数几乎一一对应,唯一区别是远程名称参数用--remote(Git 用--origin)。文档明确标注:目前 jj 只支持克隆/拉取/推送 Git 仓库,非 Git 仓库的互操作尚不支持。
3.2 拉取与推送
jj git fetch [--remote <remote>] # 等价于 git fetch [<remote>] jj git push --all [--remote <remote>] # 等价于 git push --all [<remote>] jj git push --bookmark <name> [--remote <remote>] # 等价于 git push <remote> <name>在源码层面,jj git push的能力比表格展示的更丰富。查看 cli/src/commands/git/push.rs,其帮助文本说明:默认推送的是指向remote_bookmarks(remote=<remote>)..@的已跟踪书签与标签;--bookmark/--tag推送指定的书签或标签,--all推送全部书签和标签,--change则根据指定提交的 change ID 自动生成书签名。此外,push 还支持--deleted以删除远程上的书签/标签。
需要注意:jj git push --all只影响被跟踪的书签(tracked bookmarks)。新克隆仓库中的书签是否出现在jj log中,取决于是否执行了jj bookmark track <name>@<remote>(对照表 YAML 中的 TODO 注释也提到了这一点)。
3.3 远程管理
jj git remote add <remote> <url>与 Git 完全等价,配套的还有jj git remote remove、jj git remote rename、jj git remote set-url、jj git remote list(见 cli/src/commands/git/remote/)。
四、查看状态、差异与历史日志
4.1 状态与差异
| 需求 | jj 命令 |
|---|---|
| 状态摘要 | jj st |
| 当前变更的差异 | jj diff |
| 某变更自身的差异 | jj diff -r <revision> |
| 某变更到当前变更的差异 | jj diff --from <revision> |
| A 到 B 的差异 | jj diff --from A --to B |
| A..B 范围内所有变更 | jj diff -r A..B |
jj diff -r A..B中的A..B是 revset 的范围语法,jj会给出从 A 到 B 的所有变更的累计差异——这比 Git 的git diff A...B(三点 diff)更符合直觉。
4.2 日志查看
| 需求 | jj 命令 |
|---|---|
| 当前提交祖先的日志 | jj log -r ::@ |
| 全部可达提交的日志 | jj log -r 'all()'或jj log -r :: |
| main 之外的分支 | jj log |
| pickaxe 搜索 | jj log -r 'diff_lines(regex:stuff)' |
关于 pickaxe 的写法需要特别说明:官方 web 版 YAML 中写的是jj log -r 'diff_contains(stuff)',但查看 revset 实现 lib/src/revset.rs 可以发现,diff_contains()已被标记为废弃:
map.insert("diff_lines", |diagnostics, function, context| { if function.name != "diff_lines" { // TODO: Remove in jj 0.44+ diagnostics.add_warning(RevsetParseError::expression( "diff_contains() is deprecated; use diff_lines() instead", function.name_span, )); } ... }); map.insert("diff_contains", map["diff_lines"]);也就是说,diff_contains目前仍可用(作为diff_lines的别名并发出弃用警告),但新代码应使用diff_lines。与之配套的还有diff_lines_added()与diff_lines_removed(),分别只匹配新增/删除行。
4.3 查看单条变更
jj show <revision>等价于git show <revision>,同时展示描述与差异。
五、文件的增删与内容搜索
Jujutsu 最省心的特性之一是自动快照:工作副本的任何改动都会在下次命令执行时自动记录到当前变更中,因此"添加文件"不再需要显式git add:
| 需求 | jj 命令 |
|---|---|
| 添加文件到当前变更 | touch filename(自动被跟踪) |
| 删除文件 | rm filename(自动记录删除) |
| 取消跟踪但保留文件 | jj file untrack filename |
| 修改文件 | 直接编辑 |
| 列出已版本化文件 | jj file list |
| 在版本化文件中搜索 | jj file search --pattern=foo |
| 逐行追溯 | jj file annotate <path> |
jj file untrack有一个易被忽略的细节:文件名必须匹配某个 ignore 模式才能保持未跟踪,否则它仍会被工作副本快照重新纳入跟踪(详见工作副本文档)。
内容搜索推荐使用内置的jj file search(实现见 cli/src/commands/file/search.rs),它支持:
--revision <revset>:指定在哪个 revision 中搜索(默认@);--pattern <pattern>:字符串模式,语法为kind:pattern,省略 kind 时默认按正则匹配;glob 模式下需要整行匹配,例如--pattern 'glob:*foo*';--name-only:只打印包含匹配的文件路径;--line-number/-n:在匹配行前加行号。
六、提交、描述信息与丢弃
6.1 提交与描述
| 需求 | jj 命令 |
|---|---|
| 完成当前变更并开新变更 | jj commit |
| 修改当前变更描述 | jj describe |
| 修改上一变更描述 | jj describe @- |
| 修改任意变更描述 | jj describe X |
Git 无法直接修改任意历史提交的描述(需要 fixup + autosquash 的组合拳),而jj describe X一行搞定——这正是"所有命令可作用于任意 commit"的体现。
6.2 丢弃与恢复
| 需求 | jj 命令 |
|---|---|
| 放弃当前变更 | jj abandon(对应git reset --hard,但可撤销) |
| 清空当前变更 | jj restore |
| 放弃父提交但保留其差异 | jj squash --from @-(对应git reset --soft HEAD~) |
| 丢弃部分文件的改动 | jj restore <paths>... |
| 暂时收起当前变更 | jj new @- |
对照表中"放弃当前变更"特别标注 Git 的git reset --hard不可撤销,而jj abandon之后随时可以用jj undo找回——Jujutsu 的操作日志机制让"危险操作"不再危险。
"暂时收起"对应 Git 的git stash,但语义完全不同:jj new @-只是在你当前提交的父提交之上新建一个空变更并切换过去,原来的工作副本提交并不会消失,而是作为兄弟提交留在历史中,之后用jj edit X即可回到它继续工作。没有单独的 stash 概念,也不需要它。
七、分支(Bookmark)与标签(Tag)管理
7.1 书签即分支
Jujutsu 中与 Git 分支对应的是bookmark(书签),命令集中在 cli/src/commands/bookmark/:
| 需求 | jj 命令 |
|---|---|
| 列出书签 | jj bookmark list(简写jj b l) |
| 创建书签 | jj bookmark create <name> -r <revision> |
| 向前移动 | jj bookmark move <name> --to <revision>(简写jj b m <name> -t <revision>) |
| 向后/侧移 | jj bookmark move <name> --to <revision> --allow-backwards |
| 删除书签 | jj bookmark delete <name> |
与 Git 的一个关键区别:Git 的git branch -f不允许把分支移到其自身后代之外的位置,而jj bookmark move默认也不允许向后移动,需要显式加--allow-backwards。此外还有jj bookmark set、jj bookmark rename、jj bookmark track/untrack等命令用于远程书签跟踪管理。
7.2 标签
标签命令位于 cli/src/commands/tag/(list/set/delete/track/untrack):
| 需求 | jj 命令 |
|---|---|
| 列出标签 | jj tag list |
| 创建标签 | jj tag set <name> -r <revision> |
| 删除标签 | jj tag delete <name> |
| 列出包含某 revision 的标签 | jj tag list -r '<revision>::' |
| 列出已并入某 revision 的标签 | jj tag list -r '::<revision>' |
最后两条用到了 revset 的范围运算符:<revision>::后缀形式选择该 revision 的所有后代(等价于git tag --contains),::<revision>前缀形式选择其所有祖先(等价于git tag --merged)。revset 的完整语法见 docs/revsets.md。
八、历史改写:rebase、squash、split、diffedit 与 arrange
8.1 Rebase 三兄弟
| 需求 | Git 命令 | jj 命令 |
|---|---|---|
| 把书签 A 及其分支移到 B 上 | git rebase B A | jj rebase -b A -o B |
| 把变更 A 及其全部后代移到 B 上 | git rebase --onto B A^ <bookmark> | jj rebase -s A -o B |
| 重排 A-B-C-D 为 A-C-B-D | git rebase -i A | jj rebase -r C --before B |
| 一次重排多个提交 | git rebase -i | jj arrange |
-b(branch)只移动书签指向的提交,-s(source)则连后代一起移动,-r(revision)精确到单个提交并用--before/--after指定新位置。jj arrange(实现见 cli/src/commands/arrange.rs)则提供了交互式重排多个提交的界面,替代git rebase -i的 todo 编辑。
值得强调:Git 的git rebase B A之后,A 的其他后代分支往往需要单独再 rebase 一次;而 jj 的jj rebase -b A -o B会一并移动 A 的所有后代,无需逐个处理。
8.2 Squash 与 Split
| 需求 | Git 命令 | jj 命令 |
|---|---|---|
| 把当前变更并入父变更 | git commit --amend -a | jj squash |
| 交互式并入部分差异 | git add -p; git commit --amend | jj squash -i |
| 并入某个祖先 | git commit --fixup=X; git rebase --autosquash X^ | jj squash --into X |
| 任意变更之间的部分合并 | Git 不支持 | jj squash -i --from X --into Y |
| 拆分工作副本变更 | git commit -p | jj split |
| 拆分任意变更 | Git 不支持 | jj split -r <revision> |
| 编辑任意变更的差异 | Git 不支持 | jj diffedit -r <revision> |
对照表开头示例中的jj squash -r <revision>也是同理:把任意指定 revision 的差异并入其父提交。
8.3 复制提交
jj duplicate <source> -o <destination>等价于 Git 的git co <destination>; git cherry-pick <source>,但无需切换工作副本即可在任意目标上复制任意来源提交。
九、冲突处理:操作永不中断
这是 Jujutsu 相对 Git 最具颠覆性的设计之一。Git 中执行rebase/merge/cherry-pick遇到冲突会中断操作,要求你解决冲突后git add再--continue;而 jj 的处理方式是:
echo resolved > filename; jj squashjj 的操作从不会中断——冲突会被记录在提交内部并继续执行,之后你随时可以解决冲突并继续。这意味着"写一半的 rebase"不再存在,也就根本没有--continue这个动作。冲突的详细处理方式参见 docs/conflicts.md。
十、操作日志:undo、redo 与 op revert
Jujutsu 的每次命令都会产生一条操作记录,构成一棵操作日志树。Git 没有与之对应的概念:
| 需求 | jj 命令 |
|---|---|
| 查看操作日志 | jj op log |
| 撤销上一次操作 | jj undo(配套jj redo) |
| 回滚某个历史操作 | jj op revert |
jj undo撤销最近一次操作,jj redo重做被撤销的操作;jj op revert则可以把仓库状态回滚到操作日志中任意一个操作之后的状态。配合jj abandon、jj restore等命令,几乎所有"手滑"操作都可以无损找回,这是 Git 无法提供的安全网。操作日志的原理与并发模型见 docs/technical/concurrency.md。
十一、jj 独有、Git 无法直接做到的能力
从对照表中可以提炼出 jj 相对 Git 的差异化能力,这些能力全部源于"变更(change)是一等公民、所有命令可作用于任意 commit"的模型:
- 任意提交的描述修改:
jj describe X,Git 需要 fixup + autosquash 组合; - 任意提交的拆分与差异编辑:
jj split -r <revision>、jj diffedit -r <revision>,Git 只能借助rebase -i的 edit 动作间接模拟; - 任意变更之间的部分差异搬移:
jj squash -i --from X --into Y,Git 完全没有对应操作; - 操作日志与全量撤销:
jj op log/jj undo/jj op redo/jj op revert,Git 的 reflog 只覆盖引用变更且不构成完整操作历史; - 无需 stash 的上下文切换:
jj new创建新变更后旧变更仍保留,随时可jj edit返回。
Git 与 jj 的更多理念对比,可参考 docs/git-comparison.md 与 docs/git-compatibility.md。
十二、延伸阅读
- 完整 CLI 参考:cli/docs/cli-reference.md(web 版为 web/docs/src/content/docs/cli-reference.md)
- revset 表达式语言:docs/revsets.md
- 书签(分支)模型:docs/bookmarks.md
- 工作副本与自动快照:docs/working-copy.md
- 冲突表示与解决:docs/conflicts.md
- 上手教程:docs/tutorial.md
【免费下载链接】jjA Git-compatible VCS that is both simple and powerful项目地址: https://gitcode.com/GitHub_Trending/jj/jj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考