章节 ▾ 第二版

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

基础分支与合并

让我们通过一个简单的分支与合并的例子,演示一个在现实世界中你可能会用到的工作流。你将按照以下步骤进行:

  1. 在网站上做一些工作。

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

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

在这个阶段,你接到一个电话,说另一个问题很关键,你需要一个热修复。你将做以下事情:

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

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

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

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

基础分支

首先,假设你正在开发你的项目,并且已经在 master 分支上提交了几次。

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

你已经决定要处理你在公司使用的任何问题跟踪系统中的 issue #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'

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

接下来,您需要进行一个 hotfix。 让我们创建一个 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 分支

您可以运行测试,确保 hotfix 是您想要的,最后将 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 会通过将指针向前移动来简化事情,因为没有不同的工作需要合并 — 这被称为“快进 (fast-forward)”。

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

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

在部署完您超级重要的修复程序后,您就可以切换回之前中断的工作了。 但是,首先您将删除 hotfix 分支,因为您不再需要它 — master 分支指向相同的位置。 您可以使用 git branch-d 选项删除它。

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

现在您可以切换回 issue #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 中时再集成这些更改。

基本合并

假设您已确定您的 issue #53 工作已完成并准备好合并到您的 master 分支中。 为了做到这一点,您将像之前合并 hotfix 分支一样,将您的 iss53 分支合并到 master 中。 您所要做的就是检出您希望合并到的分支,然后运行 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 分支。 您可以关闭您的 issue 跟踪系统中的 issue,并删除该分支。

$ git branch -d iss53

基本合并冲突

有时,这个过程不会顺利进行。 如果您在要合并的两个分支中以不同的方式更改了同一文件的同一部分,Git 将无法干净地合并它们。 如果您对 issue #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 分支,因为那是您在运行 merge 命令时检出的分支)是该块的顶部(======= 以上的所有内容),而您的 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