分支是git中非常重要的一块,同时在我们开发过程中对我们团队开发起到了很大的作用。
那么何为分支?在我们开发中是如何使用分支的?
一、分支的概述
假设你准备开发一个新的功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没有写完,不完整的代码库可能会导致别人干不了活。如果等代码全部写完再一次性提交,没有写完只能先暂时保存在本地仓库中。这样存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建一个属于自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,在一次性合并到原来的分支上,这样,即安全,有不影响别人工作。
可见使用分支的目的就是可以把你的工作从开发主线上分离开来,以免影响开发主线。
其他很多版本控制系统中都有某种形式支持的分支。但是这些系统对分支这一块的实现不是很好(几乎是完全创建一个源码目录的副本),效率极其低。
git中的分支很轻,创建一个新的分支是瞬间可以完成的事情,不同分支之间的切换也是非常便捷的。所以很多开发者在使用git进行版本控制的时候是很愿意去使用分支的。
git中的分支是如何实现的呢?下面就介绍一下其中的基本流程。
1、Git中分支的实现
我们知道git保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。在进行提交时,git会保存一个提交对象(git学习笔记一中有提到)。这个提交对象包含了很多的信息,其中就有一个指向暂存区内容快照的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交会有父对象,由多个分支合并产生的提交对象有多个父对象。
git分支,其实仅仅是指向提交对象的可变指针,git的默认分支名是master。在多次提交操作之后,你其实已经有一个指向最后那个提交对象的master分支了。它会在每次体积哦啊操作中自动向前移动。
git的“master”分支并不是一个特殊分支,它和其他分支完全没有区别。之所以几乎每个仓库都有master分支,是因为git init命令默认创建了它并且大多数人都懒得改动它。
通俗的讲,我们可以把分支理解成时间轴,在没有创建任何分支的时候我们只有一个分支即master主分支,master指向提交,HEAD指向的是master(指向当前分支)。每次提交后,master分支都会往下走一步。提交的次数越多,这个时间轴就会越长。
二、创建分支与切换分支
1.创建一个分支,但是仍然停留在当前分支
git branch [branch-name][commit]*
创建一个分支dev01,可以把dev01看成一个指针,指向master相同的提交,但是HEAD现在还是指向master,表示当前所在的分支还是master分支。
新建分支时,可以指向指定的commit使用命令:
git branch branch-name commitID
2、新建一个分支并切换到这个新建的分支
git checkout -b dev
创建一个分支dev,改变HEAD的指向,工作区中的文件并没有发生任何改变。不过从现在开始,对工作区的修改和提交就是针对dev分支的,比如新提交一次后,dev的指针往前移动一步,而master指针不变。
3.只切换分支
git checkout [branch-name]
切换到指定分支,并更新工作区git checkout -
切换到上一个分支
4、新建一个分支与远程分支简历追踪关系
git branch --track [branch] [remote-branch]
关于与远程分支,现在不展开讲了,后面会在远程仓库中介绍
三、查看分支
git branch:列出所有本地分支
git branch -r:列出所有远程分支
git branch -a:列出所有本地和远程分支
四、分支合并
git merge [branch]
合并指定分支到当前分支。
比如我们创建了一个新的分支dev,并且切换到当前分支。在当前分支中我们对a。txt进行了修改。当我们切换回master分之后,再查看a.txt文件,才发现刚才修改的内容不见了。
因为刚才的提交是在dev分支上,而master分支此刻的提交并没有改变:
现在,我们把dev分支的工作成果合并到master分支上:
注意在合并分支的时候先要切换到master分支上。因为git merge命令合并指定分支到当前分支。合并后,再查看a.txt的内容,就可以看到和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward信息,表示这次合并时“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。(不是每次合并都是这种模式)。其他的方式我们后面再说。
合并完之后我们就可以放心地删除dev分支了。
git branch -d dev
删除之后就只剩下master分支了
五、解决冲突
如果同一个文件在合并分支时都被修改了则会引起冲突。
下面借鉴阮一峰老师的例子,说一些如何解决冲突。
先准备一个新dev分支:
修改a.txt的内容,改为:“a hello second”
在dev分支上提交:
切换到master分支上:
在master分支上把a.txt文件中的内容修改一下:“a hello thrid”
在master上提交:
现在,master分支和dev分支各自都分别有新的提交,变成了这样:
这种情况下,git无法执行“快速合并”,只能试图把各自修改合并起来,但这种合并就可能会有冲突,我们试试看:
果然冲突了,git显示a.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:
我们可以直接查看a.txt文件的内容:
Git用>>>>>>标记出不同分支的内容我们修改如下后保存:“a hello thrid”
再提交:
现在,master分支和dev分支变成如下:
用带参数的git log也可以看到分支的合并的情况:
最后,删除dev分支:
小结:当git无法自动合并分支时,就必须首先解决冲突,解决冲突之后,再提交,合并完成。
用git log --graph命令可以看到分支合并图。
六、git rebase
(1)、基本
rebase(变基)用于把一个分支的修改合并到当前分支。
假设你现在基于master分支,创建了一个mywork分支。
假设master分支上已经有两个提交了。如图:
现在我们在这个分支上做一些修改,然后生成两个提交。
但是与此同时,有人也在master上进行了一些修改并且做了提交,这就意味着master和mywork这两个分支各自“前进”了,它们之间“分叉”了。
在这里你可以通过merge命令将mywork与master分支进行合并。如图:
但是,如果你想让mywork分支历史看起来像没有经历过任何合并一样,你也许可以使用git rebase:
这些命令会把你的mywork分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch)(这些补丁被放到.git/rebase目录中),然后把mywork分支更新为最新的master分支,最后把保存的这些补丁应用到mywork分支上。
当mywork分支更新之后,它会指向这些新创建的提交,而那些老的提交会别丢弃。如果运行垃圾收集命令(git gc)这些丢失的提交就会删除。
(2) git rebase -i
用-i标记运行git rebase开始交互式rebase。交互rebase给你在合并过程中修改单个提交的机会,而不是盲目地将所有提交都移到新的基上。你可以删除,分割提交,更改提交的顺序。它就像是打了鸡血的git commit --amend一样。
用法
将当前分支rebase到base上,但是使用可交互的形式。它会打开一个编辑器,你可以为每个将要reabse放入提交输入命令。这些命令决定了每个提交将会怎样被转移到新的基上去。你还可以对这些提交进行排序。
交互式rebase给了你控制项目历史的完全掌握。它给了开发人员很大的自由,因为它可以提交一个混乱的历史,开发人员只需专注于写代码,然后回去恢复干净。
大多数开发者喜欢在并入住代码库之前用交互式rebase来完善他们的feature分支。他们可以将不重要的提交合并在一起,删除不需要的,确保所有东西在提交到正式项目历史前是整齐的。
例子:
最后那个命令会打开一个编辑器,包含mywork的两个提交,和一些提示:
保存并关闭编辑器开始rebase。两一个编辑器打开,询问你合并后的快照的提交信息。在定义了提交信息之后,rebase就完成了,你可以在git log输出中看到那个提交。整个过程可以用下图可视化。
注意缩并的提交和原来两个提交的ID都不一样,告诉我们这确实是一个新的提交。最后,你可以执行一个快速向前的合并,来将完善的mywork分支整合进主代码库。
(4)、解决冲突
在rebase的过程中,也许会出现冲突,在这种情况下git会停止rebase并会让你去解决冲突;在解决冲突后,用“git add”,命令去更新这些内容的索引,然后你无需执行git commit,只要执行git rebase --continue,这样git会继续应用余下的补丁。
在任何时候,你可以用--abort参数来终止rebase的行动,并且mywork分支会回到rebase开始前的状态。
(5)、git rebase和git merge的区别
现在我们可以看一下用合并merge和rebase所产生的历史区别:
当我们使用git log来参看commit时,其commit的顺序也有所不同。
假设C3提交于9:00AM,C5提交于10:00AM,C4提交于11:00AM,C6提交于12:00AM,对于使用git merge来合并所看到的commit的顺序(从新到旧)是:C7,C6,C4,C5,C3,C2,C1
对于使用git rebase来合并所看到的commit的顺序(从新到旧)是:C7,C6',C5',C4,C3,C2,C1
因为C6'提交知识C6提交的克隆,C5'提交只是C5提交的克隆。
从用户的角度来看使用git rebase来合并后所看到的commit的顺序(从新到旧)是:C7,C6,C5,C4,C3,C2,C1
另外,我们在使用git pull命令的时候,可以使用--rebase参数,即git pull --rebase,这里表示把你的本地当前分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch,这些补丁放到“。“.git/rebase”目录中),然后把本地当前分支更新为最新的“origin”分支,最后把保存的这些补丁应用到本地当前分支上。
(6)、git reflog
如果在回退以后又想再次回到之前的版本,git reflog可以查看所有分支的所有操作记录(包括commit和reset的操作),包括已经删除的commit记录,git log则不能查看已经删除的commit记录。
如下图所示:
引用日志只对提交到本地仓库的更改有效,而且只有移动操作会被记录。
七、分支管理策略
master主分支应该是非常稳定,用来发布新版本,一般情况下不允许在上面工作,工作一般情况下在新建的dev分支上工作,工作完后,dev分支代码稳定后就可以合并到主分支master上了。
接下来我们会介绍git中的远程仓库。
领取专属 10元无门槛券
私享最新 技术干货