Git 作为当下最流行的版本控制系统,在各个软件项目上得到了广泛的应用。
本文是廖雪峰的 Git 教程 [1] 的学习笔记的第一部分,主要介绍了版本控制的重要性和 Git 的工作原理。
廖的教程的内容比较浅,但是足够让人对 Git 有一个基本的了解。作为内容深度的补充,我参考了包括 《Pro Git》 [2] 在内的一些其他资料,对部分细节问题做了更深入的探讨。
在阅读本文时,可以参考廖的教程中相关内容的示例,以加深理解。
引言
如果你对编程稍有了解,我相信你一定听说过 Git 的大名;如果你拥有一个 GitHub 的账号,那么毫无疑问,你已经开始接触 Git 了。
对我而言,我接触 Git 已经有一段时间了。但由于我没有在这个主题上进行过系统的学习,因此我只知道几个最基本的命令、几个最基础的用法。我能够勉强使用 Git,但在使用的过程中却难免心惊胆战,生怕一不小心操作失误,造成无法挽回的后果。
Git 对我来说其实没有产生任何帮助效果,我仅仅是通过它把文件提交到服务器上,只是把它作为一个多人共享文件传输工具。那么为什么 Git 会如此流行,它究竟能给我们带来什么好处?怎样才能利用好 Git 这个工具,让它充分发挥价值?
为了回答这些疑问,我们首先需要对版本控制的重要性做一个了解。在此基础上,我们可以看到 Git 相对其他版本控制系统的优势所在。通过掌握 Git 的设计思路和基本的操作方法,我们可以自如地使用 Git,而不再如履薄冰;并且能够充分享受 Git 为我们带来的便利。
版本控制
重要性
人生没有后悔药可吃,一旦做出选择就无法更改。但是代码却有办法时光回溯,回到之前的某个时间节点。就好比玩游戏时,在做出一个选择之前,我们可以保存一个存档,并且我们随时可以通过读取存档回到当时的进度重新做出选择。版本控制之于项目就好比存档之于游戏。
在一个项目中,版本控制是十分重要的一环。试想某天,由于一个功能不再需要了,因此相关的代码被全部去掉。但是在经过很多个版本后,由于需求的变动,又需要重新加回这个功能。如果没有版本控制,这个功能就不得不完全重写了。但是,如果有版本控制,通过回溯到未删除该功能的版本,就可以直截了当地回退当时的修改,从而恢复相关的代码。
在项目中,通过版本控制系统,及时提交修改,把改动固定为一个又一个版本,就可以很方便地追踪每次的改动。如果有需要,可以随时回退到之前的版本。
事实上,手动保存文件的副本也是一种版本控制的方法。但是这样做十分低效。版本控制系统可以自动为我们完成大量重复的工作,使我们无需手动维护历史记录。
对个人来说,一个又一个版本连续地记录了每次的改动。在每次修改后,可以与上个版本进行比较, review 所有改动,确保新提交的部分全部都是需要的。如果某个版本发现旧的功能出了问题,可以通过回退之前的版本,确定是哪次提交导致的问题。通过改动记录,可以更轻松地找到问题所在。
对团队来说,版本控制系统提供了一种有效的多人协作方式。这也是大部分使用 SVN 和 Git 的非程序员对版本控制系统的理解。每个人都可以从中央服务器获得项目最新的版本,将自己的新的工作成果提交。如果出现了冲突,就要解决冲突后再提交。作为多人共享文件传输的工具,这也是我在对 Git 进行学习之前对 Git 的使用方式。
使用理念
请牢记,
不能 commit 就像用 Word 写文档不能 save 一样危险。
在过去,我还不理解,对于一个人开发的独立项目,版本控制有什么用?我会在做了很多改动后,才进行一次提交。这次提交也仅仅是为了把文件上传到服务器,提交时并不会去 review 做出的具体修改。
而在学习如何使用 Git 后,我会把原来的一个大的提交分解为许多小的提交。每当我完成一个小的功能,哪怕只有两三行修改,我也会进行一次提交。
少量多次的提交能给我们带来的好处包括以下两点。
review 提交
review 自己的提交是一个很好的习惯,可以直接审视自己改动的部分,确保提交的都是需要的东西。比如说,可以再确认一遍代码是否符合规范、逻辑是否正确、调试用的代码是否全部被删除了。
如果一次提交上千行代码,你会有耐心一行行地 review 吗?但是假如只有数十行呢?少量多次的提交方便了 review 改动。
当你习惯于 review 自己的代码,你会发现许多问题在 review 阶段就能被自己发现。以前,我从不会去 review 自己的改动;但是现在,我十分地依赖 review 代码。如果不能 review 改动,我甚至会本能地抵触提交。
定位 bug
如果某个版本发现旧的功能出了问题,可以通过回退之前的版本,确定是在哪次提交后产生了问题。通过比照这次提交的改动记录,可以更轻松地找到问题所在。
更少量的改动记录,会使得 bug 的定位更加简单。试想,从上千行代码或是数十行代码中找 bug,哪个更容易?
Git 的优势
版本控制自然导致产生版本库,它负责记录每次提交产生的版本。根据版本库所在位置的不同,可以将版本控制系统分为两类:
- 集中式版本控制系统,它的版本库仅存放在中央服务器。
- 分布式版本控制系统,每个人本地都拥有一个完整的版本库。
Git 作为一个分布式版本控制系统,对比 SVN 这种集中式版本控制系统,体现出了巨大的优势。
- 不用联网,在本地就可以进行 commit 以及查看历史版本
- 安全性高,每个人都可以通过本地的版本库重新建立中央服务器
- 优秀的分支设计
关于分支的话题,我们在之后具体介绍分支使用的时候再做说明。
安全性
我们先来谈一下 Git 的中央服务器。虽然说分布式版本控制系统的版本库存在于每一个终端上,因此不存在概念上的中央服务器。但是我们可以将一个终端只用来接受提交,并且所有人从它这里拉取最新的版本。那么这个终端就成为了事实上的中央服务器。这个中央服务器起到了方便多人协作的作用。
对于集中式版本控制系统,如果中央服务器的版本库出问题了,那么在维修结束之前,所有人都无法执行与版本相关的操作。但是对于 Git,因为每个人本地都有一个版本库,因此可以用任何一个人本地的版本库重新建立中央服务器,以恢复正常工作。
本地操作
本地操作是我要着重说明的一点。你可能会觉得,现在网速这么快,本地操作相对联网操作应该也没有特别的优势。但事实上,联网操作就存在不稳定的可能性。对版本控制的高频操作更会放大这一点。我们公司目前用 SVN 管理要发布的项目,如果网络出现了波动或者读取的数据量过大,就要经历一段无谓的等待。
此外, Git 能在本地提交,也就是说每次提交都是私有的,每当我想要记录下一些改动就可以进行一次 commit,哪怕此时这部分功能尚未完成,项目无法运行。我可以在进行了多次本地 commit,做了大量修改,完成这部分功能后,再将相关文件全部 push 到中央服务器。
对比 SVN,每次提交都必须提交到中央服务器,也就是说每次提交都是公共的。这使得我们必须保证每次提交后,项目仍然是可运行的。显然,这不利于我们贯彻少量多次的提交原则,便利性大大不如 Git。
Git 的工作原理
Git 管理的文件有三个状态:
- Modified,文件有未提交的修改。
- Staged,文件有未提交的修改,但都已经标记会在下次提交。
- Committed,文件没有未提交的修改。
对应这三个状态,产生了三个区域的概念:
- Working Directory,工作区, Modified 状态的修改位于工作区。
- Index / Staging Area,暂存区,这里记录的是下次将会提交的修改,即 Staged 状态的文件修改。
- Repository,版本库,这里记录了所有的历史版本,对应 Committed 状态的文件。版本库中的 HEAD 指针指向此次 commit 的上一个版本。
请注意, Index 中记录的是将要提交的修改而不是文件。也就是说,如果你在一个文件上做了修改,把这些改动添加到 Index 后,在这个文件上再次做修改,却没有添加到 Index 中,那么此时提交这个文件,只会把之前已经添加到 Index 中的那部分修改给提交到 Repository 中。
追踪修改相比追踪文件,确实体现出了更高的灵活性。这是设计思路上的进步。在我看来, Index 的存在,实现了 commit without record。每次 add changes 到 Index 中,相当于一次不产生版本的小 commit,而一次 commit 会将之前若干次 add 的改动固定为一个版本记录下来。
Git 包含的命令有许多,但是这些操作背后的原理其实就是文件的改动在上述的三个区域中互相流动。值得一提的是,使用 Git 的最基本的工作方式,就是把改动从 Working Directory 添加到 Index,再把 Index 里的改动提交到 Repository ,以固定成版本。
参考资料
[1] Git教程 - 廖雪峰的官方网站