附录 2:使用 Git 版本管理
Git 最初是为 Linux 内核开发的版本管理系统。Linux 内核是由大量开发者协作维护的复杂项目,因此 Git 提供了很多高级功能。
不过,完成本书的编译器项目并不需要掌握 Git 的全部功能。个人开发时,重点是能把每个小步骤可靠地保存下来:提交、查看历史、丢弃错误修改,以及把代码推送到远端仓库。下面先给出本书最常用的命令。
git add 文件名把新建文件加入版本管理。
git commit -A把工作树中的修改汇总成一次提交。执行后会打开编辑器,输入提交说明即可。
git reset --hard放弃自上次提交以来工作树中的所有修改。这个命令不可逆,执行前要确认没有需要保留的内容。
git log -p查看过去的提交以及每次提交带来的具体修改。
git push把本地提交推送到 GitHub 等远端仓库。
使用 Git 的工作流程
版本管理系统用于保存文件的修改历史。Git 管理的数据库称为仓库。从仓库取出的、供你编辑的目录树称为工作树。你平时修改源文件、运行测试、编译程序,都是在工作树里进行的。
当一组修改告一段落后,就把它们作为一次“提交”写回仓库。提交以后,你可以继续进行下一组修改;如果后来发现问题,也可以根据提交历史回退、比较或定位错误。
本书推荐的节奏是:实现一个小功能,补上测试,运行测试确认通过,然后提交。不要等到写了很多代码以后才提交,也不要把无关的修改揉在一次提交里。
提交时的注意事项
- 提交信息可以写中文,重点是清楚。例如:“添加
*和/运算符,但暂未处理优先级”。 - 提交粒度要小。重构和功能开发最好拆成不同提交。
- 不必急着使用分支等高级功能。个人完成本书项目时,线性提交历史已经足够。
- 功能代码和对应测试应放在同一次提交里。
- 提交前先运行测试,确保已有功能没有被破坏,新功能也确实可用。
理想状态是:无论检出哪一次提交,项目都能编译并通过当时已有的测试。偶尔把未通过测试的修改提交进去也不必过度紧张;个人学习项目中通常没有必要为了这类小失误改写 Git 历史,只要在下一次提交中修正即可。
Git 的内部结构
理解 Git 时,可以把它看成一种内容寻址的文件系统。普通文件系统通常通过文件名访问内容;Git 则根据内容计算哈希值,并用这个哈希值作为对象的名字。
这种机制称为 content-addressable storage,也就是“内容寻址存储”。内容相同,哈希值就相同;内容不同,哈希值几乎不可能相同。因为对象名称由内容决定,所以只要哈希值不变,内容就不能被悄悄篡改。
Git 中的提交本身也是对象。一个提交对象会记录提交说明、当前目录树中各文件对象的哈希值,以及上一个提交的哈希值。由于每次提交都引用前一次提交,最新提交的哈希实际上间接覆盖了整个历史。这就是 Git 能可靠记录历史的基础。
分支可以理解为指向某个提交对象的名字。比如 master 或 main 指向当前分支的最新提交。提交新版本时,Git 创建新的提交对象,然后把分支名移动到这个新对象上。
学习 Git 命令时,始终记住这个模型会很有帮助:文件内容形成对象,目录树引用对象,提交引用目录树和上一个提交,分支名指向某个提交。