Git是目前最流行的源代码管理工具。大量的软件项目由 GitHub、Bitbucket 和 GitLab 这样的云服务平台或是私有的Git仓库来管理。Git分支管理并没有普遍适用的最佳做法,简单来说,在项目开发中使用多个分支会带来额外的管理和维护开销,但是多个分支对于项目的团队合作、新功能开发和发布管理都是有一定好处的。不同的团队可以根据团队人员组成和意愿、项目的发布周期等因素选择最适合的策略,找到最适合团队的管理方式。
经过一段时间的调研和讨论,我们选用Forking工作流作为我们开发的工作流。Forking工作流是分布式工作流,充分利用了Git在分支和克隆上的优势。可以安全可靠地管理大团队的开发者(developer),并能接受不信任贡献者(contributor)的提交。这种工作流不是使用单个服务端仓库作为中央代码基线,而让各个开发者都有一个服务端仓库。这意味着各个代码贡献者有2个Git仓库而不是1个:一个本地私有的,另一个服务端公开的。
Forking工作流的一个主要优势是,贡献的代码可以被集成,而不需要所有人都能push代码到仅有的中央仓库中。开发者push到自己的服务端仓库,而只有项目维护者才能push到正式仓库。这样项目维护者可以接受任何开发者的提交,但无需给他正式代码库的写权限。效果就是一个分布式的工作流,能为大型、自发性的团队(包括了不受信的第三方)提供灵活的方式来安全的协作。也让这个工作流成为开源项目的理想工作流。
Forking工作流要先有一个公开的正式仓库存储在服务器上。一个新的开发者想要在项目上工作时,不是直接从正式仓库克隆,而是fork正式项目在服务器上创建一个拷贝。这个仓库拷贝作为他个人公开仓库,其它开发者不允许push到这个仓库,但可以pull到修改。在创建了自己服务端拷贝之后,开发者执行git clone命令克隆仓库到本地机器上,作为私有的开发环境。要提交本地修改时,push提交到自己公开仓库中 —— 而不是正式仓库中。然后,给正式仓库发起一个pull request,让项目维护者知道有更新已经准备好可以集成了。
对于贡献的代码,pull request也可以很方便地作为一个讨论的地方。我们可以在此做code review,贡献者可以在该分支上继续修改代码,直到满足项目维护者的要求。
维护者pull贡献者的变更到自己的本地仓库中,检查变更以确保不会让项目出错,合并变更到自己本地的master分支,然后push master分支到服务器的正式仓库中。到此,贡献的提交成为了项目的一部分,其它的开发者应该执行pull操作与正式仓库同步自己本地仓库。
另外一种做法是贡献者先从正式仓库将最新代码更新到自己的本地,如果有冲突,先解决冲突。然后提交给自己的远程公开代码库,然后通过远程公开代码库给中心代码库发pull request请求,维护者审核代码后,如果满足要求,就合并到中心代码库中。
在Forking工作流中,公开正式仓库的叫法只是一个约定,理解这点很重要。从技术上来看,各个开发者仓库和正式仓库在Git看来没有任何区别。事实上,让正式仓库之所以正式的唯一原因是它是项目维护者的公开仓库。
所有的个人公开仓库实际上只是为了方便和其它的开发者共享分支。各个开发者应该用分支隔离各个功能,在Forking工作流中这些分支会被pull到另一个开发者的本地仓库中。在多个人合作开发功能时,这个非常实用。因为在功能没有完全开发完成前,不想将代码合并到中心代码库中。
git init --bare /path/to/repo.git
和任何使用Git项目一样,第一步是创建在服务器上一个正式仓库,让所有团队成员都可以访问到。 通常这个仓库也会作为项目维护者的公开仓库。公开仓库应该是裸仓库,不管是不是正式代码库。
其它所有的开发需要fork正式仓库。 可以用git clone命令用SSH协议连通到服务器,拷贝仓库到服务器另一个位置。Fork操作基本上就只是一个服务端的克隆。Github,Gitlab,Bitbucket等上可以点一下按钮就让开发者完成仓库的fork操作。这一步完成后,每个开发都在服务端有一个自己的仓库。和正式仓库一样,这些仓库应该是裸仓库。
git clone https://[email protected]/your/repo.git
相比前面介绍的工作流只用了一个origin远程别名指向中央仓库,Forking工作流需要2个远程别名 —— 一个指向正式仓库,另一个指向开发者自己的服务端仓库。别名的名字可以任意命名,常见的约定是使用origin作为远程克隆的仓库的别名 (这个别名会在运行git clone自动创建),upstream(上游)作为正式仓库的别名。
git remote add upstream https://your.com/maintainer/repo
需要自己用上面的命令创建upstream别名。这样可以简单地保持本地仓库和正式仓库的同步更新。 注意,如果上游仓库需要认证(比如不是开源的),你需要提供用户:
git remote add upstream https://[email protected]/maintainer/repo.git
在刚克隆的本地仓库中,开发者可以像其它工作流一样的编辑代码、提交修改和新建分支:
git checkout -b some-feature
git commit -a -m "Add first draft of some feature"
所有的修改都是私有的直到push到自己公开仓库中。如果正式项目已经往前走了,可以用git pull命令获得新的提交:
git pull upstream master
由于开发者应该都在专门的功能分支上工作,pull操作结果会都是快进合并。
git push origin feature-branch
origin远程别名指向开发者自己的服务端仓库,而不是正式仓库。
当项目维护者收到pull request,他要做的是决定是否集成它到正式代码库中。
直接在pull request中查看代码,讨论修改,如果没有问题,直接合并。有问题的话,pull request 的发起者可以继续在其分支上修改,然后提交到自己远程公开代码库,这时,不需要再次提交pull request。更新的代码会自动被正式代码库的管理者看到。
由于正式代码库往前走了,开发的主分支需要和正式仓库做同步:
git pull upstream master
将本地的master分支再推送到远程个人远程公开代码库的master
git push origin master
此时一个开发流程结束。本地主分支(master),个人远程公开代码仓库(master),正是仓库(master)上master分支的数据一致。再进行新功能开发时,需要在本地的master分支上重新checkout一个新的feature分支,继续下一轮的操作。之前的分支可以删除,以减少影响。
git branch -d some-featuregit checkout -b some-new-feature
比如,如果团队的几个人协作实现一个功能,可以在开发之间用相同的方法分享变更,完全不涉及正式仓库。这使得Forking工作流对于松散组织的团队来说是个非常强大的工具。任一开发者可以方便地和另一开发者分享变更,任何分支都能有效地合并到正式代码库中。当要发起一个Pull Request,你所要做的就是请求(Request)另一个开发者(比如项目的维护者)来pull你仓库中一个分支到他的仓库中。这意味着你要提供4个信息以发起Pull Request:源仓库、源分支、目的仓库、目的分支。