【学习总结】Git学习-参考廖雪峰老师教程六-分支管理

学习总结之Git学习-总


目录:

一、Git简介
二、安装Git
三、创建版本库
四、时光机穿梭
五、远程仓库
六、分支管理
七、标签管理
八、使用GitHub
九、使用码云
十、自定义Git
期末总结


六、分支管理

创建与合并分支
解决冲突
分支管理策略
Bug分支
Feature分支
多人协作
Rebase

6.0.0 分支是个啥?

分支就是<平!行!宇!宙!!!>,这两个平行宇宙互不干扰。
在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
(这个逗逼的例子哈哈哈哈哈哈)

分支在实际中有什么用呢?<感觉像一个独立的支线任务>

假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

(所以分支好像是用来保存自己的工作进度啥的..保证进度不丢失,同时不影响主线其他人的进度。最后平行世界合并的时候,皆大欢喜。。)

  • 日常捧Git黑SVN:
    其他版本控制系统如SVN等都有分支管理,但是创建和切换分支慢得让人无法忍受
    Git的分支无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。

------------------------------------------

6.1 创建与合并分支

Git鼓励大量使用分支:

查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>

6.1.1 图示介绍

master分支:主分支,指向提交(commit的内容)
HEAD:指向master,所以HEAD指向的就是当前分支。

1 < 默认的主分支:HEAD-->master >

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
每次提交,master分支都会向前移动一步(head因为是指向master的,就跟着往前一步,仍然指向master),这样,随着你不断提交,master分支的线也越来越长:

2 < 新建分支+切换到新分支:HEAD-->dev >

创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
(Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!)

3 < 当前commit在新分支dev上:HEAD-->dev >

从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变

4 < 合并分支dev到master:master替代dev,即master-->(dev的commit点) >

假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并

5 < 删除分支:dev完成历史使命可以退场了,因为dev的commit都移交给了主分支master,故删除没有影响 >

合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:

6.1.2 以上步骤的一个demo演示

(注意:在learngit里的readme.txt基础上进行,注意路径正确后再做以下操作)

  • git branch - 监测作用,此命令会列出所有分支,当前分支前面会标一个*号。

step1 - git checkout -b dev - 创建并切换分支到dev(-b参数表示创建并切换,相当于git branch dev和git checkout dev)
step2 - 在dev分支正常提交commit
step3 - git checkout master - 切换回主分支master
step4 - git merge dev - 把dev分支的工作成果合并到master分支上 <看来是master本位的操作!!>
(更正:不是master本位,git merge branch_name这个命令是合并分支branch_name到当前分支,恰好是master)
step5 - git branch -d dev - 查看readme确认合并完成后,可以删除dev分支


Fast-forward - 这次合并是“快进模式”,即直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。
(下一节讲冲突时不能快速合并)

  • 创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

------------------------------------------

6.2 解决冲突

当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件<手动编辑>为我们希望的内容,再提交。
git log --graph - 命令可以看到分支合并图。

下面是一个demo:

6.2.1 feature1分支 - AND simple

检查好路径、分支是否正确 --> 切换到新建的分支feature1 --> 在新分支修改readme为AND并addcommit

6.2.2 master分支 - & simple

切换回master分支 --> 在master分支修改readme为&并addcommit

6.2.3 提交时冲突

现在,master分支和feature1分支各自都分别有新的提交
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突

merge失败时用git status查看具体冲突(其实merge命令那里已经给出冲突文件了)

6.2.4 手动解决冲突

vi进文本文件手动解决冲突 --> add并commit修改后的文件
(我也不清楚这是不是相当于不要feature1分支的合并了,还是在merge合并的基础上,只修改了冲突部分,不冲突部分还是照常合并 - 后续的6.2.6部分进行验证 )
(看评论区,貌似因为当前在master分支下,因此手动修改的是master分支的内容,feature1分支的内容还是原来那样)

  • 注:手动修改是把多余的标记不同分支的符号都删掉,就剩下自己需要的就好了

现在,master分支和feature1分支变成了下图所示:

6.2.5 查看分支的合并情况并删除多余分支

命令:git log --graph --pretty=oneline --abbrev-commit - 查看分支合并情况

6.2.6 思考部分:关于含冲突merge中不冲突部分是否正常合并的验证:Git管理的是修改?

新建分支feature2,并分别在feature2master上修改readme

  • 相同之处为AND good& good,另加一句完全相同,一句完全不同。


做着做着我突然醒悟了,Git管理的是修改而不是文件,所以只要是同一个文件在不同分支上进行了修改,不管你里面的内容是一样的还是不一样的,都算是修改了同一个文件的内容,那样就冲突了,就不能自动合并了。

(评论区看到一个说,在不同分支修改了readme为同样的版本,依然显示冲突,我觉得跟这个是一个意思了。。)

嘿呀,看来git还没智能到逆天的程度呀!!

后来我又皮了一下,解决冲突后再输入merge命令,结果报错合并不了了。。
试着checkout切换到feature2分支,报错
试着branch -d删除feature2分支,报错
我????
伤不起呀。。

  • 注:合并失败后,又手快删了另一个分支,然后卡在合并ing(merging)状态出不来,关了git bash窗口重启也没用。
    在Google上看到一个方法: git reset --merge 好像是撤销本次merge,反正终于退出那个尴尬状态了。。

  • 注:分支好像是主要在master,但如果在某个时间点创建新分支,那么新分支继承了原分支的东西,然后继续沿着一条线往前走,这和图示中画的一致。

  • 注:然后我发现了一个问题,就是不同分支且分支内容不同的时候,本地文件夹里的东西是什么样的,毕竟这个相比辣么抽象的git来说更具体一点。神奇的事情是,比如我在Git窗口里位于master时,本地的readme文件里显示的是master分支里readme的内容,而我切换到另一个分支feature2的时候,再看本地的readme,竟然也跟着切换成了另一个分支里修改的内容。而单纯看本地的话,并没有两个文件或者两个不同的地方放这两个不同的readme,感觉自己的三观突然被刷新了!!!!!看得见摸得着的东西也魔怔了!!!!!!!! 啊啊啊啊啊~!!!!

------------------------------------------

6.3 分支管理策略

Git分支十分强大,在团队开发中应该充分应用。

合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

分支策略

在实际开发中的分支管理原则:

  • master分支应该是非常稳定的,仅用来发布新版本,平时不能在上面干活;

  • 干活都在dev分支上,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

  • 每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

  • 所以,团队合作的分支看起来就像这样:

以下是一个demo:

6.3.1 强制禁用Fast forward模式以保留merge生成的commit使分支信息更完整

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,<删除分支后,会丢掉分支信息>。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

6.3.2 具体步骤:

新建并切换至dev分支 --> 在dev分支下修改readmecommit --> 切回master分支并使用--no-ff进行合并(禁用Fast forward)--> git log看看分支历史

  • 命令:git merge --no-ff -m "merge with no-ff" dev:本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去

可以看到,不使用Fast forward模式,merge后就像这样:

6.3.3 一些疑惑

其实我还是挺懵逼的....

1 截一个评论区,--no-ff保留了每个commit使log有迹可循,大概是这个意思。。

2 另一个我也想问的问题,什么叫在dev分支上干活??



  • git有个最佳实践:
    master是主分支,用来做正式发布版之后的保留历史
    其他分支包括dev用来做正常开发
    多个feature用来做某些特性功能
    release用来做发布版历史,每次发布都是用release打包
    hotfix用来做发布版之后的一些及时迭代修复bug的工作。

这是图中的地址

还是有很多疑惑,觉得哪里模模糊糊的,又说不太清楚。。。先继续往前走吧,加油鸭!

------------------------------------------

6.4 Bug分支

修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;

当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。

  • 背景:当需要修复一个代号101的bug的任务时,可以创建一个分支issue-101来修复它,但是当前正在dev上进行的工作还没有提交:

以下是一个demo:

6.4.1 stash - 把当前工作现场“储藏”起来先

在dev分支上的工作只进行到一半,还没法提交,但是现在必须优先修复bug
故可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:

6.4.2 创建bug临时分支,修复、合并并删除

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:
(虽说是临时分支,但感觉和dev啥的平级的。“临时”说的可能是稍后合并就删除了所以临时)

6.4.3 切回原分支继续干活

Git用stash存储的内容的恢复一下,有两个办法:

  • 一:git stash apply - 恢复后,stash内容并不删除,需要用git stash drop来删除
  • 二:git stash pop - 恢复的同时把stash内容也删了
    (不指定的情况下应该是全部恢复或删除,我猜的。。)

git stash list:查看存储区的内容
git stash apply <stash_list_name>(eg:git stash apply stash@{0}):恢复指定条目的内容

------------------------------------------

6.5 Feature分支

开发一个新feature,最好新建一个分支

丢弃一个未被合并分支,git branch -D <name>强行删除

  • 每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支

背景:
接到一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船
*顺利:在新分支开发完毕,切回master,合并,删除
*不顺利:该新功能夭折,未合并分支需要强行删除

这里没有新操作,故没有demo

  • 对从dev新建分支的一点感想:
    应该是可以从多层次的不同结点创建新分支的,只要你自己还捋得清就行了。。目前感觉还是好抽象。。

------------------------------------------

6.6 多人协作<pull、push势必存在各种冲突及其解决>

git remote -v - 查看远程库信息

本地新建的分支如果不推送到远程,对其他人就是不可见的

git push origin branch-name - 从本地推送分支。如果推送失败,先用git pull抓取远程的新提交

git checkout -b branch-name origin/branch-name - 在本地创建和远程分支对应的分支,本地和远程分支的名称最好一致;

git branch --set-upstream branch-name origin/branch-name - 建立本地分支和远程分支的关联

git pull - 从远程抓取分支前,先pull,如果有冲突,要先处理冲突。


多人协作的工作模式通常是这样:
  • 首先,可以试图用git push origin <branch-name>推送自己的修改;

  • 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;

  • 如果合并有冲突,则解决冲突,并在本地提交;

  • 没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!

  • 如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>


6.6.1 远程仓库

  • 从远程仓库克隆,实际上Git自动把本地的master分支和远程的master分支对应起来了
  • 并且,远程仓库的默认名称是origin

git remote:要查看远程库的信息
git remote -v:显示更详细的信息
(显示可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址)

6.6.2 推送分支

  • 推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上

git push origin master - 推送master分支
git push origin dev - 推送dev分支(下一步要用到所以dev也一并推送了)




推送分支的选取:
  • master分支是主分支,因此要时刻与远程同步

  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步

  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug

  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发

总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!

6.6.3 抓取分支<涉及模拟另一个小伙伴的角色扮演>

  • 背景:模拟另一个小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
    (选择在本机的另一个目录d:\files下克隆)
    (在本机开了两个Git bash窗口不知道会不会错乱。。)

1 - 在另一个Git bash窗口进入d:\files目录下
2 - git clone git@github.com:anliux/learngit.git:克隆远程仓库到本地
3 - 先进入克隆到本地的learngit文件夹下<这是下一步操作的必备正确路径,不然各种报错,一定记得>
4 - git branch:查看所有当前分支(从远程库clone时,默认情况下,只能看到本地的master分支)
5 - git checkout -b dev origin/dev:小伙伴要在dev分支上开发,就必须创建远程origindev分支到本地

6 - 在dev分支上新建文件,add-commit并push

6.6.4 push冲突解决<角色扮演完毕,现在切换回自己的状态(即原bash窗口)>

1- 在原bash窗口操作
2 - 新建env.txt文件,addcommit并push:git push origin dev
3 - 显示冲突时:先git pull把最新的提交从origin/dev抓下来,本地合并,解决冲突,再推送
4 - pull失败:没有指定本地dev分支与远程origin/dev分支的链接,设置命令 - git branch --set-upstream-to=origin/dev dev
5 - 再pull,有冲突警告
6 - git status查看冲突 --> vi env.txt手动解决冲突
7 - 之后再次add、commit并push


6.6.5 关于“多人协作”小节的小结

学完感觉自己掉了层皮。不过好歹熬过去了,很赞,我很赞。加油,继续前进。

之前扫了一眼,后面的内容应该不会太让人害怕了,加油,棒棒!

------------------------------------------

6.7 Rebase - 变基

rebase - 把本地未push的分叉提交历史整理成直线;

rebase - 目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。

  • 恕我直言,扫了一遍非常没感觉。。只有“这是啥啊”的感觉.....

  • 2018-11-3更:这周忙了几天论文,几天双十一薅羊毛活动,回来再看,还是很没感觉,醉了。明天好好看,11-11前看完买松松。加油鸭!!!

总的原则是:(在Git-book看到的一句话:)

只对尚未推送或分享给别人的本地修改执行变基操作清理历史
从不对已推送至别处的提交执行变基操作

rebase操作的特点:

把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了

  • 先看一眼目前两个窗口的分支线:
    命令:git log --graph --pretty=oneline --abbrev-commit

以下是一个demo:

6.7.0 <前期准备> 为rebase变基操作演示做准备

<此刻的操作路径:d:\software\learngit>

首先将dev分支和maste分支都推送到远程库,然后再做下面的操作。
(貌似不在本分支下,向远程库推送分支也可以?6.6.2就不在本分支下推送的)

git push origin master - 推送master分支
git push origin dev - 推送dev分支

  • 我也不太懂发生了啥,中间出来dev推送不到远程,根据提示先pull,出来那个看不懂的写理由操作跳过,一路顺下来了先。。

现在的分支图如下:

6.7.1 <准备工作> hello.py的修改并commit-两次

在master分支下:
新建hello.py --> add并commit 修改1--> 第二次修改 --> add并commit修改2 --> git log查看分支图


修改前的log:

修改后的log:

  • 注意:Git用(HEAD -> master)(origin/master)标识出当前分支的HEAD和远程origin的位置
    可知本地分支比远程分支快两个提交。

当前HEAD: dca8f1a
远程origin: 2c0c87a

6.7.2 <制造冲突> 模拟多人协作环境从另一个位置上传hello.py

<此刻的操作路径:d:\files\learngit>

进入指定路径下 --> 新建hello.py文件 --> add并commit --> push推送至远程库

6.7.3 <生成凌乱分支图> 重新在本体(原路径下)push并解决冲突

<此刻的操作路径:d:\software\learngit>

原操作路径 --> 推送到远程库 --> 冲突后pull --> 自动merge失败
--> 手动解决冲突(这里还是不用保留commit了git merge --no-ff -m "merge with no-ff" dev,而且也不确定该怎么用)
--> 查看git status发现有未合并路径啥的 -->

这个图:push发现冲突,pull后手动解决冲突,然后只剩下这些了,中间的过程被吞了,之前遇到过,第二次遇到。

从GitHub查看,解决冲突后,仍然只有从另一个路径push的记录。。???
但原本地的hello.py是已经合并冲突的。

硬着头皮add并commit本地的hello.py之后再看git status是clean,并且log查询分支图好像也和廖老师的相近了。。
(貌似出现了传说中“太乱不想上传到远程库的分支图”???)

6.7.4 < rebase登场 >

git rebase - 不行啊,失败了,合并冲突什么的。。醉了,哪里看冲突在哪啊

  • 在评论区第一条看到似乎可以解决:
    需要两次commit:我已经add commit过了,rebase冲突时再次commit


再次手动修改时的冲突很迷。。。。又变成第一次提交的hello.py的版本了是怎么个原理?

算了,先继续改吧:

按评论区说的,只add,并没有commit,继续rebase,竟然成了,我???这又是什么情况??

再次看log,好像真的平了???这又是什么情况,云里雾里的。。

6.7.5 < push到远程库 >

这里再看GitHub界面,还是只有从另一个路径push的东西

push一下:

再次从GitHub查看hello.py的内容:

  • 超级奇怪:本地两次commit的竟然分开了,变基的时候只和第一次修改冲突了,第二次原封不动在这呢。。
  • 而且在6.7.3里第一次手动解决的冲突都没了怎么?

再次非常迷糊地查看分支图:

6.7.6 < 非常规小结 >

妈呀,这一节终于走到了这里,全程懵逼。

rebase貌似是可以变基,但是总感觉哪里不对劲,因为变基以后commit版本号的变化总觉得哪里不对劲。

似有似无的一种感觉是,变基是因为合并了,或者说,在合并的基础上又commit了(并不,最后只有add)

总之很懵。先这样,继续往前走吧,这一节期待后续的顿悟之类的。

posted @ 2018-10-30 17:08  anliux  阅读(514)  评论(0编辑  收藏  举报