多人协同分支管理
git干货系列:(五)多人协同工作之分支管理
前言
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git
的时候,另一个你正在另一个平行宇宙里努力学习SVN
。如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git
又学会了SVN
!
正文
分支简介
为了真正理解 Git
处理分支的方式,我们需要回顾一下Git
是如何保存数据的。
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。在进行提交操作时,Git
会保存一个提交对象(commit object
)。知道了Git
保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。
Git
的分支,其实本质上仅仅是指向提交对象的可变指针。 Git
的默认分支名字是 master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 它会在每次的提交操作中自动向前移动。
Git
的 “master” 分支并不是一个特殊分支。它就跟其它分支完全没有区别。 之所以几乎每一个仓库> 都有 master 分支,是因为git init
命令默认创建它,并且大多数人都懒得去改动它。
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git
的分支是与众不同的,无论创建、切换和删除分支,Git
在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
分支创建
Git
是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing
分支, 你需要使用 git branch
命令:
1 |
$ git branch testing |
这会在当前所在的提交对象上创建一个指针。
那么,Git
又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD
的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD
概念完全不同。 在 Git
中,它是一个指针,指向当前所在的本地分支(译注:将 HEAD
想象为当前分支的别名)。 在本例中,你仍然在master
分支上。 因为 git branch
命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。
你可以简单地使用 git log
命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate
。
1 |
$ git log --oneline --decorate |
正如你所见,当前 “master” 和 “testing” 分支均指向校验和以 f30ab
开头的提交对象。
分支切换
要切换到一个已存在的分支,你需要使用git checkout
命令。 我们现在切换到新创建的 testing
分支去:
1 |
$ git checkout testing |
这样 HEAD
就指向 testing
分支了。
上面的创建分支和切换分支命令可以合起来用下面这个命令来替代。
1 |
$ git checkout -b testing |
那么,这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:
1 |
$ vim test.rb |
如图所示,你的 testing
分支向前移动了,但是 master
分支却没有,它仍然指向运行 git checkout
时所指的对象。 这就有意思了,现在我们切换回 master
分支看看:
1 |
$ git checkout master |
这条命令做了两件事。 一是使 HEAD 指回 master
分支,二是将工作目录恢复成 master
分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略testing
分支所做的修改,以便于向另一个方向进行开发。
可以使用 git branch
命令查看当前分支,注意前面带*
的表示当前分支
Note
分支切换会改变你工作目录中的文件
在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目> 录会恢复到该分支最后一次提交时的样子。 如果Git
不能干净利落地完成这个任务,它将禁止切换分支。
合并分支(快速合并)
假如我们在testing
上的工作完成了,就可以把testing
合并到master
上。Git
怎么合并呢?最简单的方法,就是直接把master
指向testing
的当前提交,就完成了合并,这里你需要使用git merge
命令
1 |
$ git merge testing |
git merge
命令用于合并指定分支到当前分支。合并后,再查看内容,就可以看到,和testing
分支的最新提交是完全一样的。
注意到上面的Fast-forward
信息,Git
告诉我们,这次合并是“快进模式”,也就是直接把master
指向testing
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
删除分支
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支,这里需要使用git branch -d
命令来删除分支
1 |
$ git branch -d testing |
分支合并冲突
人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
准备新的dev
分支,继续我们的新分支开发:
1 |
$ git checkout -b dev |
修改README.md
内容,添加一样内容”day day up~”,在dev
分支上提交:
1 |
$ git commit -am "one commit" |
切换到master
分支:
1 |
$ git checkout master |
Git
还会自动提示我们当前master
分支比远程的master
分支要超前1个提交。
在master
分支上把README.md
文件的最后改为 good good study
,然后提价
1 |
$ git commit -am "two commit" |
现在,master
分支和dev
分支各自都分别有新的提交,变成了这样:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
1 |
$ git merge dev |
果然冲突了!Git告诉我们, README.md文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
1 |
$ git status |
我们可以直接查看README.md
的内容:
1 |
$ cat README.md |
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存:
1 |
#gitLearn |
再提交:
1 |
$ git commit -am 'merge commit' |
现在,master
分支和dev
分支变成了下图所示:
用带参数的git log
也可以看到分支的合并情况:
1 |
$ git log --graph --pretty=oneline --abbrev-commit |
最后,删除feature1
分支:
1 |
$ git branch -d dev |
合并分支(普通合并)
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git
就会在merge
时生成一个新的commit
,这样,从分支历史上就可以看出分支信息。
下面我们实战一下--no-ff
方式的git merge
:
首先,仍然创建并切换dev
分支:
1 |
$ git checkout -b dev |
修改README.md文件,并提交一个新的commit:
1 |
$ git commit -am 'submit' |
现在,我们切换回master
:
1 |
$ git checkout master |
目前来说流程图是这样:
准备合并dev
分支,请注意--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后就像这样:
分支管理策略
实际公司开发的时候一般3个分支就可以了:
- mster 主分支用来发布
- dev 日常开发用的分支
- bug 修改bug用的分支
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本,你和你的小伙伴们每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了;bug
分支用来处理日常bug,搞定后合到dev分支即可;
假设远程公共仓库,有一个master
和一个dev
分支,进行多人协作开发时候(每个人的公钥必须加入到远程账号下,否则无法push
), 每个人都应该clone
一份到本地。 但是clone
的只是master
,如果远程的master
和dev
一样,没关系;如果不一致,则需要clone
出dev
分支 git checkout -b dev origin/dev
之后每个人在本地的dev
分支上独自开发(最好不要在mast
上开发), 开发完成之后push
到远程dev
, git push origin dev
。 之后审核人再确定是否合并dev
到master
。
团队多人开发协作
当你从远程仓库克隆时,实际上Git自动把本地的master
分支和远程的master
分支对应起来了,并且,远程仓库的默认名称是origin
。
要查看远程库的信息,用git remote
:
1 |
$ git remote |
或者,用git remote -v
显示更详细的信息:
1 |
$ git remote -v |
上面显示了可以抓取和推送的origin
的地址。如果没有推送权限,就看不到push的地址。
推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git
就会把该分支推送到远程库对应的远程分支上:
1 |
$ git push origin master |
如果要推送其他分支,比如dev
,就改成:
1 |
$ git push origin dev |
抓取分支
多人协作时,大家都会往master
和dev
分支上推送各自的修改。
现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key
添加到GitHub
)或者同一台电脑的另一个目录下克隆:
1 |
$ git clone [email protected]:tengj/gitStudy.git |
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master
分支。不信可以用git branch
命令看看:
1 |
$ git branch |
现在,你的小伙伴要在dev
分支上开发,就必须创建远程origin
的dev
分支到本地,于是他用这个命令创建本地dev
分支(程分支dev要先创建)。
1 |
$ git checkout -b dev |
创建dev分之后,先同步远程服务器上的数据到本地
1 |
$ git fetch origin |
现在,他就可以在dev
上继续修改,然后,时不时地把dev
分支push
到远程:
1 |
$ git commit -am 'test' |
你的小伙伴已经向origin/dev
分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:
1 |
$ git push origin dev |
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull
把最新的提交从origin/dev
抓下来,然后,在本地合并,解决冲突,再推送:
1 |
$ git pull origin dev |
因此,多人协作的工作模式通常是这样:
- 首先,可以试图用
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
分支管理就学完了,整理一下所学的命令,大体如下:
1 |
git branch 查看当前分支 |
文章作者:嘟嘟MD
发布时间:2016-03-27, 17:03:56
最后更新:2019-08-18, 11:37:29
原始链接:http://tengj.top/2016/03/27/git5branch/
许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。