Git分支
1 分支简介
1.1 提交对象(commit object)
Git在进行提交操作的时候,会保存一个提交对象,该提交对象包含的信息有:
- 指向暂存内容快照的指针
- 这里简单说一下快照这一概念(本人也不太懂)
- 快照是数据存储的某一时刻的状态记录;备份则是数据存储的某一个时刻的副本
- 快照仅仅记录逻辑地址和物理地址的对应关系
- 备份就是将物理数据做一次复制
- 我们只要知道快照速度快很多,通常情况下占用的空间比备份少很多就行了。如果要研究清楚就得使用搜索引擎之类的了
- 作者的姓名和电子邮箱
- 提交时的提交说明
- 指向它的父对象的指针。当然首次提交的提交对象没有父对象,其余的提交对象都有至少一个父对象
1.2 Git是如何保存数据的
-
为了形象地说明Git是如何保存数据的,我们先假设我们现在有一个工作目录,里面包含三个将要被暂存和提交的文件;
-
如果我们这时候执行暂存操作,那么暂存操作就会为每一个文件计算校验和,然后把当前版本的文件快照保存到Git仓库中(Git使用blob对象来保存它们),最后将校验和放入暂存区域中等待提交。如果我们对上面的三个文件都进行了暂存操作,此时Git仓库中就新增了三个blob对象;
-
随后如果我们进行提交操作,这是Git会先计算每一个子目录的校验和(此时只有一个子目录就是根目录),其后将这些校验和以树对象的形式保存在Git中。然后再创建一个提交对象,提交对象包含上述的信息,此外该对象还保存着一个指向刚才那个树对象的指针。如此一来,Git就可以在需要的时候重现此次保存的快照
-
最终可用下图表示初次提交后各个对象之间的关系
-
稍作修改之后再次提交,那么这次的提交对象就会包含一个指向上一次的提交对象(父对象)的指针,如下图所示
1.3 分支的实质
Git的分支实质上就是指向提交对象的可变指针。Git的默认分支是master,在多次提交之后,我们其实已经拥有了一个指向最后一个提交对象的master分支。
- 注 Git的master分支和其他的分支实质上没有任何区别,它也没有任何特殊的地方。之所以用的多,是因为
git init
会默认创建它,然后人们懒得改。
2 分支的创建
-
注 本文的图片大部分来自Git官方的中文教程中的图片
-
在简单了解了分支的概念之后,我们就应该很想知道分支是怎么创建的。
-
实际上这很简单,我们只需要使用
git branch <branchName>
命令就可以创建一个分支了。 -
执行了上面的命令之后,
-
Git实际上就新建了一个可以移动的指向提交对象的指针。例如
$ git branch testing
这个命令就新建了一个
testing
的指针指向当前的提交对象(也就是最新的提交对象)。如下图所示 -
那么Git是如何知道当前在哪一个分支上呢?实际上在Git中还有一个名为
HEAD
的特殊指针,该指针指向当前所在的本地分支,如下图所示 -
使用
git branch <branchName>
命令只是新建了一个分支,但是并不会自动切换到新建的分支中去。也就是HEAD
指针不会自动指向该新建的指针。 -
我们可以使用
git log --decorate
命令查看当前的各个分支所指向的提交对象。
3 切换分支
-
上面我们已经学习了如何新建一个分支,我们应该怎么样切换到这些已存在的分支上呢?
-
我们需要使用
git checkout <branchName>
来切换到分支testing上。例如$ git checkout testing
-
命令执行之后它就会提示我们现在已经切换到testing上了
-
根据上面的讲述,我们应该知道了分支的切换,实际上就是
HEAD
指针改变了它所指向的对象。这里在执行该指令之后,HEAD
就指向了testing
。如下图所示
-
-
然后我们在
testing
分支上做一些修改然后再提交- 如图所示,
testing
分支向前移动了,但是master
分支却没有,依然停留在原地。
- 如图所示,
-
然后我们切换回到
master
分支$ git checkout master
-
此时,
HEAD
就会指向master
。如下图所示 -
这一条命令做了两件事:
- 使
HEAD
指向master
- 将工作目录中的文件内容恢复为
master
分支所指向的快照的内容
- 使
-
也就是说,你现在切换回来的话,你就相当于回退到了一个较老的版本,它会忽略掉
testing
分支所做的修改
-
-
从上面的描述来看,分支的切换,是会改变我们工作目录中的内容的。如果Git不能干净利落地改变工作目录中的内容,也就是在切换过程中如果可能使文件内容混淆错乱,这时候Git将会禁止分支的切换。换句话说,Git中进行分支的切换时可以保证数据的准确性的,否则Git会阻止这种会导致工作区数据内容错乱的切换。
-
随后,我们又在
master
分支上进行了修改和提交,这时候,项目的提交历史就会产生分叉,如下图所示 -
我们可以不断地在分支之间进行切换和工作,这些分支之间的提交互不干扰,因此在它们上进行的工作也互不干扰。在实际成熟之后,我们还可以把它们合并起来。
-
使用
git log --oneline --decorate --graph --all
命令可以查看项目的整个提交历史,项目的分支分叉情况,以及各个分支的指向。 -
Git分支实质上是仅包含其所指对象的检验和(包含40个字符的SHA-1值)的文件,因此它的创建和销毁都异常高效。创建一个新分支就相当于在文件中写入41个字节。
-
在创建分支的同时切换到创建的分支的命令如下
git checkout -b <newBranchName>
4 分支的新建和合并
-
首先我们在
master
分支上已经进行了一些工作,产生了一些提交,如下图 -
然后可能想开发一个新功能,我们新建一个分支
iss53
来进行这个开发工作$ git checkout -b iss53
-
结果如图
-
-
然后我们在
iss53
分支上进行了一些工作并进行了一次提交,就变成如下这样 -
这时候我们发现
master
上存在bug,那么这个时候,分支的优势就体现出来了,因为我们如果想修复bug,只需要将分支切换回master
就可以将工作区中的内容恢复到当时的版本了,而不是需要将iss53
分支上的修改撤销。$ git checkout master
- 注 我们在切换分支的时候务必确保当前所在的分支上的所有工作都已经进行了提交,否则会导致分支的切换失败
-
然后我们创建一个新分支
hotfix
用于修复bug$ git branch hotfix $ git checkout hotfix
-
随后在其上进行了一些工作,进行了一次提交,就变成下面这样
-
4.1 分支的快进合并
-
然后经过测试,我们发现,
C4
的修改把bug修复了,然后我们就可以将hotfix
分支合并回到master
上-
首先我们要将分支切换到我们想要并入的分支,本例中就是
master
分支$ git checkout master
-
然后执行合并命令
git merge <branchName>
,此处的branchName是要合并进来的分支,本例中是hotfix
分支$ git merge hotfix
-
执行命令之后的输出信息如下
$ git checkout master $ git merge hotfix Updating f42c576..3a0874c Fast-forward index.html | 2 ++ 1 file changed, 2 insertions(+)
-
-
在上面的合并输出的时候,我们应该有注意到这样的一个词语Fast-forward。这个词语打含义是
-
当我们试图合并两个分支的时候,如果顺着一个分支走下去能够到达另外一个分支,则在合并这两个分支的时候,只会将落后的指针向前推进到另外一个指针的地方
-
因为这样的合并没有需要解决的分歧,所以叫做快进
-
快进合并之后的结果如图所示
-
-
在完成了合并之后,我们就可以删除其中的某一个分支,例如本例中,要把
hotfix
分支删除,则我们就可以通过git branch -d <branchName>
命令进行删除操作。本例为$ git branch -d hotfix
-
此时,分支的情况就变成了下图这样
-
-
然后我们又切换回到
iss53
分支上继续进行开发,如下图所示 -
这里需要注意的是,我们在
hotfix
分支上所做的修改并没有包含在iss53
分支上。如果我们需要其中的修改数据,我们可以使用git merge master
命令将master
分支合并到iss53
分支上。另外我们也可以等iss53
分支的开发任务完成之后再把它合并回到master
分支上。
4.2 分支的分叉合并
-
假设此时我们已经完成了开发,打算将
iss53
分支合并到master
分支上,这时候和前面的合并操作没有什么分别-
切换到
master
分支$ git checkout master
-
把
iss53
分支合并进来$ git merge iss53
-
然后可以将多余的分支
iss53
删除$ git branch -d iss53
-
-
但是这次合并的输出信息和和上次合并
hotfix
分支的情况有点不一样,如下所示$ git checkout master Switched to branch 'master' $ git merge iss5