章节 ▾ 第二版

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

基本分支与合并

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

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

  2. 为正在进行的新用户故事创建一个分支。

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

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

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

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

  3. 测试完成后,合并紧急修复分支,并推送到生产环境。

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

基本分支

首先,假设你正在进行你的项目,并且在 master 分支上已经有了几个提交。

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

你决定要处理问题 #53,无论你的公司使用什么问题跟踪系统。要同时创建一个新分支并切换到它,你可以使用 git checkout 命令和 -b 选项:

$ 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 将不允许你切换分支。在切换分支时,最好保持干净的工作状态。有一些方法可以绕过这个问题(即,stashingcommit amending),我们将在后面的 Stashing and Cleaning 部分介绍。现在,让我们假设你已经提交了所有的更改,所以你可以切换回你的 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 的紧急修复分支

你可以运行你的测试,确保紧急修复符合你的要求,最后通过 git merge 命令将 hotfix 分支合并回你的 master 分支以部署到生产环境。

$ 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 master 将你的 master 分支合并到你的 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 没有仅仅向前移动分支指针,而是创建了一个由这个三方合并产生的新快照,并自动创建一个指向它的新提交。这被称为合并提交,其特殊之处在于它有多个父提交。

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 选择的默认工具以外的合并工具(在这种情况下,因为命令是在 macOS 上运行的,Git 选择 opendiff),你可以在“以下工具之一”的顶部找到所有支持的工具列表。只需输入你更喜欢的工具的名称即可。

注意

如果你需要更高级的工具来解决棘手的合并冲突,我们将在 Advanced Merging 部分更详细地介绍合并。

退出合并工具后,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
#

如果你认为这能帮助未来查看此合并的人,你可以修改此提交消息,添加有关你如何解决合并以及你为什么进行这些更改的详细信息(如果这些更改不明显)。