Git
章节 ▾ 第二版

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

基本分支与合并

让我们通过一个简单的分支和合并示例来了解在实际场景中可能会用到的工作流程。您将按照以下步骤操作:

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

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

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

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

  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(+)

您会注意到该合并中出现了“快进”短语。因为您合并的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分支中的文件中。如果您需要将其拉取,您可以将master分支合并到iss53分支中,方法是运行git merge master,或者您可以等待将这些更改集成,直到您决定稍后将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 protected]</div>
=======
<div id="footer">
 please contact us at [email protected]
</div>
>>>>>>> iss53:index.html

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

<div id="footer">
please contact us at [email protected]
</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