# Git：有记忆的保存按钮

关于 Git 与版本控制的速成课。21 个概念——Version Control、Repository、Commit、Staging Area、HEAD、Branch、main、Merge、Merge Conflict、Remote 与 origin、Clone/Push/Pull/Fetch、Pull Request、Log、Revert 与 Reset、Rebase 等——每个都有英文名称、一幅画面、一张图示和一张带例子的卡片。结尾附有关系图和一页速查表。核心想法：围绕 Git 的恐惧大多只是因为词陌生——学会它们，它就变成一张让你安心的安全网。

- **Author:** Fedor Chishchin
- **Level:** intro
- **Reading time:** ~30 分钟阅读
- **Published:** 2026-06-13
- **Source:** https://fedorthinks.com/zh/learn/git-express

---

## 1. 为什么需要版本控制

在命令之前，先看问题。一旦你感受到版本控制消除的那种痛，Git 的每一个特性读起来就都是显而易见的答案，而不是莫名其妙的术语。

### 地图：六组共 21 个概念

> 一张地铁线路图：先看到所有线路的全貌，再一站一站地坐过去。

先讲**基础**——什么是版本控制、Git 和 repository。然后每组一章：**commit**（快照是怎么保存的）、**branch**（并行的工作线）、**merge** 与 conflict、**remote** 与团队协作，以及 **history** 与安全的撤销。最后一章讲**好习惯**、关系图和速查表。

_Diagram: 六组概念——第 1–6 章依次对应。第 7 章是把一切串起来的习惯。_

### 怎么读每个概念

> 一本护照：每个人的栏目都一样，所以很容易比较。

从这里往后，每个概念都有相同的几部分：它的**英文名称**（记住它——这是你查文档和 Git 报错时要搜的词）、一幅来自日常生活的画面、一张**图示**，以及一张**卡片**：_是什么_、_怎么运作_、一个*例子*（常带命令）、_注意点_，以及它与其他概念的*关联*。结尾是关系图和速查表。

### Version Control — 用它代替塞满 _final_v2_FINAL 文件的文件夹

> 一张桌子被 `report.doc`、`report_v2.doc`、`report_final.doc`、`report_final_FINAL.doc` 淹没——没人确定哪个是最新的，而周二那个好版本永远丢了。

人人都经历过手动留版本的方式：复制文件，加个后缀，碰运气。它瞬间就崩了——你分不清哪个最新，找不回被覆盖的版本，两个人一起编辑就意味着其中一个的工作会丢。**版本控制**正是为终结这一切而生的工具：一个项目，它的全部历史被安全保存，每一个版本都可以找回。

_Diagram: 左——一堆副本，不知道哪个才是真的。右——一个文件加一份完整历史：任何版本都能回来。_

#### Version Control (版本控制，VCS)

- **问题:** 带后缀的副本：不知道哪个最新，旧的没了，两个人互相覆盖。
- **思路:** 一个项目 + 完整的修改历史，任何版本都能恢复。
- **例子:** 不再有 `report_final_FINAL.doc`——只有一个 `report.doc`，加上它每个版本的记录。
- **注意:** 只有把修改存进系统里才管用，而不是存在系统之外。
- **关联:** **Git** 是最流行的版本控制系统。它把历史保存在 **Repository** 里。

### Git — 一条快照时间线

> 一台相机，每次你说「保存这个」就把你整个项目拍下来——于是你可以翻回去，看到过去任意时刻一切的模样。

**Git** 是几乎人人都在用的版本控制系统。它把你的项目记录成一连串 **snapshot**——每一个都是你选择保存的那一刻所有文件的完整画面。结果是一条你可以穿行的时间线：看到什么变了、何时变的、由谁改的，并回到任意过去的状态。它是一个永远不会忘记任何一次保存的保存按钮。

_Diagram: 每次保存都是所有文件的一份完整快照。高亮的是自上次保存以来改动过的文件。_

#### Git (版本控制系统，快照)

- **是什么:** 使用最广泛的版本控制系统。
- **怎么运作:** 把项目存成一条时间线，上面是你选定时刻所有文件的 **snapshot**。
- **例子:** 快照 3 加入了 `style.css`；一个月后，你可以精确回到快照 2。
- **注意:** 吓人的是词，而不是难度：术语只是需要学一学。
- **关联:** 每个快照就是一个 **Commit**；时间线存放在 **Repository** 里。

### Repository — 项目加上它的全部历史

> 一本书把它曾经的每一版草稿都装订在书后——你手里既有当前的文字，也有它如何变成这样的完整记录。

**repository**（或 **repo**）就是被 Git 跟踪之后的项目文件夹：当前的文件*加上*每一次快照的隐藏历史（它存在一个隐藏的 `.git` 文件夹里）。clone 一个 repo，你得到的不只是今天的代码，而是完整的历史。它是你工作的基本单位——repo 就是「Git 记得的一个项目」，本课其余的一切都是你在某个 repo 里做的事。

_Diagram: 一个 repository = 你看到的工作文件 + 一个藏着每份快照的隐藏 .git 文件夹。_

#### Repository (repo，仓库)

- **是什么:** 一个被 Git 盯着的项目文件夹：文件 + 全部历史。
- **怎么运作:** 历史存在隐藏的 `.git` 文件夹里；工作文件就是你现在看到的样子。
- **例子:** `git init` 把一个文件夹变成 repo；`git clone` 把别人的 repo 整个复制下来，历史也在内。
- **注意:** 删掉 `.git`，只剩文件——整个历史就没了。
- **关联:** 由 **Commits** 组成；分为 **local**（你的）和 **remote**（在服务器上）。

> **框架问题:** 「如果我改了点东西、明天又后悔了，或者今天我们两个人改了同一个文件——我能干净地恢复吗？版本控制就是让这个答案永远是『能』的系统。」

> 版本控制用一个项目和一份完整、可恢复的历史，取代了那个塞满 _final_FINAL 副本的文件夹。Git 把这份历史存成一条快照时间线。

## 2. commit

Git 里最重要的一个词就是 commit——时间线上一份保存下来的快照。弄懂一次 commit 是怎么产生的，你就弄懂了整个系统的心跳。

### Commit — 一份保存下来的快照

> 你整个项目在选定瞬间的一张照片，订着一张说明那一刻是什么的便条——永久钉在时间线上。

**commit** 是项目历史中的一份快照：文件的一个被记录下来的状态，带着唯一的 ID（像 `e81f53` 这样的 hash）和一条说明。每个 commit 都指回它前面那个——它的 **parent**——形成一条链，这条链就是你的历史。你不会不停地 commit，而是在有意义的节点——「加了登录」「修了日期 bug」——这样时间线读起来就是一连串有意为之的步骤，每一步都是你能回去的地方。

_Diagram: 四个 commit：每个都有 ID 和 message，每个都指回前一个。这条链就是历史。_

#### Commit (快照)

- **是什么:** 项目的一份保存下来的快照，带有 ID、作者、时间和 message。
- **怎么运作:** 每个 commit 指向它的 parent；parent 链就是历史。
- **例子:** `git commit -m "fix date bug"` 创建 commit `e81f53`。
- **注意:** commit 只保存你放进 **staging area** 的内容，而不是你做过的每一处修改。
- **关联:** 在 **Staging Area** 里组装，由 **Commit Message** 标注，最新的一个由 **HEAD** 标记。

### Staging Area — 封箱之前先把箱子装好

> 桌上的一个快递箱：你把这次要寄的东西恰好放进去，其他的留在一边，然后才封上胶带寄出。

Git 不会自动给你改过的所有东西都拍快照。你要先挑出哪些改动进入下一个 commit，方法是把它们 **stage**（`git add`）——放进 **staging area**，也就是你正在装的那只箱子。然后 `git commit` 把恰好那只箱子封起来。这两步——先把该放一起的 stage 好，再 commit——正是 Git 历史能保持干净的原因：每份快照里装什么由你决定，而不是把所有改动一股脑倒进去。

_Diagram: 改了三个文件，但只有关于登录的两个进了箱子（staging area）。commit 恰好封起这两个；notes.txt 等下一次。_

#### Staging Area (暂存区，the index)

- **是什么:** 位于你的修改和 commit 之间的一只「箱子」。
- **怎么运作:** `git add` 把改动放进箱子，`git commit` 把它封成一份快照。
- **例子:** 改了 `login.js`、`login.css` 和 `notes.txt`——只 stage 前两个，commit「add login」。
- **注意:** 忘了 `git add`，这个改动就不会进入 commit。
- **关联:** 从你的工作文件装入，倒进一个 **Commit**。是**小 commit** 的基础。

### Commit Message — 写给未来的便条

> 在那只封好的箱子上贴标签，写明里面是什么——这样几个月后有人扫一眼货架，不用拆开就知道每个箱子里有什么。

每个 commit 都带着一条 **commit message**，说明改了什么、为什么改。它感觉像杂活，但日后是纯粹的黄金：当你在找搞坏了东西的那次改动，或想弄懂某一行为什么存在时，这些 message 就是项目的故事。一条好 message（「fix timezone bug in invoice export」）能把历史从一堵晦涩 ID 组成的墙，变成一份可读的日志。写给六个月后读它的人——通常就是你自己。

_Diagram: 左——什么也说明不了的日志。右——读起来像项目故事的日志。_

#### Commit Message (commit 上的说明)

- **是什么:** commit 上的一条简短说明：改了什么、为什么改。
- **怎么运作:** 用 `git commit -m "…"` 写下；之后在 **log** 里显示在每份快照旁边。
- **例子:** 差：「fix」「wip」。好：「fix timezone bug in invoice export」。
- **注意:** 现在省下五秒，六个月后要花上几个小时去挖。
- **关联:** 给 **Commit** 贴标签；让 **Log** 变得可读。

### HEAD — 你的「你在这里」标记

> 时间线地图上的「你在这里」圆点——标出你当前站在哪一份快照上。

**HEAD** 是 Git 指向你此刻在历史中所处位置的指针——通常是当前 branch 上最新的 commit。你做一个新 commit 时，它就加在 HEAD 之后，然后 HEAD 前移到它上面。你很少直接去碰 HEAD，但这个词到处都会出现，知道它只是表示「我在时间线上的当前位置」，你读到的大部分内容就不再神秘了。

_Diagram: HEAD 指向 main 分支，main 指向最新的 commit。下一个 commit 落在它后面，HEAD 随之前移。_

#### HEAD (「你在这里」指针)

- **是什么:** 指向你所在 commit 的指针（通常通过 branch）。
- **怎么运作:** 新 commit 紧接在 HEAD 之后，HEAD 移到它上面。
- **例子:** `HEAD → main → c4`：你在 main 上，位于 commit c4。commit 一下——现在你就在新的那个上了。
- **注意:** 这个词在 Git 的提示里看着吓人，其实只是「当前位置」。
- **关联:** 随每个 **Commit** 移动；**Switch** 把它移到另一个 branch。

> **commit 问题:** 「这个 commit 是否只包含一个连贯的改动，并带着一条 message，能告诉陌生人——或未来的我——它做了什么、为什么做？」

> commit 是一份保存下来的快照。你 stage 恰好该放一起的改动，用一条 message 把它们封好——而 HEAD 标出你站在哪里。

## 3. branch

branch 是 Git 不再只是一个花哨的保存按钮、而成为让许多人——或许多想法——同时推进又互不踩脚的方式的地方。

### Branch — 一条并行的工作线

> 把手稿复印一份，你可以在自己的那份上大胆涂改重写，而原稿保持干净，别人还能继续在上面工作。

**branch** 是一条独立的开发线：你自己的那份时间线副本，你可以在上面 commit，而不影响任何人的工作。你为开发一个功能或尝试一个想法创建 branch，在上面随意 commit，主版本在你准备好之前保持原样。branch 让一个团队能在同一个 repo 里同时做十件事而不乱：每个人在自己的线上。

_Diagram: feature/login 分支从 main 分出。它的 commit f1–f3 单独存在，main 看不到它们。_

#### Branch (工作线，分支)

- **是什么:** 一个 repo 内部一条独立的 commit 线。
- **怎么运作:** branch 只是贴在某条线最新 commit 上的一个可移动标签；新 commit 会让它前移。
- **例子:** `git switch -c feature/login`——为登录表单创建一个 branch 并切换过去。
- **注意:** 一个活得太久的 branch 会偏离 main，合并时很痛苦。
- **关联:** 从 **main** 分出，通过 **Merge** 或 **Rebase** 回来，在 **Pull Request** 里被讨论。

### main — 被信任的那条线

> 一本大家都信任的书的官方上架版——草稿在别处写，只有完成并获批的作品才会装订进去。

按惯例，主 branch 叫 **main**（老一些的 repo 可能叫 **master**）。它保存项目稳定、可用的版本——你会拿去发布的那个。新工作在其他 branch 上进行，只有在准备好并经过评审后才并入 main。把 main 当作被信任的那条线，而绝不是草稿纸，是大多数健康团队工作流背后那条简单的纪律。

_Diagram: main 一条直线向前。feature/search 和 fix/date 分支从它分出，只有准备好时才通过 merge (M) 回来。_

#### main (主分支，原名 master)

- **是什么:** 主 branch，保存可发布的、可用的版本。
- **怎么运作:** 没人直接往里写；工作在评审后通过 merge 从其他 branch 进来。
- **例子:** `feature/search` 和 `fix/date` 分支在准备好后合并进 main。
- **注意:** 直接 commit 到 main，有可能搞坏大家都依赖的东西。
- **关联:** 通过 **Merge** 和 **Pull Request** 接收工作；每个 **Branch** 都从它出发。

### Switch (checkout) — 切换 branch 会改变你的文件

> 换一份摊在桌上的草稿——还是同一张桌子，但瞬间显示出完全不同的一个版本的工作。

当你 **switch** 到某个 branch（`git switch`，以前是 `git checkout`），Git 会把你文件夹里的文件改成与该 branch 的快照一致。切到你的功能 branch，你的修改就出现了；切回 main，它们就从视野里消失（被安全保存，没有丢）。Git 里的 branch 又便宜又即时，所以每个功能或修复建一个是很正常的：不是什么沉重的动作，而是日常的一步。

_Diagram: 同一个文件夹：在 main 上有两个文件；执行 git switch feature/login 之后——一个改过的 app.js 和一个新的 login.js。_

#### Switch / Checkout (切换分支)

- **是什么:** 把你移到另一个 branch 上的命令。
- **怎么运作:** Git 把你文件夹里的文件改写成那个 branch 的快照，并把 **HEAD** 移到那里。
- **例子:** `git switch feature/login`——`login.js` 出现；`git switch main`——它被隐藏，但很安全。
- **注意:** 未 commit 的修改可能会阻止切换——先把它们 commit 掉。
- **关联:** 在各个 **Branch** 之间移动 **HEAD**。

> **branch 问题:** 「我是不是在它自己的 branch 上做这项工作，保持 main 干净——这样未完成或坏掉的想法永远不会碰到被信任的版本？」

> branch 是一条并行的工作线，你可以随意 commit 而不打扰任何人。main 是被信任的版本；新工作待在自己的 branch 上，直到准备好。

## 4. merge 与 conflict

分到各个 branch 上的工作，迟早要重新汇合。merge 是 Git 把各条工作线重新合在一起的方式，而 conflict 是它唯一需要你来做决定的时刻。

### Merge — 把两个 branch 重新合在一起

> 把你那份单独草稿上的修改折回官方手稿，于是这本书现在既含原文，也含你的新章节。

**merge** 把一个 branch 的 commit 合并进另一个——通常是把完成的功能 branch 折回 main。Git 查看两边各改了什么，把它们编织成一条同时包含两份工作的时间线，常常通过一个 **merge commit**——一个有两个 parent 的 commit。大多数时候这是自动且毫不费力的：两个 branch 改的是不同的文件或同一文件的不同部分，Git 只是把它们缝在一起。

_Diagram: 功能 branch（f1、f2）回到 main。merge commit M 把两条线连在一起——它有两个 parent。_

#### Merge (合并分支)

- **是什么:** 把一个 branch 的工作合并进另一个。
- **怎么运作:** Git 比较两边的改动，创建一个带两个 parent 的 **merge commit**。
- **例子:** `git switch main` → `git merge feature/login`——登录表单现在进了 main。
- **注意:** 如果两边改了同一行，就会出现 **conflict**，由你来决定。
- **关联:** 另一种做法是 **Rebase**。可能引发 **Merge Conflict**。在团队里它要经过 **Pull Request**。

### Merge Conflict — 对同一行的两次修改

> 两个编辑各自把同一句话改成了不同的样子——没有机器能知道你想要哪个，所以它停下来问你。

当两个 branch 以不相容的方式改了*同一个文件的同一处*，就会发生 **merge conflict**。Git 猜不出哪个版本才对，所以它暂停，标出那个位置，并把两个版本并排给你看。这不是错误，也不是灾难——这是 Git 正确地拒绝悄悄丢掉某人的工作。conflict 第一次碰到时让人害怕，恰恰因为利害（别丢了工作）很清楚。

_Diagram: 在 main 上第 3 行变成了“Hello, team!”，在 feature 上——“Hi, world!”。Git 无从知道哪个对，所以停了下来。_

#### Merge Conflict (conflict，合并冲突)

- **是什么:** 两个 branch 对文件中的同一处改成了不同的内容。
- **怎么运作:** Git 中止这次 merge，并在文件里标出有争议的行。
- **例子:** `greeting.txt` 的第 3 行：“Hello, team!” 对 “Hi, world!”。
- **注意:** 这不是坏了——这是 Git 拒绝悄悄丢掉某人的工作。
- **关联:** 出现在 **Merge** 或 **Rebase** 期间；由 **Conflict Resolution** 解决；有**短命 branch** 时更少见。

### Conflict Resolution — 解决 conflict 只是做个选择

> 读完两句被改写的话，然后写出你真正想要的最终版本——留下一个、另一个，或者两者混合——然后继续往前走。

你通过编辑被标记的文件来**解决** conflict，改成你想要的版本——留下你的、留下他们的，或把两者合并——删掉 `<<<<<<<`、`=======`、`>>>>>>>` 这些标记，然后 commit 结果。这就是全部的秘密：conflict 是 Git 把一个只有人能做的决定交给你，而解决就是做出这个决定。当 branch 短命、你又常常 merge 时，conflict 会变得更小也更少，因为两边永远不会漂得太远。

_Diagram: 左——带 conflict 标记的文件：两个版本夹在标记之间。右——人做出的选择。然后 git add 和 git commit。_

#### Conflict Resolution (解决冲突)

- **是什么:** 手动选出有争议处的最终版本。
- **怎么运作:** 编辑文件，删掉 `<<<<<<<` / `=======` / `>>>>>>>` 标记，然后 `git add` 并 `git commit`。
- **例子:** “Hello, team!” 和 “Hi, world!” 变成了 “Hi, team!”。
- **注意:** 要选得好，就得理解两个版本——不确定就问作者。
- **关联:** 在 **Merge Conflict** 之后完成 **Merge**。

> **merge 问题:** 「这些 branch 重新汇合时，它们是否改了同样的行——如果 Git 要我解决 conflict，我对两个版本的理解是否足以选对？」

> merge 把两个 branch 编织成一条时间线。conflict 只是对同一行的两次修改——Git 拒绝去猜，请你来选。

## 5. remote 与团队

到目前为止，一切都在你的机器上。要和别人合作——或备份你的工作——就意味着把你本地的 repo 连到云端的共享 repo。

### Local 与 Remote — 两份副本，origin 是共享的那份

> 你在家里的个人工作版手稿，以及办公室里整个团队保持同步的共享主稿。

Git 是**分布式**的：你机器上有一份完整的 repo 副本（**local**），通常在服务器上还有一份共享副本（**remote**）——在 GitHub 或 GitLab 这类服务上。你所有的工作都在本地进行——很快，甚至可以离线——然后与 remote 同步，把它分享出去并拉入别人的工作。默认的 remote 按惯例叫 **origin**。两份副本，刻意保持同步。

_Diagram: 团队里每个人都有一份完整的 repo 副本。GitHub 或 GitLab 上共享的 origin 副本让所有人保持同步。_

#### Local / Remote (本地与远程 repo，origin)

- **是什么:** local 是你自己那份完整的 repo 副本；remote 是服务器上的共享副本。主要的 remote 叫 **origin**。
- **怎么运作:** 在本地工作（甚至离线），然后与 remote 同步。
- **例子:** GitHub 上的 repo 是 origin；你和队友各自在笔记本上有一份副本。
- **注意:** 你没有 push 的工作只存在你的笔记本上——笔记本坏了它也就没了。
- **关联:** 副本通过 **Clone / Push / Pull / Fetch** 同步；**Pull Request** 存在于 remote 上。

### Clone、Push、Pull、Fetch — 四个同步动作

> 把办公室的主稿复印带回家（clone），把你完成的页面送回去（push），再收取其他人的新页面（pull）。

四个词在 local 和 remote 之间搬运工作。**Clone** 做出一份 remote repo 的第一个本地副本。**Push** 把你的 commit 送上 remote，让别人能看到。**Pull** 把别人的新 commit 拉进你的副本并合并。**Fetch** 下载新内容但暂不合并，让你先看看。掌握这四个，你就能协作：它们就是保持同步的全部词汇。

_Diagram: Clone——第一份副本。Push——把你的送上去。Pull——把他们的拉下来并合并。Fetch——只下载，不合并。_

#### Clone / Push / Pull / Fetch (四个同步命令)

- **Clone:** `git clone <url>`——你第一份完整的 remote repo 本地副本。
- **Push:** `git push`——把你的 commit 送到 remote。
- **Pull:** `git pull`——下载别人的 commit 并合并进来（fetch + merge）。
- **Fetch:** `git fetch`——下载新内容但暂不合并：先看看。
- **注意:** 不先 pull 就 push 常会被拒绝：先把别人的改动拉进来，再送出你的。
- **关联:** 在 **Local** 与 **Remote** 之间搬运 **Commits**；pull 内部会做一次 **Merge**。

### Pull Request — 团队如何评审工作

> 把你的新章节交给编辑，他们阅读、评论、批准之后，才会装订进官方的书里。

在 GitHub 这类平台上，你通常不会直接 merge 进 main。你 push 自己的 branch，然后开一个 **pull request**（PR；在 GitLab 上叫 merge request）——一份合并你工作的提议，队友会在其中**评审**、留评论，并在它并入 main 之前批准。这是第二双眼睛发现问题、团队确认某个改动已准备好的检查站。团队里的 Git 大多数就发生在 PR 里。

_Diagram: push 分支，开 PR，队友评审。要求修改——修好再 push。批准——merge 进 main。_

#### Pull Request (PR，merge request)

- **是什么:** 把一个 branch 合并进 main 的提议，附带讨论和评审。
- **怎么运作:** push branch → 开 PR → 评论与修改 → 批准 → merge。
- **例子:** PR「add login form」：队友请你给一个函数改名，你 push 了修改，PR 获批。
- **注意:** 没人会真去读一个巨大的 PR——保持它们小。
- **关联:** 存在于 **Remote** 上，承载一个 **Branch**，以 **Merge** 进 **main** 收尾。

> **remote 问题:** 「我开工前拉了最新的吗？我完成的工作 push 并提出来了吗——好让团队能看到，也没有什么只存在于我的笔记本上？」

> local 是你的副本；remote（origin）是共享的那份。clone、push、pull 和 fetch 让它们保持同步——而 pull request 是团队在合并前评审的地方。

## 6. 撤销与 history

Git 最深的安慰在于：几乎没有什么是真正丢掉的。知道如何读 history、如何安全地退回去，会让 Git 从吓人变成让人安心。

### Log — 项目的故事

> 翻阅一叠带日期、有署名的每一版草稿——准确看到每一步改了什么、是谁改的。

**log**（`git log`）是 commit 的列表，按顺序排列的你项目的历史，每条都带着它的 message、作者和时间。它回答「发生了什么，什么时候？」，是弄懂任何代码库或追查任何 bug 的起点。出问题时，log 让你找到引入问题的那个确切的 commit，因为每个改动都被记录了。一份你能读懂的历史，就是一份你能调试的历史。

_Diagram: git log：commit 从新到旧排列，带有作者、日期和 message。日期坏了？去找那个改了日期解析的 commit。_

#### Log (commit 历史，git log)

- **是什么:** 每个 commit 的列表：ID、作者、时间、message。
- **怎么运作:** `git log` 从最新的 commit 到最旧的依次显示历史。
- **例子:** 日期在 6 月 11 日坏了——log 显示 commit `e81f53`「change date parsing」。
- **注意:** log 有多大用处，取决于里面的 commit message 写得怎么样。
- **关联:** 读取 **Commits** 组成的链；它的价值来自 **Commit Message**。

### Revert 与 Reset — 两种撤销：诚实的和改写的

> 撤销一个错误的两种方式：在记录里加一条标注清楚的更正（revert），或者悄悄撕掉最后一页，仿佛什么都没发生过（reset）。

Git 给了你安全退回去的办法。**Revert** 创建一个新 commit 来撤销之前的某个 commit——错误留在历史里，它的撤销记录叠在上面，这既诚实，对共享的工作也安全。**Reset** 把你移回到较早的一个 commit，改写近期历史，就好像那些 commit 从没发生过——强大，但对别人已经拿到的工作很危险。经验法则：公开的东西用 revert，只有私人的、本地的工作才用 reset。

_Diagram: 上，revert：错误的 C 还在，一个新 commit 把它撤销。下，reset：HEAD 跳回 B，C 从历史中消失。_

#### Revert vs Reset (用 commit 撤销 / 回退历史)

- **Revert:** `git revert C`——一个撤销 C 的新 commit。历史保持完整。
- **Reset:** `git reset B`——把 branch 移回 B，就像 C 从没发生过。
- **例子:** 在 main 上用一个 commit 搞坏了生产环境——revert。在本地搞砸了、从没 push 过——reset。
- **注意:** 对队友已经拉走的内容做 reset，会让整个团队失去同步。
- **关联:** 作用于 **Commits** 和 **HEAD**；reset 和 **Rebase** 一样会改写历史。

### Rebase — 重放你的工作；遵守黄金法则

> 不是把你那份旁支草稿带着明显接缝订到手稿上（merge），而是把你的修改干净地重写在最新版本之上，就像你一开始就是从它出发的（rebase）。

**Rebase** 是 merge 的替代做法：它不是用一个 merge commit 把两条线连起来，而是把你的 commit 重放到另一个 branch 之上，得到一条整洁的直线历史。在分享之前整理你自己的 branch，它非常好用。但它会*改写* commit（会创建它们的新副本），所以有一条**黄金法则**：永远不要对别人已经拉走的历史做 rebase。改写共享的历史，会让所有人失去同步。私人的工作用 rebase；合并共享的工作用 merge。

_Diagram: 之前：X 和 Y 从 B 分出。git rebase main 之后：它们的副本 X′ 和 Y′ 落在 C 之后——一条直线，没有 merge commit。_

#### Rebase (重放 commit，变基)

- **是什么:** 把你的 commit 重放到另一个 branch 之上。
- **怎么运作:** Git 取出你的 commit，在新的基点上重新创建它们的副本。
- **例子:** 在 `feature` 上运行 `git rebase main`——你的 X 和 Y 现在落在 main 上新的 C 之后。
- **注意:** **黄金法则**：永远不要对别人已经拉走的历史做 rebase。
- **关联:** **Merge** 的替代做法；可能引发 **Merge Conflict**；和 **Reset** 一样会改写历史。

> **history 问题:** 「如果这里出了问题，我能读 log 找到问题出在哪吗——我是在用安全的工具撤销（共享的工作用 revert），而不是改写别人已经拿到的历史？」

> log 是可读的故事；revert 和 reset 是你退回去的办法。rebase 能得到整洁的历史——但永远不要改写别人已经拉走的历史。

## 7. 用好它

Git 有很多命令，但好的实践只是一份简短的习惯清单。照着做，Git 就始终是一张让人安心的安全网，而不是偶尔发生的危机。

### Small Commits — 小而专注的 commit

> 一本相册，每张照片捕捉一个清晰的瞬间并配着说明——比一张把所有东西一次拍糊的照片有用得多。

**小步且频繁**地 commit（小的、原子的 commit），让每个 commit 都是一个连贯的改动，并带一条解释它的 message。细小又标注清楚的 commit，让历史可读，bug 易于追踪，撤销也精准：你可以 revert 一个精确的改动，而不丢掉其他任何东西。一个把一周混杂的工作一股脑倒进去的巨型 commit 则正相反——无法评审，无法理清，也无法部分撤销。这条纪律只花几秒，却在项目的整个生命周期里持续回报。

_Diagram: 一周一个巨大的 commit，对比四个小 commit：每个只关于一件事，任何一个都能单独撤销。_

#### Small Commits (小的原子 commit)

- **问题:** 一个巨型 commit 无法评审、无法理清，也无法部分撤销。
- **思路:** 一个 commit = 一个连贯的改动 + 一条清晰的 message。
- **例子:** 不要「week of work」——而是「add login form」「style login page」「fix date bug」。
- **代价:** 花几秒钟去 `git add` 对的文件并写好 message。
- **关联:** 建立在 **Staging Area** 和 **Commit Message** 之上；让 **Revert** 和 **Log** 变得有用。

### Feature Branch Workflow — 每个功能一个 branch，常常合并

> 每个新章节在自己的副本上起草，趁它还小就折回去——这样没有哪份草稿会偏离全书太远，以至于重新合并变成一场硬仗。

每一项工作都在**它自己的 branch**（功能 branch）上做，保持它短命，并常常合并回去。长命的 branch 会偏离 main 很远，让 merge 变成一堆痛苦的 conflict；小 branch 频繁合并则始终轻松。再配上**先 pull 再 push**——先把别人的改动拉进来——你就是在持续集成，而不是到最后才撞车。这个节奏是顺畅的团队 Git 的核心。

_Diagram: 上——很快就回来的短 branch：conflict 始终很小。下——一条漂得很远的长 branch：最后是一大堆 conflict。_

#### Feature Branch Workflow (每个功能一个 branch)

- **问题:** 所有人都往 main 写——互相搞坏；一条长 branch——最后一座 conflict 的大山。
- **思路:** 一个任务——一个短 branch，通过 PR 在几天而不是几周内合并。
- **例子:** `fix/date`：pull、建 branch、两个 commit、push、PR、评审、merge——一天内搞定。
- **代价:** 你得把工作切成小块，并且常常 pull main。
- **关联:** 把 **Branch**、**Pull**、**Push**、**Pull Request** 和 **Merge** 串在一起。

### 这些概念如何连在一起

> 一条流水线：从文件里的一处修改到被信任的版本——一条路，沿途有清晰的站点。

整条路是这样的。你的**工作文件**里的修改，通过 `git add` 进入 **staging area**，再通过 `git commit` 变成一个 **commit**，你在 **log** 里读到它。commit 在 **branch** 上累积；branch 通过 **merge** 或 **rebase** 回来，有时会遇到 **conflict**。在团队里，你把 branch **push** 到 **remote**，开一个 **pull request**，评审之后 merge 进 **main**。如果出了问题——**revert** 或 **reset**。

_Diagram: 本课概念之间的主要联系：从一处修改到 main，再回来。_

#### 你的日常 Git 习惯

- 开工前先**拉取最新**，这样你是在当前的工作之上构建。
- 在 **branch** 上工作，绝不直接在脆弱的 main 上。
- 边做边写**小 commit** 和**清晰的 message**。
- **常常 merge**，趁 branch 还短，让 conflict 保持很小。
- **Push** 你的工作，并开一个 **pull request** 请人评审。
- 共享的工作用 **revert** 撤销；永远不要对别人已有的历史做 **rebase**。

#### 你把 Git 用好了的迹象

- 你的**历史读起来**像一个由带标签的小步骤组成的清晰故事。
- 新工作在 **branch** 上；**main** 始终能用。
- 你**先 pull 再 push**，并趁 branch 还小就 merge。
- 共享的工作你用 **revert**，绝不改写共享的 **history**。
- Git 给你的感觉像一张**安全网**——你放心地实验，因为知道总能回得来。

> 好的 Git 就是几个习惯：先 pull、把工作放在 branch 上、写小 commit 和清晰的 message、常常 merge、merge 前先评审。命令很多；纪律很短。

## 速查表：全部 21 个概念

### 基础

- **Version Control** (VCS): 一个项目 + 完整历史；任何版本都能回来。
- **Git** (快照时间线): 最流行的版本控制系统；把项目存成快照。
- **Repository** (repo): 项目文件夹 + 藏在 `.git` 里的历史。

### commit

- **Commit** (快照): 一份保存下来的快照，带 ID 和指向其 parent 的链接。
- **Staging Area** (暂存区): commit 之前的箱子：`git add` 装箱，`git commit` 封箱。
- **Commit Message** (便条): 写给未来的便条：改了什么、为什么改。
- **HEAD** (你在这里): 指向当前 commit 的指针；随每个 commit 前移。

### branch

- **Branch** (工作线): 一条不碰别人工作的并行 commit 线。
- **main** (主分支（master）): 被信任的、始终可用的版本。
- **Switch / Checkout** (切换分支): 切到一个 branch：文件夹里的文件变成它的快照。

### merge 与 conflict

- **Merge** (合并): 把 branch 合到一起；常通过一个有两个 parent 的 commit。
- **Merge Conflict** (冲突): 两个 branch 把同一行改成了不同内容；Git 请你来决定。
- **Conflict Resolution** (解决冲突): 选出最终版本，删掉标记，commit。

### remote 与团队

- **Local / Remote** (origin): 你那份完整的副本和服务器上的共享副本。
- **Clone / Push / Pull / Fetch** (同步): 第一份副本 · 送上你的 · 拉下他们的并合并 · 只下载。
- **Pull Request** (PR，评审): 评审后把 branch 合并进 main 的提议。

### 撤销与 history

- **Log** (历史): commit 的列表：谁、何时、做了什么、为什么。
- **Revert vs Reset** (撤销): Revert——一个新的撤销 commit（用于共享的工作）。Reset——改写（只用于你自己的）。
- **Rebase** (重放): 把 commit 重放到另一个 branch 之上。绝不用于共享的历史。

### 好习惯

- **Small Commits** (原子 commit): 一个改动——一个带清晰 message 的 commit。
- **Feature Branch Workflow** (每个功能一个 branch): 每个任务一个短 branch，通过 PR 常常合并。

速成课结束 · 7 章 · 21 个概念

接下来是练习：拿任何一个小项目，运行 `git init`，随着你改动做几次 commit——然后创建一个 branch，做一处修改，再把它合并回来。故意制造一个 conflict：在两个 branch 上改同一行，然后把它解决掉。只要你在一个无关紧要的东西上把每件事都做过一遍，恐惧就会蒸发。但请把一个想法放在所有想法之上：Git 是一个永远不会忘记的保存按钮，也是让许多人工作而不互相覆盖的方式。每一个吓人的词，都只是一个平静、可恢复的步骤的名字——而现在，你已经掌握了它们。
