图解git,用手绘图带你理解git中分支的原理和应用
大家好,今天我们来聊git当中一个非常非常重要的特性,就是branch。
git branch可以说是git当中最重要的概念了,甚至没有之一。因为git最重要的使用场景就是协同开发,大家一起在一个项目当中开发不同的功能。正是由于有了分支的概念,可以让大家在开发的时候互不影响。如果没有这个功能,git的其他功能做的再好,可能都没有用。
所以某种程度上可以理解为,学git最重要的就是学习分支的相关内容。当然分支的相关内容和命令非常多,我们想要瞬间全部都学会显然不太现实。但对这个概念有一些理解,懂得一些基本命令的用法应该还是做得到的。
在理解分支这个概念之前,我们需要先来介绍一下Git的结构。
Git的结构
branch的英文就是树枝,后来衍生出了分叉、支路等意思。这个单词非常形象,因为git仓库的所有提交节点之间的关系,其实就是一棵树,所以一个分支也可以看成是树上的一条链路。但是这样有一个小问题是,如果说分支是一条链路的话,那么这个链路上的每一个节点代表的是一个commit提交,意味着一份代码快照,有些像是游戏存档。
一个branch上有多个commit,一个游戏也可以有多个存档,但是当下显然只能加载一个。所以git当中用一个指针指向当前加载的commit,也就是说纵向来看一个分支代表的是一连串的提交,但在git当中我们使用的分支其实是一个指针,一个在commit当中切换的指针。
我们来看个例子,比如一开始的时候我们只有默认分支master,它指向当前的一个提交。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1fw484baj30vk09caao.jpg)
现在我们使用git branch test命令创建一个测试分支,执行之后,其实只不过是多了一个指针也指向当前的commit。git当中的结构变成这样:
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1fy59uv5j30r00asmy1.jpg)
当我们在test分支上做了改动提交之后,git会产生一个新的提交,并且移动test指针,而master指针会留在原地。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1fzzmil8j317o0hcabr.jpg)
如果我们再回到master也进行了改动和提交之后,又会产生新的节点,并且这个节点会和test的节点区分开,形成新的链路,于是就形成了一棵树的样子。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1h5ilh3kj31e60ic40t.jpg)
分支与HEAD指针
怎么分支创建,我们刚才已经讲过了,可以通过git branch加上我们想要的分支名来完成。使用了这个命令之后,git内部会创建一个新的指针指向当前的commit。
有一个问题是git怎么知道我们当前的代码在哪里呢?即使知道了代码在哪个分支上,又怎么确定在哪一个节点呢?其实git内部还有一个特殊的指针叫做HEAD,它指向的是当前代码仓库的位置。当我们提交代码的时候,不止只有分支的这些指针会往前移动,HEAD指针也会随着移动。
其实HEAD指针不仅可以往前移动,还可以移动到任意节点上,哪怕不再当前的分支上也可以。移动HEAD指针需要用到git checkout命令,它可以指定HEAD指针移动到其他位置。既可以是某一个分支,也可以是根据commit id来确定的节点。
比如我们当前在master分支,我们要切换到test分支上,我们只需要运行:
git checkout master
另外,使用git checkout命令加上参数-b,我们还可以创建分支。比如假如当前test分支不存在,我们可以通过git checkout -b test来创建,并且还会自动切换到新建出的test分支上。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1gm9sv2oj31d208ajtn.jpg)
我们在新的分支上做了提交之后,可以通过git log --oneline --decorate命令来查看每一个分支所指向的位置。这里的oneline是将log压缩到一行展示,decorate用来查看分支所指向的位置。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1go5ngykj30s605wmy2.jpg)
我们可以发现test和master分支指向的提交不同,并且当前我们的HEAD在test上,说明我们当前在test分支。我们前面说了git checkout命令可以改变HEAD指针指向的位置,假如我们在当前目录下执行git checkout 18a417,这个18a417对应的是add article 6这个提交。这个提交是在master分支的,是test分支的上游,我们使用命令会自动将HEAD跳转到master分支。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1grratkgj319y0nmn0u.jpg)
使用之后我们发现的确到了master分支,这里由于我配置了zsh工具,它会提示我当前所处的位置是比master分支指向的最新位置落后3个提交。
这也验证了我们说的,HEAD指针可以随意跳转。现在想必你们应该能理解上一篇文章当中介绍的,撤销当前分支的命令git reset HEAD^的含义了,HEAD指的就是HEAD指针,^表示的上一个提交。如果是前多个提交,我们可以用~加数字的形式来表示。比如上图当中划了红线标注的master~3,就表示master节点上3个提交。
分支合并
最后来简单说说分支合并,我们在使用git进行协同开发的过程当中,虽然大家都在各自的分支。但是最后代码还是要合并到一起的,这样才可以投入使用。git当中代码的合并是通过分支合并来体现的。
比如当前的这一篇文章被我加在了test分支当中,这显然是不行的,因为使用方不可能一一去理解每一个分支做了什么,当中的代码逻辑。所以大多数的分支只是暂时的,用来暂时完成一项功能的,等功能完成之后,一般都会再合并回master分支,将所有的改动合并进去。
合并的方式非常简单,我们只需要先checkout我们想要合并的目标分支。比如我们要合并到master,就checkout到master。然后使用git merge test命令,表示和test这个分支合并。
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1h30r7xbj31w00asq5i.jpg)
合并之后,如果没有报错就算是合并成功了。它会展示出来合并进来的代码改动,我们注意到日志里有一个fast-forward这个单词,它表示快速合并。快速合并的意思也很简单,因为我们test分支是从master分支当中切出去的。后来master分支就再也没有进行过改动,那么当我们合并的时候,其实只需要移动一下master指针,将它移动到test分支上即可。
我们用图来展示,合并前:
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1h672ksqj315k0gegn8.jpg)
合并后:
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1h6jmowej311o0hcmyr.jpg)
那如果我们在master分支上也有改动,不再是待合并分支的直接上游,会发生什么呢?
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1hal03pej31du0kujy1.jpg)
上图当中我们做了一系列操作,首先我们创建了一个叫做test_merge的分支,在其中创建了一个文件叫做a.txt,接着我们切回master分支创建了b.txt。最后我们把两个分支合并。
合并当然也没有问题,但是我们来用git log来查看一下日志:
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1hbfqj3bj30xg0lkgoh.jpg)
会发现日志里多了一个commit,这个commit并不是我提交的,而是它自动产生的。我们一样用图来展示一下,这是合并前:
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1heqm59bj31fw0hyjtp.jpg)
合并之后:
![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gj1hggmzvhj31gi0qkdjg.jpg)
由于不再拥有直接上下游关系了,所以git创建了一个新的commit用来合并两个分支的代码。当我们合并完成之后,我们就可以把没用的分支都删除了。删除的命令是git branch -d test。
当然git merge的时候并不是永远都一帆风顺的,难免会遇到冲突。所谓的冲突也就是两个人修改了同一份代码,git会不知道应该保留哪一个,于是提示冲突,让程序员自己搞定。关于git merge时遇到冲突怎么办的问题,我们放到下一篇文章当中和大家分享。
今天的文章就到这里,衷心祝愿大家每天都有所收获。如果还喜欢今天的内容的话,请来一个三连支持吧~(点赞、关注、转发)
本文使用 mdnice 排版
- END -