Git
章节 ▾ 第二版

3.1 Git 分支 - 分支简述

几乎所有版本控制系统都支持某种形式的分支功能。分支意味着你从主开发线分离出来,继续进行工作,而不会影响主开发线。在许多版本控制工具中,这是一个相当昂贵的过程,通常需要你创建源代码目录的新副本,对于大型项目来说,这可能需要很长时间。

有些人将 Git 的分支模型称为其“杀手级功能”,它确实使 Git 在版本控制系统社区中脱颖而出。为什么它如此特别?Git 分支的方式非常轻量级,使得分支操作几乎是瞬时的,在分支之间切换也通常同样快。与许多其他版本控制系统不同,Git 鼓励频繁地分支和合并工作流程,甚至一天内多次进行。理解和掌握此功能为你提供了一个强大而独特的工具,并且可以完全改变你的开发方式。

分支简述

为了真正理解 Git 的分支方式,我们需要退一步,检查一下 Git 如何存储其数据。

你可能还记得在 什么是 Git? 中,Git 不是以一系列变更集或差异的形式存储数据,而是以一系列快照的形式存储数据。

当你进行提交时,Git 会存储一个提交对象,该对象包含指向你所暂存内容快照的指针。此对象还包含作者的姓名和电子邮件地址、你输入的消息以及指向直接在该提交之前的提交或提交(其父提交或父提交)的指针:初始提交为零个父提交,普通提交为一个父提交,合并两个或多个分支的提交为多个父提交。

为了形象化地说明,假设你有一个包含三个文件的目录,你暂存了所有文件并进行了提交。暂存文件会计算每个文件的校验和(我们在 什么是 Git? 中提到的 SHA-1 哈希),将该版本的每个文件存储在 Git 仓库中(Git 将它们称为blob),并将该校验和添加到暂存区

$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'

当您运行 git commit 创建提交时,Git 会对每个子目录(在本例中,仅为根项目目录)进行校验和,并将它们存储为 Git 仓库中的树对象。然后,Git 创建一个提交对象,其中包含元数据以及指向根项目树的指针,以便在需要时重新创建该快照。

您的 Git 仓库现在包含五个对象:三个 *blob*(每个代表三个文件之一的内容)、一个 *tree*(列出目录的内容并指定哪些文件名存储为哪些 blob)以及一个 *commit*,其中包含指向该根树的指针以及所有提交元数据。

A commit and its tree
图 9. 提交及其树

如果您进行一些更改并再次提交,下一个提交将存储指向紧接其前的提交的指针。

Commits and their parents
图 10. 提交及其父级

Git 中的分支只是一个指向这些提交之一的轻量级可移动指针。Git 中的默认分支名称为 master。当您开始进行提交时,您会得到一个指向您最后一次提交的 master 分支。每次提交时,master 分支指针都会自动向前移动。

注意

Git 中的“master”分支不是一个特殊的分支。它与任何其他分支完全相同。几乎每个仓库都有一个的原因仅仅是因为 git init 命令默认创建它,并且大多数人没有费心去改变它。

A branch and its commit history
图 11. 分支及其提交历史

创建新分支

当您创建一个新分支时会发生什么?嗯,这样做会为您创建一个新的指针,您可以四处移动。假设您要创建一个名为 testing 的新分支。您可以使用 git branch 命令来完成此操作

$ git branch testing

这将创建一个新的指针指向您当前所在的同一个提交。

Two branches pointing into the same series of commits
图 12. 两个分支指向同一系列提交

Git 如何知道您当前位于哪个分支?它保留了一个名为 HEAD 的特殊指针。请注意,这与您可能习惯使用的其他 VCS 中的 HEAD 概念大不相同,例如 Subversion 或 CVS。在 Git 中,这是一个指向您当前所在的本地分支的指针。在本例中,您仍然位于 master 上。git branch 命令只创建了一个新分支,但没有切换到该分支。

HEAD pointing to a branch
图 13. HEAD 指向分支

您可以通过运行一个简单的 git log 命令来轻松地看到这一点,该命令会显示分支指针指向的位置。此选项称为 --decorate

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit

您可以看到 mastertesting 分支就在 f30ab 提交旁边。

切换分支

要切换到现有分支,您需要运行 git checkout 命令。让我们切换到新的 testing 分支

$ git checkout testing

这将移动 HEAD 以指向 testing 分支。

HEAD points to the current branch
图 14. HEAD 指向当前分支

这有什么意义呢?嗯,让我们再进行一次提交

$ vim test.rb
$ git commit -a -m 'Make a change'
The HEAD branch moves forward when a commit is made
图 15. 当提交时,HEAD 分支向前移动

这很有趣,因为现在您的 testing 分支已经向前移动,但您的 master 分支仍然指向您运行 git checkout 切换分支时的提交。让我们切换回 master 分支

$ git checkout master
注意
git log 不会一直显示所有分支

如果您现在运行 git log,您可能会想知道您刚刚创建的 "testing" 分支去哪里了,因为它不会出现在输出中。

该分支并没有消失;Git 只是不知道您对该分支感兴趣,它试图向您展示它认为您感兴趣的东西。换句话说,默认情况下,git log 只会显示您已检出的分支以下的提交历史。

要显示所需分支的提交历史,您必须明确指定它:git log testing。要显示所有分支,请将 --all 添加到您的 git log 命令中。

HEAD moves when you checkout
图 16. 检出时 HEAD 移动

该命令做了两件事。它将 HEAD 指针移回指向 master 分支,并将您工作目录中的文件恢复到 master 指向的快照。这也意味着您从此处进行的更改将与项目的旧版本发生分歧。它基本上会倒退您在 testing 分支中完成的工作,这样您就可以朝另一个方向前进。

注意
切换分支会更改您工作目录中的文件

需要注意的是,当您在 Git 中切换分支时,您工作目录中的文件将发生变化。如果您切换到旧的分支,您的工作目录将被恢复到上次在该分支上提交时的样子。如果 Git 无法干净地做到这一点,它将完全不允许您切换。

让我们做一些更改并再次提交

$ vim test.rb
$ git commit -a -m 'Make other changes'

现在您的项目历史已经分叉(参见 分叉历史)。您创建并切换到一个分支,在该分支上完成了一些工作,然后切换回您的主分支并完成其他工作。这两项更改都隔离在单独的分支中:您可以随时在这些分支之间切换,并在准备好时将它们合并在一起。并且您只需使用简单的 branchcheckoutcommit 命令就能完成所有这些操作。

Divergent history
图 17. 分叉历史

您还可以使用 git log 命令轻松地看到这一点。如果您运行 git log --oneline --decorate --graph --all,它将打印出您提交的历史记录,显示您的分支指针在哪里以及您的历史记录是如何分叉的。

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Make other changes
| * 87ab2 (testing) Make a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 Initial commit of my project

因为 Git 中的分支实际上是一个简单文件,其中包含它指向的提交的 40 个字符的 SHA-1 校验和,所以创建和销毁分支非常便宜。创建一个新分支就像将 41 个字节写入一个文件一样简单快捷(40 个字符和一个换行符)。

这与大多数旧的 VCS 工具分支的方式形成鲜明对比,后者涉及将项目的所有文件复制到第二个目录中。这可能需要几秒钟甚至几分钟,具体取决于项目的规模,而在 Git 中,这个过程总是瞬时的。此外,因为我们在提交时记录了父级,所以自动为我们找到合适的合并基点,并且通常非常容易做到。这些功能鼓励开发人员经常创建和使用分支。

让我们看看为什么要这样做。

注意
同时创建新分支并切换到该分支

通常情况下,您会创建一个新分支,并希望同时切换到该新分支——可以使用 git checkout -b <newbranchname> 在一个操作中完成此操作。

注意

从 Git 版本 2.23 开始,您可以使用 git switch 而不是 git checkout

  • 切换到现有分支:git switch testing-branch

  • 创建一个新分支并切换到该分支:git switch -c new-branch-c 标志代表创建,您也可以使用完整标志:--create

  • 返回到您之前检出的分支:git switch -

scroll-to-top