<audio id="audio" title="30 | 用好源代码管理工具，让你的协作更高效" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ac/17/acd3b39473f8843cfafc3886d98d1817.mp3"></audio>

你好，我是宝玉。在今天，源代码管理工具在软件项目中已经是标准配置了，几乎每个软件项目都会应用到，可以说是最基础的项目开发工具。选择也很多，可以自己搭建源代码管理服务，也可以直接用网上托管的服务，例如GitHub、GitLab、BitBucket等。

但同样是应用源代码管理工具，为什么有的团队就能做到代码质量高，随时能发布新版本，高效开发？而有的团队却不能做到高效开发，拿到的代码也不稳定，合并时冲突很多？

今天，我将带你了解一下源代码管理工具，以及如何才能应用好源代码管理工具，以保证代码质量稳定，协作高效。

## 源代码管理工具发展简史

源代码管理工具也叫版本控制系统，是保存文件多个版本的一种机制。每一次有人提交了修改，这个修改历史都会被版本控制系统记录下来。如下图所示，每一次对内容的修改，都会形成一个当前项目完整内容的快照。

[<img src="https://static001.geekbang.org/resource/image/90/5f/90178c04135e72aecf2bdd262bbf915f.png" alt="" title="图片来源：《什么是版本控制？》">](http://www.git-tower.com/learn/git/ebook/cn/command-line/basics/what-is-version-control#start)

源代码管理工具从诞生到现在已经有40多年的历史了，经历了四个阶段。

#### 没有源代码管理工具的时代

早些年开发软件可没有我们这么幸运，在1972年之前都没有任何工具可以帮助我们做源代码管理。

这就意味着，当你开发时，必须要告知团队里的其他人，你正在对哪些文件进行编辑，好让他们不要操作相同的文件。当你花了很长时间完成你的修改后，可能这些文件早已经被团队里的其他开发成员修改或者删除了。

除了协作的问题，还有一个问题就是版本问题。没有源代码管理，你得经常性对项目的文件保存备份，很麻烦，而且还是一样有不少问题：

- 很难知道做了哪些修改，你可能需要挨个目录去查看文件修改时间；
- 对版本命名是一个很麻烦的事情，每次备份都得有一个名字；
- 很难知道两次备份之间，做了哪些修改。

#### 本地版本管理

最早的版本控制系统是SCCS（Source Code Control System），诞生于1972年，它实现了对单个文件保留多个版本，这就意味着你可以看到每一个文件的修改历史了。

后来又有了RCS （Revision Control System），它具有更好的文件比较算法，通过登录同一台中央大型机，可以实现每个人签出自己的拷贝。

但这个阶段只能本机使用，而且一次只能修改一个文件，无法满足好多人协作的问题。

#### 集中式版本管理

1986年问世的CVS（Concurrent Versions System）是第一个采用集中式的服务器来进行版本库的管理工作，所有文件和版本历史都放在服务端，每个用户通过客户端获取最新的代码，可以多个人编辑一个文件，并且能提交到服务器合并在一起。

再后来的SVN（Subversion）则对CVS进行了很多优化，比如支持文件改名、移动、全局版本号等，这些优化很大部分程度上解决了CVS存在的一些缺陷，所以在2000年后逐步取代了CVS成为主流的源代码管理工具。

[<img src="https://static001.geekbang.org/resource/image/52/72/52f5b217fe0cfb88a24918d755ffb772.png" alt="" title="图片来源：How Version Control Systems Work">](http://code.snipcademy.com/tutorials/git/introduction/how-version-control-works)

不过，这类集中式源代码管理工具，过于依赖服务器，如果服务器出问题或者连不上，就没法用了，如果服务器损坏，所有的版本历史也会丢失。

#### 分布式版本管理

分布式版本管理工具的典型代表就是Git，分布式版本控制系统的整个代码库的副本都可以存储在用户的本地系统上，这样文件和版本控制操作变得非常容易，离线也可以操作，如果主存储库关闭或者删除，可以很容易从本地存储库恢复。

现在Git已经逐步替代了SVN、CVS等源代码管理工具，成为最主流的源代码管理工具。

Git的主要问题是学习成本要稍微高一点，要花点时间理解它的工作原理和记住主要的命令。

[<img src="https://static001.geekbang.org/resource/image/60/02/603379637ba76d67ddcc21f1d515b202.png" alt="" title="图片来源：How Version Control Systems Work">](http://code.snipcademy.com/tutorials/git/introduction/how-version-control-works)

## 如何选择合适的源代码管理系统

现在源代码管理系统已经有很多的选择，你可以选择网上托管的代码管理服务，或者是自己搭建。

自己搭建的好处就是可以有更多的控制，但是需要有自己的服务器，自己搭建环境，还要后续的维护。用网上的托管平台，可以减少运维成本，功能也很强大，但对平台有一定的依赖。

我的建议是如果项目规模不大，隐私要求不高，完全没必要自己搭建，可以直接选择网上的托管平台。这样可以节约很多时间成本，而且还可以方便和一些第三方服务，例如持续集成等进行整合。

如果希望对源代码管理有更多控制，也能接受运行维护上的投入，就可以选择自己搭建。

#### 自己搭建源代码管理系统

- Git

Git本身是开源免费的，所以每个人都可以搭建自己的Git Server，具体的操作执行我就不在此处展开了，网上有很多教程。例如：《 [服务器上的 Git - 在服务器上搭建 Git](http://git-scm.com/book/zh/v2/%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E7%9A%84-Git-%E5%9C%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E6%90%AD%E5%BB%BA-Git) 》《[搭建自己的 Git 服务器](http://www.aneasystone.com/archives/2018/12/build-your-own-git-server.html)》

- GitLab

Git自带的Server默认是没有Web界面进行管理的，只能用命令行操作交互，这在操作上有很多不便利性，尤其是不方便做代码审查，所以可以安装[GitLab的社区版本](http://about.gitlab.com/install/)，开源免费的，有Web操作界面，可以像GitHub一样提交Pull Request，并且和CI （持续集成）系统例如GitLab CI、Jenkins都有很好的集成。

网上也有很多安装教程：《[在自己的服务器上部署 GitLab 社区版](http://www.cnblogs.com/restran/p/4063880.html)》《[GitLab的安装及使用教程](http://yq.aliyun.com/articles/74395)》。

- Gerrit

[Gerrit](http://www.gerritcodereview.com)是由Google开发的，用于管理Google Android项目源代码的一个系统。它支持Git和代码评审。参考教程：《[Gerrit代码审核服务器搭建全过程](http://www.jianshu.com/p/5ae7f5978814)》

#### 网上的代码托管平台

- GitHub

[GitHub](http://github.com)现在已经是全球最流行的代码托管平台，功能强大，和第三方服务集成非常好。而且私有的代码库如果不超过3个人都是免费的，我自己很多个人项目就都是放在GitHub上托管。

[GitHub](http://about.gitlab.com)的Web UI非常强，尤其是代码浏览和审查，在网站上就可以提交Pull Request和进行代码审查。不过GitHub不提供CI服务，需要和第三方CI服务集成。

- GitLab

[GitLab](http://about.gitlab.com)的网上托管服务很多地方和GitHub都很类似，但是价格更便宜。例如免费用户可以支持无限的私有项目，也内置了CI的支持。

- Coding

[Coding](http://coding.net)是国内一个不错的代码托管平台，5人以下的私有库免费，内置了CI支持，同时还有项目管理工具支持。

其他的服务还有像：[码云](http://gitee.com)、[阿里云 Code](http://crp.aliyun.com)、[百度效率云](http://xiaolvyun.baidu.com)、[腾讯Git代码托管](http://cloud.tencent.com/product/tgit)、[华为云 CodeHub](http://www.huaweicloud.com/product/codehub.html)，这里就不一一介绍了。

## 如何用好源代码管理工具？

用好源代码管理工具，有三个简单可行的原则：

#### 原则一：要频繁的提交

很多开发人员不愿意轻易提交代码到源代码管理中心，喜欢“憋个大招”，本地做了大量修改，希望代码能“完美”。但这样做却没能享受到频繁提交带来的好处。

频繁提交，这意味着你每次提交的代码变更是比较少的，便于Code Review，同时如果出现问题，也可以迅速定位或者直接回滚。

频繁地提交，也让团队成员可以及时同步最新代码，不至于在最后合并时，产生有大量的合并冲突。

**频繁提交，不意味着提交不完整的内容，而是将要提交的内容分拆，并且保证完整性。**

比如说，有一个涉及前后端的功能，可以拆分成前端和后端两次提交，各自有独立的代码和测试；比如说你开发新功能的时候发现有代码需要重构，那么对于重构的代码单独一次提交，不要和新功能的代码提交放一起。

#### 原则二：每次提交后要跑自动化测试

在前面章节我们学习了自动化测试和持续集成，自动化测试是非常有效的质量保障手段，而持续集成工具，可以让每次提交代码后，能自动地运行相应的自动化测试代码，有效保障提交代码的质量。

源代码管理的第二个原则，就是每次提交，必须要运行自动化测试代码，如果测试不通过就不能合并，要对问题进行甄别和修复，确保提交的代码质量是没问题的。

#### 原则三：提交的代码要有人审查

代码审查是自动化测试之外，一种非常行之有效的提高质量的手段，通过代码审查，可以发现代码中潜在的问题。通过代码审查，也可以加强团队的技术交流，让水平高的开发人员Review，可以帮助提升整体代码水平；Review高水平的代码也是一种非常有效的学习方法。

**怎么做好代码审查呢？**

我的经验是，在审查别人代码的时候，先了解清楚这个提交的代码要解决的是什么问题，想象一下如果是自己来写这个代码会怎么写。这样在审查的时候，就能发现一些和自己不一样的地方，别人好的地方我们可以学习，不对的地方应该指出。

对于审查出来的问题，可以分成三个类型：

- 问题：如果对代码有不清楚的地方，可以作为问题提出，进一步澄清确认；
- 建议：原来的实现没有太大问题，但是可以有不同的或者更好的实现；
- 阻塞：代码有明显问题，必须要修改。

这样对于被审查的人可以针对你的问题进行针对性修改。

这三个原则很简单，可以有效提升代码质量，减少合并冲突，及时发现问题，从而让你的协作更高效。

## 我该选择什么样的开发流程？

现在基于源代码管理有三种主要的开发流程：

- [Git flow](http://nvie.com/posts/a-successful-git-branching-model/)
- [GitHub flow](http://guides.github.com/introduction/flow/)
- [GitLab flow](http://docs.gitlab.com/ee/workflow/gitlab_flow.html)

阮一峰老师有一篇文章《[Git 工作流程](http://www.ruanyifeng.com/blog/2015/12/git-workflow.html)》对它们有详细介绍。在这里我重点介绍一下GitHub flow，因为它简单易懂，另外，它也对上面我提到的三个原则有很好的支持。

当然，我并不是说你一定要选择GitHub flow，而是在理解它后，可以基于它的流程衍生出适合自己项目特点的开发流程。

#### GitHub开发流程

GitHub开发流程的关键在于两点：

1. 有一个稳定的分支，例如master；
1. 每次创建新功能或者修复Bug，必须创建一个分支。最后通过代码审查和自动化测试后，才能合并回稳定分支。

通过这样的开发流程，就相当于把自动化测试和代码审查作为一种强制性要求了，所有的修改必须要通过代码审查和自动化测试通过才能合并，从而保证有一个可以随时部署发布的稳定分支。

我们具体看看基于GitHub flow是如何开发的。

- **第一步：创建一个分支**

分支是Git中的核心概念，整个GitHub流程都是基于分支展开的，master分支是要一直保持稳定的，不能直接在master上开发。

无论你是要开发一个新功能还是修复一个Bug，第一件事永远是从master创建一个分支出来。

[<img src="https://static001.geekbang.org/resource/image/ce/1a/ce1be843617e669e47f5bd6f2c2b801a.png" alt="" title="图片来源：GitHub网站截图">](http://guides.github.com/introduction/flow/)

- **第二步：提交更新**

当创建好分支后，就可以基于分支开始工作了，这时候就可以按照前面建议的原则，频繁的提交更新。注意每次提交的时候，要加上说明性的信息，让其他人明确知道你这次提交的内容是什么，如果开发过程中，发现错误了，还可以随时回滚之前的更改。

[<img src="https://static001.geekbang.org/resource/image/6f/07/6f54abdcfa102a13eda2bd5329e08707.png" alt="" title="图片来源：GitHub网站截图">](http://guides.github.com/introduction/flow/)

- **第三步：创建一个Pull Request**

在开发完成后，创建一个Pull Request（合并请求，简称PR，GitLab中叫Merge Request），创建PR时，通常要附上描述性的信息，关联上相应的Ticket连接，让其他人知道你这个PR要完成什么任务。创建好PR后，其他人就可以直观的看到你所有的修改。

[<img src="https://static001.geekbang.org/resource/image/1a/d6/1a665275ab20b3fd3a7711db357271d6.png" alt="" title="图片来源：GitHub网站截图">](http://guides.github.com/introduction/flow/)

- **第四步：讨论和代码审查**

当你的PR提交后，团队的其他人就可以对PR中的代码修改进行评论。比如说代码风格不符合规范、缺少单元测试、或者很好没有问题。PR的主要目的就是为了方便大家做代码审查。

根据代码审查的结果，你可能要做一些修改，那么只要继续提交更新到这个分支就可以了，提交更新后，PR就会自动更新，其他人可以基于你的更新进一步的讨论和审查，直到通过代码审查。

[<img src="https://static001.geekbang.org/resource/image/9c/97/9c324020e64e963c5d47b4b51ea1fb97.png" alt="" title="图片来源：GitHub网站截图">](http://guides.github.com/introduction/flow/)

- **第五步：部署测试**

在合并前，还需要把分支的修改进行测试。理论上来说，需要将修改的内容部署到测试环境测试，但这样效率太低了，所以通常的做法是借助持续集成工具，在每次提交代码后，就运行自动化测试代码，自动化测试代码全部通过后，就可以认为质量是可靠的。

这也意味着你需要让项目中的自动化测试代码保持一定的测试覆盖率，否则质量还是难以保障的。

[<img src="https://static001.geekbang.org/resource/image/13/5f/13236779ae4780cf55ccb3d3cda5065f.png" alt="" title="图片来源：GitHub网站截图">](http://guides.github.com/introduction/flow)

- **第六步：合并**

当你的代码通过了代码审查和自动化测试，就可以将代码合并到master分支了。合并后，之前的分支就可以删除，但你之前所有的提交记录在master都可以看到，所以完全不用担心丢失历史版本记录。

[<img src="https://static001.geekbang.org/resource/image/0d/35/0da9251ea9b5a791a8b4ff536fee3035.png" alt="" title="图片来源：GitHub网站截图">](http://guides.github.com/introduction/flow/)

以上就是GitHub开发流程的主要步骤，通过分支开发新功能或者修复Bug，强制通过代码审查和自动化测试才能合并master，从而保证master的稳定。

#### GitHub开发流程的几个常见问题

基于这个流程我再补充几个常见问题：

- **怎么发布版本？**

要发布版本的话，从master上创建一个Tag，例如v1.0，然后将Tag v1.0上的内容部署到生产环境。

- **怎么给线上版本打补丁？**

如果线上发布的版本（例如v1.0）发现Bug，需要修复，那么基于之前的Tag创建一个分支（例如hotfix-v1.0-xxx）出去，在分支上修复，然后提交PR，代码审查和自动化测试通过后，从分支上创建一个新的Tag （例如v1.0.1），将新的Tag发布部署到生产环境，最后再把修改合并回master。

- **如果我经常需要打补丁，有没有比Tag更好的办法？**

每次发布后，可以创建一个发布版本的分支，例如release-v1.0，每次打补丁，都直接从发布分支release-v1.0而不是master创建新的分支（例如hotfix-release-v1.0-xxx），修复后提交PR，代码审查和自动化测试通过后，合并回分支release-v1.0，然后基于release-v1.0分支发布补丁。

最后将合并的PR，借助git的cherry-pick命令再同步合并回master。

上面的例子其实主要是说明一下，GitHub Flow只是一种基础的开发流程，你完全可以基于GitHub Flow，衍生出适合你自己项目特点的开发流程。

无论你基于哪一种开发流程，最好能做到这两点：

1. 有一个稳定的代码分支；
1. 在合并分支之前，对代码有审查，自动化测试要能通过。

这样你才能做到可以随时发布，质量稳定，高效协作。

## 总结

源代码管理工具也叫版本控制系统，是保存文件多个版本的一种机制，可以记录文件的历史版本。

用好源代码管理工具，有三个简单可行的原则：

- 原则一：要频繁的提交；
- 原则二：每次提交后要跑自动化测试；
- 原则三：提交的代码要有人审查。

基于源代码的开发流程，要保证好两点：

1. 有一个稳定的代码分支；
1. 在合并分支之前，对代码有审查，自动化测试要能通过。

用好源代码管理工具，设计好开发流程，保证好代码质量，你的协作才能更高效。

## 课后思考

你所在项目用的是什么源代码管理工具？你觉得有什么优缺点？你基于源代码管理工具的开发流程是怎么样的？有哪些可以改进的地方？欢迎在留言区与我分享讨论。

感谢阅读，如果你觉得这篇文章对你有一些启发，也欢迎把它分享给你的朋友。
