5.1 分布式工作流程
Git的分布式特性,可使协作开发更加灵活,在中心式版本控制系统(CVCS)中,每个用户可视为连接到中心hub的一个节点,而在Git中,每个用户既是节点又是hub,用户既可以向远程仓库推送数据,也可维护同一个远程仓库,由此产生了多种的工作流程,当然各有优缺点,而用户可单独选用,也可以混合使用。
中心式工作流
在CVCS中,只有一种协作模式,即中心式工作流,只有中心hub(或称仓库)可放置代码,所有用户节点必须连接到中心hub,才能进行开发,同时需要保证中心hub的数据同步,
这意味着,如果两个协作者同时基于中心hub的数据,进行后续变更,首个协作者者完成推送后,第二个协作者必须合并首个协作者的推送,再推送自己的变更,同时不能覆盖首个协作者的推送,这一点已被Git继承。
如果公司或团队内部,已经习惯了中心式工作流,迁移到Git,无需任何改变,只需配置一个仓库,将推送权限授予所有用户,Git同样不允许用户推送的相互覆盖。
举例说明,John和Jessica处于并行开发中,John率先完成开发,并推送到服务器,当Jessica完成开发后,向服务器推送数据时,却被服务器驳回,原因是,Jessica必须获取合并John的提交,首先完成与服务器的同步后,才可推送自己的数据,这类工作流的优势在于,大多数人已经习惯了这种模式,当然这类工作流并不局限于小团队,基于Git的分支功能,可满足多条分支上,数百人的同步开发。
集中管理工作流
Git允许用户同时使用多个远程仓库,每个开发者都能写入自己的远程仓库,并读取他人的仓库,在这种情况下,通常会存在一个标准仓库,或称之为官方仓库,如果用户想参与该项目,则需要创建一个官方仓库的公开副本,再将自己的开发结果,推送公开副本,之后可告知官方仓库的维护者,获取开发数据,然后维护者可将用户仓库,设为远程仓库,获取并测试用户的代码,最后合并到本地仓库,再推送到官方仓库,整个流程如下,
- 项目维护者可向官方仓库推送数据
- 代码贡献者需克隆官方仓库,再进行开发
- 代码贡献者将开发结果,推送到自己的远程仓库
- 代码贡献者使用邮件,告知维护者,申请合并自己的开发成果
- 维护者添加代码贡献者的远程仓库,进行本地合并
- 维护者将本地仓库的变更,推送到远程仓库
GitHub和GitLab均采用这类常见的工作流,任何人都能创建项目副本,并查看他人的开发成果,另一个优势在于,项目成员之间不会相互打扰,维护者可在任意时间,获取代码贡献者的数据,代码贡献者也无需等待自己的数据,被合并到官方仓库。
分层集中(司令官与副官)工作流
这是集中管理工作流的一个变种,适用于拥有数百位协作者的巨型项目,比如Linux内核,多位集中管理员负责整个仓库的不同部分,他们被称为副官,所有副官之上,还有一位司令官,司令官可将所有协作者的工作成果,推送到远程仓库,整个工作流程如下:
- 开发者基于远程仓库的最新master分支,创建特性分支,并开始工作,之后将衍合提交,可简化提交历史,便于上层的合并
- 副官们可将下层开发者们的特性分支,合并到自己的本地分支master
- 司令官可将副官们的master分支,合并到自己的本地分支master
- 最后,司令官可将本地分支master推送到远程仓库的分支master
上述工作流并不常见,只适用于巨型项目,或者需要分级管理的环境,它只允许司令官分配任务,收集来自不同部分的大量变更。
5.2 贡献代码
由于Git足够灵活,有多种协作开发的方式,给出一个贡献代码的标准方法,这相当困难,因为每个项目都存在差异,比如贡献者的数量,选定的工作流,用户权限,外部用户的沟通方式,
首先是贡献者的数量,在小团队中,贡献代码相对简单,而在大型项目中,数千位贡献者每天会产生成百上千个提交,更多的贡献者意味着,
保持代码的整洁,以及易于合并,会变得更加困难,所以每个贡献者都必须保证自己的代码,在合并过程中,不会出现过期或严重错误。
其次是工作流的选择,在中心式工作流中,所有用户是否都需要写入权限,项目维护者是否需要检查所有的补丁,而所有补丁需要通过同级评审还是核准,在分层管理中,如何保证提交的快速处理。
之后是用户权限,在工作流的影响下,用户是否拥有写入权限,在贡献代码时,则有相当大的差异,如果没有写入权限,项目需要提供一个接受贡献的方式或策略,这将影响到用户单次贡献的工作量,以及贡献的间隔时间。
只有想清楚以上问题的答案,才能得到一个有效的工作流,以及实现高效的代码贡献。
提交指南
在Git自身的项目中,提供了一份文档,其中包含了一些技巧,可基于补丁的方式,创建一个提交,参见Git源码包的Documentation/SubmittingPatches文件。
首先用户更新中,不能包含空白符号的错误,Git提供了一个检查工具git diff --check,如下的红色块,因为空白符的问题,可能会骚扰到其他协作者。
其次,保证每次提交都是一个独立逻辑单元,以使得每次提交都易于理解,千万不要采用扔垃圾的方式,打包提交,应当提供每次提交的明确的指向性,如果同一文件中,进行多次修改,可使用git add --patch,实现局部暂存,其实无论用户提交一次,还是提交十次,项目分支上,呈现的最后结果都是一致的,但提交十次,可使协作者更容易查看变更的细节。同时这类方式,更有利于提交的发布和恢复,之前的内容中,已经介绍了一些技巧,用于修整提交历史,以及暂存区文件的处理,以期望获得一个更简洁和易于理解的提交历史。
最后值得注意的事,养成高质量提交描述的编写习惯,可使协作开发更简单,一般情况下,提交描述只有一行,并且不超过50个字符,同时简明扼要,提交描述之后是一个空白行,空白行之后,将给出更多的提交细节,其中包含了修改原因,修改前后的对比,在提交描述的编写中,推荐使用一般语态,以下是Tim Pope总结的提交描述的模板,
标题,不超过50个字符的总结
如果有必要,应给出更详细的解释,但不要超过72个字符,
在某些情况下,标题行将视为邮件标题,后续文本将视为邮件正文,
使用空白行,分割标题行和正文,如上,除非正文被忽略,
如果忽略空白行,类似于rebase的工具,将无法正确解析。
使用一般语态,编写提交描述,更符合git merge和git revert等命令,
生成的结果。
后续描述,应放置在空白行之后。
- 前缀也使用圆点符号
- 前缀通常会使用连字符或星号,后跟一个空格符,并使用空白行,分割文本行,这可自行约定
- 使用缩进
运行git log --no-merges,用户可查看Git自身项目中,包含的格式规整的提交描述,注意,本教程的大多数提交,为了简化描述,都没有提供一个格式规整的提交描述。
小型团队
如果是几人参与的私有闭源项目,所有项目成员都具有远程仓库的推送权限,在这种情况下,可选择简单的中心式工作流,即使在这类工作流中,Git相比于中心式CVS,同样具备优势。举例说明,两个开发者加一个远程仓库的故事,首位开发者John克隆了远程仓库,并完成了修改,再提交到本地仓库,
# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'remove invalid default value'
[master 738ee87] remove invalid default value
1 files changed, 1 insertions(+), 1 deletions(-)
第二个开发者Jessica也克隆了远程仓库,并将一个变更,提交到本地仓库,
# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
1 files changed, 1 insertions(+), 0 deletions(-)
之后Jessica将工作成果,推送到远程仓库,
# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
1edee6b..fbff5bc master -> master
在推送操作的输出结果中,最后一行给出了一个有价值的信息,基本格式为<原有提交的校验码>…<当前提交的校验码> [本地分支名] -> [远程分支名],继续故事,之后John也将本地仓库,推送到远程仓库,
# John's Machine
$ git push origin master
To john@githost:simplegit.git
! [rejected]
master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'
John推送出错的原因,John的本地仓库与远程仓库不同步,首先John需下载Jessica的推送数据,注意Jessica的最后提交的验证码为fbff5bc,即下图的fbff5,738ee为John本地仓库的最新提交,
$ git fetch origin
...
From john@githost:simplegit
+ 049d078...fbff5bc master -> origin/master
将Jessica的推送数据,与John的最新提交进行合并,
$ git merge origin/master
Merge made by the 'recursive' strategy.
TODO | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
这时John需测试Jessica的推送数据,对自己的代码是否有影响,完成测试后,John可将合并生成的最新提交,推送到远程仓库,
$ git push origin master
...
To john@githost:simplegit.git
fbff5bc..72bbc59 master -> master
这时Jessica又创建了另一个分支issue54,并向本地仓库中,提交了三次更新,同时她并未获取远程仓库中,John的最新推送,
John告知Jessica,自己已向远程仓库,推送了最新数据,为了避免推送失败,Jessica需要获取远程仓库的最新数据,
# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
fbff5bc..72bbc59 master -> origin/master
当新建分支的开发完成后,Jessica需要了解John的工作成果中,哪些可以合并,可运行git log进行查找,
$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date: Fri May 29 16:01:27 2009 -0700
remove invalid default value
issue54…origin/master格式是一个过滤器,用于显示,包含在origin/master分支,但并未包含在issue54分支的提交,基于上述输出