Git由浅入深之分支管理

几乎全部的版本控制系统都以分支的方式进行操做,分支是独立于项目主线的一条支线,咱们能够在不影响主线代码的状况下,在分支下进行工做。对于传统的一些版本控制工具来讲,咱们一般须要花费比较多的时间拷贝主线代码,建立一个分支,而且对分支的管理效率也愈来愈不使人满意,而现在备受推崇的Git确实名副其实,Git中的分支很是轻量,咱们能够随时随意建立任意数量的新分支,几乎感受不到什么延时,并且对分支的操做也很高效,如,切换分支,暂存内容,分支合并,分支提交等。javascript

Git分支的不同凡响

上一节咱们提到相对于其余大多数版本控制系统,Git分支是轻量且高效的,为何呢?答案在前几篇已经有提到:传统的版本控制系统存储的数据是文件的变动,而Git则是存储一系列的文件快照(snapshot)。java

Git分支的这些特性,使得分支对咱们几乎没有什么限制,通常针对每个功能或需求均可以随意建立分支,而在传统的版本控制系统,这样几乎是不现实的。git

当咱们向服务器提交数据时,Git会存储一个提交对象(commit object),这个存储对象包括一系列有用信息,详见上一篇中提交对象缓存

Git主干分支(master)

master,有主人,大师的意思,在Git是一般做为主干分支,Git初始化仓库时,默认建立的分支名就是master,就像默认的远端主机别名是origin同样,大多数人不会修改它,这并不说明它与别的分支有什么区别,你能够随意修更名称。服务器

分支类型

在Git中,除了默认的master主干分支,咱们建立的每个分支,通常可分为两种:工具

  • 长运行分支(Long-Running branch):与master并行,长期存在使用的分支,如用以测试项目稳定性或做为主分支;
  • 主题分支(topic branch):针对每个需求或功能或bug而暂时建立的分支,一旦任务完成,便可能回收。

分支指针(HEAD)

Git中有一个HEAD指针,始终指向当前分支,如图可见,项目当前处在master分支,以前一共有三次提交:测试

提交历史记录

上图可见,第一行显示了当前项目全部分支,HEAD -> master代表当前所处分支为master,咱们能够总结以下图:spa

branch

咱们能够在项目根目录.git文件下找到一个HEAD文件:vi .git/HEAD,其内保存了指向当前分支最新提交的指针:3d

HEAD

该指针指向refs/heads/分支名文件,咱们进入.git/refs/heads/目录,其下以分支名为文件名列出了全部分支:版本控制

当前分支head

咱们查看当前分支文件,执行vi master:

当前分支ref

能够看到,其内存储的就是当前分支的最新一次提交对象ID。

建立分支(git branch, git checkout -b)

接下来,假设有一个需求A,咱们建立一个分支work-a:

git checkout -b 分支名复制代码

-b参数声明为建立新分支

建立新分支

新分支HEAD

等价于如下两条指令:

git branch 分支名
git checkout 分支名复制代码

切换分支(git checkout)

git checkout 分支名复制代码

表示切换到该分支,上文提到指定-b配置即说明建立新分支。

注:在切换分支前,必定确保当前分支的修改已经提交或者缓存。

多分支并行

咱们常常会遇到同时须要开发多个功能和需求,或者忽然发现线上bug须要紧急处理,咱们只须要提交当前分支修改,而后切换到主干分支,从其基础上再切出一个新分支fix-bug1:

提交修改

能够看到,在work-a分支上咱们新增了一次提交:b287b8e22470b20cc98e6224a8023708b4cc6989

建立多分支

多分支结构图

如今咱们在fix-bug1分支上修复bug后,进行提交:

提交及历史

能够看到,在fix-bug1分支上多了一个提交:ca270e6,如今整个结构就变成以下图:

结构图

合并分支(git merge)

咱们已经修复了某bug或完成了功能开发,这时要作的是把代码并入主干,,固然通常公司或团队都须要通过代码审查,才能并入主干,在此略过不谈,分支合并相关指令:

git merge 分支名复制代码

该指令告诉Git将指定分支合并到当前分支,固然是可能出现冲突的,咱们按照指示解决冲突,便可。

如今咱们先切换到master分支,而后把fix-bug1分支并入主干:

合并分支

能够看到执行git merge指令后,状态信息显示:

  • 第一行Updating,告诉咱们提交记录更新至ca270e6;
  • 第二行Fast-forward,即快速推动,说明Git直接将当前分支推动到指向新提交对象;
  • 后面是merge的内容信息

合并分以后结构

非快速推动合并(no fast-forward)

如今,咱们再次建立一个分支fix-bug2,并进行几回修改提交:

屡次提交

屡次提交后,状态以下:

屡次提交后结构

咱们经过非快速推动方式合并分支进主干分支:

非快速推动方式合并

如上图,指定--no-ff即声明进行非快速推动合并,第二行的Merge made by the 'recursive' strategy代表经过非快速推动方式合并,咱们发现除了分支上进行的提交记录外,Git建立了一个新的提交对象:7a657a,使用
git log --graph指令查看其信息:

提交记录图

如图,快速推动方式合并入主干的fix-bug1分支的提交记录直接并入主线,且不会建立新的提交对象;而对于非快速推动方式合并的fix-bug2分支,其提交历史也都保存,可是并未进入主线,而是保存了一条支线,同时,在主线上建立一个新的提交对象。

最后描述其结构如图:

非快速推动合并后结构

非快速推动与快速推动合并(fast-forward & no fast-forward)

从上例,对比一下两种方式合并分支的异同:

  • 提交对象都会保存;
  • 报存提交对象方式不一样:快速推动方式是直接在主线(合并主分支)上,添加这些提交对象,即直接移动HEAD指针;而非快速推动方式是将提交对象保存在支线,而后在主线新建一个提交对象,修改HEAD指针及新建提交对象的指针,并且此新建提交对象有两个父提交对象(即有两个parent指针)。
  • 合并后分支指向不一样:快速推动合并后,两个分支将同时指向最新提交对象,而非快速推动合并后,合并主分支指向新建的提交对象,另外一分支指向不变。

咱们查看一下新建立提交对象:

新建立提交对象

能够看到该提交对象中有两个指针指向父提交对象,一个指向主线中的父提交对象,一个指向fix-bug2分支合并而来的支线父提交对象。

三路合并(three-way merge)

除了以前提到的两种合并的状况,其实还存在这样一种状况,就是如今假如我完成了work-a分支的开发,须要将其并入主干,咱们能看到当前master主干分支已经推动到7a6576了,而work-a分支指向b287b8,二者有共同祖先提交对象6d50f6,咱们将其合并:

三路合并

上图第二行代表这次是经过非快速推动方式合并,咱们查看提交对象记录图:

三路合并提交对象记录图

结构如图:

三路合并结构图

咱们发现,三路合并结构是在须要合并的两个分支的最新提交对象的基础上,建立一个新提交对象(4ae14b),将合并主分支(即执行合并指令时,当前所处分支)的HEAD指针前移指向该提交对象,该提交对象有两个父提交对象,分别为合并前待合并分支的最新提交对象(即b287b8和7a657a)。

关于三路合并须要明确:

  • 三路合并实际上是一种非快速推动合并方式;
  • 三路合并的前提是两个分支有共同祖先提交对象;

分支冲突(conflict)

在合并分支,不可避免会发生冲突,当咱们在两个分支对同一文件同一部分进行不一样修改后,发起合并时就会提示有冲突,假设咱们有work-b分支,在其基础上切出新分支work-b-1,而后在两分支上分别对README.md文件同一部分进行不一样修改并提交,而后将work-b-1分支合并到work-b分支:

合并时提示冲突

发现README.md文件有冲突,查看该文件:

查看冲突

如上图,列出了两个分支的不一样修改,HEAD代表当前分支的修改内容,下面是work-b-1分支的修改,咱们选择须要保留的内容,删除其余无关信息和内容,而后保存该文件,查看当前状态:

查看状态

根据提示,解决冲突后提交:

解决冲突

查看分支

对于建立过但并未删除的分支,咱们能够查看分支列表,依然使用git branch指令,不传入任何参数:

分支列表

图中列出了全部分支,前面带星号的表示当前分支,固然咱们还能够查看指明最新提交信息的分支列表,能够添加-v参数:

分支详情列表

筛选分支

除了能够查看全部分支列表,Git还支持筛选已合并或未合并至当前分支的全部分支:

  • --merged参数指明筛选已合并分支;
  • --no-merged参数指明筛选未合并分支。

筛选分支

删除分支(git branch -d)

当分支合并入主干后,也许咱们再也不须要那个分支了,咱们须要将其删除,使用指令:

git branch -d 分支名复制代码

以前介绍到使用git branch是建立新分支,而指定-d参数,说明须要删除该分支:

删除分支

远程分支(remote branch)

咱们注意到,前文所讲述的分支都是存在本地的,即本地分支,还须要了解远程分支,如[remote]/[branch]这种形式,表示是远端主机的某分支,关于远端主机详情请查看,其实远程分支和本地分支基本理论概念仍是相同的,区别是有些指令不一样而已:

git checkout -b test origin/develop复制代码

以上指令即从远程分支(远端主机origin上的develop分支)切出新的本地分支test分支。

跟踪分支(tracking branch)

前文已经介绍了本地分支和远程分支的概念及操做,那么这两类分支之间应该有某种关系将他们关联起来,本地项目都须要与远端主机仓库同步(pull & push),当咱们从一个远程分支切出(建立)一个本地分支时,这个分支就叫跟踪分支(tracking branch),而远程分支叫上游分支(upstream branch)。

当咱们克隆一个远端仓库时,会默认建立一个跟踪分支master,其上游分支就是远端主机别名/master

建立跟踪分支

建立跟踪分支指令以下:

git checkout -b 本地分支名 远端主机别名/远程分支名复制代码

固然也能够不指定分支名,使用远程分支同名:

git checkout --track 远端主机别名/远程分支名复制代码
修改跟踪关系

有时候,可能须要为本地分支设置其上游分支,添加-u参数:

git branch -u 远端主机别名/远程分支名复制代码

以上指令就指明当前分支跟踪某远端主机的远程分支。

查看跟踪分支(git branch -vv)

使用如下指令查看分支的上游分支:

git branch -vv复制代码

跟踪分支列表

上图输出信息第二行代表master分支跟踪远程origin/master分支,ahead 7代表本地有7个提交未推到服务器,其余分支不是跟踪分支,没有上游分支。

删除远程分支

对于再也不须要的远程分支,是能够删除的:

git push origin --delete test复制代码

以上指令删除远端主机origin的test分支,可是在垃圾回收以前,Git服务器仍然会保留分支数据,咱们能够很方便的恢复数据,以后会详细介绍。

变基(rebase)

Git中有两种方式整合不一样分支的修改:第一种是前文介绍的合并(merge),另外一种就是本节的主题变基(rebase)。

变基其实与前文提到的三路合并(three-way merge)很有渊源:

三路合并结构图

如图work-a分支与主干master分支合并后,建立一个新提交对象,咱们还能够经过变基完成两个分支的修改整合,因为work-a分支已合并到master分支,咱们在work-a分支再提交一次修改e0ae7dc,而后咱们将work-a分支对master分支进行变基:

分支变基

执行变基时,因为两个分支对同一文件同一部分进行了不一样修改,会提示冲突,须要解决冲突,咱们修改文件解决冲突,而后查看状态:

解决冲突后变基状态

上图,第一行rebase in progress; onto 4ae14b3说明当前分支针对4ae14b3快照进行变基,第三到第五行分别说明:

  • 第三行:解决冲忽然后执行git rebase --continue指令继续变基;
  • 第四行:执行git rebase --skip指令,跳过解决冲突;
  • 第五行:执行git rebase --abort指令,终止变基,回到分支变基前状态。

下面第6到第八行说明:

  • 第七行:使用git reset HEAD <file>指令撤销某文件变动;
  • 第八行:使用git add <file>指令标记冲突为已解决状态。

最后一行no changes added to commit (use "git add" and/or "git commit -a"),说明还没有标记冲突,须要使用指令标记变动,在继续执行变基:

解决冲突继续变基

变基后历史记录图

如上图,变基后,在主线上建立新提交对象640b83,并修改work-a分支指针指向该提交对象:

变基后结构图

以后咱们能够正常的合并:

变基后合并

如图,主线分支更新提交对象到640b83a,第二行Fast-forward说明这次合并属于快速推动合并方式,结构以下:

变基合并后结构图

三路合并与变基

基于上例,三路合并,整合修改变动后会保留分支的原始提交记录,新建立提交对象有两个父提交对象,一个在主线上,一个在待合并分支上;而变基则不能保留待合并分支的原始提交记录,主线上新建的提交对象只有一个位于主线上的父提交对象。更多变基相关内容计划单独出文介绍。

至于到底选用哪一种方式整合变动,变基仍是合并,这个一直有争论,没有哪种方式绝对合理,咱们只须要把握一个原则:不管变基仍是合并,你应该只操做本地历史记录,任何已经推到服务器并入主干的内容和提交历史不该该更改。

更多相关阅读请移步

相关文章
相关标签/搜索