Skip to content

9.3 分支管理

分支是 Git 最强大的特性,借助分支我们可以随时在主线任务中开辟分线任务,使多个任务同步进行。在某个分线任务完成后,我们还可以将其合并回主线,这对多任务协同推进非常有利。

在团队协作中,分支还可用于管理工作流程。我们可以决定哪个分支用于开发,哪个分支用于测试,并对分支的拉取、推送、合并等设置权限,这样不同阶段的工作在不同的分支上进行,可以有条不紊地推进项目进度,避免协作混乱。

9.3.1 分支简介

大多版本控制系统都提供了分支的功能,但 Git 的分支无疑是其中最轻量的一个。使用过 Git 分支的朋友们会有这种体验:分支的创建和切换非常快速,仿佛瞬间完成,这得益于它精妙的设计。

那么分支是如何实现的呢?其实很简单,Git 中的分支只是一个指向某个提交的可变指针(指针可以理解为索引)。当我们创建一个新提交时,指针向前一位;当我们回退提交时,指针再后移一位 ——— 表示分支的指针会随着提交改变而移动。

Git 初始化后默认会创建主分支,名为 master(最新版改为 main)。如果没有新建分支,那么所有的提交都是在主分支上进行。当创建一个新分支时,Git 会再创建一个指针指向同一个提交,此时两个分支是一样的,如图 10-x 所示:

当 Git 中存在多个分支时,Git 如何标记哪个是当前分支呢?这里又引出了另一个特殊指针 ——— HEAD。HEAD 指针会指向某一个分支,被指向的分支就是当前分支。因此切换分支的原理也简单,只不过是修改 HEAD 指针的指向而已。

了解了分支创建和切换的原理,接下来在实践中尝试。

9.3.2 分支创建,删除,切换

Git 管理分支使用 “git branch” 命令来实现。第一个要用到的命令是查看当前分支,命令如下:

sh
$ git branch --show-current
main

上面命令打印出当前分支是 main,这是我们的默认分支,在这个分支上已经创建了多个提交。现在创建一个新的 test 分支,使用以下命令:

sh
$ git branch test
$ git checkout test

这里用到了两个命令:第一个命令是创建 test 分支,第二个命令是切换到 test 分支。因为创建分支后默认不会切换,为了使用便捷,还可以将这两个命令合并为一个:

sh
$ git checkout -b test

切换到 test 分支后,使用 “git log” 查看提交记录,发现之前在主分支 main 上创建的提交都在,此时 test 分支与 main 分支一摸一样。这是因为创建分支时会基于当前分支创建,本质上是把当前分支的快照复制了一份,因此当前分支的提交记录等信息也会保留。

分支创建后,我们在新的分支上创建一个提交。修改任意一处代码,然后创建提交:

sh
$ git add .
$ git commit -m 'test first commit'

假设创建后的 commitId 是 “4f41e5c”,现在我们再切换回主分支:

sh
$ git checkout master

执行完命令后,你会立刻发现编辑器里的修改消失了。使用 “git log” 查看,果然 “4f41e5c” 这个提交没有了,这说明新分支创建的提交,不会与另一个分支同步,但创建分支之前的提交是共享的。

事实上,提交并不是不存在了,而是不属于当前分支管理。我们使用 “git show 4f41e5c” 命令依然可以查看到这个提交的信息。不过因为这个提交不在当前分支上,因此也无法对该提交进行操作。

现在我们删除 test 分支,使用以下命令:

js
$ git branch -d test
error: The branch 'test' is not fully merged

执行命令后报错了,错误信息提示我们 test 分支上存在提交未合并,不允许删除。这是因为 Git 发现 “4f41e5c” 这个提交在 master 分支上不存在,为了避免我们丢失代码,建议我们先合并再删除。

如果我们非常确认要删除这个分支,那么使用以下命令强制删除:

js
$ git branch -D test

注意:删除某个分支,必须要切换到另一个分支,不可删除当前分支。

9.3.3 分支合并

上一步我们在删除分支时遇到了提示合并的情况,而“分支合并”正是分支管理中极其重要且最容易出错的部分,本小节我们来详细介绍如何合并分支。

分支合并是指将 A 分支的提交合并到 B 分支上,在 B 分支上将两者的提交整合起来。分支合并使用 “git merge” 命令来实现,对于两个分支上不同的提交结构,可以分为以下三种合并方式。

  1. 快进(Fast Forward)

假设 A 分支有 a,b,c,d 四个提交,B 分支有 a,b 两个提交。现在将 A 分支合并到 B 分支上,方法如下:

sh
$ git checkout B # 切换到 B 分支
$ git merge A # 合并 A 分支到 B 分支

合并之后,B 分支的提交就变成了 a,b,c,d。在这次合并的过程中不会出现冲突,也没有生成额外的提交,因为两个分支在同一条基线上,没有分叉,只是将 A 分支前进的部分追加到了 B 分支。从原理的角度来说,就是将 B 分支的指针前进了两位,没有其他操作。

这里要解释一下,何为分叉?简单来说,当两个分支上都存在各自唯一(其他分支没有)的提交,此时就产生了分叉。

上面例子中 B 分支的所有提交在 A 分支中都存在,因此两个分支没有产生分叉。这种情况在 Git 中就被称为快进(Fast Forward)。

  1. 非快进,无冲突

假设 A 分支有 a,b,c 三个提交,B 分支有 a,b,f 三个提交,很显然两个分支出现了分叉。现在将 A 分支合并到 B 分支上,方法照旧:

sh
$ git checkout B
$ git merge A

执行命令后,终端会进入编辑模式,显示文本 “Merge branch 'A'”。这其实是 Git 自动生成的一个表示合并的提交,文本内容就是提交描述。保存并退出后,这个提交就创建了,我们把这个提交标记为 m。

现在使用 “git log” 查看提交,会发现 B 分支的提交记录变成了这样:a,b,f,c,m。相当于合并过来了提交 c,并创建了提交 m ——— 这种情况会多创建一个 m 提交。

再使用 “git status” 查看状态,发现工作区是干净的。这表示虽然两个分支产生了分叉,但是 c 提交和 f 提交中没有对同一文件的不同修改,因此没有产生冲突。

  1. 非快进,有冲突

还是以前面的 A、B 分支为例,提交记录一摸一样,区别是 c 提交和 f 提交中存在对于同一文件的不同修改。当我们以相同的方式合并,此时就会产生冲突,合并被终止,并将冲突的文件状态标记为未暂存,提示我们在工作区中解决冲突后再合并。

这很合理,因为 Git 发现了两个版本中有不同的修改,自然要询问我们想要应用哪个版本。在上图中可以看到,当前分支的版本被称为“当前更改”,合并而来的版本被称为“传入的更改”。我们选择一个版本,这个版本的修改就会被应用。

解决冲突之后,我们将修改加入暂存区并重新提交:

sh
$ git add .
$ git commit -m '修复合并冲突'

提交之后,合并自动完成。注意:本次提交其实也是一个 m 提交,只是因为有冲突,需要我们解决冲突并手动提交;而当没有冲突时,这个提交是自动创建的。

9.3.4 分支管理策略

分支的创建、切换、合并非常灵活,这让我们充分利用 Git 的强大能力。但是在一些需要团队协作的中大型项目中,Git 的灵活性又会带来不小的麻烦。比如分支庞大杂乱,成员之间随意合并,会带来各种覆盖,冲突,丢失等问题。

为了解决这种情况,我们必须制定分支使用的标准和规范,这就是分支管理策略,也叫工作流(Workflow)。当前市面上已经有成熟的工作流模式,主要包含三种:

  • Git Flow
  • GitHub Flow
  • GitLab Flow

我们以最基础的 Git Flow 为例,介绍这个工作流中定义了哪些使用 Git 分支的规范。

Git flow

Git flow 是最早诞生工作流,它规定了项目中存在两个长期分支:

  • master:主分支,包含已经发布的最新代码。
  • develop:开发分支,包含开发完成的最新代码。

日常开发都是在 develop 分支上进行,当某个功能或某个修改需要发布时,就将其合并到 master 分支,然后将 master 分支的最新代码部署。master 分支一般落后于 develop 分支,因为 develop 分支上存在已开发但未部署的提交。

通常情况下,当功能开发完成需要发布时,将 develop 分支合并到 master 分支即可。

除了两个长期分支,针对 develop 分支还有两个短线分支:

  • feat-*:功能分支(feature branch)
  • fix-*:补丁分支(hotfix branch)

假设我发现了一个 bug,此时要解决它,不能直接在 develop 分支上修改,正确的做法是创建一个新分支 “fix-abug” 来修改。这个分支名统一以 “fix-” 为前缀,这样我们可以一眼看出它是一个补丁分支。当修改完成后,将这个分支合并回 develop 分支,并删除。

同理,当需要新增功能时,新建一个 “feat-new” 分支来开发新功能,开发完成后合并回 develop 分支并删除。因为短线分支只承担临时的开发任务,当完成后就要删除,从而避免分支混乱。

Git flow 是一个基本的工作流,当然我们也可以根据实际情况修改这个工作流,目的就是更规范更合理的推进开发,避免问题。比如我们有严格的测试流程,那么还可以添加一个长期分支 staging 表示测试,测试通过后再合并到 master,这样更完善了。