编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 质量保障

  • 产品思考

  • 软实力

  • 开发流程

  • Git应用

    • README
    • 版本控制的诞生:从一场灾难说起
      • 一、凌晨三点崩溃
      • 二、追根溯源
      • 三、手工版本管理
        • 3.1 命名大法
        • 3.2 动手体验
      • 四、初代方案SVN
        • 4.1 中央服务器
        • 4.2 分支之慢
      • 五、Git 的诞生
        • 5.1 至暗时刻
        • 5.2 数据模型
        • 5.3 一探究竟
      • 六、三区四状态
      • 七、Git 非 GitHub
      • 八、初次配置
      • 九、综合实战
        • 场景设定
        • 实战开始
        • 🎯 时光倒流
        • 🎯 悔过操作
      • 十、本章回顾
        • 📎 本章涉及的命令速查
    • 单人工作流:在 Git 的时光机里自由穿梭
    • 分支:Git 的灵魂——像开平行宇宙一样开发
    • 远程协作:把你的代码推到全世界
    • Git特种作战:stash、cherry-pick、bisect三件套
    • 团队工作流实战:从一个人能打到一队人能战
    • Git故障排除:遇到报错不再慌的急诊手册
    • Git 场景速查地图:遇到问题对号入座
    • 常见操作实践:从理论到实战的最后一步
  • 技术模版

  • 技术规范

  • markdown

  • mermaid

  • license

  • 博客部署

  • 技术招聘

  • 测试经验

  • 技术
  • Git应用
杨充
2025-06-06
目录

版本控制的诞生:从一场灾难说起

# 第1章 · 版本控制诞生

# 一、凌晨三点崩溃

2025年5月的一个深夜,小李盯着屏幕上刺眼的错误日志,手心全是汗。

他的个人项目——一个做了三个月的博客系统,在添加了「夜间模式」功能后彻底崩了。更糟的是,他发现自己改动了 12 个文件,已经完全记不清哪些改过、改之前是什么样。最致命的一击是:他没有备份。

翻遍电脑,只找到一个两周前的压缩包:blog_v2_final_旧版.zip。这意味着两周的心血白费了。

小李瘫在椅子上,脑子里反复回放一个念头:

"如果我能回到一个小时前该多好。如果我能回到昨天下午 3 点那个还能跑的版本该多好。"

这不是小李一个人的灾难。在 Git 诞生之前,几乎每个程序员都经历过类似的故事。


# 二、追根溯源

站起来倒杯水,我们冷静思考一下:小李遭遇的本质是什么?

他不是缺代码,他的代码都在硬盘里。他缺的是「时间维度的管理能力」。

换句话说,他需要的不是更好的编辑器,而是一台「时光机」——能够:

  1. 记录每一次改动:知道谁、在什么时候、改了什么、为什么改。
  2. 自由穿越历史:想回到昨天下午 3 点的状态?一键完成。
  3. 并行多条时间线:一边继续开发新功能,一边能随时修线上的 bug。
  4. 多人同时工作不打架:两个人在不同城市,各自写各自的,最后能自动合并。

这就是「版本控制」要解决的四个核心命题:

核心能力 一句话描述
记忆 自动记录每一次保存的完整快照
穿越 随时回到任意一个历史版本
平行 同时推进多条开发线,互不干扰
协作 多人修改同一项目,智能合并冲突

这四个能力听起来天经地义,但在 Git 出现之前,我们是怎么做的?


# 三、手工版本管理

# 3.1 命名大法

在没有工具的时候,版本管理全靠文件名:

毕业论文_v1.doc
毕业论文_v2_导师修改.doc
毕业论文_v3_最终版.doc
毕业论文_v3_最终版_真的最终.doc
毕业论文_v3_最终版_真的最终_打死不改.doc
毕业论文_v3_最终版_真的最终_打死不改_2.doc ← 还是改了
1
2
3
4
5
6

你有没有会心一笑?这不仅是段子,更是真实的血泪史。

手工命名的致命缺陷:

  • 只记录「结果」,不记录「过程」——你不知道从 v2 到 v3 到底改了什么
  • 空间爆炸——100 个版本就是 100 份完整文件,硬盘在哭泣
  • 协作灾难——小张的 v5 和小王的 v6 怎么合并?手动逐行对比!
  • 无法回退到中间状态——你只能回到有备份的那些时间点

# 3.2 动手体验

打开终端,我们来手动模拟一次这个痛苦的过程:

# 创建一个"项目"
mkdir my-project
cd my-project
echo "第一版代码" > main.py
echo "这是README" > README.md

# "版本1"——手工备份
cp -r . ../my-project-v1

# 继续开发...改了很多东西
echo "第二版代码,加了很多功能" > main.py
echo '新功能:用户登录' >> main.py
echo '修复:空指针崩溃' >> main.py
echo '优化:数据库查询速度' >> main.py

# "版本2"——又手工备份
cp -r . ../my-project-v2

# 想比较两个版本的差异?用肉眼一行行看吧
echo "现在请你用肉眼比较 v1 和 v2 的区别..."
echo "目录已经膨胀了:"
ls ../ | grep my-project
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

运行完之后你会发现:

  • 磁盘上多了两份额外的完整副本
  • 你不知道 v1 和 v2 之间具体改了什么——只能自己一行行看
  • 如果你想回到「加了登录功能、但还没修复崩溃」的那个中间状态?回不去了,因为你没有在那个时间点做备份

这就是没有版本控制的日常。而我们的项目往往有几百个文件、几十个开发者,灾难程度再乘一百倍。


# 四、初代方案SVN

# 4.1 中央服务器

时间来到 2000 年,Subversion(SVN)出现了。它的思路很直接:

搞一台服务器当中央仓库,所有人把代码提交上去,服务器记录变更历史。

          ┌─────────┐
          │ SVN 中央 │ ← 唯一真理来源
          │  服务器  │
          └────┬─────┘
       .───────┼───────.
       ▼       ▼       ▼
    张三     李四     王五
1
2
3
4
5
6
7

工作流程:每天上班 svn update 拉最新代码 → 改代码 → svn commit 提交到服务器。

解决了一部分问题:

  • ✅ 不用手动备份了,服务器自动记录版本
  • ✅ 知道谁在什么时候提交了什么
  • ✅ 大家基于同一个仓库协作

但新的问题来了:

  1. 离开网络就是废物。飞机上想写代码?不行。服务器挂了?全员停工。
  2. 分支是奢侈品。在 SVN 里创建分支不是建个引用,而是把整个项目复制一份。大型项目动辄几 GB,开个分支要等几分钟。
  3. 提交即公开。你本地的实验性代码,不提交就丢了,提交了所有人都能看到。

# 4.2 分支之慢

我们用一张图来感受一下:

Git 创建分支:┌─ 新建一个 40 字节的指针文件 ─┐   0.01 秒
SVN 创建分支:┌─ 复制整个项目目录到 branches/ ─┐  几分钟
              └─ 10,000 个文件 × 几 GB ─┘
1
2
3

这就是集中式最大的痛——中心化带来单点故障,分支成本高导致不敢分支,不敢分支导致功能混在一起,混在一起导致更不敢回退。 死循环。


# 五、Git 的诞生

# 5.1 至暗时刻

2005 年,Linux 内核社区面临一个棘手问题:他们使用的商业版本控制工具 BitKeeper 收回了免费使用权。

Linux 内核是什么样的项目?全球几千名开发者,数百万行代码,每天数百个提交。

Linus Torvalds(Linux 创始人)决定自己写一个。他的目标极其明确:

"我要一个工具:快、分布式、保证数据完整性、能轻松分支。"

两周后,Git 诞生了。请注意,是两周。

这听起来像一个传说,但 Linus 能这么快写出 Git 的根本原因是他想通了一件事——

# 5.2 数据模型

Linus 没有把 Git 设计成一个复杂的版本管理系统。他做了一个极其关键的认知转换:

SVN 的思路:记录「文件的变化」(Delta-based)
Git 的思路:记录「整个项目的快照」(Snapshot-based)

用拍照来类比:

SVN 思路 Git 思路
版本1 拍一张完整的照片 📷 拍一张完整的照片 📷
版本2 只记录「与版本1的差异」 再拍一张完整的照片 📷
版本3 只记录「与版本2的差异」 又拍一张完整的照片 📷
看版本2 从 v1 开始 + 补丁2 直接拿出第2张照片

Git 的做法听起来浪费空间,但实际上 Git 对相同内容的文件只存一份(通过 SHA-1 哈希去重),所以空间效率反而很高。更重要的是:

这一设计让 Git 天生是分布式的。 每个人本地都是一个「完整相册」,不需要依赖任何服务器。

# 5.3 一探究竟

不废话,直接上手,来看看 Git 到底是怎么存东西的:

# 初始化一个 Git 仓库
mkdir git-under-the-hood && cd git-under-the-hood
git init

# 看看 .git 目录里有什么
ls .git
# 输出:HEAD  config  description  hooks  info  objects  refs
1
2
3
4
5
6
7

这个 .git 目录,就是 Git 的全部。你的整个仓库历史都在这里。

往里放一个文件:

echo "Hello Git" > hello.txt
git add hello.txt
git commit -m "第一次提交"

# 重新看看 .git/objects
ls .git/objects/
# 输出:目录多了一堆东西,比如 8a/  9b/  fa/  ...
1
2
3
4
5
6
7

让我们深挖一步。Git 把每一个文件、每一个目录结构、每一次提交,都存成一个 object(对象),用内容的 SHA-1 哈希值做文件名。

# 看看最新的 commit 对象
git log --oneline
# 输出类似:8a0f3b2 第一次提交

# 用 cat-file 查看这个 commit 内部长什么样
git cat-file -p 8a0f3b2
1
2
3
4
5
6

你会看到类似这样的输出:

tree 9b4d2f1a8c...
author 杨充 <yangchong@example.com> 1717679237 +0800
committer 杨充 <yangchong@example.com> 1717679237 +0800

第一次提交
1
2
3
4
5

commit 指向一个 tree,tree 指向一个 blob:

# 继续追踪:commit → tree → blob
git cat-file -p 9b4d2f1
1
2

输出:

100644 blob fa49b0779...  hello.txt
1

最后看看这个 blob——它就是源文件本身:

git cat-file -p fa49b07
# 输出:Hello Git
1
2

这就是 Git 存储数据的底层链条:

   commit(提交)
     │
     ├── tree(目录快照)
     │     └── blob(文件内容:"Hello Git")
     │
     ├── parent(指向父提交)
     └── author / committer / message
1
2
3
4
5
6
7

🔍 探索发现:Git 不存文件名,文件名存在 tree 里;Git 不存 diff,每次都存完整文件;相同内容的文件只存一份。这一切加起来,就构成了史上最可靠的版本控制系统。


# 六、三区四状态

了解了底层之后,我们来到 Git 的用户视角。Git 把你的工作空间分成三个逻辑区域:

┌──────────────┐     git add      ┌──────────────┐    git commit     ┌──────────────┐
│   工作区      │ ──────────────→ │   暂存区      │ ───────────────→ │   版本库      │
│  Working     │                 │   Staging    │                  │  Repository  │
│  Directory   │ ←────────────── │    Area      │ ←─────────────── │              │
│              │   git restore   │              │  git reset HEAD  │              │
└──────────────┘                 └──────────────┘                  └──────────────┘
     │                                                                     │
     │  (你实际编辑文件的地方)                                              │  (.git 目录)
     │                                                                     │
     └───────────────── 这里的一切都只是普通文件 ──────────────────────────┘
1
2
3
4
5
6
7
8
9
10

对应四种文件状态:

状态 英文 含义 文件在哪
未跟踪 Untracked 新创建的文件,Git 还没开始管它 工作区
已修改 Modified 文件改了,但还没告诉 Git「我准备提交这个」 工作区
已暂存 Staged 已经标记好了,下次 commit 会包含它 暂存区
已提交 Committed 安全保存在 Git 的版本库里了 版本库

动手验证——用一个文件亲自走一遍这四种状态:

cd ~ && mkdir git-zone-lab && cd git-zone-lab
git init

# Step 1:创建文件 → 未跟踪(Untracked)
echo "第一行内容" > test.txt
git status
# 输出:Untracked files: test.txt

# Step 2:add → 已暂存(Staged)
git add test.txt
git status
# 输出:Changes to be committed: new file: test.txt

# Step 3:commit → 已提交(Committed)
git commit -m "创建 test.txt"
git status
# 输出:nothing to commit, working tree clean

# Step 4:修改文件 → 已修改(Modified)
echo "第二行内容" >> test.txt
git status
# 输出:Changes not staged for commit: modified: test.txt

# Step 5:再次 add → 回到已暂存
git add test.txt
git status
# 输出:Changes to be committed: modified: test.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

现在你应该能体会到:Git 的每一个命令,本质上都是在三个区域之间搬运数据。 git add 是从工作区搬到暂存区,git commit 是从暂存区搬到版本库,git restore 是从版本库/暂存区搬回工作区。


# 七、Git 非 GitHub

这是一个新手最常见的误解,必须澄清:

Git GitHub
是什么 版本控制工具 代码托管平台
装在哪 你的电脑上 云端服务器
需要网络吗 不需要 需要
能在本地工作吗 ✅ 完全离线工作 ❌ 得上网页
类比 相机 📷 朋友圈 📱

Git 是「工具」,GitHub 是「用这个工具的人在云端聚会的地方」。没有 GitHub,Git 照样能工作;没有 Git,GitHub 就不存在。

除了 GitHub,还有 GitLab、Gitee、Bitbucket 等等。它们都是「Git 仓库的托管平台」,底层用的都是 Git。


# 八、初次配置

在开始正式旅程之前,做一件很简单的事:告诉 Git 你是谁。

git config --global user.name "你的名字"
git config --global user.email "你的邮箱"

# 验证配置
git config --list
1
2
3
4
5

--global 表示全局配置,对这台电脑上所有 Git 仓库生效。这个信息会附加在每一次 commit 上,让协作者知道「是谁做的这个改动」。


# 九、综合实战

现在,让我们回到本章开头小李那个绝望的夜晚。假设小李一开始就用了 Git,故事会变成什么样?

# 场景设定

小李要做一个小型博客项目,需求是这样的:

  1. 搭建项目骨架(HTML + CSS)
  2. 实现文章列表功能
  3. 实现文章详情页
  4. 不小心引入了一个样式 bug,页面变红了
  5. 需要回退到「文章列表完成」的那个干净状态

# 实战开始

# ==================== 第 1 步:初始化项目 ====================
mkdir my-blog && cd my-blog
git init

echo "<!DOCTYPE html>
<html>
<head><title>小李的博客</title></head>
<body><h1>欢迎来到我的博客</h1></body>
</html>" > index.html

echo "body { font-family: sans-serif; background: #fff; }" > style.css

git add .
git commit -m "feat: 初始化博客项目骨架"

# 查看第一个 commit 的 hash
git log --oneline
# 假设输出:a1b2c3d feat: 初始化博客项目骨架


# ==================== 第 2 步:添加文章列表 ====================
echo '<h2>文章列表</h2>
<ul>
  <li><a href="post1.html">Git 入门指南</a></li>
  <li><a href="post2.html">CSS 小技巧</a></li>
</ul>' >> index.html

git add index.html
git commit -m "feat: 添加文章列表模块"

# hash: e4f5g6h


# ==================== 第 3 步:添加侧边栏 ====================
echo '<aside>
  <h3>关于我</h3>
  <p>一个爱写代码的博主</p>
</aside>' >> index.html

git add index.html
git commit -m "feat: 添加侧边栏模块"

# hash: i7j8k9l


# ==================== 第 4 步:添加文章详情页 ====================
echo '<!DOCTYPE html>
<html>
<head><title>Git 入门指南</title></head>
<body>
<h1>Git 入门指南</h1>
<p>Git 是一个非常强大的版本控制工具……</p>
</body>
</html>' > post1.html

git add post1.html
git commit -m "feat: 添加文章详情页"

# hash: m0n1o2p


# ==================== 第 5 步:「手滑」引入 bug ====================
# 小李尝试加一个红色主题,结果把整个页面搞崩了
echo "body { font-family: sans-serif; background: #ff0000; color: #ff0000; }" > style.css

git add style.css
git commit -m "feat: 添加红色主题"  # ← 这个 commit 引入了 bug

# hash: q3r4s5t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

现在小李的项目长这样:

q3r4s5t  feat: 添加红色主题        ← HEAD(当前位置,有问题!)
m0n1o2p  feat: 添加文章详情页      ← 想回到这里
i7j8k9l  feat: 添加侧边栏模块
e4f5g6h  feat: 添加文章列表模块
a1b2c3d  feat: 初始化博客项目骨架
1
2
3
4
5

# 🎯 时光倒流

# 方式一:看看历史长什么样
git log --oneline --graph

# 方式二:回去看一眼 v3 的状态(只看不改)
git checkout m0n1o2p
# 此时浏览器打开 index.html,一切正常!红色消失了!
# 看完了切回来:
git checkout master  # 或 git switch -

# 方式三:彻底回退到 v3(丢弃红色主题那个 commit)
git reset --hard m0n1o2p

git log --oneline
# 输出:
# m0n1o2p feat: 添加文章详情页
# i7j8k9l feat: 添加侧边栏模块
# e4f5g6h feat: 添加文章列表模块
# a1b2c3d feat: 初始化博客项目骨架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

红色主题就像从未存在过。

# 🎯 悔过操作

# 哦不,我其实还是想要红色主题的那个 commit,只是刚才太冲动了!
# 别慌,Git 的后悔药:
git reflog
# 输出:
# m0n1o2p HEAD@{0}: reset: moving to m0n1o2p
# q3r4s5t HEAD@{1}: commit: feat: 添加红色主题
# ← 看到了!q3r4s5t 还在!

git reset --hard q3r4s5t  # 回来了!
1
2
3
4
5
6
7
8
9

💡 本章最重要的心法:只要 commit 过,Git 就不会真正删除你的数据。哪怕你用 reset --hard 丢了它,30 天内都能通过 reflog 找回来。这就是为什么 Git 被称为「后悔药最多的工具」。


# 十、本章回顾

我学会了什么 一句话总结
版本控制的本质 管理「时间」而非「文件」——记录每一刻的快照,让你自由穿越
集中式 vs 分布式 SVN 依赖中央服务器,Git 每人本地都有完整仓库
三个区域 工作区 → 暂存区 → 版本库,所有命令都在它们之间搬运数据
四种状态 Untracked / Modified / Staged / Committed
Git 的数据模型 blob → tree → commit,SHA-1 哈希保证完整性
Git ≠ GitHub Git 是工具,GitHub 是托管平台
核心心法 只要 commit 过,就能找回来

🚀 准备好了吗? 下一章我们将深入探索 Git 的时间机器——git log、git diff、git reset——学会在历史中自由飞翔。


# 📎 本章涉及的命令速查

# 安装验证
git --version

# 初始配置
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"
git config --list

# 仓库操作
git init                        # 初始化仓库
git add <file>                  # 添加到暂存区
git commit -m "message"         # 提交到版本库
git status                      # 查看文件状态
git log --oneline --graph       # 查看提交历史
git checkout <commit-hash>      # 查看历史版本(detached HEAD)
git reset --hard <commit-hash>  # 回退到指定版本
git reflog                      # 查看所有 HEAD 移动记录(后悔药)

# 底层探索
ls .git                         # 查看 Git 内部结构
git cat-file -p <object-hash>   # 查看 Git 对象内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#Git
上次更新: 2026/06/07, 10:26:12
README
单人工作流:在 Git 的时光机里自由穿梭

← README 单人工作流:在 Git 的时光机里自由穿梭→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式