源代码控制是开发人员最好的朋友,它为他们提供了一种在不丢失重要更改的情况下试验代码的方法。源代码控制是在整个开发过程中跟踪和维护代码更改的能力。虽然这可能包括代码,但它也可以用于文档、资产(如图像)和其他资源。能够测试某些条件和重构代码而不必担心代码库是大多数开发人员认为的超能力。
在团队环境中工作时,源代码控制非常重要。如果开发人员签入代码并后来意识到他们犯了错误,源代码控制为开发人员提供了一种恢复更改或更新分支并重新提交的方法。不使用某种源代码控制的公司几乎总是会发出警告。
在本章中,我们将介绍开发人员在使用源代码控制时在行业中使用的常见做法。我们还将介绍为您的代码实现分支工作流的各种方法,并检查每个工作流中的不同类型的分支。为了结束本章,我们将回顾开发人员在使用源代码控制时的常见礼仪。
在本章中,我们将介绍以下主题:
- 分支策略
- 创建短暂分支
- 提交前始终“获取最新”
- 理解常见做法
在本章结束时,您将理解以有组织的方式创建代码存储库的最佳方法,以及开发者社区中的常见准则。
技术要求
虽然本节涉及许多有关源代码控制的指南,但本章的唯一要求是一台具有任何操作系统的计算机。Git 是可选的。
如果您没有安装 Git,您可以在以下 URL 下载并安装它:https://git-scm.com/
我们使用 mermaid-js 直观地展示分支策略。截至 2022 年 2 月,GitHub 页面现在支持 Mermaid-js。其中一些部分将包括美人鱼图来演示不同的分支层次结构。
有关 Mermaid-js 的更多信息,请导航至以下 URL:https://mermaid-js.github.io
分支策略
在本节中,我们将探讨各种分支策略,解释每种策略的工作原理,并强调它们之间的差异。
虽然每家公司都有其独特的工作流程,但我们将重点介绍行业中一些常用的策略。
GitFlow 是最初的工作流程,业内每个人都很熟悉它,它的后续版本也通过对工作流程进行微小的更改而得到了改进。
在接下来的部分中,我们将讨论每个工作流程,但首先,我们必须理解 GitFlow 的基础知识。
GitFlow
业界最常见、最成熟的工作流程之一是 GitFlow。它由 Vincent Driessen 于 2010 年创建。
最小的 Git 存储库应具有以下分支:
- main/master/trunk(从此时起称为 main)
-develop
创建新存储库时,main 分支是您开始使用的。此分支的目的是始终拥有稳定且可用于生产的代码,以便随时发布。
develop 分支用于编写新代码并防止未经测试的代码合并到 main。
如果您是从事副项目的独立开发人员,这可能是合适的工作流程。如果 develop 中一切正常,您可以将更改合并到 main 并部署第一个版本。
好消息是您可以进一步发展分支层次结构。您可以轻松创建其他分支,例如功能、发布或修补程序分支,以获得更好的工作流程,我们将在后面介绍。
请记住,下面讨论的每个分支工作流程都允许任何团队(无论是 1 名开发人员还是 50 名开发人员)对 GitFlow 有扎实的理解。
在任何源代码控制系统中,通常有三种类型的分支用于协助管理软件工作流程:功能、发布和修补程序分支。
功能分支
功能分支将新功能隔离到单个分支中,因此开发人员可以编写代码而不必担心影响 develop 分支中的核心代码。
在以下示例中(参见图 1.1),一个团队创建了一个 GitHub 存储库。它将其主分支称为 main,将开发分支称为 develop。
图 1.1:GitFlow 中的功能分支
每个人都收到任务后,一名开发人员被指派创建 Settings 功能。他从 develop 分支创建了一个名为 feature/settings 的分支。
另一位开发人员被分配了Printing功能,并从develop分支创建了feature/printing分支。
命名分支
除了标准化的主分支和开发分支外,命名分支的一种常见方法是在名称前添加前缀。以下是一些示例:
* feature/、features/或feature-:分支名称应尽可能具有描述性和帮助性。例如,feature/1234-settings与Settings功能相关,并包含一个任务编号,以参考可能的要求。另一种常用方法是使用分配给该功能的人员的姓名或首字母(feature/jd-settings)。
* “bug/<userstory/task number>-/”:此示例有助于立即识别错误。此技术的一个例子可以是bug/1234-string-overflow。在分支前添加“bugfix”也是可以接受的。
一旦完成并且所有内容都获得批准,每个开发人员都会将他们的更改合并到 develop 分支中。
发布分支
发布分支用于最后一刻的完善、小错误修复和/或准备发布软件的新版本(多么令人兴奋!)
让我们来看看发布分支如何适应我们之前的示例。下图显示了 GitFlow 中的发布分支的样子:
图 1.2:GitFlow 中的发布分支
最初,开发人员根据他们被分配的任务创建一个功能分支。一旦他们将更改合并到 develop,就会从 develop 分支创建一个新版本。发布分支将合并到 main 并标记版本号。主分支现在将合并到 develop 分支,以便开发人员在发布过程中发生代码更改时获得最新更改。
它与功能分支完全相同,但如果您注意到,我们正在从 develop 分支而不是从 main 分支创建 release 分支。
在创建 release 分支并确认其按预期工作后,release 分支将合并到 main 分支。
合并到 main 后,建议以某种方式识别成功发布。按照惯例,带有版本号的标签是最好的方法。
热修复分支
虽然大多数开发人员不会犯编码错误(嗯),但有时需要立即更改 main 分支。
回到我们的例子,似乎开发人员的代码有问题。当任何人选择设置选项时,应用程序就会崩溃,导致应用程序无法使用。它需要一个修补程序分支。
下图显示了如何实现修补程序分支的示例:
图 1.3:GitFlow 中的修补程序分支
修补程序分支是从main分支创建的,一旦代码经过验证,就需要合并回main和develop分支。
GitFlow 中的长期运行分支是 main 和develop。短期分支包括功能、修补程序和错误修复分支。
现在我们已经介绍了 GitFlow 及其分支类型,我们将研究下一个工作流程,称为 GitHub Flow,以及它与 GitFlow 的不同之处。
GitHub Flow
随着时间的推移,GitFlow 已经发展成为更简单的工作流程。这些工作流程中的第一个是 GitHub Flow,它于 2011 年创建。
GitHub Flow 旨在通过删除 develop 分支并从 main 分支创建功能来简化流程。
下图显示了功能分支如何与修补程序分支一起工作。
图 1.4:GitHub 流程中的修补程序分支
在图 1.4 中,创建了两个功能,并将两个功能合并回 main。立即发布了 1.0.0 版本。1.0.0 发布后,网站上的一些文本有误,法律团队要求修复。
其中一名开发人员创建了一个修补程序分支,更改了标签,请求 PR,获得批准,将更改合并到 main,更新了版本,并立即将代码部署到生产中。
修补程序和功能分支有什么区别?修补程序是从 main/master 创建的分支,代码签入、审查、更新并立即合并回 main/master。功能分支更像是一种有组织或有计划的方法。功能分支是从开发分支创建的,代码签入、审查并合并回功能分支。功能分支计划合并到发布分支。
那么,发布分支在哪里?在每个工作流程中,都有某种发布分支,我们将按如下方式进行审查。此分支的概念是始终拥有一个无错误、经过测试且随时可以部署的版本。一些小型初创公司在起步时使用这种类型的工作流程。由于 GitFlow 被视为行业基准,因此当团队成长并寻求更结构化的工作流程时,很容易应用 GitFlow 概念。
在 GitHub 流程中,这里的长期运行分支再次是主分支,其中短期分支是功能、修补程序和错误修复分支。
在回顾了 GitHub 流程之后,让我们继续讨论最后一种常用的分支策略,即 GitLab Flow。
GitLab Flow
我们将介绍的最后一个工作流程是 GitLab Flow。GitLab Flow 创建于 2014 年,它采用了与 GitFlow 工作流程不同的方法,并使用功能驱动开发将功能分支与问题跟踪相结合。
GitLab Flow 将发布分支转换为稳定的环境分支,例如生产和 QA。当然,您可以根据需要创建任意数量的“环境分支”。如果我们有一个 QA 环境分支,则可以使用它来测试最终产品。在 图 1*.5* 中,我们看到从 主 分支创建的标准 功能 分支以及另外两个环境分支(预生产和生产)。
图 1.5:GitLab Flow
在 GitLab Flow 中,主分支被视为测试分支。无论是 QA 还是经理,它都是测试功能分支的地方。
与 GitHub Flow 类似,所有内容都合并到 主 分支中。提交功能分支后,将进行代码审查(这些是强制性的),并将其合并到主分支,然后运行所有测试(是的,全部)。如果测试运行时间超过五分钟,请将它们配置为并行运行。
在主分支中完成测试后,主分支将被推送到预生产进行进一步测试,最后推送到生产。由于 GitLab Flow 中的发布基于标签,因此每个标签都应创建一个新版本。
如果开发人员引入了一个错误,则必须先在主分支中修复该错误,然后再在环境分支中修复。开发人员必须创建一个错误修复分支,提交 PR 批准,进行代码审查,并将代码与运行与错误相关的测试合并,然后才能继续执行工作流程。
在主分支中测试后,它会被标记并自动提升到预生产,然后是生产。此工作流程中的长期运行分支包括主分支和环境分支。短期分支是功能、修补程序和错误修复分支。
对于本节中讨论的每种策略,我们都看到了它们是如何从最初的 GitFlow 演变而来,并(请原谅我的双关语)发展成为更好的工作流程的。
创建短期分支
初始化存储库并创建第一个分支后,即可开始为功能编写代码。
虽然这很令人兴奋,但本指南更适用于团队,而不是构建副项目的个人。团队越大,这对您的工作流程就越重要。
让我们看一个使用多个功能分支的示例,如图 1.6 所示。
图 1.6:长期功能分支(功能/设置)
每个人都被分配了各自的功能分支,这些分支是为他们创建的。如您所见,开发人员正在完成他们的功能并将其签入 develop。
但是,负责设置功能(feature/settings)的开发人员落后了。他们的分支变得陈旧,因为他们一周都没有更新代码。它只包含他们第一次创建分支时的功能。
如果他们决定将代码提交到存储库而不更新分支,您认为会发生什么?会有很多不高兴的开发人员。为什么?
feature/settings 分支将被提交并覆盖所有合并到 develop 分支更改的人。分支的预期寿命越短,您遇到合并冲突的可能性就越小。
最好每天(如果不是每天两次)执行更新,以防止分支过时。
理解常见做法
从技术上讲,知道如何使用源代码控制只是成功的一半。另一半是在使用源代码控制的同时以团队合作者的身份工作。牢记队友的能力将使您在职业生涯中走得更远,因为您将成为一名体贴和值得信赖的开发人员。
以下部分旨在作为指导方针,帮助您在团队环境中成功工作。如果您作为开源项目的个人开发人员工作,那么实施这些做法也无妨。
私有时重新设置基准,公开时合并
在私下处理功能分支时,有时可能需要多次提交。这些提交会给 main/master 分支增加不必要的噪音。
重新定基代码需要进行多次本地提交,并使用一次提交更新另一个分支。它本质上重写了提交历史记录。当然,这与合并不同。合并是从一个分支获取所有提交并将整个进度合并到另一个分支的过程。合并会保留提交的整个历史记录。
可以将重新定基想象为在向潜在买家展示房屋之前先打扫一下。如果您在本地分支中犯了许多错误,您希望主/主分支提供清晰简洁的注释,说明提交时对代码应用了哪些内容。
提交前始终“获取最新”
关于保持分支更新的话题,在提交代码之前“获取最新”是一个好习惯。
“获取最新”是指您从中央存储库检索任何更新并将更新应用于本地代码存储库。
无论您使用的是 Git、Team Foundation Server (TFS) 还是其他源代码控制系统,您都需要时刻牢记团队的利益,始终获取最新的代码更新。每种源代码控制工具都有自己的获取最新版本代码的方法。无论您使用哪种工具,获取最新版本始终是一个好习惯。
由于 Git 因其灵活性和精细的源代码控制方法而被视为行业标准,因此大多数开发环境都提供了开箱即用的界面(图形或命令行)来与 Git 交互。
使用 Git,有几种方法可以推送和拉取更改:
- 获取:将远程元数据检索到本地存储库。
- 拉取:检索远程元数据并将这些更改的副本拉取到本地存储库。
- 推送:将提交推送到远程分支。
- 同步:先执行拉取,然后再执行推送的过程。请记住,获取最新更改并将这些更改应用于您自己的代码,然后将您提交的更改推送到服务器的存储库。
在提交之前,最好先发出拉取以检索所有内容,然后再提交代码。
提交前始终构建和测试
虽然在提到我们之前的指南后,这似乎是一个简单的概念,但仍有许多开发人员在提交代码时仍然忽略了这一步。拉取最新代码后,下一步是编译代码并针对它运行本地单元测试。不要假设您拉取的代码没有错误。
一个常见的情况是,在周五下午,开发人员 B 执行拉取操作来更新他们的代码,提交代码而不进行编译,然后跑向门口。
他们不知道的是,开发人员 A 在开发人员 B 之前提交了代码。代码没有编译,而开发人员 A 已经离开去度周末了。现在开发人员 B 拉取了代码,却无法编译。
周一,他们发现他们的代码根本无法构建,单元测试也没有通过。
或者更糟的是,他们在周五晚上接到一个电话,得知了这个消息。
避免提交二进制文件
源代码控制系统已经存在了一段时间,而且大多数都已经过时了(SourceForge,有人知道吗?),但它们都被用作源代码存储库。
最近,有许多内容管理系统(CMS)使用源代码系统作为内容存储库,用于管理和版本控制网站的资产,例如图像、MP3 和视频。
然而,对于开发人员来说,我们的内容就是源代码。大多数开发人员存储库甚至还没有 Word 文档那么大。
如果开发人员希望通过提交所有内容来保留系统的“快照”,那么这就违背了源代码控制的目的。
对于 .NET 应用程序,编译应用程序意味着 \bin 和 \obj 文件夹将包含程序集。这些程序集在编译时会自动创建,并且不需要提交到存储库。
在大多数源代码控制系统中,存在某种忽略文件,用于在签入代码之前过滤和删除存储库中的膨胀内容。例如,在 Git 中,有一个 .gitignore
文件,它应该包括这些 \bin 和 \obj 目录以及构建解决方案或项目不需要的其他文件类型。
作为一般准则,当您克隆存储库并立即在新机器上构建它时,无论是内部企业项目还是 GitHub 上的开源框架,都不应出现任何错误。
如果将程序集从您自己的项目或第三方项目提交到 Git 是为了使其保持可运行状态,那么您做错了。最好不要将任何二进制文件提交到 Git。
如果您需要第三方库的特定版本,请考虑使用 NuGet 包管理。将 NuGet 包添加到您的项目时,它会自动连接并检索特定版本并将其放入 \bin 文件夹中,使应用程序每次都能编译、构建并成功运行。
使用标签进行版本控制
使用源代码控制时,标签非常有用。事实上,它们是 GitLab Flow 的驱动力。虽然标签对于源代码控制非常有用,但它们也可以用于邪恶目的。例如,一些公司在整个工作流程中使用标签作为注释,这是不推荐的。标签提供了一种在地面上放置标志的方法,以表示“这是版本 x.x.x”。它们是稳定版本的代码快照的标记。这表示此标记处的代码应在不更改代码且不出现错误的情况下构建、编译、测试和运行。
最好在整个工作流程中严格使用标记来对版本进行版本控制。