在版本回退里已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
分支的作用很强大,假设你准备开发一个新功能,但是需要两周才能完成,第一周写了部分代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。现在有了分支,你可以创建一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全又不影响别人工作。
分支创建与切换
Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点。每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。首先,我们来创建dev分支并切换到dev分支上
$ git checkout -b dev
git checkout 命令加上 –b参数表示创建并切换,相当于如下2条命令:
$ git branch dev
$ git checkout dev
也就是说,创建分支的命令是git branch,这会在当前所在的提交对象上创建一个指针。而要切换到一个已存在的分支,你需要使用 git checkout 命令。请牢记:当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。
你可以简单地使用 git log 命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate。
可以看到当前 “master” 和 “dev” 分支均指向校验和以 9b1105c 开头的提交对象。
查看分支
然后,用git branch命令查看当前分支(加上-a参数可以查看远程分支,远程分支会用红色表示出来(如果你开了颜色支持的话)):
$ git branch
git branch查看分支,会列出所有的分支,当前分支前面会添加一个星号。然后,我们就可以在dev分支上正常提交,比如对test.txt做个修改,加上一行“777777”,然后提交:
现在,dev分支的工作完成,我们就可以切换回master分支:
切换回master分支后,再查看一个test.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
现在我们可以把dev分支上的内容合并到分支master上了,可以在master分支上,使用如下命令 git merge dev :
$ git merge dev
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。
删除分支
合并完成后,就可以放心地删除dev分支了(在Git v1.7.0 之后,删除远程分支可以使用git push origin --delete <branchName>,否则,可以使用这种语法,推送一个空分支到远程分支,其实就相当于删除远程分支:git push origin :<branchName>):
$ git branch -d dev
删除后,查看branch,就只剩下master分支了:
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
解决冲突
Git虽然很智能了,但毕竟不是人,在合并分支的时候难免会遇到各种问题,这些问题还是需要我们自己解决。现在我们再建一个分支dev1,
在test.txt添加一行内容“888888”,然后提交,如下所示:
现在切换到master分支上,也在test.txt最后一行添加内容,内容为"999999",如下所示:
Git还会自动提示我们当前master分支比远程的master分支要超前1个提交,在master上提交:
现在,master分支和dev1分支各自都分别有新的提交。这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突:
Git告诉我们,test.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:
可以直接查看readme.txt的内容:
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,其中<<<HEAD是指主分支修改的内容,>>>>>dev1 是指dev1上修改的内容,打开test.txt文件,修改下如下后保存:
现在已经是我们想要的效果了,提交:
用带参数的git log也可以看到分支的合并情况:
最后,删除dev1分支:
.
分支管理
通常合并分支时,Git会用“Fast forward”模式,但这种模式下,删除分支会丢掉分支信息。如果强制禁用“Fast forward"模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。现在我们来使用带参数 –no-ff来禁用”Fast forward”模式。
首先,仍然创建并切换dev分支:
修改test.txt文件,并提交一个新的commit:
现在切换回master,合并dev分支,使用“--no-ff”参数。因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
合并后,我们用git log看看分支历史:
分支策略:首先master主分支应该是非常稳定的,也就是用来发布新版本,一般情况下不允许在上面干活,干活一般情况下在新建的dev分支上干活,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master上发布1.0版本。你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
现在已经创建、合并、删除了一些分支,让我们看看一些常用的分支管理工具。
git branch 命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表:
注意 master 分支前的 * 字符:它代表现在检出的那一个分支(也就是说,当前 HEAD 指针所指向的分支)。 这意味着如果在这时候提交,master 分支将会随着新的工作向前移动。 如果需要查看每一个分支的最后一次提交,可以运行 git branch -v 命令:
--merged 与 --no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。 如果要查看哪些分支已经合并到当前分支,可以运行 git branch --merged:
因为之前已经合并了 dev 分支,所以现在看到它在列表中。 在这个列表中分支名字前没有 * 号的分支通常可以使用 git branch -d 删除掉——你已经将它们的工作整合到了另一个分支,所以并不会失去任何东西。
查看所有包含未合并工作的分支,可以运行 git branch --no-merged命令。
Bug分支
开发过程中,遇到bug是难免的,有了bug就需要修复,在Git中,分支是很强大的,每个bug都可以通过一个临时分支来修复,修复完成后,合并分支,然后将临时的分支删除掉。假设我在开发中接到一个404 bug,我们可以创建一个404分支来修复它,但是,当前的dev分支上的工作还没有提交。不是我不想提交,而是工作没有完成,我们还无法提交,怎么办呢?还好,Git还提供了一个stash功能,可以把当前工作现场 ”隐藏起来”,等以后恢复现场后继续工作
现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支issue-404,现在修复bug,然后提交:
修复完成后,切换到master分支,并完成合并,最后删除issue-404分支:
现在,我们可以回到dev分支上了
工作区是干净的,那么我们工作现场去哪里呢?我们可以使用命令 git stash list来查看下:
工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,可以使用如下2个方法:
1. git stash apply恢复,恢复后,stash内容并不删除,你需要使用命令git stash drop来删除。
2. 另一种方式是使用git stash pop,恢复的同时把stash内容也删除了。
现在再用git stash list查看,就看不到任何stash内容了
多人协作
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
要查看远程库的信息 使用 git remote,使用使用 git remote –v查看远程库的详细信息
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
推送分支
就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,使用命令git push origin master,这样,Git就会把该分支推送到远程库对应的远程分支上,如果要推送其他分支,比如dev,就改成git push origin dev
但并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
抓取分支
多人协作时,大家都会往master分支上推送各自的修改。
首先我的mygit目录的dev分支也要推送到远程去,如下:
现在我们可以模拟另外一个同事,可以在另一台电脑上(注意要把SSH key添加到github上)或者同一台电脑上另外一个目录克隆,新建一个目录名字叫mygit2,克隆远程的库到本地来
现在目录下生成如下所示:
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支
现在我们的小伙伴要在dev分支上做开发,就必须把远程的origin的dev分支克隆到本地来,于是可以使用命令创建本地dev分支:git checkout –b dev origin/dev
现在小伙伴们就可以在dev分支上做开发了,开发完成后把dev分支推送到远程库时。
小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的 解决冲突完全一样。解决后,提交,再push,先来看下test.txt内容
手动解决完了再提交,再push到远程库里面去
因此,多人协作的工作模式通常是这样:
首先,可以试图用git push origin branch-name推送自己的修改;
如果推送失败,则因为远程分支比你的本地更早,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name。
这就是多人协作的工作模式,一旦熟悉了,就非常简单。