Linus创建了Linux,Linux的壮大是靠全世界热心的志愿者参与的。世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!
时间到了2002年,Linux系统已经发展了十年,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错),于是BitMover公司怒了,要收回Linux社区的免费使用权。
Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们。但,这是不可能的。实际情况是这样的:
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git诞生!一个月之内,Linux系统的源码已经由Git管理了!大家可以体会一下,牛是怎么定义的!
Git迅速成为最流行的分布式版本控制系统。尤其是2008年,GitHub网站上线。它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
集中式版本控制系统:版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑。所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器比作一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
分布式版本控制系统:没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样工作的时候,就不需要联网了,因为版本库就在电脑上。
既然每台电脑上都有一个完整的版本库,那多个人如何协作呢?
比方说你在自己电脑上改了文件A,你的同事在他的电脑上改了文件A。你们俩之间只需要把各自的修改推送给对方,就可以互相看到对方的修改了。
实际使用分布式版本控制系统的时候,很少在两台电脑之间推送版本库的修改。因为可能两台电脑之间不能互相访问,也可能其中一台电脑压根就没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,这个服务器的作用仅仅是用来方便“交换”大家的修改。没有它大家也一样干活,只是交换修改不方便而已。
集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
分布式版本控制系统的每台电脑里都有完整的版本库,某台电脑坏掉了不要紧,从其他电脑里复制一份就可以。因此,代码安全性要高很多。
Git的优势是拥有极其强大的分支管理,把Svn等远远抛在了身后。
每次提交代码,Git都会生成支点,它们串成一条时间线,这条时间线就是一个分支。
每次提交,master
分支都会向前移动一步。随着你不断提交,master
分支的线也越来越长。目前只有一条时间线,在Git里,这个分支叫主分支。
创建新的分支:
例如dev
时,Git新建一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上。
重点:HEAD
指向的就是当前正在使用的分支!
创建好dev分支开始,对工作区的修改和提交就是针对dev
分支了。比如新提交一次后,dev
指针往前移动一步,而master
指针不变。
假如在dev
上的工作完成了,就可以把dev
合并到master
上。最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并。
合并分支完成后,如果dev
分支不在使用,可以直接删除dev
分支。删除dev
分支就是把dev
指针给删掉,就剩下了一条master
分支。
Git 作为一个源码管理系统,不可避免涉及到多人协作。协作必须有一个规范的工作流程,让大家有效地合作,使得项目井井有条地发展下去。“工作流程”在英语里,叫做“workflow”或者“flow”。
三种广泛使用的工作流程:
三种工作流程,有一个共同点,都采用“功能驱动式开发”(Feature-driven development,简称FDD)。
它指的是,需求是开发的起点,先有需求再有功能分支(feature branch)或者补丁分支(hotfix branch)。完成开发后,该分支就合并到主分支,然后被删除。
项目存在二个长期分支:
- 主分支(master
)
- 开发分支(develop
)
master
用于存放对外发布的版本,任何时候在这个分支拿到的,都是稳定的分布版;develop
用于日常开发,存放最新的开发版。
项目存在三种短期分支:
这三种分支都属于临时性需要,使用完以后,应该删除,使得代码库的常设分支始终只有master
和develop
。
它是为了开发某种特定功能,从develop
分支上面分出来的。开发完成后,要再并入develop
分支。它的命名,可以采用feature-*
的形式。
它是指发布正式版本之前(即合并到master分支之前),需要有一个预发布的版本进行测试。预发布分支是从develop
分支上面分出来的,预发布结束以后,必须合并进develop
和master分支。它的命名,可以采用release-*的形式。
软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。修补bug分支是从master
分支上面分出来的。修补结束以后,再合并进master
和develop
分支。它的命名,可以采用fixbug-*的形式。
Git flow总结:
优点是清晰可控,缺点是相对复杂,需要同时维护两个长期分支。大多数工具都将master当作默认分支,可是开发是在develop
分支进行的,这导致经常要切换分支,非常烦人。
更大问题在于,这个模式是基于“版本发布”的,目标是一段时间以后产出一个新版本。但是很多网站项目是“持续发布”,代码一有变动,就部署一次。这时master分支和develop
分支的差别不大,没必要维护两个长期分支。
它是Git flow的简化版,专门配合“持续发布”。它是 Github.com 使用的工作流程。
它只有一个长期分支master
,因此用起来非常简单。操作步骤:
1、根据需求,从master
拉出新分支,不区分功能分支或补丁分支。
2、新分支开发完成,有需要讨论的时候,就向master
发起一个Pull Request(简称PR)。
3、PR既是一个通知,让别人注意到你的请求,又是一种对话机制,大家一起评审和讨论你的代码。对话过程中,你还可以不断提交代码。
4、PR被接受时,合并进master
,重新部署。把新拉出来的那个分支删除。
Githubdevelop
flow总结:
优点就是简单,对于“持续发布”的产品是最合适的流程。
它的问题在于假设要成立。master
分支的更新与产品的发布是一致的。保证master
分支的最新代码就是当前线上的代码。
有些时候并非如此,代码合并进入master
分支,并不代表它就能立刻发布。例如,苹果商店的APP提交审核以后,等一段时间才能上架。如果还有新的代码提交,master
分支就会与刚发布的版本不一致。另一个例子是有些公司有发布窗口时间,只有指定时间才能发布新的版本,这也会导致线上版本落后于master
分支。
它是 Git flow 与 Github flow 的综合。它吸取了两者的优点,既有适应不同开发环境的弹性,又有单一主分支的简单和便利。它是 Gitlab.com使用的工作流程。
它的最大原则叫做“上游优先”(upsteam first),即只存在一个主分支master
,它是所有其他分支的“上游”。只有上游分支采纳的代码变化,才能应用到其他分支。
Gitlab flow 分成两种情况,适应不同的开发流程。
1、持续发布:
它建议在master
分支以外,建立不同的环境分支。“开发环境”的分支是master
,“预发环境”的分支是pre-production,“生产环境”的分支是production。
开发分支是预发分支的“上游”,预发分支是生产分支的“上游”。代码的变化,必须由“上游”向“下游”发展。例如,生产环境出现bug,新建一个功能分支修正bug,先把它合并到master
,确认没有问题。再cherry-pick到pre-production,确认没有问题。才能进入production。
只有紧急情况,才允许跳过上游,直接合并到下游分支。
2、版本发布:
它对于“版本发布”的项目,建议的做法是每一个稳定版本,都要从master
分支拉出一个分支,比如2-3-stable、2-4-stable等等。
以后,只有修补bug,才允许将代码合并到这些分支,并且此时要更新小版本号。
Gitlab flow总结:
优点是很多公司都使用这种工作流程。
首先,每次开发新功能,都应该新建一个单独的分支。
功能分支合并进master
分支,必须通过Pull Request(Gitlab.com里面叫做 Merge Request)。
Pull Request本质是一种对话机制,可以在提交的时候@相关人员或团队,引起他们的注意。及时的查看提交的内容。
master
分支应该受到保护,不是每个人都可以修改这个分支,以及拥有审批 Pull Request 的权力。
Github.com 和 Gitlab.com 都提供“保护分支”(Protected branch)这个功能。
GitHub.com 的issue
功能就如同TODO list
。想要在下一步完成的工作,如feature
添加、bug
修改等,都写成一个个的issue
。放在上面,可以作为提醒,可以统一管理。
开发完成后,在提交说明里面,可以选择性的与某个issue关联。比如在message
中添加#n,就可以与第n个issue
进行关联。
commit message title, #1
只要commit message
里面有下面这些动词 + 编号,就会关闭对应的issue。
- close #n
- closes #n
- closed #n
- fix #n
- fixes #n
- fixed #n
- resolve #n
- resolves #n
- resolved #n
`commit message title, fix #n`
Git有两种合并:一种是“直进式合并”(fast forward),不生成单独的合并节点;另一种是“非直进式合并”(none fast-forword),会生成单独节点。
前者不利于保持commit信息的清晰,也不利于以后的回滚。建议总是采用后者(即使用–no-ff参数)。只要发生合并,就要有一个单独的合并节点。