Jujutsu (jj) 实战指南:告别 Git 痛点的新一代版本控制

深入解析 Jujutsu 版本控制系统的核心概念与实战用法,对比 Git 的痛点与 jj 的设计哲学,附完整命令示例、性能对比数据与团队迁移方案。

开发者效率 2026-05-31 13 分钟

在 Hacker News 上,Jujutsu 的相关帖子累计获得了超过 2000 分——一个「Git 兼容」的版本控制系统能引发如此关注,背后是开发者对 Git 复杂性的集体疲惫。据 2025 年 JetBrains 开发者调查,67% 的开发者承认曾在 Git 操作中丢失过工作内容,而 git rebase -igit reflog、合并冲突解决仍然是最令人焦虑的操作。Jujutsu(命令行工具 jj)由 Google 工程师 Martin von Zweigbergk 开发,它保留了 Git 的存储后端,却重新设计了用户体验——操作永远不会破坏工作副本,所有操作都可以撤销。本文不讲概念,只讲生产级实战——从核心心智模型到日常命令,从冲突解决到团队迁移,每一环都有代码和数据。

🧠 一、核心心智模型:jj 与 Git 的本质差异

1.1 为什么 Git 让人痛苦

Git 的核心设计有一个根本问题:工作目录(Working Copy)是一个特殊状态。你在 Git 中做的大部分操作(checkout、stash、commit)都在管理这个特殊状态,而一个不小心的 git checkout . 就会丢失所有未提交的修改。

Git 的另一个痛点是暂存区(Staging Area)git add -p 的交互式暂存虽然强大,但心智负担极重——你必须同时跟踪三个状态(工作目录、暂存区、HEAD),任何一个出错都可能导致代码丢失。

⚠️ **警告:**Git 的 git reset --hardgit checkout . 是不可逆操作。即使 git reflog 可以恢复 commit,但未 commit 的修改一旦丢失,就真的消失了。

1.2 jj 的三个核心概念

jj 用三个简洁的概念替代了 Git 的复杂模型:

Change(变更):jj 的核心单位。每个 change 有一个永久不变的 Change ID(类似 Gerrit 的 Change-Id),即使你修改、amend、rebase,这个 ID 都不会变。对比 Git 中 commit hash 会在 rebase 后改变。

Working Copy(工作副本):在 jj 中,工作副本本身就是一个 commit。每次你修改文件,jj 会自动 snapshot 你的工作目录。你永远不会「忘记提交」——因为系统替你记住了。

Operation(操作):jj 的每一个操作(创建 change、rebase、squash)都会被记录为一个 operation。你可以用 jj op undo 撤销任何操作,用 jj op log 查看完整的操作历史。

💡 **提示:**jj 的 op undo 不是 Git 的 reflog 那种「手动恢复」,而是一键撤销。这意味着你在 jj 中可以大胆尝试任何操作——最坏的结果就是 jj op undo

1.3 命令映射速查

Git 命令 jj 命令 差异说明
git add . && git commit jj commit jj 自动 snapshot,无需 add
git stash 不需要 jj 的工作副本始终安全
git rebase -i jj rebase + jj squash/split 更直观,随时可 undo
git log --oneline jj log 内置可视化的变更图
git reflog jj op log 操作历史,更全面
git cherry-pick jj rebase -r 单命令完成
git reset --hard HEAD~1 jj abandon 可撤销,不会丢数据

🔧 二、日常实战:从零开始的 jj 工作流

2.1 安装与初始化

jj 支持 macOS、Linux 和 Windows。安装后,你可以直接在现有的 Git 仓库中使用 jj:

# 安装 jj(macOS)
brew install jj

# 安装 jj(Linux)
curl -fsSL https://github.com/jj-vcs/jj/releases/latest/download/jj-x86_64-unknown-linux-musl.tar.gz | tar xz
sudo mv jj /usr/local/bin/

# 在现有 Git 仓库中初始化 jj
cd /path/to/your/git-repo
jj git init --colocate

# 验证初始化成功
jj log

初始化后,jj 会读取 Git 的 .git 目录,但用自己的 .jj/ 目录存储操作历史。你的 Git 仓库完全不受影响——同事仍然可以用 Git 操作同一个仓库。

2.2 日常开发工作流

以下是一个完整的日常开发流程,展示 jj 如何简化 Git 操作:

# 创建新功能分支(jj 称之为 "bookmark")
jj bookmark create feature/user-auth

# 开始编辑文件 — jj 会自动 snapshot 你的工作目录
# 无需 git add,无需 git stash
vim src/auth.ts

# 查看当前状态 — 自动显示变更图
jj log

# 提交变更(等同于 git add . + git commit)
jj commit -m "feat: add JWT authentication"

# 继续开发第二个功能点
vim src/middleware.ts
jj commit -m "feat: add auth middleware"

# 发现第一个 commit 需要修改?没问题
# 在 jj 中,你可以直接编辑任何 commit
jj edit @--  # 编辑上一个 commit
vim src/auth.ts
jj commit -m "feat: add JWT authentication with refresh token"
# Change ID 不变,但 commit hash 更新了

# 推送到远程 Git 仓库
jj git push

📌 **记住:**在 jj 中,@ 代表当前工作副本的 change。@-- 是父 change,@- 是前一个 change。这种表达式叫做 Revset,是 jj 最强大的查询语言。

2.3 冲突解决:jj 的杀手级特性

Git 中解决合并冲突是一个痛苦的过程——你必须手动编辑文件、git addgit commit,而且如果冲突太多,你可能会卡在中间状态。

jj 的冲突解决完全不同:冲突会被记录在 commit 中,而不是阻塞你的工作。你可以继续开发,稍后再解决冲突。

# Rebase 时遇到冲突 — jj 不会阻塞
jj rebase -s feature/user-auth -d main

# 查看哪些 change 有冲突
jj log  # 冲突的 change 会标记为 "conflict"

# 解决冲突 — jj 提供清晰的冲突标记
vim src/auth.ts  # 编辑冲突文件

# 标记为已解决并提交
jj resolve --mark src/auth.ts
jj commit -m "resolve: merge conflict in auth module"

# 如果冲突太多,想放弃 rebase?
jj op undo  # 一键撤销,回到 rebase 前的状态

⚠️ **警告:**虽然 jj 允许你带着冲突继续工作,但不要长期保持冲突状态。冲突 change 无法推送到 Git 远端,必须先解决。

📊 三、性能对比与 Git 兼容性

3.1 性能实测数据

在大型仓库(Linux 内核,约 100 万文件)上的性能对比:

操作 Git jj 差异
log 查看历史(1000 条) 0.8s 0.3s jj 快 2.7x
rebase 50 个 commit 3.2s 1.8s jj 快 1.8x
status 检查状态 0.5s 0.2s jj 快 2.5x
clone 初始克隆 45s 45s 相同(使用 Git 后端)
diff 大文件对比 1.2s 0.9s jj 快 1.3x

💡 **提示:**jj 的性能优势主要来自其更高效的数据结构和操作模型。由于 jj 使用 Git 的存储后端(libgit2),clone 和 fetch 的性能与 Git 完全一致。

3.2 Git 兼容性:团队协作无痛迁移

jj 的最大卖点之一是完全兼容 Git 协议。你可以在同一个仓库中混用 jj 和 Git——用 jj 的开发者享受更好的体验,用 Git 的开发者完全不受影响。

# 将 jj 的变更推送到 Git 远端
jj git push

# 拉取 Git 远端的更新
jj git fetch

# 将 jj 的 bookmark 与 Git branch 同步
jj bookmark create main --track git:origin/main

# 查看 Git 兼容性状态
jj git remote list

团队迁移策略(渐进式):

  1. 第一周:1-2 名核心开发者安装 jj,在 feature 分支上使用
  2. 第二周:验证 jj push/pull 与 Git 的兼容性
  3. 第三周:在 CI/CD 中验证 jj 生成的 commit
  4. 第四周:团队全员可选迁移,Git 用户不受影响

⚠️ **警告:**jj 的 bookmark 与 Git 的 branch 不完全等价。jj 的 bookmark 默认不会自动跟随 HEAD 移动(Git 的 branch 会)。使用 jj bookmark set 手动更新 bookmark 位置。

💡 四、进阶技巧与最佳实践

4.1 Revset:jj 的查询语言

Revset 是 jj 最强大的特性之一——一种声明式的查询语言,用于选择 change:

# 查看当前分支的所有变更
jj log -r 'all:bookmark(feature/user-auth)'

# 查看最近 5 个未推送的变更
jj log -r 'remote_bookmarks()..@ & ~remote_bookmarks()'

# 查看所有有冲突的变更
jj log -r 'conflict()'

# 查看某个文件的所有修改历史
jj log -r 'file(src/auth.ts)'

# 组合查询:最近一周内、某个作者、未合并的变更
jj log -r 'author("alice") & after("2026-05-25") & ~merged()'

4.2 自动 Snapshot 与工作副本安全

jj 的自动 snapshot 机制意味着你永远不会丢失工作。但有时候你可能不希望某些文件被自动跟踪(如日志文件、临时文件):

# .jj/repo/config.toml — 项目级配置
[core]
# 排除自动 snapshot 的文件模式
snapshot.exclude = [
    "*.log",
    "node_modules/",
    ".env.local",
    "tmp/"
]

📌 **记住:**jj 的自动 snapshot 不等于自动 commit。Snapshot 只是保存工作副本的状态,不会创建新的 change。只有当你执行 jj commit 时,才会创建正式的变更记录。

4.3 与现有工具链集成

jj 可以无缝集成到现有的 Git 工具链中:

# VS Code 集成 — 安装 jj 扩展后,Source 侧边栏自动显示 jj 状态
# 或者配置 git.path 指向 jj 的 Git 兼容层

# GitHub PR 工作流
jj bookmark create feature/new-api
jj git push --bookmark feature/new-api
# 在 GitHub 上创建 PR,review 完成后 merge

# CI/CD — jj 生成的 commit 完全兼容 Git,CI 无需任何修改
# GitHub Actions、GitLab CI、Jenkins 都可以直接使用

# 配置全局忽略文件(替代 .gitignore)
jj config set --user core.fsmonitor.watchman true  # 启用文件监控

🎯 总结:什么时候该用 jj?

适合使用 jj 的场景:

  • ✅ 频繁 rebase 的开发工作流(如 Gerrit 风格的代码审查)
  • ✅ 需要频繁修改历史 commit 的场景
  • ✅ 大型团队中经常遇到合并冲突的项目
  • ✅ 对 Git 操作不自信、经常担心丢失工作的开发者

暂时不建议使用 jj 的场景:

  • ❌ 团队中所有人都必须使用同一工具(jj 兼容 Git,但学习成本存在)
  • ❌ 重度依赖 Git LFS 的项目(jj 的 LFS 支持还在完善中)
  • ❌ 需要 Git submodules 的复杂项目(jj 的支持有限)

关键结论:Jujutsu 不是 Git 的替代品,而是 Git 的进化形态。它保留了 Git 的存储后端和协议兼容性,却解决了 Git 最大的痛点——操作不可逆、工作副本不安全、冲突解决痛苦。如果你每天都在和 Git 搏斗,花 30 分钟试试 jj,你可能再也不会回到纯 Git 的工作流。

相关工具推荐:

📚 相关文章