现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

  1. 开发某个网站。
  2. 为实现某个新的需求,创建一个分支。
  3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理:

  1. 返回到原先已经发布到生产服务器上的分支。
  2. 为这次紧急修补建立一个新分支,并在其中修复问题。
  3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。
  4. 切换到之前实现新需求的分支,继续工作。

分支的新建与切换

git branch git checkout git checkout -b

首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新(见图 3-10)。

图 3-10. 一个简短的提交历史

现在,你决定要修补问题追踪系统上的 #53 问题。顺带说明下,Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 iss53。要新建并切换到该分支,运行git checkout 并加上-b 参数

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

这相当于执行下面这两条命令:

$ git branch iss53
$ git checkout iss53

图 3-11 示意该命令的执行结果。

图 3-11. 创建了一个新分支的指针

接着你开始尝试修复问题,在提交了若干次更新后,iss53 分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 HEAD 指针正指向 iss53,见图 3-12):

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

图 3-12. iss53 分支随工作进展向前推进

现在你就接到了那个网站问题的紧急电话,需要马上修补。有了 Git ,我们就不需要同时发布这个补丁和 iss53 里作出的修改,也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回master 分支。

不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法(分别叫做 stashing 和 commit amending)。目前已经提交了所有的修改,所以接下来可以正常转换到master 分支:

$ git checkout master
Switched to branch "master"

此时工作目录中的内容和你在解决问题 #53 之前一模一样,你可以集中精力进行紧急修补。这一点值得牢记:Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。

接下来,你得进行紧急修补。我们创建一个紧急修补分支 hotfix 来开展工作,直到搞定(见图 3-13):

$ git checkout -b 'hotfix'
Switched to a new branch "hotfix"
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix]: created 3a0874c: "fixed the broken email address"
1 files changed, 0 insertions(+), 1 deletions(-)

图 3-13. hotfix 分支是从 master 分支所在点分化出来的

有必要作些测试,确保修补是成功的,然后回到 master 分支并把它合并进来,然后发布到生产服务器。用 git merge 命令来进行合并:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast forward
README | 1 -
1 files changed, 0 insertions(+), 1 deletions(-)

请注意,合并时出现了“Fast forward”的提示。由于当前 master 分支所在的提交对象是要并入的 hotfix 分支的直接上游,Git 只需把master 分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)

现在最新的修改已经在当前 master 分支所指向的提交对象中了,可以部署到生产服务器上去了(见图 3-14)。

git branch -d

图 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。

在那个超级重要的修补发布以后,你想要回到被打扰之前的工作。由于当前 hotfix 分支和 master 都指向相同的提交对象,所以hotfix 已经完成了历史使命,可以删掉了。使用git branch 的-d 选项执行删除操作:

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

现在回到之前未完成的 #53 问题修复分支上继续工作(图 3-15):

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53]: created ad82d7a: "finished the new footer [issue 53]"
1 files changed, 1 insertions(+), 0 deletions(-)

图 3-15. iss53 分支可以不受影响继续推进。

不用担心之前 hotfix 分支的修改内容尚未包含到 iss53 中来。如果确实需要纳入此次修补,可以用git merge master 把 master 分支合并到iss53;或者等iss53 完成之后,再将iss53 分支中的更新并入 master。

分支的合并 git merge

在问题 #53 相关的工作完成之后,可以合并回 master 分支。实际操作同前面合并 hotfix 分支差不多,只需回到master 分支,运行git merge 命令指定要合并进来的分支:

$ git checkout master
$ git merge iss53
Merge made by recursive.
README | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

请注意,这次合并操作的底层实现,并不同于之前 hotfix 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的提交对象(C4)并不是 iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:

图 3-16. Git 为分支合并自动识别出最佳的同源合并点。

这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建 一个指向它的 提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。

图 3-17. Git 自动创建了一个包含了合并结果的提交对象C6。

既然之前的工作成果已经合并到 master 了,那么 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
index.html: needs merge
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# unmerged: index.html
#

任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。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 mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突:

$ git mergetool
merge tool candidates: kdiff3 tkdiff xxdiff meld gvimdiff opendiff emerge vimdiff
Merging the files: index.html Normal merge conflict for 'index.html':
{local}: modified
{remote}: modified
Hit return to start merge resolution tool (opendiff):

如果不想用默认的合并工具(Git 为我默认选择了 opendiff,因为我在 Mac 上运行了该命令),你可以在上方”merge tool candidates”里找到可用的合并工具列表,输入你想用的工具名。我们将在第七章讨论怎样改变环境中的默认值。

退出合并工具以后,Git 会询问你合并是否成功。如果回答是,它会为你把相关文件暂存起来,以表明状态为已解决。

再运行一次 git status 来确认所有冲突都已解决:

$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# 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.
#

如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。

分支的管理

到目前为止,你已经学会了如何创建、合并和删除分支。除此之外,我们还需要学习如何管理分支,在日后的常规工作中会经常用到下面介绍的管理命令。

git branch 命令不仅仅能创建和删除分支,如果不加任何参数,它会给出 当前所有分支 的清单:

$ git branch
iss53
* master
testing

注意看 master 分支前的 * 字符:它表示 当前所在的分支。也就是说,如果现在提交更新,master 分支将随着开发进度前移。若要查看各个分支最后一个提交对象的信息,运行git branch -v

$ git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes

要从该清单中筛选出你已经(或尚未)与当前分支合并的分支,可以用 --merged--no-merged 选项(Git 1.5.6 以上版本)。比如用git branch --merged 查看哪些分支已被并入当前分支(译注:也就是说哪些分支是当前分支的直接上游。):

$ git branch --merged
iss53
* master

之前我们已经合并了 iss53,所以在这里会看到它。一般来说,列表中没有 * 的分支通常都可以用 git branch -d 来删掉。原因很简单,既然已经把它们所包含的工作整合到了其他分支,删掉也不会损失什么。

另外可以用 git branch --no-merged 查看尚未合并的工作:

$ git branch --no-merged
testing

它会显示还未合并进来的分支。由于这些分支中还包含着尚未合并进来的工作成果,所以简单地用 git branch -d 删除该分支会提示错误,因为那样做会丢失数据:

$ git branch -d testing
error: The branch 'testing' is not an ancestor of your current HEAD.
If you are sure you want to delete it, run 'git branch -D testing'.

不过,如果你确实想要删除该分支上的改动,可以用大写的删除选项 -D 强制执行,就像上面提示信息中给出的那样。

利用分支进行开发的工作流程

现在我们已经学会了新建分支和合并分支,可以(或应该)用它来做点什么呢?在本节,我们会介绍一些利用分支进行开发的工作流程。而正是由于分支管理的便捷,才衍生出了这类典型的工作模式,你可以根据项目的实际情况选择一种用用看。

长期分支

由于 Git 使用简单的三方合并,所以就算在较长一段时间内,反复多次把某个分支合并到另一分支,也不是什么难事。也就是说,你可以同时拥有多个开放的分支,每个分支用于完成特定的任务,随着开发的推进,你可以随时把某个特性分支的成果并到其他分支中。

许多使用 Git 的开发者都喜欢用这种方式来开展工作,比如仅在 master 分支中保留完全稳定的代码,即已经发布或即将发布的代码。与此同时,他们还有一个名为develop 或next 的平行分支,专门用于后续的开发,或仅用于稳定性测试 — 当然并不是说一定要绝对稳定,不过一旦进入某种稳定状态,便可以把它合并到master 里。这样,在确保这些已完成的特性分支(短期分支,比如之前的iss53 分支)能够通过所有测试,并且不会引入更多错误之后,就可以并到主干分支中,等待下一次的发布。

本质上我们刚才谈论的,是随着提交对象不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支总是比较靠前(见图 3-18)。

图 3-18. 稳定分支总是比较老旧。

或者把它们想象成工作流水线,或许更好理解一些,经过测试的提交对象集合被遴选到更稳定的流水线(见图 3-19)。

图 3-19. 想象成流水线可能会容易点。

你可以用这招维护不同层次的稳定性。某些大项目还会有个 proposed(建议)或 pu(proposed updates,建议更新)分支,它包含着那些可能还没有成熟到进入next 或master 的内容。这么做的目的是拥有不同层次的稳定性:当这些分支进入到更稳定的水平时,再把它们合并到更高层分支中去。再次说明下,使用多个长期分支的做法并非必需,不过一般来说,对于特大型项目或特复杂的项目,这么做确实更容易管理。

特性分支

在任何规模的项目中都可以使用特性(Topic)分支。一个特性分支是指一个短期的,用来实现单一特性或与其相关工作的分支。可能你在以前的版本控制系统里从未做过类似这样的事情,因为通常创建与合并分支消耗太大。然而在 Git 中,一天之内建立、使用、合并再删除多个分支是常见的事。

我们在上节的例子里已经见过这种用法了。我们创建了 iss53 和 hotfix 这两个特性分支,在提交了若干更新后,把它们合并到主干分支,然后删除。该技术允许你迅速且完全的进行语境切换 — 因为你的工作分散在不同的流水线里,每个分支里的改变都和它的目标特性相关,浏览代码之类的事情因而变得更简单了。你可以把作出的改变保持在特性分支中几分钟,几天甚至几个月,等它们成熟以后再合并,而不用在乎它们建立的顺序或者进度。

现在我们来看一个实际的例子。请看图 3-20,由下往上,起先我们在 master 工作到 C1,然后开始一个新分支 iss91 尝试修复 91 号缺陷,提交到 C6 的时候,又冒出一个解决该问题的新办法,于是从之前 C4 的地方又分出一个分支iss91v2,干到 C8 的时候,又回到主干master 中提交了 C9 和 C10,再回到iss91v2 继续工作,提交 C11,接着,又冒出个不太确定的想法,从master 的最新提交 C10 处开了个新的分支dumbidea 做些试验。

图 3-20. 拥有多个特性分支的提交历史。

现在,假定两件事情:我们最终决定使用第二个解决方案,即 iss91v2 中的办法;另外,我们把 dumbidea 分支拿给同事们看了以后,发现它竟然是个天才之作。所以接下来,我们准备抛弃原来的iss91 分支(实际上会丢弃 C5 和 C6),直接在主干中并入另外两个分支。最终的提交历史将变成图 3-21 这样:

图 3-21. 合并了 dumbidea 和 iss91v2 后的分支历史。

请务必 牢记 这些分支全部都是 本地分支,这一点很重要。当你在使用分支及合并的时候,一切都是在你自己的 Git 仓库中进行的 — 完全不涉及与服务器的交互。

git 使用详解(9)-- 分支的新建与合并 git branch -d、merge、 --merged/--no-merged/-v的更多相关文章

  1. Git应用详解第三讲:本地分支的重要操作

    前言 前情提要:Git应用详解第二讲:Git删除.修改.撤销操作 分支是git最核心的操作之一,了解分支的基本操作能够大大提高项目开发的效率.这一讲就来介绍一些分支的常见操作及其基本原理. 一.分支概 ...

  2. Git应用详解第七讲:Git refspec与远程分支的重要操作

    前言 前情提要:Git应用详解第六讲:Git协作与Git pull常见问题 这一节来介绍本地仓库与远程仓库的分支映射关系:git refspec.彻底弄清楚本地仓库到底是如何与远程仓库进行联系的. 一 ...

  3. Git详解之分支使用

    前言 几乎每一种版本控制系统都以某种形式支持分支.使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作.在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本 ...

  4. Git 命令详解及常用命令

    Git 命令详解及常用命令 Git作为常用的版本控制工具,多了解一些命令,将能省去很多时间,下面这张图是比较好的一张,贴出了看一下: 关于git,首先需要了解几个名词,如下: 1 2 3 4 Work ...

  5. Git应用详解第二讲:Git删除、修改、撤销操作

    前言 前情提要:Git应用详解第一讲:Git分区,配置与日志 在第一讲中我们对Git进行了简单的入门介绍,相信聪明的你已经了解Git的基本使用了. 这一讲我们来进一步深入学习Git应用,着重介绍Git ...

  6. Git应用详解第四讲:版本回退的三种方式与stash

    前言 前情提要:Git应用详解第三讲:本地分支的重要操作 git作为一款版本控制工具,其最核心的功能就是版本回退,没有之一.熟悉git版本回退的操作能够让你真真正正地放开手脚去开发,不用小心翼翼,怕一 ...

  7. Git应用详解第五讲:远程仓库Github与Git图形化界面

    前言 前情提要:Git应用详解第四讲:版本回退的三种方式与stash 这一节将会介绍本地仓库与远程仓库的一些简单互动以及几款常用的Git图形化界面,让你更加方便地使用git. 一.Git裸库 简单来说 ...

  8. Git初探--笔记整理和Git命令详解

    几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...

  9. iOS开发——开发实战篇&版本控制SVN和Git使用详解

     版本控制SVN和Git使用详解     公司的实际开发中,在天朝使用较多的还是SVN,因为SVN是集中式的,在天朝上班你们都懂的!     -----------------svn--------- ...

随机推荐

  1. Ansible之系列命令详解

    ansible系列命令有:ansible.ansible-doc.ansible-playbook.ansible-vault.ansible-console.ansible-galaxy.ansib ...

  2. (Codeforce)The number of positions

    Petr stands in line of n people, but he doesn't know exactly which position he occupies. He can say ...

  3. VMware Workstation Pro(15.5)下安装Windows_Server_2008_R2

    一.新建虚拟机 1.打开VMware Workstation Pro 15.5虚拟机,点击新建虚拟机 2.选择典型(推荐),单击下一步 3.选最后一个 稍后安装操作系统,点击下一步 4.进来页面,选择 ...

  4. nyoj 47-过河问题 (贪心)

    47-过河问题 内存限制:64MB 时间限制:1000ms Special Judge: No accepted:2 submit:5 题目描述: 在漆黑的夜里,N位旅行者来到了一座狭窄而且没有护栏的 ...

  5. 【SpringBoot | Druid】SpringBoot整合Druid

    SpringBoot整合Druid Druid是个十分强大的后端管理工具,具体的功能和用途请问阿里爸爸 1. 在pom.xml中导入包 <!-- alibaba 的druid数据库连接池 --& ...

  6. ZeroC ICE的远程调用框架 AMI与AMD -Why?

    在Ice有两种异步使用的方式,AMI和AMD.AMI是异步方法调用,AMD是异步方法调度(分派).前者用在代理端,后者用在饲服实现端. AMI其实就是在代理端,使用Future机制进行异步调用,而不阻 ...

  7. Java程序性能优化之性能概述

    性能的基本概念 一).什么叫程序的性能? 程序运行所需的内存和时间. 二).性能的表现形式: 1).执行速度: 程序的反应是否迅速,响应时间是否足够短. 2).启动时间:程序从运行到可以处理正常业务所 ...

  8. 移动端vue项目的图片上传插件

    有一移动端项目,使用的vant-ui.可是vant自带的Uploader似乎不支持一次选择多张图片上传的功能. 于是乎:在https://www.npmjs.com/查找发现找到 vue-upload ...

  9. Android的系统框架的深入认识

    Android采用层次化系统架构,官方公布的标准架构如下图所示.Android由底层往上分为4个主要功能层,分别是linux内核层(Linux Kernel),系统运行时库层(Libraries和An ...

  10. Asis CTF 2016 b00ks理解

    ---恢复内容开始--- 最近在学习堆的off by one,其中遇到这道题,萌新的我弄了大半天才搞懂,网上的很多wp都不是特别详细,都得自己好好调试. 首先,这题目是一个常见的图书馆管理系统,虽然我 ...