关于Git的分支管理
几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。和许多其他版本控制系统不同,Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。
在前面的内容中我们提到,Git保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。
在进行提交操作时,git会保存一个提交对象(commit object),该提交对象会包含一个指向暂存内容的快照指针,同时,还包含了作者的姓名和邮箱、提交时输入的信息以及指向他的父对象的指针。
每次提交,Git都会把他们串成一条时间线,这条时间线就是一个分支。从我们之前的学习到现在,我们只使用了一条主分支,即master
分支,HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以HEAD
指向的就是当前分支
开始的时候,master
分支是一条线,Git用master
指向最新的提交,每次提交master
分支都会向前移动,随着不断地提交,master
分支的线也越来越长,再用HEAD
指向master
,就是确定当前分支,以及当前分支的提交点
当我们创建新的分支,例如dev
,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上
从现在开始,对工作区的修改和提交就是针对dev
分支了,必须新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git合并分支也很快, 通过改动指针,工作区内容也不变。
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
dev
分支,然后切换到dev
分支:1 | $ git checkout -b dev |
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:
1 | $ git branch dev |
用git branch
命令查看当前分支:
1 | $ git branch |
然后,我们就可以在dev
分支上正常提交,比如对readme.txt做个修改,加上一行:
1 | Creating a new branch is quick. |
然后提交:
1 | $ git add readme.txt |
现在,dev
分支的工作完成,我们就可以切换回master
分支:
1 | $ git checkout master |
切换回master
分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
现在,我们把dev
分支的工作成果合并到master
分支上:
1 | $ git merge dev |
git merge
命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev
分支的最新提交是完全一样的。
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
1 | $ git branch -d dev |
删除后,查看branch
,就只剩下master
分支了:
1 | $ git branch |
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
有时候合并操作并不会上述操作一样顺利。如果在不同分支的情况下都修改了同一个文件的同一个位置,这个时候Git就无法快速且有效的将两个分支合并到一起
这个时候,分支master
和feature1
分支都分别有了新的提交,我们试图合并就会出现错误:
1 | $ git merge feature1 |
Git执行了合并,但发生了冲突,因此并没有提交,这个时候需要我们解决冲突,使用git status
可以查看冲突:
1 | $ git status |
也可以选择直接查看冲突文件的具体内容:
1 | Git is a distributed version control system. |
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存:
1 | Creating a new branch is quick and simple. |
再提交:
1 | $ git add readme.txt |
现在,master
分支和feature1
分支变成了下图所示:
用带参数的git log
也可以看到分支的合并情况:
1 | $ git log --graph --pretty=oneline --abbrev-commit |
最后,删除feature1
分支:
1 | $ git branch -d feature1 |
工作完成。
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
用git log --graph
命令可以看到分支合并图。
在通常情况下,在合并分支时,我们使用的是Fast forward
模式,在这种模式下进行快速的合并,删除分支后,就会丢掉分支的信息。如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
我们测试一下在--no-ff
方式git merge
:
创建并切换分支:
1 | $ git checkout -b dev |
修改readme.txt文件,并提交一个新的commit:
1 | $ git add readme.txt |
现在,我们切换回master
:
1 | $ git checkout master |
合并分支,这里我们使用--no-ff
,表示禁用fast forward
1 | $ git merge --no-ff -m "merge with no-ff" dev |
因为本次合并要创建一个新的commit,所以加上-m
参数,把commit描述写进去。
合并后,我们用git log
看看分支历史:
1 | $ git log --graph --pretty=oneline --abbrev-commit |
可以看到,不使用Fast forward
模式,merge后就像这样:
在工作过程中,如果出现bug急需修复,但是在当前分支上的工作还没有完成,可以将分支上的工作临时存储,这里需要使用Git的stash
功能,等以后恢复现场后可以继续工作:
1 | $ git stash |
现在使用git status
查看工作区会发现工作区是干净的,所以我们可以创建临时分支来用于修复BUG
首先确定需要在哪个分支上修复Bug,假设我们需要在master
上进行操作,就从master
上创建临时分支
1 | $ git checkout master |
这里我们使用issue-101
作为临时分支,在临时分支上对目标文件进行操作后,切换至master
分支,完成合并,删除临时分支
1 | $ git checkout master |
切换至dev
分支继续之前的工作,git status
会发现工作区是干净的,并没有工作现场,我们使用git stash list
命令进行查看
1 | $ git stash list |
Git把stash的内容存放到了某个地方,我们有两种方法进行恢复:
git stash apply
恢复,stash内容并不删除,需要git stash drop
再次删除git stash pop
,恢复的同时删除stash的内容开发一个新feature,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除。
git remote -v
;1 | $ git remote -v |
上面显示了可以抓取和推送的origin
的地址。如果没有推送权限,就看不到push的地址。
1 | $ git push origin master |
推送master
分支
1 | $ git push origin dev |
推送dev
分支
master
和dev
分支上推送各自的修改。现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
1 | $ git clone git@github.com:michaelliao/learngit.git |
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master
分支。不信可以用git branch
命令看看:
1 | $ git branch |
现在,你的小伙伴要在dev
分支上开发,就必须创建远程origin
的dev
分支到本地,于是他用这个命令创建本地dev
分支:
1 | $ git checkout -b dev origin/dev |
现在,他就可以在dev
上继续修改,然后,时不时地把dev
分支push
到远程:
1 | $ git add env.txt |
其中推送到远程可能因为出现冲突而导致推送失败,所以多人协作分支的工作模式:
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>
。
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
,如果有冲突,要先处理冲突。以上内容来自廖雪峰老师的Git课程,感谢廖老师