几乎全部的版本控制系统都以分支的方式进行操做,分支是独立于项目主线的一条支线,咱们能够在不影响主线代码的状况下,在分支下进行工做。对于传统的一些版本控制工具来讲,咱们一般须要花费比较多的时间拷贝主线代码,建立一个分支,而且对分支的管理效率也愈来愈不使人满意,而现在备受推崇的Git确实名副其实,Git中的分支很是轻量,咱们能够随时随意建立任意数量的新分支,几乎感受不到什么延时,并且对分支的操做也很高效,如,切换分支,暂存内容,分支合并,分支提交等。javascript
上一节咱们提到相对于其余大多数版本控制系统,Git分支是轻量且高效的,为何呢?答案在前几篇已经有提到:传统的版本控制系统存储的数据是文件的变动,而Git则是存储一系列的文件快照(snapshot)。java
Git分支的这些特性,使得分支对咱们几乎没有什么限制,通常针对每个功能或需求均可以随意建立分支,而在传统的版本控制系统,这样几乎是不现实的。git
当咱们向服务器提交数据时,Git会存储一个提交对象(commit object),这个存储对象包括一系列有用信息,详见上一篇中提交对象。缓存
master,有主人,大师的意思,在Git是一般做为主干分支,Git初始化仓库时,默认建立的分支名就是master,就像默认的远端主机别名是origin同样,大多数人不会修改它,这并不说明它与别的分支有什么区别,你能够随意修更名称。服务器
在Git中,除了默认的master主干分支,咱们建立的每个分支,通常可分为两种:工具
Git中有一个HEAD指针,始终指向当前分支,如图可见,项目当前处在master分支,以前一共有三次提交:测试
上图可见,第一行显示了当前项目全部分支,HEAD -> master
代表当前所处分支为master,咱们能够总结以下图:spa
咱们能够在项目根目录.git文件下找到一个HEAD文件:vi .git/HEAD
,其内保存了指向当前分支最新提交的指针:3d
该指针指向refs/heads/分支名文件,咱们进入.git/refs/heads/目录,其下以分支名为文件名列出了全部分支:版本控制
咱们查看当前分支文件,执行vi master
:
能够看到,其内存储的就是当前分支的最新一次提交对象ID。
接下来,假设有一个需求A,咱们建立一个分支work-a:
git checkout -b 分支名复制代码
-b
参数声明为建立新分支
等价于如下两条指令:
git branch 分支名
git checkout 分支名复制代码
git checkout 分支名复制代码
表示切换到该分支,上文提到指定-b
配置即说明建立新分支。
注:在切换分支前,必定确保当前分支的修改已经提交或者缓存。
咱们常常会遇到同时须要开发多个功能和需求,或者忽然发现线上bug须要紧急处理,咱们只须要提交当前分支修改,而后切换到主干分支,从其基础上再切出一个新分支fix-bug1:
能够看到,在work-a分支上咱们新增了一次提交:b287b8e22470b20cc98e6224a8023708b4cc6989
。
如今咱们在fix-bug1分支上修复bug后,进行提交:
能够看到,在fix-bug1分支上多了一个提交:ca270e6
,如今整个结构就变成以下图:
咱们已经修复了某bug或完成了功能开发,这时要作的是把代码并入主干,,固然通常公司或团队都须要通过代码审查,才能并入主干,在此略过不谈,分支合并相关指令:
git merge 分支名复制代码
该指令告诉Git将指定分支合并到当前分支,固然是可能出现冲突的,咱们按照指示解决冲突,便可。
如今咱们先切换到master分支,而后把fix-bug1分支并入主干:
能够看到执行git merge
指令后,状态信息显示:
ca270e6
;如今,咱们再次建立一个分支fix-bug2,并进行几回修改提交:
屡次提交后,状态以下:
咱们经过非快速推动方式合并分支进主干分支:
如上图,指定--no-ff
即声明进行非快速推动合并,第二行的Merge made by the 'recursive' strategy
代表经过非快速推动方式合并,咱们发现除了分支上进行的提交记录外,Git建立了一个新的提交对象:7a657a
,使用git log --graph
指令查看其信息:
如图,快速推动方式合并入主干的fix-bug1分支的提交记录直接并入主线,且不会建立新的提交对象;而对于非快速推动方式合并的fix-bug2分支,其提交历史也都保存,可是并未进入主线,而是保存了一条支线,同时,在主线上建立一个新的提交对象。
最后描述其结构如图:
从上例,对比一下两种方式合并分支的异同:
咱们查看一下新建立提交对象:
能够看到该提交对象中有两个指针指向父提交对象,一个指向主线中的父提交对象,一个指向fix-bug2分支合并而来的支线父提交对象。
除了以前提到的两种合并的状况,其实还存在这样一种状况,就是如今假如我完成了work-a分支的开发,须要将其并入主干,咱们能看到当前master主干分支已经推动到7a6576
了,而work-a分支指向b287b8
,二者有共同祖先提交对象6d50f6
,咱们将其合并:
上图第二行代表这次是经过非快速推动方式合并,咱们查看提交对象记录图:
结构如图:
咱们发现,三路合并结构是在须要合并的两个分支的最新提交对象的基础上,建立一个新提交对象(4ae14b),将合并主分支(即执行合并指令时,当前所处分支)的HEAD指针前移指向该提交对象,该提交对象有两个父提交对象,分别为合并前待合并分支的最新提交对象(即b287b8和7a657a)。
关于三路合并须要明确:
在合并分支,不可避免会发生冲突,当咱们在两个分支对同一文件同一部分进行不一样修改后,发起合并时就会提示有冲突,假设咱们有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
参数,说明须要删除该分支:
咱们注意到,前文所讲述的分支都是存在本地的,即本地分支,还须要了解远程分支,如[remote]/[branch]这种形式,表示是远端主机的某分支,关于远端主机详情请查看,其实远程分支和本地分支基本理论概念仍是相同的,区别是有些指令不一样而已:
git checkout -b test origin/develop复制代码
以上指令即从远程分支(远端主机origin上的develop分支)切出新的本地分支test分支。
前文已经介绍了本地分支和远程分支的概念及操做,那么这两类分支之间应该有某种关系将他们关联起来,本地项目都须要与远端主机仓库同步(pull & push),当咱们从一个远程分支切出(建立)一个本地分支时,这个分支就叫跟踪分支(tracking branch),而远程分支叫上游分支(upstream branch)。
当咱们克隆一个远端仓库时,会默认建立一个跟踪分支master,其上游分支就是远端主机别名/master
。
建立跟踪分支指令以下:
git checkout -b 本地分支名 远端主机别名/远程分支名复制代码
固然也能够不指定分支名,使用远程分支同名:
git checkout --track 远端主机别名/远程分支名复制代码
有时候,可能须要为本地分支设置其上游分支,添加-u
参数:
git branch -u 远端主机别名/远程分支名复制代码
以上指令就指明当前分支跟踪某远端主机的远程分支。
使用如下指令查看分支的上游分支:
git branch -vv复制代码
上图输出信息第二行代表master分支跟踪远程origin/master分支,ahead 7代表本地有7个提交未推到服务器,其余分支不是跟踪分支,没有上游分支。
对于再也不须要的远程分支,是能够删除的:
git push origin --delete test复制代码
以上指令删除远端主机origin的test分支,可是在垃圾回收以前,Git服务器仍然会保留分支数据,咱们能够很方便的恢复数据,以后会详细介绍。
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
说明这次合并属于快速推动合并方式,结构以下:
基于上例,三路合并,整合修改变动后会保留分支的原始提交记录,新建立提交对象有两个父提交对象,一个在主线上,一个在待合并分支上;而变基则不能保留待合并分支的原始提交记录,主线上新建的提交对象只有一个位于主线上的父提交对象。更多变基相关内容计划单独出文介绍。
至于到底选用哪一种方式整合变动,变基仍是合并,这个一直有争论,没有哪种方式绝对合理,咱们只须要把握一个原则:不管变基仍是合并,你应该只操做本地历史记录,任何已经推到服务器并入主干的内容和提交历史不该该更改。