Skip to content

第四章:Git 工作流与 PR 自动化 ​

学习目标 ​

  • 让 Claude 接管你的整个 Git 工作流:分支、编码、提交、PR -- 不需要你自己输入 git 命令
  • 构建一个真实功能(暗黑模式切换),使用正确的分支策略和原子提交
  • 用 Git Worktree 实现真正的并行开发,多个 Claude 会话同时运行
  • 故意搞砸然后用 /rewind 恢复 -- 建立信心,敢让 Claude 尝试大胆的改动
  • 用管道模式在每次合并前做即时代码审查

附录链接:A05 工具调用内部机制 解释 Claude 在需要 git 信息时如何在 Read 和 Bash 之间选择。A01 提示工程 介绍如何写出能产生好 commit message 的提示。

核心概念 ​

Claude 的 Git 集成是深度的 ​

Claude Code 不只是运行 git 命令。它理解你的代码变更,通过分析 diff 写 commit message,选择性地暂存文件(从不用 git add -A),生成带测试计划的结构化 PR 描述。

完整循环,全部通过对话完成:

1. 创建分支     <- Claude 按你的命名规范取名
2. 写代码       <- Claude 的核心能力
3. 暂存变更     <- Claude 选择特定文件,跳过 .env 和密钥
4. 提交         <- Claude 读 diff 写 commit message
5. 创建 PR      <- Claude 生成标题、摘要、测试计划
6. 代码审查     <- Claude 也能审查别人的 PR

为什么"从不用 git add -A"? 因为 git add -A 会暂存所有东西 -- 包括 .env 文件、凭证、大型二进制文件和临时文件。Claude 总是在检查过变更后使用 git add <specific-files>。这是安全实践,不只是风格偏好。

GSD 的原则在这里适用:每次提交一个逻辑变更,原子任务。当你给出聚焦的指令时,Claude 自然会遵循这个原则。

Checkpoint 系统 ​

每次 Claude 编辑文件,它都会创建一个 Git checkpoint。就像游戏里的自动存档。

编辑 1: 添加暗黑模式 CSS        -> checkpoint #1
编辑 2: 添加切换组件             -> checkpoint #2
编辑 3: 接入状态管理             -> checkpoint #3
编辑 4: 重构(搞砸了)           -> checkpoint #4

编辑 4 不行:
  /rewind -> 选择 checkpoint #3
  -> 一切回到编辑 3 之后的正常状态

这是安全网。 你可以告诉 Claude "试试激进的重构"而不用担心风险。如果不行,/rewind 就回去了。Checkpoint 系统底层使用 Git 的暂存区和 stash 机制,所以你的实际分支历史保持干净。

Git Worktree:真正的并行 ​

Git worktree 从同一个仓库创建多个工作目录,每个在不同的分支上。结合 Claude Code,你可以运行并行会话来处理完全独立的功能。

my-app/                  <- main 分支
my-app-feature-dark/     <- worktree: feature/dark-mode
my-app-feature-api/      <- worktree: feature/api-v2

三个终端,三个 Claude 会话,零干扰。

这不是切换分支。 这是独立的目录,有独立的文件状态。一个 Claude 会话不可能误覆盖另一个的工作。Claude Code 原生支持这种模式 -- 当你在 worktree 目录中启动 claude 时,它会自动检测 Git 上下文。


Demo 8:用正确的 Git 工作流构建暗黑模式 ​

8
Build Dark Mode with Proper Git Workflow
Beginner~15 min

目标 ​

给一个简单的 Web 应用添加暗黑模式切换,使用正确的 Git 实践:功能分支、原子提交、干净地合并回主分支。

步骤 ​

1. 创建基础项目 ​

bash
mkdir -p ~/claude-demos/demo-08 && cd ~/claude-demos/demo-08
git init
git branch -M main

mkdir -p src

cat > src/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Demo App</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <header>
    <h1>My App</h1>
    <nav>
      <a href="#home">Home</a>
      <a href="#about">About</a>
    </nav>
  </header>
  <main>
    <p>Welcome to the demo app.</p>
  </main>
  <script src="app.js"></script>
</body>
</html>
EOF

cat > src/styles.css << 'EOF'
body {
  font-family: system-ui, sans-serif;
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
  background: #ffffff;
  color: #1a1a1a;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #e0e0e0;
  padding-bottom: 1rem;
  margin-bottom: 2rem;
}

nav a {
  margin-left: 1rem;
  color: #0066cc;
  text-decoration: none;
}
EOF

cat > src/app.js << 'EOF'
console.log('App initialized');
EOF

cat > CLAUDE.md << 'EOF'
# Demo App

## Key Commands
- Open in browser: open src/index.html (macOS) or start src/index.html (Windows)

## Standards
- Vanilla HTML/CSS/JS, no frameworks
- CSS custom properties (variables) for theming
- Commit messages: Conventional Commits, English
- One logical change per commit
EOF

git add -A
git commit -m "feat: initial app with basic layout and styling"

2. 让 Claude 构建暗黑模式 ​

bash
claude
I want to add dark mode to this app. Here's the plan:

1. Create a feature/dark-mode branch
2. Add CSS custom properties for colors (light theme as default)
3. Add a dark theme using a [data-theme="dark"] attribute on <html>
4. Add a toggle button in the header
5. Write the JS to toggle themes and persist the choice in localStorage
6. Make separate commits for each logical step

Go ahead.

你实际会看到的内容:

terminal
> [Your prompt above]

╭─ Bash ──────────────────────────────────────────────╮
│  git checkout -b feature/dark-mode                   │
╰─────────────────────────────────────────────────────╯
> y

Switched to a new branch 'feature/dark-mode'

╭─ Read ──────────────────────────────────────────────╮
│  src/styles.css                                      │
╰─────────────────────────────────────────────────────╯
> a

[Reading src/styles.css... 20 lines]

I'll start by refactoring the hardcoded colors to CSS custom properties.

╭─ Edit ──────────────────────────────────────────────╮
│  src/styles.css                                      │
│  Replace hardcoded colors with CSS variables         │
╰─────────────────────────────────────────────────────╯
> y

[Edited src/styles.css]

╭─ Bash ──────────────────────────────────────────────╮
│  git add src/styles.css                              │
│  git commit -m "refactor: use CSS custom properties  │
│  for theming"                                        │
╰─────────────────────────────────────────────────────╯
> y

[main 2a1b3c4] refactor: use CSS custom properties for theming

Now adding the dark theme color scheme...

╭─ Edit ──────────────────────────────────────────────╮
│  src/styles.css                                      │
│  Add [data-theme="dark"] rules                       │
╰─────────────────────────────────────────────────────╯
> y

[...]

╭─ Bash ──────────────────────────────────────────────╮
│  git add src/styles.css                              │
│  git commit -m "feat: add dark theme color scheme"   │
╰─────────────────────────────────────────────────────╯
> y

[feature/dark-mode 5d6e7f8] feat: add dark theme color scheme

Now I'll add the toggle button and JavaScript...

╭─ Edit ──────────────────────────────────────────────╮
│  src/index.html                                      │
│  Add toggle button in header                         │
╰─────────────────────────────────────────────────────╯
> y

╭─ Edit ──────────────────────────────────────────────╮
│  src/app.js                                          │
│  Add theme toggle with localStorage persistence      │
╰─────────────────────────────────────────────────────╯
> y

╭─ Bash ──────────────────────────────────────────────╮
│  git add src/index.html src/app.js                   │
│  git commit -m "feat: add dark mode toggle with      │
│  localStorage persistence"                           │
╰─────────────────────────────────────────────────────╯
> y

[feature/dark-mode 9a0b1c2] feat: add dark mode toggle...

Done! Three atomic commits on the feature/dark-mode branch.

3. 验证工作 ​

bash
git log --oneline --all --graph

预期输出:

terminal
$ git log --oneline --all --graph

* 9a0b1c2 (HEAD -> feature/dark-mode) feat: add dark mode toggle with localStorage persistence
* 5d6e7f8 feat: add dark theme color scheme
* 2a1b3c4 refactor: use CSS custom properties for theming
* 1234567 (main) feat: initial app with basic layout and styling

4. 合并回去 ​

还在 Claude 会话中:

Switch back to main and merge feature/dark-mode. Then show me the final git log.

刚才发生了什么? ​

1
Bash
git checkout -b feature/dark-mode
↓
2
Read
src/styles.css
↓
3
Edit
src/styles.css
↓
4
Bash
git add src/styles.css && git commit
↓
5
Edit
src/styles.css
↓
6
Bash
git add && git commit
↓
7
Edit
src/index.html, src/app.js
↓
8
Bash
git add src/index.html src/app.js && git commit

Claude 做对了什么:

  • 创建了遵循 CLAUDE.md 命名规范的功能分支
  • 做了 3 个原子提交 -- 每个是一个逻辑变更,可独立审查
  • 通过读取实际 diff 生成 commit message
  • 选择性暂存文件 -- 始终 git add <file>,从不 git add -A
  • 添加了 Co-Authored-By 尾部来表明 AI 的参与

Demo 9:用 Worktree 并行开发 ​

9
Parallel Development with Git Worktree
Beginner~15 min

目标 ​

同时处理两个功能:前端增强和后端 API,在互不干扰的并行 Claude 会话中进行。

步骤 ​

1. 从 Demo 8 创建 Worktree ​

bash
cd ~/claude-demos/demo-08
git checkout main

# 为两个功能创建 worktree
git worktree add ../demo-08-search feature/search
git worktree add ../demo-08-api feature/api-endpoints

这会创建什么:

terminal
$ git worktree list

/home/user/claude-demos/demo-08          1234567 [main]
/home/user/claude-demos/demo-08-search   1234567 [feature/search]
/home/user/claude-demos/demo-08-api      1234567 [feature/api-endpoints]

2. 终端 1:前端功能 ​

打开一个终端:

bash
cd ~/claude-demos/demo-08-search
claude
Add a search feature to this app:
1. Add a search input in the header
2. Add some sample content paragraphs to the main section
3. Implement client-side search that highlights matching text and hides non-matching paragraphs
4. Commit when done

3. 终端 2:后端功能 ​

同时打开第二个终端:

bash
cd ~/claude-demos/demo-08-api
claude
Add a simple API backend:
1. Create a server.js using Node's built-in http module (no Express needed)
2. Implement GET /api/status that returns { status: "ok", uptime: process.uptime() }
3. Implement GET /api/config that returns the app configuration
4. Add a start script to package.json (create it if it doesn't exist)
5. Commit when done

4. 两个会话同时运行 ​

两个 Claude 会话工作的时候,彼此不知道对方的存在。它们在完全独立的目录中,有独立的文件状态。不会因为并发编辑产生合并冲突。

5. 合并两个功能 ​

bash
cd ~/claude-demos/demo-08
git merge feature/search
git merge feature/api-endpoints

git log --oneline --all --graph

6. 清理 ​

bash
git worktree remove ../demo-08-search
git worktree remove ../demo-08-api

刚才发生了什么? ​

为什么用 worktree 而不是切换分支?

方式问题
切换分支切换前必须 stash 或 commit。同一时间只有一个会话能工作。有忘记自己在哪个分支上的风险。
Git worktree每个功能有自己的目录。多个 Claude 会话真正并行工作。天然完全隔离。

这种模式在第九章 Agent 团队中变得至关重要,届时你将有 3 个以上的 Claude agent 同时在同一个仓库上工作。


Demo 10:故意搞砸,然后 /rewind ​

10
Break Things, Then /rewind
Beginner~10 min

目标 ​

故意让 Claude 做一个失败的重构,然后用 checkpoint 系统恢复。这会让你建立信心,敢让 Claude 尝试大胆的改动。

步骤 ​

1. 准备一个正常工作的应用 ​

bash
mkdir -p ~/claude-demos/demo-10 && cd ~/claude-demos/demo-10
git init

cat > calculator.py << 'EOF'
"""A simple calculator module with history tracking."""

class Calculator:
    def __init__(self):
        self.history = []

    def add(self, a, b):
        result = a + b
        self.history.append(f"{a} + {b} = {result}")
        return result

    def subtract(self, a, b):
        result = a - b
        self.history.append(f"{a} - {b} = {result}")
        return result

    def multiply(self, a, b):
        result = a * b
        self.history.append(f"{a} * {b} = {result}")
        return result

    def divide(self, a, b):
        if b == 0:
            raise ValueError("Cannot divide by zero")
        result = a / b
        self.history.append(f"{a} / {b} = {result}")
        return result

    def get_history(self):
        return list(self.history)

    def clear_history(self):
        self.history.clear()

if __name__ == "__main__":
    calc = Calculator()
    print(calc.add(10, 5))
    print(calc.subtract(10, 3))
    print(calc.multiply(4, 7))
    print(calc.divide(15, 3))
    print("History:", calc.get_history())
EOF

cat > test_calculator.py << 'EOF'
"""Tests for the calculator module."""
import pytest
from calculator import Calculator

def test_add():
    calc = Calculator()
    assert calc.add(2, 3) == 5

def test_subtract():
    calc = Calculator()
    assert calc.subtract(10, 4) == 6

def test_multiply():
    calc = Calculator()
    assert calc.multiply(3, 7) == 21

def test_divide():
    calc = Calculator()
    assert calc.divide(10, 2) == 5.0

def test_divide_by_zero():
    calc = Calculator()
    with pytest.raises(ValueError):
        calc.divide(10, 0)

def test_history():
    calc = Calculator()
    calc.add(1, 2)
    calc.subtract(5, 3)
    history = calc.get_history()
    assert len(history) == 2
    assert "1 + 2 = 3" in history[0]
EOF

git add -A
git commit -m "feat: calculator with tests"

2. 让 Claude 做渐进式修改 ​

bash
claude

先来一个好的改动:

Add power() and sqrt() methods to the Calculator class, with history tracking and tests. Run the tests to make sure everything passes.
terminal
> [prompt above]

[Claude edits calculator.py, adds power() and sqrt()]
[Claude edits test_calculator.py, adds new tests]

╭─ Bash ──────────────────────────────────────────────╮
│  python -m pytest test_calculator.py -v              │
╰─────────────────────────────────────────────────────╯
> y

test_calculator.py::test_add PASSED
test_calculator.py::test_subtract PASSED
test_calculator.py::test_multiply PASSED
test_calculator.py::test_divide PASSED
test_calculator.py::test_divide_by_zero PASSED
test_calculator.py::test_history PASSED
test_calculator.py::test_power PASSED
test_calculator.py::test_sqrt PASSED

8 passed ✓

好的。测试通过。Checkpoint 已创建。

再来一个好的改动:

Add a memory feature: store_memory(slot_name, value) and recall_memory(slot_name). With tests.

好的。测试仍然通过。又一个 checkpoint。

现在来个冒险的:

Refactor the entire Calculator class to use a plugin architecture. Each operation (add, subtract, etc.) should be a separate plugin class that registers with the calculator. This is a major refactor -- go for it.

这是一个大改动。Claude 会重写大部分文件。也许测试会挂。也许重构过度复杂化了。这都无所谓。

3. 用 /rewind 恢复 ​

/rewind

你会看到:

terminal
> /rewind

Available checkpoints:
  [1] feat: calculator with tests (initial)
  [2] Added power() and sqrt() methods ← 2 min ago
  [3] Added memory feature ← 1 min ago
  [4] Plugin architecture refactor ← just now

Rewind to which checkpoint? (1-4): 3

选择 checkpoint 3 -- power、sqrt 和 memory 都正常工作的状态。

4. 验证恢复 ​

Run pytest to confirm everything works
terminal
╭─ Bash ──────────────────────────────────────────────╮
│  python -m pytest test_calculator.py -v              │
╰─────────────────────────────────────────────────────╯

10 passed ✓

测试通过。激进的重构已经消失了。Power、sqrt 和 memory 完好无损。

刚才发生了什么? ​

1
Edit
calculator.py
↓
2
Bash
pytest
↓
3
Edit
calculator.py (risky refactor)
↓
4
Bash
/rewind to checkpoint #3

核心教训:/rewind 意味着你永远不需要害怕让 Claude 尝试大胆的东西。"试试函数式编程风格。""用完全不同的模式重写。""如果用组合代替继承会怎样?"放心大胆地问。不行就 /rewind,几秒钟回到原样。


常见问题排查 ​

合并冲突 ​

terminal
$ git merge feature-branch
Auto-merging src/styles.css
CONFLICT (content): Merge conflict in src/styles.css
Automatic merge failed; fix conflicts and then commit the result.

解决:在有冲突的目录中启动 Claude:

bash
claude
Resolve the merge conflict in src/styles.css. Keep functionality from both sides.

Claude 读取冲突标记(<<<<<<<、=======、>>>>>>>),理解两个分支的意图,生成干净的合并结果。它在这方面出奇地好,因为它能看到两边变更的完整上下文。

/rewind 回退太多 ​

如果你不小心 rewind 到了一个比预期更早的状态,检查 git reflog:

bash
git reflog
# 找到你想要的状态的 commit hash
git checkout <hash> -- <file>

Worktree 分支已存在 ​

terminal
$ git worktree add ../demo-api feature/api
fatal: 'feature/api' is already checked out at '/path/to/other'

解决:要么先移除已有的 worktree(git worktree remove <path>),要么用新的分支名(feature/api-v2)。

Claude 暂存了不想要的文件 ​

如果 Claude 暂存了你不想提交的文件:

Unstage that file. I don't want credentials.json in the commit.

或者手动:git reset HEAD credentials.json


深入探索 ​

管道模式做即时代码审查 ​

这是最被低估的功能之一:

bash
# 审查未提交的变更
git diff | claude -p "Review these changes. Flag security issues, performance concerns, and style violations."

# 审查特定分支
git diff main..feature/dark-mode | claude -p "Review this PR. Would you approve it?"

# 审查最后一次提交
git show | claude -p "Rate this commit from 1-10 on: message quality, change cohesion, and test coverage."

# 从提交生成 changelog
git log --oneline v1.0..v2.0 | claude -p "Generate a user-facing changelog from these commits. Group by feature/fix/breaking."

管道模式审查输出示例:

terminal
$ git diff | claude -p "Review these changes for security issues"

## Security Review

**1 issue found:**

- **HIGH**: `src/api.js:42` — User input is passed directly to `eval()`.
  This is a code injection vulnerability. Use `JSON.parse()` instead
  if you're parsing JSON, or a sandboxed evaluator if you genuinely
  need dynamic evaluation.

**No other security concerns.** The rest of the changes (CSS updates,
README edits) are safe.

管道模式是非交互的 -- Claude 读 stdin,回答,退出。完美适合集成到脚本和 CI 管道中。更多用法见第五章 Hooks。

Claude 怎么写 Commit Message ​

Claude 不是猜的。它遵循一个流程:

  1. 读 git diff --staged 看到底改了什么
  2. 查 git log 匹配项目现有的提交风格
  3. 把相关文件分组到单个提交中
  4. 跳过 .env、凭证等敏感文件
  5. 添加 Co-Authored-By: Claude ... 尾部

精心编写的 commit message 示例:

feat(auth): add JWT token refresh endpoint

- Add POST /auth/refresh endpoint with rate limiting
- Persist refresh tokens in separate table with encryption
- Add audit logging for token refresh events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

PR 创建 ​

如果你有 GitHub remote 并且装了 gh CLI:

Create a PR from the current branch to main. Include a descriptive title, a summary of changes with context, and a test plan.

Claude 使用 gh pr create 并生成结构化的 PR 正文。PR 描述会引用改了什么和为什么改,不只是列出文件。


知识检测 ​

Claude 需要提交变更。它用什么来暂存文件?
git add -A(暂存所有东西)
git add .(暂存当前目录所有东西)
git add <specific-files>(只暂存变更的文件)
git commit -a(自动暂存并提交)
切换分支和 Git worktree 有什么区别?
Worktree 比切换分支更快
Worktree 创建独立的目录,允许在多个终端中真正并行工作
它们是语法不同但功能相同的东西
Worktree 只能在 Claude Code 中使用,普通 Git 不行

练习:纯对话 Git 工作流 ​

任务 ​

用纯对话方式完成整个 Git 工作流。你不能自己输入任何 git 命令。

  1. 初始化一个新的 Git 仓库
  2. 创建项目:一个把 Markdown 转 HTML 的 Node.js CLI 工具
  3. 提交初始代码
  4. 创建 feature/frontmatter 分支
  5. 添加 YAML frontmatter 解析支持
  6. 提交并写好 commit message
  7. 切回 main
  8. 合并功能分支
  9. 显示最终的 git log

成功标准 ​

  • [ ] 没有手动输入任何 git 命令
  • [ ] 至少有 3 次提交,使用 Conventional Commits 格式
  • [ ] 功能分支被正确创建和合并
  • [ ] git log --graph 显示了分支历史
  • [ ] Claude 使用了选择性暂存(不是 git add -A)
提示
Initialize a git repo, create a Node.js CLI tool in index.js that reads
a markdown file and converts it to HTML using a simple parser (no external deps).
Add a package.json with a bin entry. Commit everything.
Create a feature/frontmatter branch. Add YAML frontmatter parsing -- when the
markdown starts with --- delimited YAML, extract it as metadata and include
it as <meta> tags in the HTML output. Commit.
Switch to main, merge feature/frontmatter, show git log --graph --oneline.

本章小结 ​

  • Claude 处理完整的 Git 生命周期:分支、编码、暂存、提交、PR、审查
  • 原子提交是标准。每次提交一个逻辑变更,选择性暂存,有意义的 message。
  • Checkpoint 在每次编辑时自动保存。/rewind 是你的撤销按钮 -- 让你敢于尝试大胆的改动。
  • Git Worktree 实现真正的并行开发。多个 Claude 会话,多个功能,零冲突。
  • 管道模式(git diff | claude -p)是即时代码审查。每次 PR 前都用它。
  • Claude 通过读 diff 写 commit message,不是猜的。它会匹配你项目现有的风格。
  • 合并冲突发生时,Claude 通过理解两个分支的意图来解决。

你已经完成了入门阶段。

你学到的一切 -- 工具调用、CLAUDE.md、上下文管理、Git 工作流 -- 在阶段性大项目中融为一体。不是 todo 应用。你要构建一个你会真正使用的工具。

或者直接跳到阶段二:进阶篇 - 第五章:Hooks 系统,开始自动化 Claude Code 本身。

基于 MIT 许可发布