惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
Hacker News: Ask HN
Hacker News: Ask HN
L
Lohrmann on Cybersecurity
PCI Perspectives
PCI Perspectives
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cyber Attacks, Cyber Crime and Cyber Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
月光博客
月光博客
W
WeLiveSecurity
T
Threat Research - Cisco Blogs
Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
The GitHub Blog
The GitHub Blog
Webroot Blog
Webroot Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
F
Full Disclosure
U
Unit 42
Jina AI
Jina AI
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
L
LINUX DO - 最新话题
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
T
Troy Hunt's Blog
腾讯CDC
T
Threatpost
H
Hacker News: Front Page
P
Palo Alto Networks Blog
博客园 - 聂微东
Last Week in AI
Last Week in AI
有赞技术团队
有赞技术团队
Help Net Security
Help Net Security
L
LINUX DO - 热门话题
N
News and Events Feed by Topic
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Spread Privacy
Spread Privacy

陈少文的网站

巨变与机遇的未来十年 Kubernetes 平台管理软件压力测试方案 使用镜像部署 Hexo 静态页面 终于等到你 - GitHub 镜像仓库服务(ghcr.io) 一起来学 Go --(6)Interface 一起来学 Go --(5)Goroutine 和 Channel 什么是函数式编程 如何在 Kubernetes 集群集成 Kata 柯里化与偏函数 使用 PyGithub 自动创建 Label 软件产品是团队能力的输出 Helm 2 、Helm 3 比较 IoT 变现 Kubernetes 中的 DNS 服务 国内的 Helm 镜像源 Harbor 使用自签证书支持 Https 访问 DevOps 工具链之 Prow 如何使用 kfctl 安装 Kubeflow VS Code 无法下载 Go 插件的工具包 工程师更应具有服务精神 你不知道的 Docker 使用技巧 使用 Docker 运行 Tensorflow 论中国 什么是左移 如何清空 Git 仓库全部历史记录 一禅小和尚 有风吹过厨房 时间的玫瑰 如何在 CentOS 安装 GPU 驱动 开发 Tips(19) 使用 Velero 备份 Kubernetes 集群 Kubernetes Cheat Sheet 开发 Tips(18) 如何构建一个 Java 工程 开发 Tips(17) KubeSpray 安装 Kubernetes 报错 ip in ansible_all_ipv4_addresses 基于 Kubernetes 和 Jenkins 搭建自动化测试系统 在 Kubernetes 上动态创建 Jenkins Slave 使用 Jenkins 进行服务拨测 开发 Tips(16) Kubernetes 签发 Ingress 证书及日常故障运维 Kubernetes 中 Deployment 的基本操作 Kubernetes 中的证书 如何使用 KubeBuilder 开发一个 Operator Kubernetes 1.6.0 安装问题汇总 镜像管理工具 -- Harbor 开发 Tips(15) Docker 如何拉取镜像 开发 Tips(14) 使用 Helm 安装 harbor 开发 Tips(13) 使用 S2I 构建云原生应用 在 Kubernetes 中使用 emptyDir、hostPath、localVolume 开发 Tips(12) 开发 Tips(11) 代码质量分析工具 SonarQube 使用 Kubeadm 安装 Kubernetes 集群 一起来学 Go --(4)常用函数 Kubernetes 中的 Ceph Kubernetes 之 Volumes Kubernetes 之 Labels、Selectors 开发 Tips(10) 开源正在重构商业模式 Kubernetes 之网络 Kubernetes 之 API 使用 Helm 和 Operator 快速部署 Prometheus Kubernetes 复杂有状态应用管理框架 -- Operator Kubernetes 的包管理器 -- Helm 一起来学 Go --(3)Go Modules 如何一步一步地优化博客方案 kubectl 实用指南 Kubernetes 中的基本概念 搭建远程 Kubernetes 开发环境 大公司和小公司的 ToB 思路 开发 Tips(9) Go 入门指南 一起来学 Go --(2)数据与逻辑结构 如何预防 Web 富文本中的 XSS 攻击 django-xss-cleaner 云工作时代 一起来学 Go --(1)背景与特点 SaaS 开发团队的不同阶段 你不知道的 Git 使用技巧 输出既服务 微服务设计 继续奔跑 开发 Tips(8) 从账户安全到二次验证 Django 性能之数据库查询优化 Django 性能之分库分表 敏捷开发之研发流程 打造一致性的团队 开发 Tips(7) Pytest 进阶学习之 Mock PaaS 部署之 buildpack Go 开发配置 领域输出才是 PaaS 的核心竞争力 Pytest 入门学习 开发 Tips(6) 如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程
一个成功的 Git 分支模型
微信公众号 · 2016-12-05 · via 陈少文的网站

这篇围绕全部源代码的控制工具Git展开。

1. 为什么是Git?

为了深入探讨git和集中式源码版本控制系统的利弊,参见这里。这方面有太多的激烈争论。作为一个开发者,相比其他工具,当前我更喜欢Git。Git的确改变了开发者关于合并与分支的思考方式。在那些经典的CVS/Subversion管理工具的世界中,合并/分支被认为是有些吓人的(“当心合并冲突,它们咬你!”),而且偶尔你得做些操作解决一些问题。

但是使用Git,这些操作都变得极度简单,这些操作被认为是你日常工作流程核心部分之一。例如,在CVS/Subversion这本书中,分支与合并在很后的章节中才被第一次讨论(针对高级用户)。但是在每一本Git书籍中,在第三章就讲到了(基础部分)。

由于它的简单性和操作命令的重复性,分支与合并操作变得不再可怕。版本控制工具被认为在分支/合并方面提供操作便利性比什么都重要。

关于工具本身,已经讨论的足够多了,下面针对开发模型进行展开。我将要介绍的这个模型不会比任何一套流程内容多,每个团队成员都必须遵守,这样便于管理软件开发过程。

2. 既分散又集中

我们使用的,且与这个分支模型配合的非常好的库,他有一个“真正”的中央仓库。注意,这个库只是被认为是中央仓库(因为Git是一个分布式的版本控制工具,在技术层面没有所谓的中央仓库)。我们将会为这个仓库起名为origin,因为所有的Git用户对这个名字都比较熟悉。

每个开发者从origin拉取和推送代码。除了集中式的推送拉取关系,每个开发者也有可能从别的开发者处拉取代码,形成自己的团队。例如当与两个或者更多的人开发一个大的特性时,或者在将代码推送到origin之前,这种代码管理模式可能有用。在上图中,存在Alice和Bob,Alice和David,Clair 和David三个子团队

技术上而言,这只不过意味着Alice定义了一个远程Git仓库,起名为bob,实际上指向Bob的版本库,反之亦然(Bob定义了一个远程Git仓库,起名为alice,实际上指向Alice的版本库)。

3. 主分支

老实说,我们讨论的开发模型受到了当前已存在模型的很大启发。集中式的版本库有两个永久存在的主分支:

  • master分支
  • develop分支

origin的master分支每个Git用户都很熟悉。平行的另外一个分支叫做develop分支。

我们认为origin/master这个分支上HEAD引用所指向的代码都是可发布的。

我们认为origin/develop这个分支上HEAD引用所指向的代码总是反应了下一个版本所要交付特性的最新的代码变更。一些人管它叫“整合分支”。它也是自动构建系统执行构建命令的分支。

当develop分支上的代码达到了一个稳定状态,并且准备发布时,所有的代码变更都应该合并到master分支,然后打上发布版本号的tag。具体如何进行这些操作,我们将会讨论

因此,每次代码合并到master分支时,它就是一个人为定义的新的发布产品。理论上而言,在这我们应该非常严格,当master分支有新的提交时,我们应该使用Git的钩子脚本执行自动构建命令,然后将软件推送到生产环境的服务器中进行发布。

4. 辅助性分支

紧邻master和develop分支,我们的开发模型采用了另外一种辅助性的分支,以帮助团队成员间的并行开发,特性的简单跟踪,产品的发布准备事宜,以及快速的解决线上问题。不同于主分支,这些辅助性分支往往只要有限的生命周期,因为他们最终会被删除。

我们使用的不同类型分支包括:

  • 特性分支
  • Release分支
  • Hotfix 分支
    上述的每一个分支都有其特殊目的,也绑定了严格的规则:哪些分支是自己的拉取分支,哪些分支是自己的目标合并分支。

从技术角度看,这些分支的特殊性没有更多的含义。只是按照我们的使用方式对这些分支进行了归类。他们依旧是原Git分支的样子。

5. 特性分支

特性分支可以从develop分支拉取建立,最终必须合并会develop分支。特性分支的命名,除了 master, develop, release-*,或hotfix-*以外,可以随便起名。

特性分支(有时候也成主题分支)用于开发未来某个版本新的特性。当开始一个新特性的开发时,这个特性未来将发布于哪个目标版本,此刻我们是不得而知的。特性分支的本质特征就是只要特性还在开发,他就应该存在,但最终这些特性分支会被合并到develop分支(目的是在新版本中添加新的功能)或者被丢弃(它只是一个令人失望的试验)

特性分支只存在开发者本地版本库,不在远程版本库。

5.1 创建特性分支

当开始开发一个新特性时,从develop分支中创建特性分支

1
2
git checkout -b myfeature develop
Switched to a new branch "myfeature"

5.2 在develop分支整合已经开发完成的特性

开发完成的特性必须合并到develop分支,即添加到即将发布的版本中。

1
2
3
4
5
6
7
8
git checkout develop
Switched to branch 'develop'
git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
git branch -d myfeature
Deleted branch myfeature (was 05e9557).
git push origin develop

–no-ff参数的作用是在合并的时候,会创建一个新的提交对象,即使是fast-forward方式的合并。这就避免了丢失特性分支的历史记录信息以及提交记录信息。比较一下

在右面的例子中,是不可能从Git历史记录中看到一个已经实现了的特性的所有提交对象-除非你去查看所有的日志信息。要想获取整个特性分支信息,在右面的例子中的确是一个头疼的问题,但是如果使用–no-ff参数就没有这个问题。

使用这个参数后,的确创建了一些新的提交对象(那怕是空提交对象),但是很值得。

不幸的是,我还没有找到一种方法使Git默认的merge操作带着–no-ff参数,但的确应该这样。

6. 发布分支

从develop分支去建立Release分支

Release分支必须合并到develop分支和master分支

Release分支名可以这样起名:release-*

Release分支用于支持一个新版本的发布。他们允许在最后时刻进行一些小修小改。甚至允许进行一些小bug的修改,为新版本的发布准要一些元数据(版本号,构建时间等)。通过在release分支完成这些工作,develop分支将会合并这些特性以备下一个大版本的发布。

从develop分支拉取新的release分支的时间点是当开发工作已经达到了新版本的期望值。至少在这个时间点,下一版本准备发布的所有目标特性必须已经合并到了develop分支。更远版本的目标特性不必合并会develop分支。这些特性必须等到个性分支创建后,才能合并回develop分支。

在release分支创建好后,就会获取到一个分配好即将发布的版本号,不能更早,就在这个时间点。在此之前,develop分支代码反应出了下一版本的代码变更,但是到底下一版本是 0.3 还是 1.0,不是很明确,直到release分支被建立后一切都确定了。这些决定在release分支开始建立,项目版本号等项目规则出来后就会做出。

6.1 创建release分支

从develop分支创建release分支。例如1.1.5版本是当前产品的发布版本,我们即将发布一个更大的版本。develop分支此时已经为下一版本准备好了,我们决定下一版的版本号是1.2(1.1.6或者2.0也可以)。所以我们创建release分支,并给分支赋予新的版本号:

1
2
3
4
5
6
7
git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

创建好分支并切到这个分支后,我们给分支打上版本号。bump-version.sh是一个虚构的shell脚本,它更改了工作空间的某些文件来反映新版本特征。(当然也可以手动改变这些文件),然后版本就被提交了。

新的分支会存在一段时间,直到新版本最终发布。在这段时间里,bug的解决可以在这个分支进行(不要在develop分支进行)。此时是严禁添加新的大特性。这些修改必须合并回develop分支,之后就等待新版本的发布。

6.2 结束一个release分支

当release分支的准备成为一个真正的发布版本时,一些操作必须需要执行。首先,将release分支合并回master分支(因为master分支的每一次提交都是预先定义好的一个新版本,谨记)。然后为这次提交打tag,为将来去查看历史版本。最后在release分支做的更改也合并到develop分支,这样的话,将来的其他版本也会包含这些已经解决了的bug。

在Git中需要两步完成:

1
2
3
4
5
6
git checkout master
Switched to branch 'master'
git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
git tag -a 1.2

这样release分支已经完成工作,tag也已经打了。

备注:你可以使用-s or -u 参数为你的tag设置标签签名。

为了保存这些在release分支所做的变更,我们需要将这些变更合并回develop分支。执行如下Git命令:

1
2
3
4
5
git checkout develop
Switched to branch 'develop'
git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

这步有可能会有合并冲突(极有可能,因为我们已经改变了版本号)。如果有冲突,解决掉他,然后提交。
现在我们已经完成了工作,release分支可以删除了,因为我们不在需要他:

git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

7. Hotfix分支

Hotfix分支从master分支建立

必须合并回develop分支和master分支

为Hotfix分支可以这样起名:hotfix-*

Hotfix分支在某种程度上非常像release分支,他们都意味着为某个新版本发布做准备,并且都是预先不可知的。Hotfix分支是基于当前生产环境的产品的一个bug急需解决而必须创建的。当某个版本的产品有一个严重bug需要立即解决,Hotfix分支需要从master分支上该版本对应的tag上进行建立,因为这个tag标记了产品版本

7.1 创建hotfix分支

Hotfix分支从master分支进行创建。例如当前线上1.2版本产品因为server端的一个Bug导致系统有问题。但是在develop分支进行更改是不靠谱的,所以我们需要建立hotfix分支,然后开始解决问题:

git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

千万别忘记在创建分支后修改版本号。
然后解决掉bug,提交一次或多次。

git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

7.2 结束hotfix 分支

完成工作后,解决掉的bug代码需要合并回master分支,但同时也需要合并到develop分支,目的是保证在下一版中该bug已经被解决。这多么像release分支啊。

首先,对master分支进行合并更新,然后打tag

1
2
3
4
5
6
git checkout master
Switched to branch 'master'
git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
git tag -a 1.2.1
备注:你可以使用-s or -u 参数为你的tag设置标签签名。

紧接着,在develop分支合并bugfix代码

1
2
3
4
5
git checkout develop
Switched to branch 'develop'
git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

这里可能会有一些异常情况,当一个release分支存在时,hotfix 分支需要合并到release 分支,而不是develop分支。当release分支的使命完成后,合并回release分支的bugfix代码最终也会被合并到develop分支。(当develop分支急需解决这些bug,而等不到release分支的结束,你可以安全的将这些bugfix代码合并到develop分支,这样做也是可以的)。

最后删除这些临时分支

1
2
git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

8. 总结

这个分支模型其实没有什么震撼人心的新东西,这篇文章开始的那个“最大图片”已经证明了他在我们工程项目中的巨大作用。它会形成一种优雅的理想模型,而且很容易理解,该模型也允许团队成员形成一个关于分支和版本发布过程的相同理念。

9. 参考