Git分支管理

来源:互联网 发布:可可粉怎么冲好喝 知乎 编辑:程序博客网 时间:2024/06/03 14:30

1.什么是分支

使用分支意味着你可以从开发主线上分离开来(即脱离主分支),然后在不影响主线的同时继续工作。这种机制在多人开发过程中非常有用,每个人只需要创建一个属于自己的分支,等团队每个人都开发完成后,将所有分支合并。

任何一种版本控制工具都有分支管理,但用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢。因为它们需要创建一个源代码目录的完整副本,对大型项目来说会简直慢到让人无法忍受。因此分支功能成了摆设,大家都不喜欢去用。

但Git的分支是”与众不同”的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。和许多其他版本控制系统不同,Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。理解分支的概念并熟练运用后,你才会意识到为什么 Git 是一个如此强大而独特的工具,并从此真正改变你的开发方式。

2.Git的分支实现方式

我们知道Git 保存的不是文件差异或者变化量,而只是一系列文件快照。这个快照在Git中被称为”commit”。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit恢复,然后继续工作,而不是把几个月的工作成果全部丢失。而我们在Git中修改了文件之后需要在对该文件进行一个commit操作,所以Git必须知道你所commit的是哪个版本,在Git中使用HEAD指针来表示(它的值为一个哈希字串)

上述的概念看起来苦涩难懂,我们通过图谱直观看一下:

$ git add README test.rb LICENSE$ git commit -m 'initial commit of my project'

当使用 git commit 新建一个提交对象前,Git 会先计算该对象每一个文件的校验和,然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。

现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象。概念上来说,仓库中的各个对象保存的数据和相互关系看起来如下图:

这里写图片描述

作些修改后再次进行提交,那么这次的提交对象会包含一个指向上次提交对象的指针(译注:即下图中的 parent 对象)。两次提交后,仓库历史会变成下图的样子:

这里写图片描述

现在来谈分支。Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字(也可以理解为主分支)。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。前面我们提到了一个叫做HEAD的指针,它表示你当前commit的是哪个版本,其实这个HEAD准确的来说是一个指向你正在工作中的本地分支的指针。

这里写图片描述

3.Git分支的管理

Git 要创建一个新的分支指针,比如创建一个新的testing分支,使用 git branch 命令:

$ git branch testing

这里写图片描述

运行git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在 master 分支里工作。

这里写图片描述

要切换到其他分支,可以执行 git checkout 命令。我们现在转换到新建的 testing 分支:

$ git checkout testing

这里写图片描述

HEAD 在你转换分支时指向新的分支 .

4.Git分支的意义

通过一个试验来具体观察Git分支的具体意义何在?我们在testing分支下修改之前master已经提交过的一个文件test.md并重新提交。

$ vim test.md$ git commit -m 'made a change'

这里写图片描述

现在我们回到 master 分支看看:

$ git checkout master

这里写图片描述

git checkout master这条命令做了两件事。它把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。

我们作些修改后再次提交:

$ vim test.md$ git commit -a -m 'made other changes'

现在我们的项目提交历史产生了分叉(如图 3-9 所示),因为刚才我们创建了一个分支,转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:我们可以 在不同分支里反复切换,并在时机成熟时把它们合并到一起。而所有这些工作,仅仅需要branch 和 checkout 这两条命令就可以完成。

这里写图片描述

由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。

5.分支的新建与切换

现在让我们来看一个简单的分支与合并的例子:

1 . 开发某个网站。
2. 为实现某个新的需求,创建一个分支。
3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的bug需要紧急修补,那么可以按照下面的方式处理:

  1. 返回到原先已经发布到生产服务器上的分支。
  2. 为这次紧急修补建立一个新分支,并在其中修复问题。
  3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。
  4. 切换到之前实现新需求的分支,继续工作。
    首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新:

这里写图片描述

现在我们有了一个新的需求question1。我们为解决该question新建一个分支iss53

$ git checkout -b iss53Switched to a new branch "iss53"

运行git checkout 并加上 -b 参数,表示如下命令:

$ git branch iss53$ git checkout iss53

这里写图片描述

接着你开始开发新功能,在提交后,iss53分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 HEAD 指针正指向 iss53)

$ vim BaseDaoImpl.java$ git commit -a -m 'added a new footer [issue 53]'

这里写图片描述

现在你就接到了那个紧急的bug,需要马上修补。有了 Git ,我们就不需要同时发布这个补丁和 iss53 里作出的修改,也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回master 分支,然后修改bug再重新发布即可。

不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。

$ git checkout masterSwitched to branch "master"

此时工作目录中的内容和你在解决问题 #53 之前一模一样,你可以集中精力进行紧急修补bug。这一点值得牢记:Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。

接下来,你得进行紧急修补。我们创建一个紧急修补分支 hotfix 来开展工作,直到搞定:

$ git checkout -b 'hotfix'Switched to a new branch "hotfix"$ vim index.html$ git commit -a -m 'fixed the broken email address'[hotfix]: created 3a0874c: "fixed the broken email address" 1 files changed, 0 insertions(+), 1 deletions(-)

这里写图片描述

经过测试之后,确保修补是成功的,然后回到 master 分支并把它合并进来,然后发布到生产服务器。用 git merge 命令来进行合并:

gitcheckoutmaster git merge hotfix
Updating f42c576..3a0874c
Fast forward
README | 1 -
1 files changed, 0 insertions(+), 1 deletions(-)

请注意,合并时出现了“Fast forward”的提示。由于当前 master 分支所在的提交对象是要并入的 hotfix 分支的直接上游,Git 只需把master 分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。

现在最新的修改已经在当前 master 分支所指向的提交对象中了,可以部署到生产服务器上去了。

这里写图片描述

在那个超级重要的bug修补发布以后,你想要回到被打扰之前的工作。由于当前 hotfix 分支和 master 都指向相同的提交对象,所以hotfix 已经完成了历史使命,可以删掉了。使用 git branch 的 -d 选项执行删除操作:

$ git branch -d hotfixDeleted branch hotfix (3a0874c).

现在回到之前未完成的 #53 问题修复分支上继续工作(图 3-15):

$ git checkout iss53Switched to branch "iss53"$ vim index.html$ git commit -a -m 'finished the new footer [issue 53]'[iss53]: created ad82d7a: "finished the new footer [issue 53]" 1 files changed, 1 insertions(+), 0 deletions(-)

这里写图片描述

不用担心之前 hotfix 分支的修改内容尚未包含到 iss53 中来。如果确实需要纳入此次修补,可以用git merge master 把 master 分支合并到 iss53;或者等 iss53 完成之后,再将iss53 分支中的更新并入 master。

6.分支的合并

在问题 #53 相关的工作完成之后,可以合并回 master 分支。实际操作同前面合并 hotfix 分支差不多,只需回到master 分支,运行 git merge 命令指定要合并进来的分支:

$ git checkout master$ git merge iss53Merge made by recursive. README |    1 + 1 files changed, 1 insertions(+), 0 deletions(-)

请注意,这次合并操作的底层实现,并不同于之前 hotfix 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的提交对象(C4)并不是 iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:

这里写图片描述

这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。

这里写图片描述
既然之前的工作成果已经合并到 master 了,那么 iss53 也就没用了。你可以就此删除它:

$ git branch -d iss53

参考:
http://www.open-open.com/lib/view/open1328069889514.html

1 0
原创粉丝点击