分享

GitLab Flow

 bananarlily 2015-12-06

原文链接:https://about./2014/09/29/gitlab-flow/

简介

相较于SVN等老旧的版本控制系统,使用git进行版本管理会让分支和合并更加容易。git允许更多样的分支策略和工作流。相比较于git出现以前应用的方法,现在几乎全部的方法都得到了改进。但是现在很多组织仍然使用一个没有明确定义的,过度复杂的或者是没有集成错误追踪系统的工作流程来工作。因此我们推荐gitlab工作流作为最佳的实践方式。它整合了feature driven development和有issue跟踪的feature branches。

使用其他版本控制系统的组织转移到git后发现很难去开始一个有效的工作流程。这篇文章描述的gitlab工作流集成了git工作流和一个错误追踪系统。它提供了一个简单、易懂并且有效地方式去使用git工作。


要使用git,你需要习惯一件事,那就是每当你提交一个和同事共享的版本时,都要先完成三个步骤。而大多数版本控制系统只需完成一步:把工作的备份提交到一个共享的服务器。在git中,你首先要把文件从文件拷贝添加到缓存区中,之后再把他们提交到本地库中,最后把他们推送到远程的共享仓库中。熟悉了这三个步骤后,分支模型就是接下来要面对的问题。


因为很多不熟悉git的组织没有约定如何使用git工作,导致工作很快变得一团糟。他们产生的最大的问题是同时存在太多的开发中的分支,每个分支都包含了部分修改。人们很难弄清楚哪一个分支应该继续开发或者把它变成产品。通常,应对这种问题的方法是采用一个标准的模型比如git flow或是github flow。我们认为这里面还有可以提升的空间,并且把它详细描述为gitlab flow。

git flow和它的问题

git工作流比较早的使用的了git分支,并且已经获取了很多的关注。git工作流提倡一个master分支和一个develop分支,同时也包括了feature分支、release分支、hotfixe分支。开发工作在develop分支进行,然后提交到release分支,最后合并到master分支。git工作流很标准但是使用很复杂,这导致了两个问题。

第一个问题是程序员必须使用develop分支来开发,而不是master分支。master分支被用来作为线上环境的分支。而作为惯例,master分支一般是默认分支,别的分支都来自master或者是最后合并到master。由于大部分工具自动的把master作为默认分支,而且git默认显示master分支,开发时不停切换分支是很麻烦的。

第二个问题是引入了hotfix和release分支,这些分支对于大部分开发者来说都是没有必要的。现在大部分机构使用持续交付,这意味着你的默认分支最后会被部署。hotfix和release应该避免他们自身引进的各种规范,例如合并到release分支。尽管一些工具解决了这些问题,但是这同样使问题变得复杂。在期间,开法者很可能会犯错,例如修改的代码直接合并到了master分支,却没有合并到develop分支。这些问题的最根本原因是git工作流程对于大多数人来说太复杂了。

github flow作为一个更简单的替代选择

git flow的一个更简单的替换方案是GitHub Flow。它只有一个feature分支和一个master分支,简单而干净,很多机构成功的使用这种方案。Atlassian推荐了一种类似的方案。合并代码到master分支并且发布,那么你应该尽量减小每次提交的代码量,坚持依赖和持续集成这些好的习惯。但是这种工作流中仍然有很多问题没有解决,例如部署、环境、发布和issue的管理。在GitLab工作流程中,我们使用其他的规则来解决提到的这些问题。

gitlab flow中的生产分支

github flow认为你可以通过合并feature分支直接把代码部署到线上。对于SaaS应用,这是可能的,但是在很多情况下并不是这样子的。一种情况是你无法控制准确的发布时间,例如IOS应用需要通过苹果的审核,或者是每天发布的时间是固定的,但是你merge的时间并不一定是那个时间。在这些例子中,你需要创建一个production分支来放置发布的代码。你可以通过合并master到production分支来发布一个新的版本。假如你想知道线上的版本包含了哪些代码,只需要查看production分支即可。而且大概的发版时间也可以从merge的信息中看到。假如你是自动部署production分支的,那么这个时间就很准确。假如你想记录更精确的时间,可以通过写脚本来给每次部署打tag。

gitlab flow的分支使用

有一个环境可以自动升级到master分支是一个好的选择。在这种情况下,环境的名字可能和分支名不同。假设你有一个缓存环境,一个preproduction环境,和一个production环境。在这种情况下master被用来作为缓存区。当有人想部署到preproduction环境,他们会创建一个从master分支到preproduction分支的merge request。而且上线代码通过合并preproduction分支到production分支。这种下行(downstream)的工作流保证了所有代码都在环境中测试过。如果你需要cherry-pick一个hotfix,常见的是在feature分支中开发,然后以合并请求合并到master分支,先不要删除feature分支。如果master分支通过ci并且运行正常,就可以合并到其它分支。如果需要更多的手工测试,你可以发送合并请求从feature分支到downstream分支。一个environment分支的极端情况是为每一个feature分支建立一个环境,正如Teatro所做的。

gitlab flow的发布分支

在很少的情况下你需要使用release分支对外界发布软件。在这种情况下,每个分支都会包含一个版本号(2-3-stable, 2-4-stable, etc)。这种stable分支都会从master开始,并且要尽可能晚的创建。创建的越晚,你就会越可能的减少bug fix的合并。在release分支发布后,只有严重的bug修复才加到release分支中。如果可能的话,这种bug的修改首先应合并到master,然后cherry pick到release分支。这样子,你就不会忘记把修复bug的代码cherry pick到master,从而防止其他分支也出这个bug。这叫做“upstream first”策略,被google和redhat使用。每次release分支修改bug之后,都要先设置tag,然后增加版本号。有些项目也有stable分支用来指向最近release版本的commit,这种情况下一般没有production分支。

在gitlab共工作流中的Merge/pull requests操作

merge和pull都在git中创建,然后指向一个人来合并两个分支。像GitHub and Bitbucket这样的工具会选择pull request的名字,因为第一个手动的操作是拉取feature分支。像 GitLab and Gitorious这样的工具会选择merge request的名字,因为对被指向的人来说这是最后一个操作。在这篇文章中,我们会称这个操作为merge request。

假如你在一个feature分支工作比较长时间,最好像其他人实时的分享你的工作内容。具体操作为:进行merge request而不指向任何人。这意味着你的代码并没有准备好做merge request,但是欢迎大家来提意见。你的团队成员可以评论代理提出意见。merge requests作为code review工具来使用,并不需要Gerrit和 reviewboard。假如review时发现了一些问题或者bug,一般会由写代码的人来push一个fix commit。当新的commit push到这个分支后,merge request的代码也会自动更新。

当你觉得合适了,就把merge request指定给熟悉你的正在做的事的人,提醒他来查看代码给你反馈。如果别人觉得你的代码不太好,他可以直接关掉merge request。

在GitLab中,保护长时间存在的分支是很常见的,这是为了防止其他开发者修改这个分支代码。因此假如你想把代码合并到一个受保护的分支,那么你可以把merge request指定给这个分支的拥有者。

gitlab flow如何处理issue

gitlab flow可以使的代码和issue之间的关系更加清晰。

代码的任何修改都应该开始于一个目标明确的issue。对于团队里的每个人来说,每一段代码的编写应都有一个明确的目标,也可以通过这个目标来判断每个feature的分支工作量大小。在gitlab glow中,每次代码的编写都源于问题追踪库中的一个issue。假如还没有issue,那么应该根据每个重要的功能点来创建issue。对于一些组织来说这是很自然的事,这些issue都已经被估计为了每个sprint点。issue的标题应该描述出最后想要的结果来,例如“作为一个管理员,我要删除用户,并且不发生错误”,而不是“管理员不能删除用户”

当你准备写代码时,首先为issue创建一个新分支,这个分支的名字应该以issue number开始,例如“15-require-a-password-to-change-it”

当你写完了代码或者想与人讨论的时候,就创建一个merge request,可以在线讨论代码,review代码。创建分支需要手动进行,你并不想每次都把push的代码合并,它很可能是一个长时间开发的分支或者是发布的分支。

假如你创建merge request没有指定给任何人,表明这是一个正在开发的分支,大家可以讨论但是不做合并。

从merge中关联和关闭问题

从commit的message中或者是merge request的信息中可以关联相关的issue,具体语法是:fixes #14, closes #67。在gitlab中,这样的操作会在issue中创建一个评论,并且merge request会显示相关联的issue。假如这个merge request被接受,这个issue会自动被关闭。

假如你仅仅想关联这个问题,但是不关闭这个问题,你可以这样写:Ducktyping is preferred. #12

假如你有一个issue关联着好几处的修改,最好的办法是为每一个修改都创建一个issue,最后把这些issue关联到一个issue。

使用rebase合并多个commits

你可以使用rebase命令把多个提交压缩成一个,并且重新给排序。假如你在开发过程中对于一个小功能有多次提交,你想要把他们合并成一个提交,或者你想给提交排序,那这个功能就很有用了。但是假如你已经把commits推到了远程分支,那么就不可以使用rebase了。如果别人拉取或cherry pick了你的commit,当你rebase你的commits时,一切就变的混乱了。如果人们已经review完了你的代码,当你rebase后,别人就没法知道你哪一块是新提交的代码了。

我们鼓励经常commit和push代码,这样别人就会知道你在做哪一部分工作。然而太多的commit,会导致提交历史很难被理解。但是总体来说,更多的commit优点还是更多一些。为了明白一处改变,我们可以查看merge commit的相关信息,这个commit把会很多小的commit分组了。

当你从feature分支把很多commit合并到master分支,就很难会做回滚操作。假如你把多个commits压缩成了一个,那么只需回滚一个就可以了。但是千万要记着,如果commits已经push了,那么就不可以再有rebase操作了。幸运的是,git提供了功能:回滚之前的一个合并。然而对于你想回滚的commits,这也需要明确的merge commit。假如你回滚了一个merge,你又改变想法了,又想回滚回去,git不允许你做这样的操作。

当你创建一个merge commit时都应该使用参数--no-ff,这样你就可以回滚这次merge了。当你接受一个merge reques时,git软件总是会创建一个merge commit。

不要用rebase对commits排序

在feature分支你可以使用rebase来把commits排到master后面,这样就会避免一个merge commit。然而如果已经推到了一个远程库,那么就不要使用rebase了。如果你按照之前的建议经常地把commit提交共享给别人,那么rebase就会导致工作变得很混乱。当你使用rebase更新feature分支时,你需要一次次解决类似的冲突。你有时候需要reuse recorded resolutions (rerere),但是如果不适用rebase,你仅仅需要一次来解决这样的冲突。也有更好的办法来避免很多的merge commits。

避免很多的merge commits办法就是不要频繁的把master分支合并到feature分支。我们接下来要讨论这三个愿原因:使用代码、解决merge冲突、长时间存在的分支。假如你需要从master合并一些代码,可以使用cherry-pickin命令;假如你的feature分支有一个冲突,那么merge commit很正常。你应该尽量避免冲突的发生。一个例子是CHANGELOG文件,每个重要的代码修改都要在这里记录。对于当前版本,每个人不应该都把修改放在文件的最下边,而是应该随机的插入现在的文件里。当多个分支都对这个文件修改时,可以减少一些冲突的合并。最后一个原因是对于长期存在的分支,又想要更新为最新的代码。Martin Fowler在他的文章中讨论者这种ci的问题。在GitLab中我们把ci和分支测试混淆是不合适的。从Martin那里引用的话:“我听说大家在每个分支的每次提交的时候都做ci,因为他们要做build,可能在一个ci服务器上。那是持续的build,这是一件好事,但是没有集成,因此并不是ci”。避免很多merge commits的方法是,你的feature分支存在时间尽量短,大多数应该少于一天。假如你的分支存在多于一天了,那么就考虑工作分的更细一些或者使用?feature toggles?。对于多于一天的分支,会有两种策略来解决。在ci策略中,你可以在开始阶段就从master合并,可以避免中途的合并。在同步策略中,你可以仅仅在规定好的时间来合并,例如一个tag。这种策略很被Linus Torvalds赞同,因为这些点的代码状态可以更好地被知道。

总之,我们应该努力避免merge commit,但是不要限制他们。你的代码应该是干净的,但是你的历史应该准确的呈现出过去发生的事情。开发软件有时候会有一些混乱的操作,这些直接反映在历史中就可以了。你可以使用工具来查看commits的可视化图,以此来理解创建代码的乱七八糟的历史。假如你错误的使用rebase,那使用工具也没法纠正这个,因为工具也不能改变commit identifier。

投票功能,在merge request

在merge request时,右上角有一个投票功能。

推送代码,删除feature分支

我们推荐大家经常的把feature分支push上去,即使还没有全部完成代码。这样做,可以防止别的成员来开始解决这个问题。在问题跟踪系统中,每个问题都会指定给一个人,也可以避免这种情况,可是有时候大家会忘记把问题指定给某个人。

当一个分支被合并后,这个分支应该被删除。在gitlab中当merge request时,这个选项是可选的,这保证了在gitlab中看到的分支都是在处理的issue的,并且当在一个新分支中重新解决一个issue时,分支可以使用之前的名字。

[图片]

要经常commit,并且写正确的、完整的描述

我们推荐经常地commit,每次完成一个功能点,都应该commit一次。这样做的优点是当写新代码或重构旧代码出现问题时,很容易回滚会旧的版本。对于SVN的用户,这是一个很大的改变,当工作完成要共享给其他人时才会commit代码。有一个小技巧是,当你准备把代码提交给别人时在进行pull/merge操作即可,中间不需要进行。

commit时需要认真的写描述内容,commi的message是最容易被看到的,应该尽量多的包含这次commi的信息。一篇文章:如何写message

merge之前做测试

在gitlab flow中,每个人从master分支拉去代码创建自己的分支,改完代码在merge request之前必须测试完毕才可以合并代码。 持续集成的开发软件(Travis,GitLab CI)在merge reeust时会显示bulid的结果,这样可以保证测试通过。这样做一个缺点是他们自己测试feature分支,没有测试合并后的代码。最好是有人可以测试合并后的代码,但是每次有人合并分支到master后就需要测试一次,这样的测试代价是昂贵的,而且而且需要经常地等待测试结果。假如没有合并冲突,feature分支合并到master的风险是可以承受的。假如有合并冲突,你需要先把master代码合并到feature分支重新进行测试。

假如你的feature分支好多天也没有关闭,那么你需要使issue更小一些。

merge别的代码注意事项

一般初始化一个feature分支时总是从最新的master拉取的代码。假如你之前就知道你的分支依赖别的代码,那么可考虑从别的分支拉去代码。假如你需要合并别的分支,那么需要在merge commit的信息中写清楚原因。假如你还没有把你的commit提交的远程库,那么可以使用rebase把你的commit合并在master或其他分支。 假如你的代码不合并upstream分支也可以正常工作,那么就不要合并。linus说“不应该在任何时候合并upstream分支,仅仅应合并主分支代码”仅仅当需要的时候合并代码可以尽量的减少merge commit,这样可以使历史更清楚。

参考

Sketch file with vectors of images in this article

Git Flow by Vincent Driessen

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多