章节 ▾ 第二版

3.2 Git 分支 - 基础分支与合并

基础分支与合并

让我们通过一个简单的分支与合并的例子,来演示你在实际工作中可能使用的流程。你将遵循以下步骤:

  1. 对网站进行一些工作。

  2. 为你正在处理的新用户故事创建一个分支。

  3. 在该分支上进行一些工作。

在这个阶段,你会接到一个电话,说另一个问题很紧急,需要你立即修复(hotfix)。你将执行以下操作:

  1. 切换到你的生产分支。

  2. 创建一个分支来添加热修复。

  3. 测试通过后,合并热修复分支,并推送到生产环境。

  4. 切换回你原来的用户故事分支并继续工作。

基础分支

首先,假设你正在你的项目上工作,并且 master 分支上已经有一些提交。

A simple commit history
图 18. 简单的提交历史

你决定在公司使用的任何问题跟踪系统中处理问题 #53。要同时创建一个新分支并切换到它,你可以运行带有 -b 选项的 git checkout 命令:

$ git checkout -b iss53
Switched to a new branch "iss53"

这相当于执行以下命令:

$ git branch iss53
$ git checkout iss53
Creating a new branch pointer
图 19. 创建新的分支指针

你在网站上工作并进行了一些提交。这样做会使 iss53 分支向前移动,因为你已经切换到该分支(也就是说,你的 HEAD 指向它)。

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
The `iss53` branch has moved forward with your work
图 20. iss53 分支随着你的工作向前移动

现在你接到电话说网站有问题,需要你立即修复。使用 Git,你不必将你的修复与你已做的 iss53 更改一起部署,也无需在将修复应用到生产环境之前花费大量精力来回滚这些更改。你只需切换回你的 master 分支即可。

然而,在你这样做之前,请注意,如果你的工作目录或暂存区有未提交的更改与你即将切换的分支冲突,Git 将不允许你切换分支。在切换分支时,最好保持工作区是干净的。有几种方法可以解决这个问题(即暂存和修改提交),我们将在 暂存与清理 中稍后介绍。现在,我们假设你已经提交了所有更改,因此你可以切换回你的 master 分支:

$ git checkout master
Switched to branch 'master'

此时,你的项目工作目录与你开始处理问题 #53 之前完全一样,你可以专注于你的热修复。这是一个重要的记忆点:当你切换分支时,Git 会将你的工作目录重置为你在该分支上最后一次提交时的样子。它会自动添加、删除和修改文件,以确保你的工作副本是你上次提交到该分支时的状态。

接下来,你有一个热修复要做。让我们创建一个 hotfix 分支,直到完成修复为止:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
Hotfix branch based on `master`
图 21. 基于 master 的热修复分支

你可以运行测试,确保热修复是你想要的,最后将 hotfix 分支合并回你的 master 分支以部署到生产环境。你使用 git merge 命令来完成此操作:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

你会注意到该合并中出现“快进”(fast-forward)字样。因为你合并的 hotfix 分支所指向的提交 C4 直接位于你当前所在的提交 C2 的前方,Git 只是简单地将指针向前移动。换句话说,当你尝试将一个提交与可以通过跟踪第一个提交的历史记录到达的提交进行合并时,Git 通过向前移动指针来简化操作,因为没有发散的工作需要合并——这被称为“快进”。

你的更改现在位于 master 分支指向的提交快照中,你可以部署修复了。

`master` is fast-forwarded to `hotfix`
图 22. master 快进到 hotfix

在你的重要修复部署后,你就可以切换回中断之前正在进行的工作了。然而,首先你要删除 hotfix 分支,因为你不再需要它——master 分支指向了相同的位置。你可以使用 git branch 命令的 -d 选项来删除它:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

现在你可以切换回你正在处理的问题 #53 分支并继续工作。

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Work continues on `iss53`
图 23. iss53 上的工作继续进行

值得注意的是,你在 hotfix 分支中所做的工作不包含在你 iss53 分支的文件中。如果你需要将其合并进来,可以通过运行 git merge mastermaster 分支合并到你的 iss53 分支中,或者你可以等到稍后决定将 iss53 分支合并回 master 时再集成这些更改。

基础合并

假设你已经决定问题 #53 的工作已完成,并准备合并到你的 master 分支。为此,你将把 iss53 分支合并到 master,就像你之前合并 hotfix 分支一样。你所要做的就是检出你希望合并到的分支,然后运行 git merge 命令:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

这看起来与你之前做的 hotfix 合并有些不同。在这种情况下,你的开发历史从某个较旧的点开始分叉。因为你当前所在分支上的提交不是你正在合并的分支的直接祖先,Git 必须做一些工作。在这种情况下,Git 会执行一次简单的三方合并,使用分支尖端指向的两个快照以及两者的共同祖先。

Three snapshots used in a typical merge
图 24. 典型合并中使用的三个快照

Git 不仅仅是将分支指针向前移动,它还会创建三方合并所产生的新快照,并自动创建一个指向该快照的新提交。这被称为合并提交(merge commit),其特殊之处在于它有多个父提交。

A merge commit
图 25. 合并提交

现在你的工作已合并,你不再需要 iss53 分支。你可以在问题跟踪系统中关闭该问题,并删除该分支:

$ git branch -d iss53

基础合并冲突

有时,这个过程并不顺利。如果你在合并的两个分支中以不同的方式更改了同一个文件的相同部分,Git 将无法干净地合并它们。如果你的问题 #53 修复修改了文件中与 hotfix 分支相同的部分,你将得到一个类似这样的合并冲突:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git 没有自动创建新的合并提交。它暂停了进程,等待你解决冲突。如果你想在合并冲突后随时查看哪些文件未合并,可以运行 git status

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

任何有合并冲突且尚未解决的文件都将列为未合并。Git 会将标准的冲突解决标记添加到有冲突的文件中,因此你可以手动打开它们并解决这些冲突。你的文件包含一个类似这样的部分:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

这意味着 HEAD 中的版本(你的 master 分支,因为这是你运行合并命令时检出的分支)是该块的顶部(======= 之上的所有内容),而你 iss53 分支中的版本看起来像底部所有内容。为了解决冲突,你必须选择其中一边或将内容自行合并。例如,你可能会通过将整个块替换为以下内容来解决此冲突:

<div id="footer">
please contact us at email.support@github.com
</div>

此解决方案包含每个部分的一小部分,并且 <<<<<<<=======>>>>>>> 行已被完全删除。在你解决了每个冲突文件中所有这些部分之后,对每个文件运行 git add 以将其标记为已解决。暂存文件会将其在 Git 中标记为已解决。

如果你想使用图形工具来解决这些问题,可以运行 git mergetool,它会启动一个合适的图形合并工具,并引导你解决冲突:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

如果你想使用除默认工具(在本例中 Git 选择 opendiff 是因为命令在 macOS 上运行)之外的合并工具,你可以在“以下工具之一”之后看到顶部列出的所有支持的工具。只需输入你想要使用的工具名称即可。

注意

如果你需要更高级的工具来解决复杂的合并冲突,我们将在 高级合并 中介绍更多有关合并的内容。

退出合并工具后,Git 会询问你合并是否成功。如果你告诉脚本已成功,它会为你暂存文件以将其标记为已解决。你可以再次运行 git status 来验证所有冲突是否都已解决:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

如果你对此满意,并且你验证了所有有冲突的文件都已暂存,你可以输入 git commit 来完成合并提交。默认情况下,提交消息看起来像这样:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

如果你认为这对于将来查看此合并的其他人会有帮助,你可以修改此提交消息,详细说明如何解决合并,并解释你进行这些更改的原因(如果这些原因不明显)。

scroll-to-top