GIT团队合作探讨之四--不同工作流优缺辨析
2016-03-28 15:15
465 查看
由于git非常强大,它可以支持非常多的协作模式,而可能正因为选择太多反而有时候对于我们如何开始开展团队协作无从下手。本文试图阐述企业团队中应用最为广泛的git 工作流,为大家理清思路,最大限度发挥git的威力起到借鉴作用。
在你阅读本文中,需要记住的是这些工作流本身只是一个参考,并不是实际的规则。我只是告诉你哪些是可能的,这样你可以从不同工作流中取其长处切合到你们自己团队的日常工作中去。
然而,即便这样,你仍然可以使用GIT来支持你的开发工作流,相比SVN你可以得到以下几个好处: 首先,你的每个团队成员都有一份项目的整个copy,这个隔绝的环境可以让开发人员独立完成他们自己的工作,他们甚至可以完全不顾别人的改动,直到他们认为需要分享后者需要拉入别的团队成员的工作时,才需要和别人打交道。
其次:git可以让你和团队用到git branch/merge带来的好处。
和trunk不同的是,默认开发分支在git中叫做master,而所有改动都commit到这个分支上。这种工作流除了master分支不需要任何其他的分支。
开发人员首先clone 中央库,在他们本地项目库中和svn一样编辑,commit代码。然而,这些commit仅仅保存在本地,而本地库和中央库是完全隔离的。这个特性使得开发人员推迟和upstream的同步直到他们认为已经到了一个合适的时机。
为了将本地变更发布到official project,开发人员通过Push本地master branch 到中央库的master branch.这和svn commit是一样的,除了把本地的所有历史都加到中央库中。
在这种情况下,开发人员在publish他们的feature到中央库之前,他们需要从中央库中fetch到最新的中央库commits并且做好rebase,以便你的本地变更实际上运行于中央库最新版本之上。这个工作好像在说:”我希望确保我的改动是要建立在别人的工作基础之上“。 这个fetch,rebase的结果是一个线性的历史记录,就像在传统的SVN工作流一样。
如果本地变更和upstream commit相冲突了,git会停止rebase流程并且给你机会手工解决这些冲突。比较棒的一点是:git 使用git status/git add来完成generating commits和resolving merge conflicts.这种特点使得开发人员可以非常方便地管理他们的merge。并且,如果他们在合并过程中出现了问题,git可以非常方便地让开发人员放弃整个rebase过程并且随时可以重新开始。
首先,他们两个人中的一人比方说John在服务器上创建an一个中央的repo库。如果是一个全新的项目,你可以创建一个全空的repo,否则你可能需要import一个已经存在的git或者svn repo。
中央库应该是一个裸库(裸库不能有工作目录),可以这样来创建:
注意一定要使用一个在host上合法的user信息来创建裸库。也请注意一般地我们会给裸库增加.git后缀,这也是一种裸库的指示。
下一步,每个开发人员通过git clone来创建他们自己的项目拷贝。
当你clone一个repo时,git会自动地增加一个被称为origin的remote,这个remote指向clone时的那个库,这往往意味着后面你可以很方便地push/pull origin来实现和中央库的交互。
记住:John的所有git add /git commit命令都会创建本地commits,John可以任意递交他的工作而不用担心在中央库上到底发生了什么。这对于一个大的可以分解成更小的,更加独立和原子性的小块的feature开发来说非常适合。
git push命令来完成。记住origin就是当John clone 中央库时git自动创建的指向这个中央库的remote connection! 上面命令中master参数告诉git需要将本地master branch和origin/master保持一致(也就是和中央库的master保持一致,因为origin/master实际上就是中央库master分支的代表)。既然自从John clone中央库后,中央库从未发生改变,这也就意味着John在push时是不会有任何冲突的,只会产生一个fast-forward merge.
她也使用相同的命令。 git push origin master
但是由于她的本地历史和中央库的历史已经分叉(原因是John已经做了push到中央库master),那么她会得到git拒绝的如下消息:
这将阻止Mary覆盖正式的commits.她需要拉取同步John的变更到她自己的repo,做好集成合并工作,然后再重试。
这条命令将upsteam的commit history整个拉取下来并且试图和她的本地commit集成:
上面的--rebase参数告诉git将Mary的所有commit放在John的变更之后,就像下面的图示一样:
如果Mary和John工作在完全不相关的feature上,那么在rebase过程中很有可能并不会产生任何冲突。而如果确实发生了冲突,则GIT就会暂停在当前commit上的rebase工作,并且输出下面的错误信息:
在我们的例子中,Mary可以运行git status命令查看问题在哪里。所有冲突的文件都在Unmerged path部分中展示出来:
随后,Mary可以根据实际情况来确定她如何处理这些conflicts。一旦她认为完美解决了这个冲突,她可以将已经合并冲突的文件stage起来,随后继续rebase工作,注意每次解决完冲突之后,原来的那个commit hash就将废弃变换为新的commit hash:
这就是冲突解决的过程,git随后会继续下一个commit的rebase过程,如果发现了冲突,则会继续上面描述的过程来解决冲突。
注意在解决冲突git add命令执行之后,不要再执行git commit命令,否则会进入detached head状态,当然你可以随时使用git rebase --abort来取消push
如果你发现你暂时已经不清楚你现在到底在rebase的什么状态下了,也不用恐慌,仅仅通过执行以下的命令你就可以完全回退到你执行git pull --rebase命令之前的状态,以便你可以再次执行这条命令。
正如上面这一段描述的,我们仅仅通过几条简单的git命令就能完全实现传统的svn开发环境。这对于传统的SVN team转型到GIT是很便利的,但是这种工作模式并未应用到git真正的分布式开发强大功能。
如果你的团队已经非常顽固依赖于中央库的工作模式(SVN技术控),但是又希望简化这种模型的合作effort,那么非常适合研究"Feature Branch Workflow"这种工作模式。 通过为每一个feature都设计一条独立的分支,那么就可以在完全合入到官方项目分支前做充分的讨论,测试和验证。
一旦你发现了Centralized Workflow中的合作冲突问题,那么在开发流程中引入feature branch是一个鼓励合作和便利化沟通的简单的方法。
Feature branch workflow的核心想法或原则是:所有的feature开发都必须在特定的branch下而不是直接在master分之下开发。这种模式将使得几个开发人员同时开发同一个feature的开发活动相对于主分支完全隔离开来。这同时也意味着master分支永远不会存在未经测试的代码,这对于持续集成的开发模式带来巨大的好处。
封装隔离一个feature的开发过程也使得充分利用pull request变得方便,而pull request本身就是一个对分支上的代码改动启动讨论的最佳实践。这也给其他开发人员在feature代码落地到主分支之前review确认代码的机会。或者,当你还在feature开发的中间阶段,你也可以发起pull request来获取其他同事的建议。
Git在master分支和feature分支两者之间技术上并未做任何区分,所以开发人员可以像centralized workflow中一样向feature分支做edit/stage/commit开发工作。
而且,feature branch可以也应该被push到中央库中。只有这样,一个feature才方便让多个开发人员共同开发。
code review是pull request的一个最大好处,但是pr实际上是一种讨论代码的同样基站。你可以将Pull request看作对一个特定分支讨论的地方。这也意味着他们可以在开发的早期就加以应用。例如,如果一个开发人员就一个特定feature需要帮助,他们要做的就是启动一个pull requet.相干人会被通知到,他们会看到相关commit对应的问题。
一旦一个PR被批准,实际的发布feature的方法是和centralized workflow是完全一样的。首先你需要确认local master是和upsteam master完全同步的。然后,你merge feature branch到master上,并且将已经更新的master分支push回中央库。
Pull request可以通过product repository management solutions比如bitbucket cloud/bitbucket server来实现。
在她开始开发feature之前,mary需要一个隔离的分支来工作,她通过git checkout -b命令来创建分支:
随后mary在她的分支上edit/stage/commit.
上面这条命令将marys-feature push到中央库(origin)上,注意-u flag则增加这个marys-feature分支作为remote tracking branch.在创建了tracking branch后,mary就可以直接使用git push命令而不用每次都输入她的marys-feature分支作为参数了。
然后,她发起一个pull request,希望能够merge她的marys-feature到master分支上去。而team成员则会被自动通知到。
这个阶段中所有Mary或者Bill(bill也可以pull这个分支到本地,自己做修改随后push)的commits都将作为这个pr的follow up,清晰记录这个过程。一旦Bill决定接受这个pull request,要么mary,要么bill需要完成feature落地的工作。
首先,执行merge动作的人Mary(或者Bill)在做feature分支向develop分支落地的过程中,需要确保base分支develop或者master要是up-to-date的。需要checkout master分支并且确保是最新的。然后git pull origin marys-feature则merge中央库的marys-feature分支。最后更新后的master分支需要再次push回到origin上去。
这个流程会产生一个merge commit.有些开发人员喜欢这样因为这个commit就是主分支合入某一个feature的标志!但是如果你希望获得线性的项目历史,也可以在执行merge之前通过rebase mary-feature分支到master分支之上来实现一个fast-forward merge.
feature branch workflow是一种非常灵活地开发项目的方式。问题是,有时这种方式显得太灵活。对于大型团队来说,通常如果给一些特定分支更加明确的角色要好很多。那么Gitflow workflow就是一个通用的用于管理feature development, release preparation以及维护的工作模式
gitflow workflow是由http://nvie.com/ 唱到并且得到业内大量实践的版本分支策略模型。
Gitflow workflow围绕项目release展开定义了一个严格的分支模型,虽然看起来这个模型比feature branch workflow要复杂一些,但是却为管理大型项目提供了一个健壮的框架。
这个工作流并未比feature branch workflow提出了任何更多的概念或者命令,它要做的是:
对每一个不同的分支定义了特定的角色,并且对这些分支之间在什么时间需要interact交互以及如何做这个交互。除了feature branch外,它使用特定的分支用于准备,维护以及记录不同的release(preparing,maintaining and recording releases).当然,你也能够用到feature branch workflow的所有好处: pull request,隔离实现性开发和更高效的合作模型。
The rest of this workflow revolves around the distinction between these two branches.
注意的是feature分支以及develop分支实际上就是上面介绍的feature branch workflow,但是Gitflow工作流并不仅仅是这些。
一旦develop分支为一个计划的release(或者说快到了一个预定义好的release date时)落地了足够多的feature时,你需要基于develop分支创建生成一个release分支。创建这个分支的同时也意味着下一个release周期的开始 所以从这一刻开始任何新的feature都不允许加到这个release分支中,唯一能做的是在这个release分支上进行bug fix,文档生成,以及其他面向release的任务可以在这个分支上继续。一旦这个release足够成熟可以用于发布和上线部署,release分支就会被merge到master分支并且在master分支打上相应的标签tag.而且,release分支需要同时merge back到develop分支,以便包含在release准备到发布的这段时间内的bugfix(注意merge到develop时你可能发现develop分支可能已经向前走过了(相对于release分支拉创的时刻))。
使用一个专用的branch来专门用于准备release使得下面的工作场景成为可能:一个team(维护团队)来打磨产品当前的release版本,而另一个team(新项目团队)则继续为下一个release计划开发新的feature(在develop分支上)。这也创建了定义良好项目开发不同phase,使得项目更具有可观性,(比如,非常容易地就说,“这周我们准备version 4.0",并且在repo的结构中能清晰反映出来)。
向master/develop上merge into
命名规范: release-*
Maintenance或者"hotfix"分支用于快速解决production release运行中发现的bug。这也是唯一一个需要从master分支来branch off的分支。只要fix完成了,这个hotfix分支就应该同时merged到master和develop(或者当前release branch)分支,而master应该被tagged一个新的version number。
有一个专门用于bugfix的分支使得你的团队在解决问题时不会打断或者打工作流的其他部分或者等待到下一个release周期。你可以把maintenace/hotfix分支看作和master分支直接联系的特设分支。
这个develop分支将会包含项目的整个开发历史,然而master则包含一个删减版的产品开发历史。其他的开发人员现在可以clone这个中央库并且创建一个remote tracking develop branch来开始工作:
现在每个人都有了一个本地的开发环境。
我们的实际例子从John和Mary各自开始在他们自己的feature branch上独立开发各自的feature开始。他们各自创建一个独立的feature分支。需要注意的是feature branch并不是从master分支上拉出来,相反,他们应该从develop分支上拉出来。
他们各自在自己的feature branch上做edit/stage/commit开发活动
第一个命令确保本地的develop分支是up to date的,要注意的是feature永远不要直接merged到master.冲突解决的过程和centralized workflow是相同的。
这个分支是用于清理release,测试,更新文档以及任何为了本版本发布的工作开展的地方,release分支就像专门用于打磨这个release的feature branch一样。
一旦Mary创建并且push了这个branch到中央库,那么该release就应该处于feature-frozen状态。任何还未落地在develop分支上的feature都会被推迟到相爱一个release cycle中。
release branch就像是feature development(develop)和public release(master)之间的一个buffer,任何时候你merge了一些东西到master,你应该tag这个commit:
git内置了一些hooks,这些hooks就是一些在repo中当特定事件发生时可以执行的脚本。我们可以配置一个hook,当任何人向master分支做了push操作或者push了一个tag之后自动执行一个git archive构建一个public deployment release.
就像release branch一样,maintenace/hotfix branch包含了重要的bugfix改动,这些改动必须被包含到develop分支上去(如果有running release分支,则也应该merge到这里)。所以Mary需要执行这个merge工作,完成后就可以删除这个hotfix/maintenance branch了。
forking workflow的优点是变更集成时没有必要让每个人都push到唯一一个中央库。开发人员push到他们自己的server-side repo中。而只有项目经理可以push到official repo中。这允许项目经理在不给每个开发人员都开放写权限的前提下来获取开发人员的commits.
这个工作流的结果是一个分布式的workflow,这个workflow提供了一个对于大型的组织,包括非信任的第三方开发团队来安全有效地展开合作。这种模式对于open source project也非常适合。
当他们本地开发告一段落后,他们将commits push到他们自己的public repo中(注意:并不是official的那个repo)。然后,他们发起一个pull request申请项目经理将他们的代码落地。pull request也作为他们讨论他的代码的最佳场所。
为了将pr中所涉及的代码合入official repo中,项目经理pull the contributor's changes到他们本地repo,检查这段代码是否会有问题,如果没有问题,他们就直接merge到项目经理的local master branch上去,然后项目经理执行push动作将master分支放到官方的repo库中。到现在,那个PR中的代码改动就成了项目代码的一部分,其他的开发人员应该从这个官方库中pull去同步到他们的本地repo中。
需要注意的是,public repo永远应该是裸库,无论他们是否官方库。所以,项目经理应该这样创建official库:
bitbucket也提供一些方便的gui来创建上述命令。项目经理也应该将code base导入到这个库中。
你可以这样操作:1.ssh到服务器上,2.执行git clone 来copy到你自己的目录中;
注意:从上面你可以看到:fork就是server-side的clone操作而已。当然对于bitbucket,允许你点击一两次鼠标来完成这个fork过程。
在这个步骤后,每一个开发人员应该有了他们自己的server-side repo,就像official repo一样,所有的这些repo必须是裸库。
我们的例子假设使用bitbucket来host这些public repo。记住,在这个条件下,每个开发人员应该有他们自己的bitbucket帐号并且他们应该用下面的命令clone他们自己的server-side repo:
在你阅读本文中,需要记住的是这些工作流本身只是一个参考,并不是实际的规则。我只是告诉你哪些是可能的,这样你可以从不同工作流中取其长处切合到你们自己团队的日常工作中去。
Centralized Workflow
对于习惯于类似SVN这样的中央库版本系统的团队来说,切换到一个分布式版本控制系统可能有些令人生畏。但是实际上,你们完全可以不用更改你们现在的工作流,也可以利用到GIT提供的强大额外好处。你的团队甚至可以和SVN一样的开发模式来工作。然而,即便这样,你仍然可以使用GIT来支持你的开发工作流,相比SVN你可以得到以下几个好处: 首先,你的每个团队成员都有一份项目的整个copy,这个隔绝的环境可以让开发人员独立完成他们自己的工作,他们甚至可以完全不顾别人的改动,直到他们认为需要分享后者需要拉入别的团队成员的工作时,才需要和别人打交道。
其次:git可以让你和团队用到git branch/merge带来的好处。
如何工作的:
就像SVN一样,这种集中式的工作流模式使用同一个中央repo库作为所有对项目变更的唯一入口。和trunk不同的是,默认开发分支在git中叫做master,而所有改动都commit到这个分支上。这种工作流除了master分支不需要任何其他的分支。
开发人员首先clone 中央库,在他们本地项目库中和svn一样编辑,commit代码。然而,这些commit仅仅保存在本地,而本地库和中央库是完全隔离的。这个特性使得开发人员推迟和upstream的同步直到他们认为已经到了一个合适的时机。
为了将本地变更发布到official project,开发人员通过Push本地master branch 到中央库的master branch.这和svn commit是一样的,除了把本地的所有历史都加到中央库中。
管理冲突 Managing Conflicts
中央库代表了官方的项目库,所以官方库中的代码递交历史应该被视为神圣地,不可莫名其妙地丢失。如果一个开发人员的本地commit和中央库分叉了的话,git将拒绝将他们的变更上传因为这会覆盖官方的commits.在这种情况下,开发人员在publish他们的feature到中央库之前,他们需要从中央库中fetch到最新的中央库commits并且做好rebase,以便你的本地变更实际上运行于中央库最新版本之上。这个工作好像在说:”我希望确保我的改动是要建立在别人的工作基础之上“。 这个fetch,rebase的结果是一个线性的历史记录,就像在传统的SVN工作流一样。
如果本地变更和upstream commit相冲突了,git会停止rebase流程并且给你机会手工解决这些冲突。比较棒的一点是:git 使用git status/git add来完成generating commits和resolving merge conflicts.这种特点使得开发人员可以非常方便地管理他们的merge。并且,如果他们在合并过程中出现了问题,git可以非常方便地让开发人员放弃整个rebase过程并且随时可以重新开始。
例子:
让我们以一个典型案例来看看一个典型的小团队是如何使用这种工作流来协同工作的。我们假设有两个开发人员,John和Mary,他们工作在不同的feature上并且通过中央库来分享他们自己的改动。首先,他们两个人中的一人比方说John在服务器上创建an一个中央的repo库。如果是一个全新的项目,你可以创建一个全空的repo,否则你可能需要import一个已经存在的git或者svn repo。
中央库应该是一个裸库(裸库不能有工作目录),可以这样来创建:
ssh user@host git init --bare /path/to/repo.git
注意一定要使用一个在host上合法的user信息来创建裸库。也请注意一般地我们会给裸库增加.git后缀,这也是一种裸库的指示。
下一步,每个开发人员通过git clone来创建他们自己的项目拷贝。
git clone ssh://user@host/path/to/repo.git
当你clone一个repo时,git会自动地增加一个被称为origin的remote,这个remote指向clone时的那个库,这往往意味着后面你可以很方便地push/pull origin来实现和中央库的交互。
再下一步,John开发他自己的功能:
在john自己本地repo中,John可以使用标准的git commit流程来开发功能:edit/stage,commit。记住:John的所有git add /git commit命令都会创建本地commits,John可以任意递交他的工作而不用担心在中央库上到底发生了什么。这对于一个大的可以分解成更小的,更加独立和原子性的小块的feature开发来说非常适合。
同时,Mary开发他自己的功能:
在上面John本地开发他自己的feature的同时,Mary也在她自己的本地环境中向John一样开发她自己的feature(edit/stage/commit)。和John一样,Mary也不用关心这时在中央库上发生了什么,她甚至根本不关心John到底在做什么,因为他们都是在本地repo中开发,是完全隔绝的。John发布他的feature
一旦John完成了他的feature开发,他希望将自己的本地commit发布到中央库,这样其他成员就可以访问他的贡献了。John通过:git push origin master
git push命令来完成。记住origin就是当John clone 中央库时git自动创建的指向这个中央库的remote connection! 上面命令中master参数告诉git需要将本地master branch和origin/master保持一致(也就是和中央库的master保持一致,因为origin/master实际上就是中央库master分支的代表)。既然自从John clone中央库后,中央库从未发生改变,这也就意味着John在push时是不会有任何冲突的,只会产生一个fast-forward merge.
Mary随后试图发布她的feature
我们看看在John成功发布feature后Mary再试图发布她的local change到中央库时会发生什么。她也使用相同的命令。 git push origin master
但是由于她的本地历史和中央库的历史已经分叉(原因是John已经做了push到中央库master),那么她会得到git拒绝的如下消息:
error: failed to push some refs to '/path/to/repo.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Merge the remote changes (e.g. 'git pull') hint: before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
这将阻止Mary覆盖正式的commits.她需要拉取同步John的变更到她自己的repo,做好集成合并工作,然后再重试。
Mary rebase on top of John's commit(s)
Mary可以使用git pull命令来拉取upstream变更到她自己的repo中。这条命令很像svn update.这条命令将upsteam的commit history整个拉取下来并且试图和她的本地commit集成:
git pull --rebase origin master
上面的--rebase参数告诉git将Mary的所有commit放在John的变更之后,就像下面的图示一样:
Mary解决一个merge冲突
Rebase是通过将local commit一个一个地在更新后的master分支上(也就是将origin/master的tip作为自己本地改动的的master起点)运行来完成的。这就意味着你将会一个commit一个commit地捕获到这个合并过程中的冲突,而不是一团糟糅在一起。这将使得你的commit尽可能地聚焦,从而获得一个干净清洁的项目历史。同样地,通过这种方式来运行,我们可以非常方便地指出bug是从哪里引入的,并且如果需要的话,可以非常方便地回退回去。如果Mary和John工作在完全不相关的feature上,那么在rebase过程中很有可能并不会产生任何冲突。而如果确实发生了冲突,则GIT就会暂停在当前commit上的rebase工作,并且输出下面的错误信息:
CONFLICT (content): Merge conflict in <some-file>
在我们的例子中,Mary可以运行git status命令查看问题在哪里。所有冲突的文件都在Unmerged path部分中展示出来:
# Unmerged paths: # (use "git reset HEAD <some-file>..." to unstage) # (use "git add/rm <some-file>..." as appropriate to mark resolution) # # both modified: <some-file>
随后,Mary可以根据实际情况来确定她如何处理这些conflicts。一旦她认为完美解决了这个冲突,她可以将已经合并冲突的文件stage起来,随后继续rebase工作,注意每次解决完冲突之后,原来的那个commit hash就将废弃变换为新的commit hash:
git add <conflicted-files> git rebase --continue
这就是冲突解决的过程,git随后会继续下一个commit的rebase过程,如果发现了冲突,则会继续上面描述的过程来解决冲突。
注意在解决冲突git add命令执行之后,不要再执行git commit命令,否则会进入detached head状态,当然你可以随时使用git rebase --abort来取消push
如果你发现你暂时已经不清楚你现在到底在rebase的什么状态下了,也不用恐慌,仅仅通过执行以下的命令你就可以完全回退到你执行git pull --rebase命令之前的状态,以便你可以再次执行这条命令。
git rebase --abort
Mary成功地发布了她的feature
在Mary成功和中央库同步之后(rebase并且解决John的变更冲突),Mary将通过下面的命令完成发布git push origin master
正如上面这一段描述的,我们仅仅通过几条简单的git命令就能完全实现传统的svn开发环境。这对于传统的SVN team转型到GIT是很便利的,但是这种工作模式并未应用到git真正的分布式开发强大功能。
如果你的团队已经非常顽固依赖于中央库的工作模式(SVN技术控),但是又希望简化这种模型的合作effort,那么非常适合研究"Feature Branch Workflow"这种工作模式。 通过为每一个feature都设计一条独立的分支,那么就可以在完全合入到官方项目分支前做充分的讨论,测试和验证。
Feature Branch workflow
一旦你发现了Centralized Workflow中的合作冲突问题,那么在开发流程中引入feature branch是一个鼓励合作和便利化沟通的简单的方法。
Feature branch workflow的核心想法或原则是:所有的feature开发都必须在特定的branch下而不是直接在master分之下开发。这种模式将使得几个开发人员同时开发同一个feature的开发活动相对于主分支完全隔离开来。这同时也意味着master分支永远不会存在未经测试的代码,这对于持续集成的开发模式带来巨大的好处。
封装隔离一个feature的开发过程也使得充分利用pull request变得方便,而pull request本身就是一个对分支上的代码改动启动讨论的最佳实践。这也给其他开发人员在feature代码落地到主分支之前review确认代码的机会。或者,当你还在feature开发的中间阶段,你也可以发起pull request来获取其他同事的建议。
如何工作的?
Feature branch workflow仍然使用一个中央库,而master分支依然代表这官方的Project history.但是和centralized workflow不同的是,我们不在直接在master分支上做edit/stage/commit,开发人员每次在开发一个新的feature前都创建一个新的feature分支并且开始在这个分支上工作。feature branch应该有语意,比如:animated-menu-items或者issue-1123.这里主要的想法就是要给每一个分支明确的,高聚焦的目的!Git在master分支和feature分支两者之间技术上并未做任何区分,所以开发人员可以像centralized workflow中一样向feature分支做edit/stage/commit开发工作。
而且,feature branch可以也应该被push到中央库中。只有这样,一个feature才方便让多个开发人员共同开发。
Pull requests
除了隔离feature开发,分支也使得通过pull request来讨论变更成为可能。一旦某些人完成了一个feature,他们并不会立即merge到master上去。相反,他们将feature branch push到中央库中并且发起一个pull request,而请求项目经理来评审并且merge代码到master。这种机制给团队成员在代码落地前实现评审。code review是pull request的一个最大好处,但是pr实际上是一种讨论代码的同样基站。你可以将Pull request看作对一个特定分支讨论的地方。这也意味着他们可以在开发的早期就加以应用。例如,如果一个开发人员就一个特定feature需要帮助,他们要做的就是启动一个pull requet.相干人会被通知到,他们会看到相关commit对应的问题。
一旦一个PR被批准,实际的发布feature的方法是和centralized workflow是完全一样的。首先你需要确认local master是和upsteam master完全同步的。然后,你merge feature branch到master上,并且将已经更新的master分支push回中央库。
Pull request可以通过product repository management solutions比如bitbucket cloud/bitbucket server来实现。
例子
下面的例子演示了作为code review机制的pull request是如何工作的:Mary开始一个新的功能开发
在她开始开发feature之前,mary需要一个隔离的分支来工作,她通过git checkout -b命令来创建分支:
git checkout -b marys-feature master//在master分支最新commit基础上创建分支。
随后mary在她的分支上edit/stage/commit.
git status git add <some-file> git commit
Mary去吃午饭
Mary在早上提交了几个commits到她的feature分支上去了,在她离开吃无法之前,将她的feature branch push到中央库上是一个好的注意。因为这将作为一个方便的备份机制,并且如果Mary在和其他开发人员协同的话,这也使他们能访问到她今早的commit。git push -u origin marys-feature//将今天早上的工作放到中央库的feature分支分享给其他成员
上面这条命令将marys-feature push到中央库(origin)上,注意-u flag则增加这个marys-feature分支作为remote tracking branch.在创建了tracking branch后,mary就可以直接使用git push命令而不用每次都输入她的marys-feature分支作为参数了。
Mary完成了她的feature开发
当Mary吃完午饭,她完成了全部feature开发。在merge到master之前,她需要启动一个pull request让团队成员知道她已经完成了工作。但是首先,她得确认中央库已经包含了她的所有最新commitsgit push //注意由于上面用了-u参数,这里就不用再次输入branch参数了
然后,她发起一个pull request,希望能够merge她的marys-feature到master分支上去。而team成员则会被自动通知到。
Bill接收到Pull request请求
Bill接收到这个PR请求,并且看了看marys-feature分支的改动。他决定在集成代码到official master之前需要做一些变更。Bill和Mary就可以通过这个系统完成几轮的交互了。这个阶段中所有Mary或者Bill(bill也可以pull这个分支到本地,自己做修改随后push)的commits都将作为这个pr的follow up,清晰记录这个过程。一旦Bill决定接受这个pull request,要么mary,要么bill需要完成feature落地的工作。
git checkout master git pull git pull origin marys-feature git merge marys-feature git push
首先,执行merge动作的人Mary(或者Bill)在做feature分支向develop分支落地的过程中,需要确保base分支develop或者master要是up-to-date的。需要checkout master分支并且确保是最新的。然后git pull origin marys-feature则merge中央库的marys-feature分支。最后更新后的master分支需要再次push回到origin上去。
这个流程会产生一个merge commit.有些开发人员喜欢这样因为这个commit就是主分支合入某一个feature的标志!但是如果你希望获得线性的项目历史,也可以在执行merge之前通过rebase mary-feature分支到master分支之上来实现一个fast-forward merge.
在Mary和Bill开发feature的同时,John也在做同样的事情
在Mary和Bill在marys-feature分支上开发的同时,John也在他自己的feature分支上做着同样的事情。通过不同的分支将不同的feature互相隔离,使得每个人都能独立地工作,然而这个工作流在必要时和别人分享工作仍然显得有点复杂和繁琐。下一步怎样?
到现在,希望你能看到feature branch实际上倍增了在centralized workflow工作流中的master分支,而且feature branch可以应用pull request作为记录团队协作的媒介。feature branch workflow是一种非常灵活地开发项目的方式。问题是,有时这种方式显得太灵活。对于大型团队来说,通常如果给一些特定分支更加明确的角色要好很多。那么Gitflow workflow就是一个通用的用于管理feature development, release preparation以及维护的工作模式
Gitflow workflow
gitflow workflow是由http://nvie.com/ 唱到并且得到业内大量实践的版本分支策略模型。
Gitflow workflow围绕项目release展开定义了一个严格的分支模型,虽然看起来这个模型比feature branch workflow要复杂一些,但是却为管理大型项目提供了一个健壮的框架。
这个工作流并未比feature branch workflow提出了任何更多的概念或者命令,它要做的是:
对每一个不同的分支定义了特定的角色,并且对这些分支之间在什么时间需要interact交互以及如何做这个交互。除了feature branch外,它使用特定的分支用于准备,维护以及记录不同的release(preparing,maintaining and recording releases).当然,你也能够用到feature branch workflow的所有好处: pull request,隔离实现性开发和更高效的合作模型。
如何工作?
Gitflow workflow依然使用一个中央库作为所有开发人员沟通的桥梁。同样地和其他workflow一样,开发人员本地开发并且push branches到中央库。唯一的区别是项目的分支结构有所不同。Historical Branches
gitflow工作流除了单一的master branch外,这个工作流使用两个分支来记录项目的历史(history of project). master分支保存着official release history,而develop分支则作为各feature分支的集成分支(integration branch)。在master branch上面经常打一个tag也是很必要的。The rest of this workflow revolves around the distinction between these two branches.
Feature branches
每一个新的feature应该呆在自己的feature branch中,并且该feature branch可以被push到中央库来做备份或者用于协作的桥梁。注意一点的是:该模型中,feature branch并不是从master分支上拉出,而是从develop分支上拉出来的!也就是说develop分支是feature branch分支的parent branch.当一个feature开发完毕,这个feature分支会被merged back到develop分支。feature永远不要直接和master分支来打交道。注意的是feature分支以及develop分支实际上就是上面介绍的feature branch workflow,但是Gitflow工作流并不仅仅是这些。
Release branches
一旦develop分支为一个计划的release(或者说快到了一个预定义好的release date时)落地了足够多的feature时,你需要基于develop分支创建生成一个release分支。创建这个分支的同时也意味着下一个release周期的开始 所以从这一刻开始任何新的feature都不允许加到这个release分支中,唯一能做的是在这个release分支上进行bug fix,文档生成,以及其他面向release的任务可以在这个分支上继续。一旦这个release足够成熟可以用于发布和上线部署,release分支就会被merge到master分支并且在master分支打上相应的标签tag.而且,release分支需要同时merge back到develop分支,以便包含在release准备到发布的这段时间内的bugfix(注意merge到develop时你可能发现develop分支可能已经向前走过了(相对于release分支拉创的时刻))。
使用一个专用的branch来专门用于准备release使得下面的工作场景成为可能:一个team(维护团队)来打磨产品当前的release版本,而另一个team(新项目团队)则继续为下一个release计划开发新的feature(在develop分支上)。这也创建了定义良好项目开发不同phase,使得项目更具有可观性,(比如,非常容易地就说,“这周我们准备version 4.0",并且在repo的结构中能清晰反映出来)。
基本的共识或者说rule:
从develop上branch off向master/develop上merge into
命名规范: release-*
Maintenance/hotfix Branches
Maintenance或者"hotfix"分支用于快速解决production release运行中发现的bug。这也是唯一一个需要从master分支来branch off的分支。只要fix完成了,这个hotfix分支就应该同时merged到master和develop(或者当前release branch)分支,而master应该被tagged一个新的version number。
有一个专门用于bugfix的分支使得你的团队在解决问题时不会打断或者打工作流的其他部分或者等待到下一个release周期。你可以把maintenace/hotfix分支看作和master分支直接联系的特设分支。
例子:
下面的例子演示这个gitflow workflow是如何来管理单个release cyle的。我们假设你已经创建了一个central repo.创建一个develop分支
第一步是在默认的master分支上创建一个develop分支。一个简单的实现方式是项目经理本地创建一个空的develop分支并且push到server上:git branch develop git push -u origin develop
这个develop分支将会包含项目的整个开发历史,然而master则包含一个删减版的产品开发历史。其他的开发人员现在可以clone这个中央库并且创建一个remote tracking develop branch来开始工作:
git clone ssh://user@host/path/to/repo.git git checkout -b develop origin/develop //创建一个remote tracking branch for develop
现在每个人都有了一个本地的开发环境。
Mary和John开始开发新的feature
我们的实际例子从John和Mary各自开始在他们自己的feature branch上独立开发各自的feature开始。他们各自创建一个独立的feature分支。需要注意的是feature branch并不是从master分支上拉出来,相反,他们应该从develop分支上拉出来。
git checkout -b some-feature develop
他们各自在自己的feature branch上做edit/stage/commit开发活动
git status git add <some-file> git commit
...
Mary结束她的feature开发
在创建了一些commit之后,Mary决定她的feature已经完工可以分享了。如果她的team使用pull request,那么现在将会是一个启动一个pull request以便申请项目经理feature落地到develop分支的最佳时机。否则如果不用pull request流程的话,她可以本地merge她的feature branch到develop分支并且push develop分支到中央库(注意:如果Mary是一个feature的team leader她则可以将她创建的feature分支Push到中央库以便和feature team member协同工作)。git checkout develop git pull origin develop git merge some-feature git push git branch -d some-feature
第一个命令确保本地的develop分支是up to date的,要注意的是feature永远不要直接merged到master.冲突解决的过程和centralized workflow是相同的。
Mary准备计划发布一个release
当John依然在他的feature上奋战时,Mary准备计划项目的第一个官方release。就像feature开发一样,她需要使用一个独立隔绝的分支来做这个release发布工作。在这个步骤中我们也应该给一个版本号:git checkout -b release-0.1 develop
这个分支是用于清理release,测试,更新文档以及任何为了本版本发布的工作开展的地方,release分支就像专门用于打磨这个release的feature branch一样。
一旦Mary创建并且push了这个branch到中央库,那么该release就应该处于feature-frozen状态。任何还未落地在develop分支上的feature都会被推迟到相爱一个release cycle中。
Mary完成了这个release的准备
一旦这个release可以发布了,Mary就将release分支merge到master和develop分支,因为在打磨过程中个,可能在该分支上已经发现并且解决了部分critical issue,这些bugfix必须能够被后续开发或者新的feature所使用。再一次说明一下,如果Mary的组织强调code review,这时候则是启动给一个pull request的最佳时机。git checkout master git merge release-0.1 git push git checkout develop git merge release-0.1 git push git branch -d release-0.1
release branch就像是feature development(develop)和public release(master)之间的一个buffer,任何时候你merge了一些东西到master,你应该tag这个commit:
git tag -a 0.1 -m "Initial public release" master git push --tags
git内置了一些hooks,这些hooks就是一些在repo中当特定事件发生时可以执行的脚本。我们可以配置一个hook,当任何人向master分支做了push操作或者push了一个tag之后自动执行一个git archive构建一个public deployment release.
终端用户发现了一个bug
在发布了一个release版本后,Mary又开始和John一起持续工作为了下一个release开发新的feature了,直到有一天终端用户开了一个tickets抱怨说在当前release中存在一个bug.为了解决这个bug,Mary或者John从master分支上那个release tag处创建一个hotfix/maintenance分支,最终经过多次commit解决这个问题,然后直接merge回master.git checkout -b issue-#001 master-tag-0.2 #Fix the bug git checkout master git merge issue-#001 git push
就像release branch一样,maintenace/hotfix branch包含了重要的bugfix改动,这些改动必须被包含到develop分支上去(如果有running release分支,则也应该merge到这里)。所以Mary需要执行这个merge工作,完成后就可以删除这个hotfix/maintenance branch了。
git checkout develop git merge issue-#001 git push git branch -d issue-#001
下一步?
记住上面所列的workflow仅仅介绍了可以怎么用git来匹配组织的工作流程和习惯,他们并不是hard-and-fast rules。所以重要的是一定要取其精华,去其糟粕。我们研究这些工作流模型的目标总是使得git能够为我所用。Forking workflow
Forking workflow和前面讲到的三种模型有些区别。不同于使用唯一一个server-side repo作为中央库,这种工作流给每一个开发人员都定义分配一个server-side repo.这意味着每一个contributor都有两个git repo:一个local one,一个public server-side one;forking workflow的优点是变更集成时没有必要让每个人都push到唯一一个中央库。开发人员push到他们自己的server-side repo中。而只有项目经理可以push到official repo中。这允许项目经理在不给每个开发人员都开放写权限的前提下来获取开发人员的commits.
这个工作流的结果是一个分布式的workflow,这个workflow提供了一个对于大型的组织,包括非信任的第三方开发团队来安全有效地展开合作。这种模式对于open source project也非常适合。
如何工作的:
和其他的git workflow一样,forking workflow起始于一个保存于public server上的public repo,但是当一个新的开发人员希望加入一个项目开发,他们要做的第一步并不是从official repo去做clone。相反,他们对official repo做fork动作以便创建一个他们有写权限的public server.这个repo作为他们自己的public repo---其他开发人员无权向这个repo做push操作,但是他们可以从这个repo中pull changes到他们的本地。在他们创建了他们自己的server-side copy后,开发人员需要执行一个git clone操作获取本地repo。这个本地repo作为他们本地的私有开发环境。当他们本地开发告一段落后,他们将commits push到他们自己的public repo中(注意:并不是official的那个repo)。然后,他们发起一个pull request申请项目经理将他们的代码落地。pull request也作为他们讨论他的代码的最佳场所。
为了将pr中所涉及的代码合入official repo中,项目经理pull the contributor's changes到他们本地repo,检查这段代码是否会有问题,如果没有问题,他们就直接merge到项目经理的local master branch上去,然后项目经理执行push动作将master分支放到官方的repo库中。到现在,那个PR中的代码改动就成了项目代码的一部分,其他的开发人员应该从这个官方库中pull去同步到他们的本地repo中。
官方repo
有一点非常重要:那就是所谓official repo这个概念在forking workflow中仅仅是个约定而已。从技术的角度来说,git并没有看到official repo和开发人员的public repo有任何不同。事实上,之所以我们称之为official repo是因为这个repo是来自于项目经理的public repo而已。Forking workflow中的分支模型
所有上面提到的个人的public repo实际上是一种和其他开发人员分享分支的便利手段。每个人应该继续使用分支来隔离各自的feature,就像在feature branch workflow和gitflow一样。唯一的区别是:这些分支的分享方式是不同的。在Forking workflow中,他们被pull到另外一个开发人员的local repo中,而在feature branch和gitflow workflow中,他们被pushed到official repo中。例子:
项目经理初始化"official repo"
对于git-based 项目,第一步要做的就是在server端创建一个可以被所有团队成员访问的official repo。典型地,这个repo会作为项目经理的Public repo需要注意的是,public repo永远应该是裸库,无论他们是否官方库。所以,项目经理应该这样创建official库:
ssh user@host git init --bare /path/to/repo.git
bitbucket也提供一些方便的gui来创建上述命令。项目经理也应该将code base导入到这个库中。
开发人员从official repo来做fork
下一步,所有其他的开发人员需要从这个official repo做fork.你可以这样操作:1.ssh到服务器上,2.执行git clone 来copy到你自己的目录中;
注意:从上面你可以看到:fork就是server-side的clone操作而已。当然对于bitbucket,允许你点击一两次鼠标来完成这个fork过程。
在这个步骤后,每一个开发人员应该有了他们自己的server-side repo,就像official repo一样,所有的这些repo必须是裸库。
开发人员从他们forked repo来做clone到本地
这时每个开发人员需要做的是从他们自己的public repo(server-side上)clone到本地。我们的例子假设使用bitbucket来host这些public repo。记住,在这个条件下,每个开发人员应该有他们自己的bitbucket帐号并且他们应该用下面的命令clone他们自己的server-side repo:
git clone https://user@bitbucket.org/user/repo.git[/code]
虽然这篇文章中的其他workflow中都使用一个单一的origin来指向中央库,而在forking workflow中却需要两个remote,一个指向offiicial repo,另一个指向开发人员自己forked的public repo.在这里,一个通用的命名惯例是:使用origin来指向你forked public repo,而使用upstream来指向official repogit remote add upstream https://bitbucket.org/maintainer/repo.git[/code]
你需要自己创建这个upstream remote,这样将使得你能够非常容易地保持你的local repo和官方repo保持一致(up to date).注意:如果你的upstream repo本身需要认证(也就是说非开源项目),那么你可能需要提供一个username:git remote add upstream https://user@bitbucket.org/maintainer/repo.git[/code]
这样的话在用户clone或者pull之前,需要提供密码开发人员在他们自己的feature上进行开发工作
在他们刚刚cloned的本地repo中,开发人员可以edit/stage/commit/create branch等等常规操作。git checkout -b some-feature #edit some code git commit -am"add first draft of some feature"
开发人员的所有改动在他们push到他们自己的public repo之前一直是私有的。并且,如果official project向前走了,他们可以通过git pull来获取到新的项目成果:git pull upstream master
既然开发人员应该在自己的特定feature branch上展开开发工作,这就应该会产生一个fast-forward merge.开发人员publish他们的feature
一旦一个开发人员认为他们的feature已经就绪,他们需要做两件事:
首先,他们把他们的commit发布到他们自己的public repo中以便别人都能看到,通过下面的命令来完成:git push origin feature-branch
在这点上,是和其他三种workflow不一样的,因为在这里origin是指向开发人员自己私有的server-side repo,而不是official repo(他们应该不具有official repo的写的权限)。
第二,他们需要通知项目经理他们需要merge他们的代码到official库中。Bitbucket提供了pull request来允许你发起这样一个请求过程。典型地,你需要集成你自己的feature branch到upstream的master分支。项目经理集成开发人员申请落地的feature
当项目经理收到这个pull request,他们的工作就是要决定是否要集成到正式代码库中。他们可以以以下两种方法中的一种:
1. 直接在pr中查看代码;
2. 将代码拉到本地repo并且手工merge
第一种option比较简单,因为他直接让项目经理看到具体的变更,并且可以在这些变更上提交comments,通过一个可视化的界面来执行merge动作。然而,第二种方法在pr可能产生merge冲突时是必须的。在这种情况下,项目经理需要做的是:从开发人员的public repo的feature branch来fetch到代码, merge到本地的master branch并且解决所有的冲突:git fetch https://bitbucket.org/user/repo.git feature-branch # Inspect the changes git checkout master git merge FETCH_HEAD
一旦项目经理将变更集成进了local master,项目经理需要做的是push到official repo中以便其他人可以访问这些新落地的feature:git push origin master
记住项目经理的origin指向的是official repo,而到现在开发人员的贡献就都在official repo中了。开发人员和official repo保持同步更新
既然official repo已经包含了新的落地代码,其他开发人员需要和official库做好同步更新:git pull upstream master
终于写完了,自己给自己一个打赏
相关文章推荐
- hadoop划分为多个输出文件
- 关于mysql的内连接,外连接,交叉连接
- jvm(6)-Class字节码文件结构总结
- Non-Maximum Suppression for Object Detection in Python
- C语言正则表达式详解 regcomp() regexec() regfree()详解
- Using TabLayout inside a Fragment; tab text invisible
- 什么是堆和栈,它们在哪儿?
- 【BZOJ1236】KPSUM,记数类问题(乱搞)
- R文件无法自动生成
- 前端经验技巧谈论
- C和C++语言&
- mybitis常见问题汇总中....
- python学习(九):异常、调式与测试
- thinkpad T450, 按Fn+ESC键实现F1,F2,..F10输入的模式变换
- update select
- ios模拟器键盘不弹出
- 多线程
- 题目1049:字符串去特定字符
- 【cocos2d-x制作别踩白块儿】第一期:游戏介绍
- 2016年,C语言该怎样写