# 使用 Jujutsu (jj) 与 GitHub：现代化的 Git 工作流


## Jujutsu 简介

[Jujutsu](https://github.com/martinvonz/jj)（也称为 `jj`）是一个下一代版本控制系统，它结合了 Git 的最佳特性，并提供了更直观和强大的工作流程。与默认将历史视为不可变的 Git 不同，Jujutsu 使历史重写变得安全和自然，为几乎所有操作提供了"撤销"按钮。

本指南将向您展示如何将 Jujutsu 与 GitHub 结合使用，利用其强大功能的同时保持与标准 GitHub 协作模式的兼容性。

## 为什么将 Jujutsu 与 GitHub 结合使用？

* **更安全的历史重写**：在 Jujutsu 中，所有历史默认都是可变的。变基、压缩和修改等操作是一等公民，而不是危险的附加功能。您不会意外丢失提交。

* **"撤销"按钮**：`jj undo` 是一个神奇的命令，可以撤销几乎任何操作，从错误的提交描述到复杂的变基操作。

* **无冲突的本地工作流程**：Jujutsu 会自动将您的工作变基到新更改之上。冲突会记录在提交本身中，因此您可以切换到其他任务并在稍后解决它们，而不会被阻塞。

* **轻松的堆叠更改**：创建和管理一系列依赖的拉取请求（堆叠 PR）非常简单，这是 Jujutsu 工作流程的核心部分。

* **工作副本只是另一个提交**：您的未提交更改始终是安全的，并被视为特殊的、可变的"工作副本提交"。切换分支时不会丢失它们。

## 先决条件

在开始之前，请确保您具备以下条件：

1. **已安装 Git**：Jujutsu 的 GitHub 集成依赖于系统上安装的 Git。

2. **已安装 Jujutsu**：按照[官方安装说明](https://github.com/martinvonz/jj/blob/main/docs/install.md)安装 Jujutsu。

3. **GitHub 账户**：您应该有一个 GitHub 账户，并已设置身份验证（例如，通过 SSH 密钥或带有 `gh` 或 `git-credential-manager` 的个人访问令牌）。

## 第 1 部分：启动新项目并推送到 GitHub

让我们从创建本地 `jj` 仓库开始，然后将其推送到 GitHub 上的新空仓库。

### 1. 在 GitHub 上创建新仓库

前往 GitHub 并创建一个新空仓库（例如，`my-jj-project`）。**不要**使用 README 或许可证文件初始化它。

### 2. 初始化本地 Jujutsu/Git 仓库

Jujutsu 内置了 Git 后端。`jj git init` 命令会创建 `.jj` 和 `.git` 目录。

```bash
mkdir my-jj-project
cd my-jj-project
jj git init --git-repo=.
```

### 3. 链接到 GitHub 远程仓库

```bash
# 替换为您的 GitHub 仓库 URL
jj git remote add origin git@github.com:YourUsername/my-jj-project.git
```

### 4. 创建您的第一次提交

在 Jujutsu 中，您不需要暂存文件。只需进行更改，然后描述工作副本来将其转换为真正的提交。

```bash
echo "# My JJ Project" > README.md
jj new # 这会在您当前提交的基础上创建一个新的空提交
```

您的工作副本现在有了 `README.md` 文件。`jj new` 命令"保存"了您之前的状态（当时是空的），并为您提供了一个新的工作副本提交来修改。

让我们描述我们的更改以完成提交。

```bash
jj describe -m "Initial commit"
```

* `jj describe` 相当于 `git commit --amend`。由于工作副本提交始终是可变的，您只需描述它即可。

### 5. 推送到 GitHub

默认情况下，Jujutsu 不会推送所有分支。您需要明确指定。让我们将当前提交推送到 GitHub 上的 `main` 分支。

```bash
# 这告诉 jj 将当前提交 (@) 推送到 'origin' 远程的 'main' 分支。
jj git push --change @ --branch main
```

您也可以使用 `jj branch create main`，然后使用 `jj git push --branch main`。

您的项目现在已经在 GitHub 上上线了！

## 第 2 部分：核心工作流程 - 克隆和贡献

这是最常见的场景：在现有项目上工作。

### 1. 克隆仓库

使用 `jj git clone` 而不是 `git clone`。

```bash
jj git clone git@github.com:some-org/some-repo.git
cd some-repo
```

### 2. 查看日志

`jj log` 是您的最佳伙伴。它为您提供了一个清晰的、基于图形的历史视图。

```bash
jj log
```

您会看到类似这样的内容，其中 `main@origin` 显示远程状态。

```
@  (empty) (working copy)
|
o  kpqxywon main@origin
|  Add new feature
...
```

### 3. 进行更改

让我们开始处理一个新功能。您不需要先创建分支！

```bash
# 1. 创建文件或进行一些编辑
echo "feature details" > my-feature.txt

# 2. 使用描述完成提交
jj describe -m "feat: Implement the core of my new feature"

# 3. 再次查看日志
jj log
```

日志现在会显示您在 `main` 之上的新提交。

```
@  qprlsmzt (working copy) feat: Implement the core of my new feature
|
o  kpqxywon main@origin
|  Add new feature
...
```

### 4. 与远程更改保持同步

当您在工作时，有人向 `main` 推送了内容。

```bash
# 从远程获取更改，类似 `git fetch`
jj git fetch

# 查看日志
jj log
```

您会看到 `main@origin` 已移动，您的提交现在"分叉"了。

```
@  qprlsmzt feat: Implement the core of my new feature
|
| o  yvmlgwon main@origin
|/|  Fix a critical bug
o |  kpqxywon 
|/   Add new feature
...
```

现在，将您的工作变基到最新的 `main` 之上。这在 `jj` 中是一个完全安全和正常的操作。

```bash
jj rebase -d main@origin
```

Jujutsu 会自动将您的提交移动到最新的远程 `main` 之上。

```
@  zmwkxyqv (working copy) feat: Implement the core of my new feature
|
o  yvmlgwon main@origin
|  Fix a critical bug
|
o  kpqxywon 
|  Add new feature
...
```

### 5. 创建拉取请求

这是关键的集成点。您将提交推送到 GitHub 上的**新分支**。

```bash
# 将您当前的提交 (@) 推送到 GitHub 上名为 'feature-branch' 的新分支
jj git push --change @ --branch feature-branch
```

* `--change @`：指定您想要推送当前所在的提交。您可以在这里使用任何提交 ID。
* `--branch feature-branch`：为 PR 创建的远程分支名称。

现在，前往 GitHub。您会看到一个"Compare & pull request"的提示，针对 `feature-branch`。像平常一样创建您的 PR。

## 第 3 部分：更新拉取请求（"哇"时刻）

这就是 Jujutsu 强大之处的体现。有人审查了您的 PR 并要求进行更改。

**传统的 Git 方式**：`git checkout feature-branch`，`git rebase -i main`，修复提交，`git push --force`。这很复杂且容易出错。

**新的 Jujutsu 方式**：

### 1. 找到要编辑的提交

您不需要检出任何内容！只需在 `jj log` 中找到提交。

```bash
jj log
# 假设您的提交是 'zmwkxyqv'
# @  ...
# o  zmwkxyqv feat: Implement the core of my new feature
# ...
```

### 2. 编辑提交

使用 `jj edit` 来"回到过去"并修改该提交。

```bash
jj edit zmwkxyqv
```

您的工作目录现在处于该提交的状态。`jj log` 会显示您的工作副本现在是该提交的*一部分*。

```
@  zmwkxyqv (working copy) feat: Implement the core of my new feature
|
o  yvmlgwon main@origin
...
```

### 3. 进行更改

修复拼写错误，添加测试等。

```bash
echo "some corrections" >> my-feature.txt
# 您甚至可以同时修改提交信息
jj describe -m "feat: Implement the core of my new feature (with corrections)"
```

### 4. 回到最新工作（可选）

如果您在顶部还有其他提交，只需运行 `jj new @-` 即可回到堆栈的顶部。在这个简单的情况下，我们已经完成了。

### 5. 推送更新

这就是神奇之处。只需再次推送到*同一分支*。

```bash
jj git push --branch feature-branch
```

Jujutsu 知道历史已经改变，并会为您执行相当于"强制推送"的操作，但它要安全得多。如果有人在远程分支上添加了提交，它不会让您推送。

您在 GitHub 上的拉取请求现在已更新为新代码和提交信息。就是这么简单。无需变基，无需强制推送，无需分支切换。

## 第 4 部分：高级工作流程 - 堆叠拉取请求

假设您有一个需要先重构的功能。您想要两个独立的 PR，其中 PR2 依赖于 PR1。

### 1. 创建第一个提交（重构）

```bash
jj describe -m "refactor: Improve the data model"
```

### 2. 创建第二个提交（功能）

使用 `jj new` 在重构之上创建一个新提交。

```bash
jj new # 创建一个新的空提交，保留重构提交
# ... 添加功能代码 ...
jj describe -m "feat: Add new user-facing feature"
```

### 3. 查看您的堆栈

`jj log` 会显示您漂亮的堆栈。

```
@  rlsmz... (working copy) feat: Add new user-facing feature
|
o  pqmnw... refactor: Improve the data model
|
o  yvmlg... main@origin
...
```

### 4. 推送两个 PR

您将每个提交推送到不同的分支。

```bash
# 将重构提交推送到它自己的分支
jj git push --change pqmnw... --branch refactor-pr

# 将功能提交推送到它自己的分支
# '@' 符号指代您当前的提交
jj git push --change @ --branch feature-pr
```

现在您在 GitHub 上有两个 PR。`feature-pr` 将显示来自*两个*提交的更改，而 `refactor-pr` 只会显示重构。一旦 `refactor-pr` 合并到 `main` 中，GitHub 会自动更新 `feature-pr`，只显示特定于功能的更改。

如果您需要更新*重构*提交，只需使用 `jj edit pqmnw...`，进行更改，然后 `jj git push --branch refactor-pr`。Jujutsu 会在本地自动将功能提交变基到您的修复之上！

## 总结和速查表

| 目标 | Git 命令 | Jujutsu 命令 |
|------|----------|-------------|
| 克隆仓库 | `git clone <url>` | `jj git clone <url>` |
| 获取最新更改 | `git pull --rebase` | `jj git fetch` 然后 `jj rebase -d main@origin` |
| 开始新工作 | `git checkout -b <branch>` | （直接开始编码） |
| 保存进行中的工作 | `git commit -m "WIP"` | `jj new` |
| 完成提交 | `git add . && git commit` | `jj describe -m "..."` |
| 修改上次提交 | `git commit --amend` | `jj describe -m "..."`（始终修改工作副本） |
| 编辑旧提交 | `git rebase -i <sha>`，将 `pick` 改为 `edit` | `jj edit <sha>` |
| 创建拉取请求 | `git push -u origin <branch>` | `jj git push --change <sha> --branch <branch>` |
| 更新拉取请求 | `git commit --amend && git push --force-with-lease` | `jj edit <sha>`，进行更改，`jj git push --branch <branch>` |
| 撤销操作 | （通常困难或不可能） | `jj undo` |

## 结论

Jujutsu 代表了版本控制的重大演进，它将 Git 的分布式功能与更直观和灵活的历史管理方法相结合。它与 GitHub 的无缝集成使其成为希望享受高级版本控制优势而无需复杂性的开发者的绝佳选择。

通过拥抱 Jujutsu 的可变历史模型，您获得了一个强大的"撤销"按钮、轻松的堆叠更改和一个无冲突的工作流程，它会适应您的开发过程而不是限制它。无论您是在处理个人项目还是与团队协作，Jujutsu 都提供了一个更流畅、更直观的体验，可以显著提高您的生产力并减少开发工作流程中的摩擦。
