在Git里面我们可以创建不同的分支,来进行调试、发布、维护等不同工作,而互不干扰。下面我们还是来创建一个试验仓库,看一下Git分支运作的台前幕后:

$rm -rf test_branch_proj
$mkdir test_branch_proj
$cd test_branch_proj
$git init
Initialized empty Git repository in /home/test/test_branch_proj/.git/

我们如以往一样,创建一个“readme.txt”文件并把它提交到仓库中:

$echo "hello, world" > readme.txt
$git add readme.txt
$git commit -m "project init"
[master (root-commit) 0797f4f] project init  1 files changed, 1 insertions(+), 0 deletions(-)  create mode 100644 readme.txt

我们来看一下工作目录(working tree)的当前状态:

$git status
# On branch master
nothing to commit (working directory clean)

大家如果注意的话,可以看到“# On branch master”这么一行,这表示我们现在正在主分支(master)上工作。当我们新建了一个本地仓库,一般就是默认处在主分支(master)上。下面我们一起看一下Git是如何存储一个分支的:

$cd .git
$cat HEAD
ref: refs/heads/master

“.git/HEAD”这个文件里保存的是我们当前在哪个分支上工作的信息。

在Git中,分支的命名信息保存在“.git/refs/heads”目录下:

$ls refs/heads
master

我们可以看到目录里面有一个名叫“master”文件,我们来看一下里面的内容:

$cat refs/heads/master
12c875f17c2ed8c37d31b40fb328138a9027f337

大家可以看到这是一个“SHA1哈希串值”,也就是一个对象名,我们再看看这是一个什么类型的对象:

$cat refs/heads/master | xargs git cat-file -t
commit

是的,这是一个提交(commit),“master”文件里面存有主分支(master)最新提交的“对象名”;我们根据这个“对象名”就可以可找到对应的树对象(tree)和二进制对象(blob),简而言之就是我能够按“名”索引找到这个分支里所有的对象。

读者朋友把我们文章里的示例在自己的机器上执行时会发现,“cat refs/heads/master”命令的执行结果和和文章中的不同。在本文里这个提交(commit)的名字是: “12c875f17c2ed8c37d31b40fb328138a9027f337”,前面我讲Git是根据对象的内容生成“SHA1哈希串值”作为 名字,只要内容一样,那么的对应的名字肯定是一样的,为什么这里面会不一样呢? Git确实根据内容来生成名字的,而且同名(SHA1哈希串值)肯定会有 相同内容,但是提交对象(commit)和其它对象有点不一样,它里面会多一个时间戳(timestamp),所以在不同的时间生成的提交对象,即使内容 完全一样其名字也不会相同。

下面命令主是查看主分支最新提交的内容:

$cat refs/heads/master | xargs git cat-file -p
tree 0bd1dc15d804534cf25c5cb53260fd03c84fd4b9
author liuhui998 <liuhui998@nospam.com> 1300697913 +0800
committer liuhui998 <liuhui998@nospam.com> 1300697913 +0800     project init

“1300697913 +0800”这就是时间戳(timestamp)。

现在查看此分支里面所包含的数据(blob)

$cat refs/heads/master | xargs git cat-file -p | head -n 1 | cut -b6-15 | xargs git cat-file -p
100644 blob 4b5fa63702dd96796042e92787f464e28f09f17d  readme.txt

查看当前的readme.txt

$git cat-file -p 4b5fa63
hello, world
$cd ..

好的,前面是在主分支(master)里面玩,下面我们想要创建一个自己的测试分支来玩一下。git branch命令可以创建一个新的分支,也可以查看当前仓库里有的分支。下面先创建一个叫“test”的分支: $git branch test 

再来看一下当前项目仓库中有几个分支:

$git branch
* master   test

我们现在签出“test”分支到工作目录里:

$git checkout test 

现在再来看一下我们处在哪个分支上:

$git branch   master
* test

好的,我们现在在“test”分支里面了,那么我们就修改一下“readme.txt”这个文件,再把它提交到本地的仓库里面支:

$echo "In test branch" >> readme.txt
$git add readme.txt
$git commit -m "test branch modified"
[test 7f3c997] test branch modified  1 files changed, 1 insertions(+), 0 deletions(-)

当看当前版本所包含的blob:

$git cat-file -p HEAD | head -n 1 | cut -b6-15 | xargs git cat-file -p 

我们现在再像前面一样的看看Git如何存储“test”这个分支的,先来看看“.git/HEAD”这个文件是否指向了新的分支:

$cd .git
$cat HEAD
ref: refs/heads/test

没错,“.git/HEAD”确实指向的“test”分支。再来看看“.git/refs/heads”目录里的内容:

$ls refs/heads
master
test

我们可以看到目录里面多了一个名叫“test”文件,我们来看一下里面的内容:

$cat refs/heads/test
7f3c9972577a221b0a30b58981a554aafe10a104

查看测试分支(test)最新提交的内容:

$cat refs/heads/test | xargs git cat-file -p
tree 7fa3bfbeae072063c32621ff08d51f512a3bac53
parent b765df9edd4db791530f14c2e107aa40907fed1b
author liuhui998 <liuhui998@nospam.com> 1300698655 +0800
committer liuhui998 <liuhui998@nospam.com> 1300698655 +0800      test branch modified

再来查看此分支里面所包含的数据(blob):

$cat refs/heads/test | xargs git cat-file -p | head -n 1 | cut -b6-15 | xargs git cat-file -p
100644 blob ebe01d6c3c2bbb74e043715310098d8da2baa4bf  readme.txt

查看当前”readme.txt”文件里的内容:

$git cat-file -p ebe01d6
hello, world
In test branch
cd ..

我们再回到主分支里面:

$git checkout master
Switched to branch 'master'
$git checkout master
$cat readme.txt
hello, world

如我们想看看主分支(master)和测试分支(test)之间的差异,可以使用git diff命令来查看它们之间的diff

$git diff test
diff --git a/readme.txt b/readme.txt
index ebe01d6..4b5fa63 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1 @@  hello, world
-In test branch

大家可以以到当前分支与测试分支(test)相比,少了一行内容:“-In test branch”。

如果执行完git diff命令后认为测试分支(test)的修改无误,能合并时,可以用git merge命令把它合并到主分支(master)中:

$git merge test
Updating b765df9..7f3c997
Fast-forward  readme.txt |  1 +  1 files changed, 1 insertions(+), 0 deletions(-)

“Updating b765df9..7f3c997”表示现在正在更新合并“b765df9”和“7f3c997”两个提交(commit)之间的内容;“b765df9”代表着主分支(master),“7f3c997”代表测试分支(test)。

“Fast-forward”在这里可以理解为顺利合并,没有冲突。“readme.txt | 1 +”表示这个文件有一行被修改,“1 files changed, 1 insertions(+), 0 deletions(-)”,表示这一次合并只有一个文件被修改,一行新数据插入,0 行被删除。

我们现在看一下合并后的“readme.txt”的内容:

$cat readme.txt
hello, world
In test branch

内容没有错,是“master”分支和“test”分支合并后的结果,再用“git status”看一下,当前工作目录的状态也是干净的(clean)。

$git status
# On branch master
nothing to commit (working directory clean)

好的,现在测试分支(test)结束了它的使命,没有存在的价值的,可以用“git branch -d”命令把这个分支删掉:

$git branch -d test
Deleted branch test (was 61ce004).

如果你想要删除的分支还没有被合并到其它分支中去,那么就不能用“git branch -d”来删除它,需要改用“git branch -D”来强制删除。

如何处理冲突(conflict)

前面说了分支的一些事情,还简单地合并了一个分支。但是平时多人协作的工作过程中,几乎没有不碰到冲突(conflict)的情况,下面的示例就是剖析一下冲突成因及背后的故事:

还是老规矩,新建一个空的Git仓库作试验:

$rm -rf test_merge_proj
$mkdir test_merge_proj
$cd test_merge_proj
$git init
Initialized empty Git repository in /home/test/test_merge_proj/.git/

在主分支里建一个“readme.txt”的文件,并且提交本地仓库的主分支里(master):

$echo "hello, world" > readme.txt
$git add readme.txt
$git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: readme.txt
#
git commit -m "project init"
[master (root-commit) d58353e] project init  1 files changed, 1 insertions(+), 0 deletions(-)  create mode 100644 readme.txt

当看当前版本所包含的blob:

$git cat-file -p HEAD | head -n 1 | cut -b6-15 | xargs git cat-file -p
100644 blob 4b5fa63702dd96796042e92787f464e28f09f17d  readme.txt

虽然前面把“readme.txt”这个文件提交了,但是暂存区里还是会暂存一下,直到下次“git add”时把它冲掉:

$git ls-files --stage
100644 4b5fa63702dd96796042e92787f464e28f09f17d 0 readme.txt

然后再创建测试分支(test branch),并且切换到测试分支下工作:

$git branch test
$git checkout test
Switched to branch 'test'

再在测试分支里改写“readme.txt”的内容,并且提交到本地仓库中:

$echo "hello, mundo" > readme.txt
$git add readme.txt
$git commit -m "test branch modified"
[test 7459649] test branch modified  1 files changed, 1 insertions(+), 1 deletions(-)

现在看一下当前分支里的“readme.txt”的“SHA1哈希串值”确实不同了:

$git cat-file -p HEAD | head -n 1 | cut -b6-15 | xargs git cat-file -p
100644 blob 034a81de5dfb592a22039db1a9f3f50f66f474dd  readme.txt

暂存区里的东东也不一样了:

$git ls-files --stage
100644 034a81de5dfb592a22039db1a9f3f50f66f474dd 0 readme.txt

现在我们切换到主分支(master)下工作,再在“readme.txt”上作一些修改,并把它提交到本地的仓库里面:

 
$git checkout master
Switched to branch 'master'
$git add readme.txt
echo "hola,world" > readme.txt
$git add readme.txt
$git commit -m "master branch modified"
[master 269ef45] master branch modified  1 files changed, 1 insertions(+), 1 deletions(-)

现在再来看一下当前分支里的“readme.txt”的“SHA1哈希串值”:

$git cat-file -p HEAD | head -n 1 | cut -b6-15 | xargs git cat-file -p
100644 blob aac629fb789684a5d9c662e6548fdc595608c002  readme.txt

暂存区里的内容也改变了:

$git ls-files --stage
100644 aac629fb789684a5d9c662e6548fdc595608c002 0 readme.txt

主分支(master) 和测试分支(test)里的内容已经各自改变了(diverged),我们现在用“git merge”命令来把两个分支合一下看看:

$git merge test
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

合并命令的执行结果不是“Fast-foward”,而是“CONFLICT”。是的,两个分支的内容有差异,致使它们不能自动合并(Auto-merging)。

还是先看一下工作目录的状态:

$git status
# On branch master
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:    readme.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

现在Git提示当前有一个文件“readme.txt”没有被合并,原因是“both modified”。

再看一下暂存区里的内容:

$git ls-files --stage
100644 4b5fa63702dd96796042e92787f464e28f09f17d 1 readme.txt
100644 aac629fb789684a5d9c662e6548fdc595608c002 2 readme.txt
100644 034a81de5dfb592a22039db1a9f3f50f66f474dd 3 readme.txt

看一下里面的每个blob对象的内容:

$git cat-file -p 4b5fa6
hello, world
$git cat-file -p aac629
hola,world
$git cat-file -p 034a81
hello, mundo

我们不难发现,“aac629”是当前主分支的内容,“034a81”是测试分支里的内容,而“4b5fa6”是它们共同父对象(Parent)里的内容。因为在合并过程中出现了错误,所以Git把它们三个放到了暂存区了。

现在我们再来看一下工作目录里的“readme.txt”文件的内容:

$cat readme.txt
<<<<<<< HEAD
hola,world
=======
hello, mundo
>>>>>>> test

“<<<<<<< HEAD“下面就是当前版本里的内容;而“=======”之下,“>>>>>>> test”之上则表示测试分支里与之对应的有冲突的容。修复冲突时我们要做的,一般就是把“ <<<<<<< HEAD”,“=======”和“ >>>>>>> test”这些东东先去掉,然后把代码改成我们想要的内容。

假设我们用编辑器把“readme.txt“改成了下面的内容:

$cat readme.txt
hola, mundo

然再把改好的“readme.txt”用“git add”添加到暂存区中,最后再用“git commit”提交到本地仓库中,这个冲突(conflict)就算解决了:

$git add readme.txt
$git commit -m "fix conflict"
[master ebe2f18] fix conflict

这里看起来比较怪异的地方是Git解决了冲突的办法:怎么用“git add”添加到暂存区去,“git add”不是用来未暂存文件的吧,怎么又来解决冲突了。不过我想如果你仔细读过上一篇文章的话就不难理解,因为Git是一个“snapshot”存储系 统,所有新增加的内容都是直接存储的,而不是和老版本作一个比较后存储新旧版本间的差异。

Git里面合并两个版本之间的同一文件,如果两者间内容相同则不作处理,两者间内容不同但是可以合并则产生一个新的blob对象,两者间内容不同但 是合并时产生了冲突,那么我们解决了冲突后要把文件“git add”到暂存区中再“git commit”提交到本地仓库即可,这就和前面一样产生一个新的blob对象。

假设我们对合并的结果不满意,可以用下面的命令来撤消前面的合并:

$git reset --hard HEAD^
HEAD is now at 050d890 master branch modified

git reset(2)命令的输出结果可以看到,主分支已经回到了合并前的状态了。

我们再用下面的命令看一下“readme.txt”文件,确认一下文件改回来没有:

$cat readme.txt
hola,world

小结

由于Git采用了“SHA1哈希串值内容寻值”、“快照存储(snapshot)”等方法, Git中创建分支代价是很小的速度很快;也这是因为如此,它处理合并冲突的方法与众不同。

在这里我想起了“C语言就是汇编(计算机硬件)的一个马甲”这句话,其实Git也就是底层文件系统的一个马甲,只不过它带了版本控制功能,而且更加 高效。Git里有些命令可能不是很好理解(如解决合并冲突用git add),但是对于系统层而言,它是最高效的,就像是C语言的数组下标从0开始一样

Git的分支与合并的更多相关文章

  1. Git入门指南十一:Git branch 分支与合并分支

    十五. Git branch 分支 查看当前有哪些branch bixiaopeng@bixiaopengtekiMacBook-Pro xmrobotium$ git branch * master ...

  2. Git branch 分支与合并分支

    Git branch 分支 查看当前有哪些branch bixiaopeng@bixiaopengtekiMacBook-Pro xmrobotium$ git branch * master 新建一 ...

  3. Git 创建分支与合并分支

    下面以branchName=>aiMdTest为例介绍 1.  下载code git clone masterUrl iva(另存文件名) 2.  创建并切换分支 cd iva git chec ...

  4. git branch 分支与合并

    在使用 git 进行分支开发与合并的时候需要用到这些命令.其他基本 git 命令参考 Git 简易食用指南 git branch 查看分支 git branch 查看当前分支情况 创建分支 git b ...

  5. Git 创建分支并合并主分支

    首先,我们创建dev分支,然后切换到dev分支: $ git checkout -b dev(等价于 $ git branch dev $ git checkout dev ) Switched to ...

  6. GIT使用—分支与合并

    一.分支名 分支名不能以斜线结尾 分支名不能以减号开头 以斜杠分割的组件不能以点开头(feature/.new) 分支名的任何地方都不能包含连个连续的点 分支名不能包含空格或空白符 分支名不能包含波浪 ...

  7. idea如何在git上将分支代码合并到主干

    1.首先将idea中的代码分支切换到master分支,可以看到我们在dev上提交的代码 在master上是没有的 2.如图所示,在remote branch 上选择分支,点击后面的三角图标,展开之后选 ...

  8. git创建分支与合并分支

    git branch myfeture 创建分支 git checkout myfeture git add --all git commit -m git push origin myfeture ...

  9. Git:创建与合并分支

    1.1创建dev分支,使用命令符 git branch 分支名称. 1.2将HEAD指针切换到dev分支,使用命名符git checkout 分支名称. 注:创建并且转移可以合并为一个步骤,使用命令符 ...

随机推荐

  1. Oracle11g安装完成后给用户解锁

    安装时候你可能忘记给普通用户scott解锁,导致安装成功后普通用户无法登录,那该怎么办呢? 先用system用户登录,登录成功之后就可以给其他用户解锁了. 如图: 同理,如果要锁定某一个用户,只需要把 ...

  2. Javascript 异步编程的4种方法详解

    你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排 ...

  3. Linux平台的boost安装全解

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css); @import url(/ ...

  4. 经典关于多态的demo

    class Foo { public int a; public Foo() { a = 3; } public int addFive() { a += 5; return a; } public ...

  5. .NET 统一用户管理 -- 单点登录

    单点登录 前言 本篇做为.Net 统一用户管理的系列文章的开山之作,主要说一个单点登录是怎么实现的,以及为啥要统一用户管理. 单点登录(Single Sign On),简称为 SSO,是目前比较流行的 ...

  6. 造一个Badge Service(徽章)的轮子

    什么是Badge Service 细心的读者朋友一定在很多Github的Repo,npm的package页面看到过诸如 的徽章.这些徽章是干什么用的? 大家看到上文中我引用的Badge的左侧,是Dow ...

  7. orm fluentdata使用相关文章

    微型orm fluentdata使用:http://www.360doc.com/content/12/1228/23/9200790_256885743.shtml

  8. Jsp的九个隐含对象

    JSP的9个隐含对象(内置对象) 不需要预先声明,就可以在jsp或者表达式中随意使用 out javax.servlet.jsp.JspWriter类型,代表输出流的对象.作业域:页面的执行期. re ...

  9. Analyze 静态分析工具中显示 大量的CF类型指针 内存leak 问题, Core Foundation 类型指针内存泄漏

    Analyze 静态分析工具中显示 大量的CF类型指针 内存leak 问题   今天使用Analyze 看了下项目,   解决办法,项目中使用了ARC,OC的指针类型我们完全不考虑release的问题 ...

  10. STUN/TURN/ICE协议在P2P SIP中的应用(二)

    1       说明 2       打洞和穿越的概念... 1 3       P2P中的打洞和穿越... 2 4       使用STUN系列 协议穿越的特点... 2 5       STUN/ ...