📙 作者简介 :RO-BERRY
📗 学习方向:致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持
目录
- 1.理解分支
- 2.创建分支
- 3.切换分支
- 4.合并分⽀
- 5.删除分支
- 6.合并冲突
- 7.分支管理策略
- 8.分支策略
- 9.bug分支
- git stash
- git stash list
- git stash pop
- 10.删除临时分支
- 创建新分支,进行开发并实现提交
- 此时被叫停打算删除
- 11.总结
1.理解分支
本章开始介绍 Git 的杀⼿级功能之⼀(注意是之⼀,也就是后⾯还有之⼆,之三……):分⽀。分⽀就是科幻电影⾥⾯的平⾏宇宙,当你正在电脑前努⼒学习 C++ 的时候,另⼀个你正在另⼀个平⾏宇宙⾥努⼒学习 JAVA。
如果两个平⾏宇宙互不⼲扰,那对现在的你也没啥影响。不过,在某个时间点,两个平⾏宇宙合并了,结果,你既学会了 C++ ⼜学会了 JAVA!
在版本回退⾥,你已经知道,每次提交,Git都把它们串成⼀条时间线,这条时间线就可以理解为是⼀个分⽀。截⽌到⽬前,只有⼀条时间线,在Git⾥,这个分⽀叫主分⽀,即 master 分⽀。
再来理解⼀下HEAD,HEAD 严格来说不是指向提交,⽽是指向master,master才是指向提交的,所以,HEAD 指向的就是当前分⽀。
那么分支在git仓库里面是如何体现出来的呢?
其中间的关系就是
HEAD指向master
master指向最新一次的提交
我们的git的所有提交可以串成一条线
这个提交时间线就相当于我们的分支
2.创建分支
在创建分支之前,我们先来看看我们仓库是有哪些分支存在的
我们可以使用branch命令,他是可以显示出我们当前仓库有哪些分支的
这里可以看到我们有且只有一个分支,那就是master分支
在我们创建git仓库的时候,master分支就会默认创建
在这里我们解释一下我们之前讲过的HEAD指针
不是一直讲的都是HEAD指针指向master指针吗?
HEAD指针其实可以指向其它分支的,被指向的分支就是当前正在工作的分支
就是因为我们只有master分支所以HEAD只能指向master
我们接下来来创建我们的第一个分支
我们使用
git branch dev
就可以创建一个dev分支出来了
当我们创建新的分⽀后,Git 新建了⼀个指针叫 dev, * 表⽰当前 HEAD 指向的分⽀是 master 分⽀。
我们再来看一下git目录下的文件
我们会发现git里refs文件下heads里不在只有我们的master文件了,还有我们新创建的dev
另外,可以通过⽬录结构发现,新的 dev 分⽀:
发现⽬前 dev 和 master 指向同⼀个修改。并且也可以验证下 HEAD ⽬前是指向 master 的。
用图来帮忙我们理解:
3.切换分支
我们想要在我们的dev上操作,那我们就需要把我们的HEAD指向dev,这样才能把dev变成我们的当前的工作分支
那么我们如何做呢?
使⽤ git checkout 命令即可完成切换,⽰例如下:
我们来验证一下我们的HEAD指针
我们发现 HEAD 已经指向了 dev,就表⽰我们已经成功的切换到了dev 上!
接下来,在 dev 分⽀下修改 ReadMe ⽂件,新增⼀⾏内容,并进⾏⼀次提交操作:
现在,dev 分⽀的⼯作完成,我们就可以切换回 master 分⽀:
切换回 master 分⽀后,发现ReadMe⽂件中新增的内容不见了!!!
为什么会出现这个现象呢?我们来看看 dev 分⽀和 master 分⽀指向
这个时候我们可以看到两个指针指向的commit ID已经不在相同了
我们对dev指向的commit ID进行查看,可以看到记录就是我们刚刚对readme文件进行的修改操作,而且这里的parent指向的commit ID就是刚才刚出创建时的commit ID
因为我们是在dev分⽀上提交的,⽽master分⽀此刻的提交点并没有变,此时的状态如图如下所⽰。
当切换到 master 分⽀之时,HEAD 就指向了 master,当然看不到提交了!
4.合并分⽀
为了在 master 主分⽀上能看到新的提交,就需要将 dev 分⽀合并到 master 分⽀
git提供了merge指令
Fast-forward 代表“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度⾮常快。
当然,也不是每次合并都能 Fast-forward,我们后⾯会讲其他⽅式的合并。
我们来查看一下master指针的指向有没有改变
发生了改变,已经指向了我们在dev分支上对readme的修改
git merge 命令⽤于合并指定分⽀到当前分⽀。合并后,master 就能看到 dev 分⽀提交的内容了。此时的状态如图如下所⽰。
5.删除分支
其实我们在将dev与master分支合并后,dev分支就可以丢弃了,它已经完成它的任务了
那我们怎么进行删除呢?
注:我们在这里只能除了dev分支之外的分支对dev分支进行删除
因为创建、合并和删除分⽀⾮常快,所以Git⿎励你使⽤分⽀完成某个任务,合并后再删掉分⽀,这和直接在master分⽀上⼯作效果是⼀样的,但过程更安全。
6.合并冲突
我们在合并两个分支的时候有可能会出现冲突的
比如说
我们在a分支上对readme文件进行了添加文件
同时b分支也没有闲着,也对readme文件进行了修改
当我们合并的时候,git如何知道这两个操作哪个保留下来呢?
这个问题就是合并冲突问题
话不多说,我们进行实操
在这里我们引入一个新的操作,可以将创建以及能转换分支变成一行代码
我们前面需要先git branch dev
然后再checkout dev
这样才完成了我们创建以及更换当前分支的行为
我们使用checkout -b可以直接进行创建且更换当前分支
我们来对readme文件进行一下修改
我们照常进行add以及commit操作
接下来我们切换到master分支再次进行操作
我们在master对readme文件添加一行为add aaaaaaa
进行add以及commit操作
这个时候我们git仓库的状态是这样的
我们这个时候来进行合并,通过我们上面的说辞是会发生冲突的
这里确实是爆出了错误,说自动合并失败,readme文件的合并出现了矛盾,我们需要改变一下结果并重新提交
我们进入readme文件编辑界面查看
发现多了不少东西
这些符号里面的就是我们的冲突代码
git无法帮我们进行操作,我们只能自己进行人为操作,谁去谁留
我们要想保留在dev修改的,那我们自己进行删除
将其余行都删掉
修复之后我们还要进行提交操作
我们在master最后进行的提交是对最终修改的提交
所以我们的master会指向我们刚刚合并后最后的提交,dev会指向在dev分支上的提交
这也很好理解
git log指令是可以看到我们对分支的操作情况的
最左边有一个小图可以看到我们对于分支的操作
7.分支管理策略
通常合并分⽀时,如果可能,Git 会采⽤ Fast forward 模式。还记得如果我们采⽤ Fast forward 模式之后,形成的合并结果是什么呢?回顾⼀下
在这种 Fast forward 模式下,删除分⽀后,查看分⽀历史时,会丢掉分⽀信息,看不出来最新提交到底是 merge 进来的还是正常提交的
但在合并冲突部分,我们也看到通过解决冲突问题,会再进⾏⼀次新的提交,得到的最终状态为:
那么这就不是 Fast forward 模式了,这样的好处是,从分⽀历史上就可以看出分⽀信息。
我们先创建一个分支dev1
再对readme文件进行修改操作以及add和commit操作
然后再对dev1和master进行合并
上面我们在合并的时候可以看到我们的模式是fast-forward模式
我们使用git log --graph --abbrev-commit
我们可以看到在这个模式下我们无法看到我们分支合并的相关操作
有的仅仅是一条最后合并的操作
–no-ff代表的是我们强制禁止fast-forward模式,后面的-m
我们输入
git merge --no-ff -m “merge dev1” dev1
请注意 --no-ff 参数,表⽰禁⽤ Fast forward 模式。禁⽤ Fast forward 模式后合并会创建⼀个新的 commit ,所以加上 -m 参数,把描述写进去。
可以看到,不使⽤ Fast forward 模式,merge后就像这样
所以在合并分⽀时,加上 --no-ff 参数就可以⽤普通模式合并,合并后的历史有分⽀,能看出来曾
经做过合并,⽽ fast forward 合并就看不出来曾经做过合并。
8.分支策略
在实际开发中,我们应该按照⼏个基本原则进⾏分⽀管理:
⾸先,master分⽀应该是⾮常稳定的,也就是仅⽤来发布新版本,平时不能在上⾯⼲活
那在哪⼲活呢?⼲活都在dev分⽀上,也就是说,dev分⽀是不稳定的,到某个时候,⽐如1.0版本发布
时,再把dev分⽀合并到master上,在master分⽀发布1.0版本
你和你的⼩伙伴们每个⼈都在dev分⽀上⼲活,每个⼈都有⾃⼰的分⽀,时不时地往dev分⽀上合并就
可以了。
所以,团队合作的分⽀看起来就像这样:
9.bug分支
假如我们现在正在 dev2 分⽀上进⾏开发,开发到⼀半,突然发现 master 分⽀上⾯有 bug,需要解决。在Git中,每个 bug 都可以通过⼀个新的临时分⽀来修复,修复后,合并分⽀,然后将临时分⽀删除。
我们创建一个dev2分支
在dev2分支上对readme文件进行添加数据
假设我们在开发过程中出现了bug
我们一开始的需求是在dev2上进行一系列的开发,并且我们的开发并没有完成,所以我们不能进行提交也就是add以及commit操作
同时不能在这个分支上解决bug
现在我们就需要另开一个分支来帮助解决bug
我们移动到master分支
这里也显示了,我们对readme文件有所修改
这个也是因为我们在dev2分支对readme文件修改后没有进行add以及commit操作
所以我们对于readme的修改是对所有分支都会有影响的,因为没有提交所以git没有对这个修改进行管理
我们在master分支上也可以看到
git stash
Git 提供了 git stash 命令,可以将当前的⼯作区信息进⾏储藏,被储藏的内容可以在将来某个时间恢复出来。
那我们这个分支被保存在哪里呢?
我们可以看到在refs文件里多了一个stash文件,这就是储存地点
⽤ git status 查看⼯作区,就是⼲净的(除⾮有没有被 Git 管理的⽂件),因此可以放⼼地创建分⽀来修复bug。
储藏 dev2 ⼯作区之后,由于我们要基于master分⽀修复 bug,所以需要切回 master 分⽀,再新建临时分⽀来修复 bug
我们在master分支里看readme文件和原来的没有修改过的是一样的
我们在master分支新建分支
我们假设发现是readme文件里少了一个s
我们给加上
bug已经修复完毕了
将分支删除
我们还要接着进行开发
切换到dev2分支
我们需要将其隐藏的恢复出来
git stash list
我们可以先使用git stash list
查看我们的stash里面存储了哪些信息
这里只有我们刚刚对于readme进行的修改信息
git stash pop
我们将隐藏信息释放
指令为 git stash pop
我们打开看我们刚才修复的bug
但我们注意到了,修复 bug 的内容,并没有在 dev2 上显⽰
这是因为我们在创建这个分支的时候是没有加上那个s的
此时的状态图为:
Master 分⽀⽬前最新的提交,是要领先于新建 dev2 时基于的 master 分⽀的提交的,所以我们在 dev2 中当然看不⻅修复 bug 的相关代码。
我们的最终⽬的是要让 master 合并 dev2 分⽀的,那么正常情况下我们切回 master 分⽀直接合并即可,但这样其实是有⼀定⻛险的。
是因为在合并分⽀时可能会有冲突,⽽代码冲突需要我们⼿动解决(在 master 上解决)。我们⽆法保证对于冲突问题可以正确地⼀次性解决掉,因为在实际的项⽬中,代码冲突不只⼀两⾏那么简单,有可能⼏⼗上百⾏,甚⾄更多,解决的过程中难免⼿误出错,导致错误的代码被合并到 master 上。
此时的状态为:
解决这个问题的⼀个好的建议就是:最好在⾃⼰的分⽀上合并下 master ,再让 master 去合并dev ,这样做的⽬的是有冲突可以在本地分⽀解决并进⾏测试,⽽不影响 master 。此时的状态为:
对应的实操演⽰如下,要说明的是,以下演⽰的merge操作,没有使⽤ --no-ff ,但上述的图⽰是
禁⽤ Fast forward 了模式后得出的,主要是为了⽅便解释问题
我们的开发完成了
再进行add以及commit操作
解决冲突并提交
切回master,并合并(这个时候就不会出现冲突)
最后删除开发的分支因为任务已经完成
10.删除临时分支
软件开发中,总有⽆穷⽆尽的新的功能要不断添加进来。
添加⼀个新功能时,你肯定不希望因为⼀些实验性质的代码,把主分⽀搞乱了,所以,每添加⼀个新功能,最好新建⼀个分⽀,我们可以将其称之为 feature 分⽀,在上⾯开发,完成后,合并,最后,删除该 feature 分⽀。
可是,如果我们今天正在某个 feature 分⽀上开发了⼀半,被产品经理突然叫停,说是要停⽌新功能的开发。虽然⽩⼲了,但是这个 feature 分⽀还是必须就地销毁,留着⽆⽤了。这时使⽤传统的 git branch -d 命令删除分⽀的⽅法是不⾏的。演⽰如下:
创建新分支,进行开发并实现提交
此时被叫停打算删除
我们切回master并删除
这个时候根据提示无法进行删除,需要使用git branch -D dev3
11.总结
分⽀在实际中有什么⽤呢?假设你准备开发⼀个新功能,但是需要两周才能完成,第⼀周你写了50%的代码,如果⽴刻提交,由于代码还没写完,不完整的代码库会导致别⼈不能⼲活了。如果等代码全部写完再⼀次提交,⼜存在丢失每天进度的巨⼤⻛险。
现在有了分⽀,就不⽤怕了。你创建了⼀个属于你⾃⼰的分⽀,别⼈看不到,还继续在原来的分⽀上正常⼯作,⽽你在⾃⼰的分⽀上⼲活,想提交就提交,直到开发完毕后,再⼀次性合并到原来的分⽀上,这样,既安全,⼜不影响别⼈⼯作。
并且 Git ⽆论创建、切换和删除分⽀,Git在1秒钟之内就能完成!⽆论你的版本库是1个⽂件还是1万个⽂件。