几乎每种VCS(Version Control System)都提供各自不同的分支方案。分支可以让你暂时离开主线,进行你自己的试验性工作而不会扰乱开发。Git的分支操作是Git和其他VCS相比的杀手锏,既方便又快捷,Git所倡导的工作方式是经常创建和合并分支,甚至一天几次都不为过。
什么是分支
要想真正理解Git是如何分支的,我们有必要回顾Git如何存储数据。
Git以快照(snapshot)的形式存储数据,而不记录改变或差异。每当你commit时,Git就存储一个commit对象,这个对象包含一个指针,而这个指针指向staged状态下的改变。
所谓staged状态,就是执行
git add之后的状态。
另外,commit对象还包含作者的名字和邮箱、commit message,以及指向父commit的指针。
所谓父commit,就是你所修改的版本。
* 首次commit没有父commit;
* 普通的commit只有一个父commit,也就是上一个commit;
* 对于merge之后的commit,有多个父commit。
假设我们新建了一个本地repo,写了三个新文件README、test.rb和LICENSE,添加并提交这三个文件。
$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'
Git会构建如下的数据结构
图片中出现了三种对象,我按照自底向上的思路来分别解释这三种对象:
1. blob对象:代表一个普通文件,上面5b1d3的字样是它的SHA-1码,是一个blob对象的唯一ID。
2. tree对象:代表一个目录,有自己的ID,同时也存储了下属blob和tree的ID。
3. commit对象:代表一次commit,有自己的ID,同时存储了根节点的ID等信息。而这个根节点所形成的树形结构就是刚才说的快照(snapshot)。
多个commit对象会形成一张链表。
而分支就是这张链表上的一个指针。每次提交代码都会在指针后面插入新的commit对象,并移动到新插入的对象。
每个Git项目有一个默认的分支master。
如何创建分支
创建一个新分支就是在刚才的链表上创建一个新指针。假设创建一个名为testing的分支
$ git branch testing
这个命令创建一个指向当前版本的新指针。
问题来了,Git怎么知道你当前所在的commit是哪个?原来,Git还维护一个HEAD指针,指向当前所在的branch指针。
如何切换分支
切换分支在Git中的术语叫checkout。熟悉SVN的同学会认为这和svn switch比较像。
$ git checkout testing
这个命令会做两件事:
* 将HEAD指针切换到testing分支
* 将工作目录的内容整体替换为testing分支所指的commit对象。
切换分支以后,你的commit就提交在testing分支而不是master分支了(假设你切换之前在master)。
再用git checkout master切换回master,让你的HEAD指针回到master分支,
并做一个commit,如此一来,f30ab就有了两个“下游commit”。
合并分支(一) 处理bug分支
合并分支在Git中叫做merge。毕竟,做项目的时候搞出那么多侧重点不同的branches,最终总要合并的。
试想,你在工作的时候接到PM的通知,有一个紧急bug需要修复,那么你需要做:
1. 暂存:暂存(stash)当前分支的更改。
2. 修改:在生产分支(主分支或者feature分支)上创建并切换到hotfix分支,在hotfix分支上改bug。
3. 提交:hotfix通过测试之后,提交hotfix分支。
4. 合并:合并hotfix分支并推送到远程repo。
3. 还原:切换到自己的branch上面,从暂存区恢复工作。
假设你自己的工作branch叫iss53,生产分支是主分支,你已经在iss53上提交过一个版本,并且还有未提交的更改。
首先暂存当前分支的工作,
$ git stash
然后在master分支上创建并切换到hotfix分支,
$ git checkout master
$ git checkout -b hotfix
在hotfix上改完bug、测试成功后,提交hotfix分支。版本树如下。
$ git checkout master
$ git merge hotfix
合并分支之后的情况如下。
现在可以删除hotfix分支了。之后是否提交到远程repo是自愿的。
$ git branch -d hotfix
$ git push
终于改完了bug,可以回到自己的工作了。
$ git checkout iss53
这个chechout只是切换分支,并没有将你暂存的更改还原回来。你需要用
$ git stash pop
来把暂存区的工作取回来。
关于暂存命令
git stash有很多有用的用法。刚才的pop是“取出并删除”。还有一种取出方法是git stash apply,这个是“取出不删除”。
stash命令的详细用法请查看廖雪峰Git教程 - Bug分支。
合并分支(二) 合并工作分支
上一个例子中,hotfix分支是master分支的后继(downstream)。合并的结果就是master指针向下移动一个节点。这种情况下merge命令会提示Fast-forward。
另外一种情况就是两个分支不构成后继关系,如图
假设你要把iss53合并到master上面,由于master和iss53不构成后继关系,Git会帮你创建一个merge commit,这个commit有两个父commit,如图
合并分支(三) 处理冲突
假如不同分支的同一个文件的同一部分有不同的改动,就会发生合并冲突(merge conflict)。Git会暂停合并工作,直到你手动处理冲突。
怎么看冲突小结
Git发现合并冲突之后,会在你的工作文件文件里有冲突的地方做一些标志,不过别担心,Git会保留原文件的备份的。这种标志姑且称之为“冲突小结”。一个冲突小结的示例如下
<<<<<<< HEAD: main.cpp
int dfs(char** maze) {
=======
template <class T>
int dfs(T** maze) {
>>>>>>> wenke: main.cpp
夹在 <<<<<<< 和======= 之间的代码是HEAD版本中main.cpp的代码,而======= 和 >>>>>>> 之间的是wenke分支的main.cpp的代码。一种解决方法就是修改HEAD/main.cpp,根据需要选取二者之一的内容,把整个冲突小结替换掉。这回再merge就不会有冲突了。
后记
浏览了几本Git教材,如《Git in Practice》、《Version Control with Git》、《Pro Git》等,发现《Pro Git》是最对我胃口的。它用简洁的图例、深入浅出地讲解了Git的原理,使你具备举一反三地使用Git的能力;第二本秉承了Oreilly家族一贯的风格,很严谨,很学究气,因此不适合初学,而适合给熟练工对抗遗忘;第一本起名很吸引人,但是结构略显散乱,一共涉及六十多个Git技巧,可以给熟练工看一看,补充一下自己没想到的用法。
本文内容不全是《Pro Git》的原文,部分掺杂了我自己的理解,希望各位大大们不吝赐教。
个人认为,学完了分支管理,就可以上马做项目了。在项目中发现问题,回到书中进行验证,这样才能学到真知。
推荐一个很强的Git博客,就是廖雪峰Git教程,比我不知道高到哪里去了。
本文深入浅出地介绍了Git的分支管理,包括分支的概念、创建与切换分支的方法、合并分支的过程及解决冲突的技巧,旨在帮助读者理解并掌握Git分支管理的核心技能。
1371

被折叠的 条评论
为什么被折叠?



