版本控制:从抄目录到 Git 的对象存储¶
1. 这一章讲什么¶
DevOps 八阶段里的 Code 阶段靠什么转?答案是版本控制系统(Version Control System,VCS)——管代码改动的系统。这一章做两件事:一是把 VCS 的家谱讲清(手工抄目录 → SCCS → Git),二是把 Git 讲透,透到打开 .git 目录能看见里面怎么存东西。这是全书动手密度最高的一章,每个概念都带一条可以在自己机器上敲的命令。
2. 顶层全景¶
主线例子(全章共用):在本地建一个 first_git_repo 仓库,建文件、提交两次、开分支、合并、推到 GitHub——每步都给出真实的命令输出和哈希值。
3. 核心原理¶
3.1 为什么需要 VCS:没有它的时候怎么干活¶
在没有 VCS 的年代,开发者靠在不同目录里复制文件来记版本——费人力、费存储、易出错1。家谱由此展开,每一代修上一代的病:
| 年代 | 工具 | 修了什么病 |
|---|---|---|
| 1960s 初 | IEBUPDTE(IBM 大型机) | 用打孔卡时代的工具批量更新软件 |
| 1972 | SCCS(Marc Rochkind) | 第一个专为源码修订设计的工具:能追踪修订、召回旧版2 |
| 1982 | RCS(Walter Tichy) | 存储和检索更高效3 |
| 1986 | CVS(Dick Grune) | 多人同时改同一个项目而不互相覆盖4 |
| 2000 | Subversion(CollabNet) | 原子提交(要么全部提交成功、要么全部不算)、更好地管二进制文件和目录版本5 |
| 2005 | Git(Linus Torvalds) | 分布式:每人本地一份完整历史,至今是事实标准6 |
顺带澄清三个常混的名字:Git 是版本控制系统本身(Linus 写的),GitHub 和 GitLab 是托管 Git 仓库的网站,都在 Git 之上加了协作和 CI/CD 功能;GitLab 可自托管,GitHub 不行7。
3.2 集中式 vs 分布式:服务器挂了谁能干活¶
集中式(CVCS,如 SVN):中央服务器存全部历史,每人只有工作副本(自己机器上那份代码)。管权限和备份方便,但服务器一宕,所有人既无法协作也无法提交8。
分布式(DVCS,如 Git):每个贡献者本地都是完整镜像,含全部历史。提交先落在本地,联网后再 push;断网照常在本地提交,连上再同步9。Git 就是这么为 Linux 内核那种几百人协作的规模设计的。
VCS 的六大好处书里用了一组绝妙的类比——全是 Google Docs:并发编辑像多人同改一份文档;历史追溯像「修订记录」;回滚像撤销;远程备份像存云端;分支像另存一份再改;冲突标注像两人改了同一段时文档高亮10。读不懂 Git 的人,回想一下两人在线协作文档的样子,就都懂了。
3.3 文件的四个状态:一次真实的提交走一遍¶
主线例子开走。mkdir first_git_repo && cd first_git_repo,然后 git init——它只是在目录里建了一个隐藏子目录 .git,里面装仓库的全部元数据;删掉 .git,这个目录对 Git 来说就是普通文件夹11。
在仓库里 touch readme.md 建一个空文件,git status 会报它是 untracked(未跟踪)。Git 里一个文件有四种状态,主线例子每步踩一个12:
- Untracked(未跟踪):新文件,Git 还没管它。
- Modified(已修改):执行
echo "This is the first line." >> readme.md之后,文件改了但还没暂存。 - Staged(已暂存):
git add readme.md把它放进暂存区(staging area,也叫 index)——「下次提交会带上它」,但还没真正进历史。 - Committed(已提交):
git commit -m "committing the first file."落库。输出:
[master (root-commit) a5edbe6] committing the first file.
1 file changed, 1 insertion(+)
create mode 100644 readme.md
逐行读这个输出:a5edbe6 是这次提交的哈希前 7 位——Git 里每个提交的身份证号;root-commit 表示这是仓库的第一个提交;100644 是 Unix 文件模式,普通不可执行文件;1 insertion(+) 加了一行13。
第二次提交:加一行、再建个 secondfile.txt、git add .(句点=暂存当前目录全部改动)、git commit -m "This is the second commit",得到新哈希 a64d9e614。git log 能看到全部历史:每条含完整 40 位哈希、作者、带时区的时间戳、提交信息,以及 (HEAD -> master)——HEAD 是当前分支指针,表示你现在站在哪15。
推到远端:git remote add origin <URL> 把本地仓库连上 GitHub 同名空仓库,再 git push origin main,队友就能从任何机器拉到16。
3.4 分支不是什么大动作:一枚会走的指针¶
这是 Git 里最容易被「分支」二字吓到、其实最轻量的概念。分支不是装提交的房间,只是一枚指向某个提交的、可移动的指针——书里的原话,像 C 语言里的指针17。
主线例子里 git branch Branch1 建分支后,git checkout Branch1 切换——此时 HEAD 这枚特殊指针从指向 master 改为指向 Branch1;之后的新提交就长在 Branch1 上,而 master 指针原地不动,还指着 3c9b7b18。所以「开分支」在 Git 里便宜到近乎免费:复制一枚指针,不是复制整个项目。对比之下 SVN 的分支是服务器上的目录拷贝,重得多。
fork(复刻) 是另一层概念:把远端别人的仓库整份复制成你自己的独立项目,改它不影响原项目——书里举的真实例子:freenginx 就是从 nginx fork 出来的19。改完想让原作者采纳,就开 pull request 请求合并;GitLab 叫 merge request,同一件事两个名字20。
3.5 merge vs rebase:两种把分支合回来的姿势¶
主线例子:Branch1 领先 main 两个提交,git checkout main 后 git merge Branch1:
Updating a64d9e6..3c9b07b
Fast-forward
secondfile.txt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
这叫 fast-forward(快进)合并:main 没动过,直接把指针往前拨到 Branch1 的位置,不产生额外提交21。如果两边都动过同一文件的不同行,就要 three-way merge(三路合并):拿两个分支的末端和它们的共同祖先三方比对,合成一个新提交22。
merge conflict(合并冲突):两人改了同一文件的同一行。书里的具体例子:main 上有人加了第三行「This is not the second line.」(提交 e3f607),Branch1 上有人在同一位置加了「This is the third line」(提交 d42600),合并时 Git 无法自动裁决——它不会替你选,只能停下来让人工决定第三行留哪个、还是合成一句,然后标记冲突已解决、完成合并23。
rebase(变基):不合并,而是把 Branch1 上的提交整个「重放」到 main 的最新提交之上——git checkout Branch1 然后 git rebase main。效果:历史变成一条直线,没有合并提交;代价:重放出来的提交是新提交(哈希全变),等于改写了历史。有冲突时 Git 会暂停 rebase,解完再继续24。什么时候用哪个:想保留分支存在过的痕迹用 merge;想让历史干净成一条线、且这条分支没被别人基于它开发,用 rebase。
3.6 pull vs clone:同步 vs 第一次搬家¶
git pull origin main = 把远端最新改动拉下来同步进你已有的本地仓库;git clone <URL> = 第一次把整个远端仓库(含全部历史和所有分支)搬到本地25。
3.7 .git 里面长什么样:对象、哈希与压缩¶
这一章最硬核的部分(3.6):Git 的存储不是「文件名 → 内容」,而是 「内容 → 身份证号」,这个机制叫内容寻址(content-addressable)26。
四种 Git 对象27: - blob:存一个文件的内容(只管内容,不管文件名)。 - tree:存目录结构——文件名、文件模式、每个条目指向的 blob 或子 tree 的哈希。 - commit:存某一时刻仓库的状态——指向一棵 tree,外加作者、时间、提交信息、父提交的哈希。 - tag:指向某个 commit 的指针,常用来标记发布点(如 v1.0)。
数据完整性靠 SHA-1:每个对象的内容算出一个 40 位十六进制哈希当唯一身份证号。内容变一个比特,哈希就变;取数据时 Git 重算哈希和存货比对,对不上就是文件损坏或被动过28。
主线例子的下半场(换到 my_second_repo,提交一个空文件):提交哈希是 35132d1。进 .git/objects 目录看,会发现文件夹 35/ 里躺着文件 132d16f52228e36a066e317fb805cd6bac9d98——哈希前两位是目录名,剩下的 38 位是文件名29。用 git cat-file -p 35132 解开这个提交,里面露出三行:tree ac415abe…(指向 tree 对象)、author Chinmaya Dehury <dehury@xyz.com> 1713863805 +0300(时间戳是 Unix epoch 秒数,即 1970-01-01 以来的秒数)、added first empty file(提交信息)30。再解 tree 对象 ac415,得到 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 first-empty_file.txt;e69de29… 这个哈希是空文件的哈希——任何 Git 仓库里任何空文件都是它,所以「空文件」内容全球只有一份哈希31。
压缩:对象落盘前用 zlib 无损压缩,所以直接打开 objects 里的文件看到的是乱码,git cat-file -p 解压还原32。
一句诚实的补充:SHA-1 已被学术界找出碰撞漏洞(书里也承认),但 Git 的用法(哈希里还混入长度等信息)让攻击成本极高;社区讨论过迁往 SHA-256,尚未完成33。
4. 作者的判断与证据¶
照原书教法照单接住的部分:四状态、HEAD 指针、对象模型、.git/objects 的目录结构、空文件哈希 e69de29…——这些是 Git 教学里最经得起检验的讲法,书里全部给了可复现的实验。
书里明写的判断:DVCS 优于 CVCS 的论点是「断网也能干活、服务器不再是单点」9,这是设计层面的论据,不是性能实测。CVCS 的「集中管权限和备份」优势也被如实列出,态度算公允。
书中未展开、读者容易误会的:rebase 的「改写历史」在多人协作分支上是危险操作(force push 会丢队友提交)——书里只讲了机制和冲突处理,没讲这条安全红线,我们在此补上(依据通用知识,不在书里)。
5. 边界与局限¶
- 只教单线协作(一个人 + 一个远端),没讲 GitFlow、trunk-based development 等团队分支策略——第 1 章提了 GitFlow 的名字,展开得另找资料。
- GitHub 部分(3.7)是注册账号、建仓库级别的入门截图,价值随时效衰减。
- SHA-1→SHA-256 的迁移讨论书里点到为止。
6. 可带走的¶
- VCS 家谱:抄目录 → SCCS(1972)→ RCS(1982)→ CVS(1986)→ SVN(2000)→ Git(2005),每代修上代的病16。
- 集中式服务器宕机全员停工;分布式每人一份完整历史,断网照提交89。
- Git 文件四状态:untracked → modified → staged → committed;
git add进暂存,git commit进历史12。 - 分支是移动指针,HEAD 是当前位置指针;开分支近乎免费1718。
- 同一行被两边改出不同内容 = 合并冲突,Git 不替你裁决23。
- merge 留痕迹,rebase 重放成直线但改历史;公共分支上 rebase 是危险操作24。
- Git 是内容寻址:40 位 SHA-1 哈希即身份证号,内容变则哈希变,篡改无所遁形2628。
- 空文件的哈希恒为
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391,记住它能验证任何 Git 仓库31。 - 对象落盘前 zlib 压缩;看内部用
git cat-file -p <哈希>32。
7. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| VCS 家谱(SCCS→Git) | 3 - Version control system | text/08-ch03-3-version-control-system.txt:16(搜「IEBUPDTE」) · text/08-ch03-3-version-control-system.txt:26(搜「Linus Torvalds」) |
| VCS 六大好处(Google Docs 类比) | 3 - Version control system | text/08-ch03-3-version-control-system.txt:60(搜「Concurrency」) |
| CVCS vs DVCS | 3 - Version control system | text/08-ch03-3-version-control-system.txt:105(搜「single point」) |
| Git init 与四状态 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:131(搜「subdirectory named .git」) · text/08-ch03-3-version-control-system.txt:139(搜「four main states」) |
| 提交输出逐行解读 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:163(搜「root-commit」) |
| push 到远端 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:220(搜「git remote add origin」) |
| 分支=指针、HEAD 指针 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:246(搜「lightweight and movable pointer」) · text/08-ch03-3-version-control-system.txt:255(搜「special pointer」) |
| fork(freenginx 例) | 3 - Version control system | text/08-ch03-3-version-control-system.txt:271(搜「is forked from the original nginx」) |
| fast-forward 与三路合并 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:331(搜「Fast Forward Merge」) · text/08-ch03-3-version-control-system.txt:332(搜「three-way merge」) |
| 合并冲突实例 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:336(搜「same section」) |
| merge vs rebase | 3 - Version control system | text/08-ch03-3-version-control-system.txt:371(搜「rewrites the history」) |
| pull vs clone | 3 - Version control system | text/08-ch03-3-version-control-system.txt:383(搜「Pulling in Git is the process」) |
| 四种 Git 对象 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:394(搜「blobs, trees, commits, and tags」) |
| SHA-1 与内容寻址 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:429(搜「SHA-1 cryptographic hash」) |
| .git/objects 目录结构实例 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:423(搜「first two characters of the commit ID」) |
| 空文件哈希 e69de29 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:484(搜「well-known hash」) |
| zlib 压缩 | 3 - Version control system | text/08-ch03-3-version-control-system.txt:497(搜「provides lossless」) |
-
出处:「3 - Version control system」第 14 段(
text/08-ch03-3-version-control-system.txt:14,搜「separate directories」)。 ↩↩ -
出处:「3 - Version control system」第 17 段(
text/08-ch03-3-version-control-system.txt:17,搜「SCCS (Source Code Control System)」)。 ↩ -
出处:「3 - Version control system」第 21 段(
text/08-ch03-3-version-control-system.txt:21,搜「Revision Control System」)。 ↩ -
出处:「3 - Version control system」第 22 段(
text/08-ch03-3-version-control-system.txt:22,搜「Concurrent Versions System」)。 ↩ -
出处:「3 - Version control system」第 24 段(
text/08-ch03-3-version-control-system.txt:24,搜「atomic commits」)。 ↩ -
出处:「3 - Version control system」第 26–30 段(
text/08-ch03-3-version-control-system.txt:26,搜「created by Linus Torvalds」)。 ↩↩ -
出处:「3 - Version control system」边栏(
text/08-ch03-3-version-control-system.txt:56,搜「hosting services for using Git」)。 ↩ -
出处:「3 - Version control system」第 107–109 段(
text/08-ch03-3-version-control-system.txt:108,搜「if the central server is down」)。 ↩↩ -
出处:「3 - Version control system」第 110–115 段(
text/08-ch03-3-version-control-system.txt:114,搜「internet connection goes down」)。 ↩↩↩ -
出处:「3 - Version control system」第 59–79 段(
text/08-ch03-3-version-control-system.txt:61,搜「edit a Google Doc」)。 ↩ -
出处:「3 - Version control system」第 130–134 段(
text/08-ch03-3-version-control-system.txt:131,搜「new subdirectory named .git」)。 ↩ -
出处:「3 - Version control system」第 139–160 段(
text/08-ch03-3-version-control-system.txt:139,搜「one of four main states」)。 ↩↩ -
出处:「3 - Version control system」第 161–173 段(
text/08-ch03-3-version-control-system.txt:163,搜「root-commit」)。 ↩ -
出处:「3 - Version control system」第 176–192 段(
text/08-ch03-3-version-control-system.txt:183,搜「a64d9e6」)。 ↩ -
出处:「3 - Version control system」第 194–212 段(
text/08-ch03-3-version-control-system.txt:197,搜「HEAD -> master」)。 ↩ -
出处:「3 - Version control system」第 219–226 段(
text/08-ch03-3-version-control-system.txt:220,搜「git remote add origin」)。 ↩ -
出处:「3 - Version control system」第 246–251 段(
text/08-ch03-3-version-control-system.txt:246,搜「lightweight and movable pointer」)。 ↩↩ -
出处:「3 - Version control system」第 255–264 段(
text/08-ch03-3-version-control-system.txt:260,搜「git checkout Branch1」)。 ↩↩ -
出处:「3 - Version control system」第 266–271 段(
text/08-ch03-3-version-control-system.txt:271,搜「forked from the original nginx」)。 ↩ -
出处:「3 - Version control system」第 353–362 段(
text/08-ch03-3-version-control-system.txt:355,搜「GitLab uses Merge Request」)。 ↩ -
出处:「3 - Version control system」第 310–331 段(
text/08-ch03-3-version-control-system.txt:324,搜「Fast-forward」)。 ↩ -
出处:「3 - Version control system」第 332–334 段(
text/08-ch03-3-version-control-system.txt:332,搜「more versions of a file」)。 ↩ -
出处:「3 - Version control system」第 335–351 段(
text/08-ch03-3-version-control-system.txt:344,搜「3c9b07 and e3f607」)。 ↩↩ -
出处:「3 - Version control system」第 364–380 段(
text/08-ch03-3-version-control-system.txt:371,搜「rewrites the history」)。 ↩↩ -
出处:「3 - Version control system」第 382–391 段(
text/08-ch03-3-version-control-system.txt:388,搜「creating a new local repository」)。 ↩ -
出处:「3 - Version control system」第 440–441 段(
text/08-ch03-3-version-control-system.txt:440,搜「content-addressable」)。 ↩↩ -
出处:「3 - Version control system」第 393–399 段(
text/08-ch03-3-version-control-system.txt:394,搜「blobs, trees, commits, and tags」)。 ↩ -
出处:「3 - Version control system」第 428–439 段(
text/08-ch03-3-version-control-system.txt:573,搜「SHA-1 cryptographic hash function」)。 ↩↩ -
出处:「3 - Version control system」第 420–425 段(
text/08-ch03-3-version-control-system.txt:423,搜「first two characters of the commit ID」)。 ↩ -
出处:「3 - Version control system」第 450–475 段(
text/08-ch03-3-version-control-system.txt:454,搜「author Chinmaya Dehury」)。 ↩ -
出处:「3 - Version control system」第 476–490 段(
text/08-ch03-3-version-control-system.txt:484,搜「well-known hash」)。 ↩↩ -
出处:「3 - Version control system」第 494–506 段(
text/08-ch03-3-version-control-system.txt:497,搜「zlib」)。 ↩↩ -
出处:「3 - Version control system」第 491–492 段(
text/08-ch03-3-version-control-system.txt:492,搜「SHA-256」)。 ↩