为什么是git?
有关Git与集中式源代码控制系统相比的优缺点的详尽讨论,请参见 Web。那里发生了许多火焰大战。作为开发人员,我比今天所有其他工具都更喜欢Git。Git确实改变了开发人员对合并和分支的看法。在我来自经典的CVS / Subversion的世界中,合并/分支一直被认为有点吓人(“当心合并冲突,它们会咬你!”),而您却偶尔会做一次。
但是,使用Git,这些操作非常便宜且简单,实际上,它们被视为日常工作流程的核心部分之一。例如,在CVS / Subversion书籍中,分支和合并在后面的章节(高级用户)中首先讨论,而在 每本 Git 书籍中,它都已在第3章(基础知识)中讨论过。
由于其简单性和重复性,分支和合并不再是令人害怕的事情。应该使用版本控制工具来帮助分支/合并。
足够了解这些工具,让我们进入开发模型。我将在此处介绍的模型本质上不过是每个团队成员必须遵循的一组程序才能进入托管软件开发过程。
分散但集中化
我们使用的存储库设置与该分支模型一起使用时很好,它是一个中央“真实”存储库。请注意,此存储库仅 被视为 中央存储库(由于Git是DVCS,因此在技术级别上没有中央存储库之类的东西)。我们将此仓库称为origin
,因为所有Git用户都熟悉该名称。
每个开发人员都拉起并推开原点。但是,除了集中的推拉关系之外,每个开发人员还可以从其他同伴那里拉出变更以组成子团队。例如,在origin
过早地将进行中的工作之前,与两个或多个开发人员一起使用一项重要的新功能可能很有用 。在上图中,有Alice和Bob,Alice和David以及Clair和David的子团队。
从技术上讲,这仅意味着Alice定义了一个Git远程服务器,名为bob
,指向Bob的存储库,反之亦然。
主要分支
从根本上说,开发模型受到了现有模型的极大启发。中央存储库包含两个主要分支,生命周期无限:
master
develop
每个Git用户都应该熟悉master
at的分支origin
。与master
分支平行,存在另一个分支,称为develop
。
我们认为origin/master
它是源代码HEAD
始终反映生产就绪状态的主要分支 。
我们认为origin/develop
这是主要分支,在该分支中,源代码 HEAD
始终反映状态以及下一版本的最新交付开发更改。有人将其称为“整合分支”。这是构建任何夜间自动构建的地方。
当develop
分支中的源代码达到稳定点并准备发布时,所有更改都应以master
某种方式合并回去,然后用发布号进行标记。如何进一步详细地进行讨论。
因此,每次将更改合并回时master
,根据定义,这是一个新的生产版本。我们通常对此非常严格,因此从理论上讲,每次提交时,我们都可以使用Git钩子脚本自动将软件构建和推出到生产服务器 master
。
支持分支
在主要分支master
和旁边develop
,我们的开发模型使用各种支持分支来协助团队成员之间的并行开发,简化功能跟踪,为生产发布做准备并协助快速解决生产中的实际问题。与主要分支不同,这些分支的生命周期总是有限的,因为它们最终将被删除。
我们可能使用的不同类型的分支机构是:
- 功能分支
- 发布分支
- 修补程序分支
这些分支中的每一个都有特定的用途,并受严格的规则约束,即哪些分支可能是其原始分支,哪些分支必须是其合并目标。我们将在一分钟内通过它们。
从技术的角度来看,这些分支绝不是“特殊的”。分支类型按我们的使用方式进行分类。它们当然是普通的旧Git分支。
功能分支
- 可能从以下分支:
develop
- 必须合并回:
develop
- 分支命名约定:
- 任何东西,除了
master
,develop
,release-*
,或者hotfix-*
功能分支(或有时称为主题分支)用于为即将发布或遥远的将来版本开发新功能。当开始开发功能时,此时可能不知道将合并该功能的目标版本。功能分支的本质是只要功能正在开发中就存在,但是最终会合并回去develop
(以确保将新功能添加到即将发布的版本中)或丢弃(如果实验令人失望)。
功能分支通常仅存在于开发人员存储库中,而不存在于中origin
。
创建一个功能分支
当开始使用新功能时,请从develop
分支分支。
$ git checkout -b myfeature开发
切换到新分支“ myfeature”
在development上包含完成的功能
可以将完成的功能合并到develop
分支中,以确保将它们添加到即将发布的版本中:
$ git checkout开发
切换到分支'develop'
$ git merge --no-ff myfeature
更新ea1b82a..05e9557
(更改摘要)
$ git branch -d myfeature
删除分支myfeature(以前为05e9557)。
$ git push origin development
该--no-ff
标志使合并始终创建一个新的提交对象,即使合并可以通过快进来执行。这样可以避免丢失有关要素分支历史存在的信息,并将所有添加了要素的提交分组在一起。相比:
在后一种情况下,无法从Git历史记录中看到哪些提交对象一起实现了功能—您将不得不手动读取所有日志消息。在后一种情况下,还原整个功能(即一组提交)确实很头疼,而如果使用了--no-ff
标志,则很容易做到 。
是的,它将创建更多(空)提交对象,但收益远大于成本。
发布分支
- 可能从以下分支:
develop
- 必须合并回:
develop
和master
- 分支命名约定:
release-*
发布分支支持新产品版本的准备。他们考虑了i的最后一刻和t的交叉。此外,它们允许进行较小的错误修复并为发布准备元数据(版本号,构建日期等)。通过在发行分支上完成所有这些工作,该develop
分支将被清除以接收下一个大型发行版的功能。
从新版本分支分支的关键时刻develop
是开发(几乎)何时反映新版本的期望状态。此时,至少必须将要构建的发行版的所有功能都合并到其中 develop
。面向将来发行版的所有功能可能都不会—它们必须等到发行分支分支出来之后。
正是在发行分支的开始,才为即将发布的发行版本分配了一个版本号,而不是更早的版本号。直到那一刻为止,该develop
分支反映了“下一个发行版”的更改,但是尚不清楚该“下一个发行版”最终将变为0.3还是1.0,直到发行分支开始。该决定是在发布分支的开始处做出的,并由项目的版本号增加规则来执行。
创建一个发布分支
从develop
分支创建发行分支。例如,说版本1.1.5是当前的生产版本,我们将发布一个大版本。的状态develop
准备好了“下一个版本”,我们已经决定,这将成为版本1.2(而不是1.1.6或2.0)。因此,我们分支并给发布分支起一个反映新版本号的名称:
$ git checkout -b release-1.2开发
切换到新的分支“ release-1.2”
$ ./bump-version.sh 1.2
文件已成功修改,版本升至1.2。
$ git commit -a -m “将版本号加至1.2”
[release-1.2 74d9424]将版本号加至1.2
1个文件已更改,1个插入(+),1个删除(-)
创建新分支并切换到该分支后,我们更改版本号。这里 bump-version.sh
是一个虚构的shell脚本,它更改了工作副本中的某些文件以反映新版本。(当然,这可以是手动更改-关键是有些文件会更改。)然后,提交被修改的版本号。
这个新分支可能在那里存在了一段时间,直到可以肯定地发布该版本为止。在此期间,错误修复程序可能会应用于此分支(而不是develop
分支)。严格禁止在此处添加大型新功能。它们必须合并到中develop
,因此,请等待下一个重要版本。
完成发布分支
当发布分支的状态准备好成为真实发布时,需要执行一些操作。首先,将release分支合并到其中 master
(因为每次提交master
都是定义上的新发行版,请记住)。接下来,master
必须标记该提交,以方便将来参考此历史版本。最后,需要将在release分支上所做的更改重新合并到中develop
,以便将来的发行版中也包含这些错误修复。
Git的前两个步骤:
$ git checkout master
切换到分支'master'
$ git merge --no-ff release-1.2
递归合并。
(变更摘要)
$ git tag -a 1.2
该版本现已完成,并已标记以供将来参考。
编辑:您也可能希望使用
-s
或-u <key>
标志以加密方式对标签进行签名。
为了保留在release分支中所做的更改,我们需要将这些更改重新合并到中develop
。在Git中:
$ git checkout开发
切换到分支'develop'
$ git merge --no-ff release-1.2
递归合并。
(更改摘要)
这一步很可能会导致合并冲突(可能甚至是因为我们更改了版本号)。如果是这样,请修复它并提交。
现在我们真的完成了,可以删除发布分支,因为我们不再需要它了:
$ git branch -d release-1.2
删除了分支release-1.2(原为ff452fe)。
修补程序分支
- 可能从以下分支:
master
- 必须合并回:
develop
和master
- 分支命名约定:
hotfix-*
修补程序分支与发布分支非常相似,尽管它们是计划外的,但它们也旨在为新的生产版本做准备。它们源于必须对不期望的实时生产版本立即采取行动。当必须立即解决生产版本中的严重错误时,可以从标记生产版本的master分支上的相应标记中分支出一个修补程序分支。
本质是团队成员(develop
分支机构)的工作可以继续进行,而另一个人正在准备快速生产修复。
创建hotfix分支
修补程序分支是从master
分支创建的 。例如,说1.2版是当前的生产版本,正在运行,并且由于严重的错误而引起麻烦。但是变化develop
仍然不稳定。然后,我们可能会分支出一个修补程序分支并开始解决问题:
$ git checkout -b hotfix-1.2.1 master
切换到新分支“ hotfix-1.2.1”
$ ./bump-version.sh 1.2.1
文件修改成功,版本升至1.2.1。
$ git commit -a -m “将版本号加至1.2.1”
[hotfix-1.2.1 41e61bb]将版本号加至1.2.1
更改了1个文件,1个插入(+),1个删除(-)
别忘了在分支后增加版本号!
然后,修复该错误,并在一个或多个单独的提交中提交此修复程序。
$ git commit -m “修复了严重的生产问题”
[hotfix-1.2.1 abbe5d6]修复了严重的生产问题
5个文件更改,32个插入(+),17个删除(-)
完成修补程序分支
完成后,该bugfix需要重新合并到中master
,但也需要重新合并到中develop
,以确保该bugfix也包含在下一个版本中。这与释放分支的完成方式完全相似。
首先,更新master
并标记发布。
$ git checkout master
切换到分支'master'
$ git merge --no-ff hotfix-1.2.1
递归合并。
(更改摘要)
$ git tag -a 1.2.1
编辑:您也可能希望使用
-s
或-u <key>
标志以加密方式对标签进行签名。
接下来,在develop
中也包含错误修正:
$ git checkout开发
切换到分支'develop'
$ git merge --no-ff hotfix-1.2.1
递归合并。
(更改摘要)
该规则的一个例外是, 当当前存在发行分支时,需要将修补程序更改而不是合并到该发行分支中develop
。在发行分支完成后,将错误修正回合并到发行分支中,最终会导致将修正修正也合并到develop
发行分支中。(如果develop
立即进行工作需要此错误修正,并且不能等待发行分支完成,则也可以安全地将错误修正合并到develop
现在。)
最后,删除临时分支:
$ git branch -d hotfix-1.2.1
删除了分支hotfix-1.2.1(以前是abbe5d6)。
总结
尽管此分支模型没有什么真正令人震惊的新东西,但本文开头的“全局”图在我们的项目中被证明非常有用。它形成了一个易于理解的优雅思维模型,并允许团队成员对分支和发布过程达成共识。
此处提供了该图的高质量PDF版本。快将它挂在墙上,以便随时参考。