学习总结之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并add
、commit
6.2.2 master
分支 - & simple
切换回maste
r分支 --> 在master
分支修改readme
为&并add
、commit
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
,并分别在feature2
和master
上修改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分支下修改readme
并commit
--> 切回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
分支上开发,就必须创建远程origin
的dev
分支到本地
-
step5:报错 - fatal: Cannot update paths and switch to branch 'dev' at the same time.
问题所在:应该是最开始并没有在另一个账号push dev这个分支,后面在这个位置克隆以后,那边才push的,所以会没有。
解决方法:最简单的就是git pull拉取一下,原因就是上面克隆在先,那边账号push在后这个行为差导致的问题。我猜的。 -
参考文献:
git 拉取远程分支报错(fatal: '' is not a commit and a branch '' cannot be created from it)
讨论-廖雪峰的官方网站-同一台电脑的另一个目录下克隆
6 - 在dev分支上新建文件,add-commit并push
6.6.4 push冲突解决<角色扮演完毕,现在切换回自己的状态(即原bash窗口)>
1- 在原bash窗口操作
2 - 新建env.txt
文件,add
、commit
并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:softwarelearngit>
首先将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:fileslearngit>
进入指定路径下 --> 新建hello.py文件 --> add并commit --> push推送至远程库
6.7.3 <生成凌乱分支图> 重新在本体(原路径下)push并解决冲突
<此刻的操作路径:d:softwarelearngit>
原操作路径 --> 推送到远程库 --> 冲突后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)
总之很懵。先这样,继续往前走吧,这一节期待后续的顿悟之类的。