Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用
master
作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。
![](https://img-blog.csdn.net/20130926081525359?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWFubGlhbmcx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
分支的新建与合并
首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新
创建一个新的分支指针。比如新建一个 testing 分支
git branch testing
它保存着一个名为
HEAD
的特别指针。请注意它和你熟知的许多其他版本控制系统(比如 Subversion 或 CVS)里的 HEAD 概念大不相同。在 Git 中,它是一个指向你正在工作中的本地分支的指针(译注:将 HEAD 想象为当前分支的别名。)。
运行git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去
,所以在这个例子中,我们依然还在 master 分支里工作
切换到其他分支,这样 HEAD 就指向了 testing 分支
git checkout testing
如果这时候进行一些修改,然后提交,
每次提交后 HEAD 随着分支一起向前移动
现在返回master分支
git checkout master
分支的新建与合并
首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新
现在,你决定要修补问题追踪系统上的 #53 问题,新建一个分支
git checkout -b iss53
这相当于执行下面这两条命令:
$
git branch
iss53
$
git checkout
iss53
接着你开始尝试修复问题,在提交了若干次更新后,iss53 分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 HEAD 指针正指向 iss53,见图 3-12):
现在你就接到了那个网站问题的紧急电话,需要马上修补。有了 Git ,我们就不需要同时发布这个补丁和 iss53 里作出的修改,也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回master 分支。
不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。
git checkout master
接下来,你得进行紧急修补。我们创建一个紧急修补分支 hotfix 来开展工作,直到搞定
git checkout -b 'hotfix'
然后回到 master 分支并把它合并进来,然后发布到生产服务器
git checkout master
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)。
由于当前 hotfix 分支和 master 都指向相同的提交对象,所以hotfix 已经完成了历史使命,可以删掉了
$
git branch -d hotfix
回到之前未完成的 #53 问题修复分支上继续工作
git checkout iss53
在问题 #53 相关的工作完成之后,可以合并回 master 分支。实际操作同前面合并 hotfix 分支差不多,只需回到master 分支,运行 git merge 命令指定要合并进来的分支
$
git checkout master
$
git merge iss53
Merge made by recursive.
README | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
请注意,这次合并操作的底层实现,并不同于之前 hotfix 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的提交对象(C4)并不是 iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算
这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。
值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。
有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)。如果你在解决问题 #53 的过程中修改了hotfix 中修改的部分,将得到类似下面的结果:
$
git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
要看看哪些文件在合并时发生冲突,可以用 git status 查阅
E:\Repositories\GitHub\testing [master +0 ~0 -0 !1 | +0 ~0 -0 !1]>
git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# (use "git push" to publish your local commits)
#
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
#
# both modified: tem.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# (use "git push" to publish your local commits)
#
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
#
# both modified: tem.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:
hello
<<<<<<< HEAD
master add
=======
new
>>>>>>> testing
解决冲突的办法无非是二者选其一或者由你亲自整合到一起,同时删除了 <<<<<<<,======= 和 >>>>>>> 这些行。
在解决了所有文件里的所有冲突后,运行
git add
将把它们标记为已解决状态
如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用
git commit 来完成这次合并提交
分支的管理
给出当前所有分支的清单
E:\Repositories\GitHub\testing [master]>
git branch
* master
testing
* master
testing
查看各个分支最后一个提交对象的信息,运行
git branch -v
要从该清单中筛选出你已经(或尚未)与当前分支合并的分支,可以用
--merge
和
--no-merged
选项
git branch --merged
利用分支进行开发的工作流程
长期分支
许多使用 Git 的开发者都喜欢用这种方式来开展工作,比如仅在 master 分支中保留完全稳定的代码,即已经发布或即将发布的代码。与此同时,他们还有一个名为develop 或 next 的平行分支,专门用于后续的开发,或仅用于稳定性测试 — 当然并不是说一定要绝对稳定,不过一旦进入某种稳定状态,便可以把它合并到master 里。这样,在确保这些已完成的特性分支(短期分支,比如之前的 iss53 分支)能够通过所有测试,并且不会引入更多错误之后,就可以并到主干分支中,等待下一次的发布。
本质上我们刚才谈论的,是随着提交对象不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支总是比较靠前(见图 3-18)。
特性分支
在任何规模的项目中都可以使用特性(Topic)分支。一个特性分支是指一个短期的,用来实现单一特性或与其相关工作的分支。可能你在以前的版本控 制系统里从未做过类似这样的事情,因为通常创建与合并分支消耗太大。然而在 Git 中,一天之内建立、使用、合并再删除多个分支是常见的事。
我们在上节的例子里已经见过这种用法了。我们创建了 iss53 和 hotfix 这两个特性分支,在提交了若干更新后,把它们合并到主干分支,然后删除。该技术允许你迅速且完全的进行语境切换 — 因为你的工作分散在不同的流水线里,每个分支里的改变都和它的目标特性相关,浏览代码之类的事情因而变得更简单了。你可以把作出的改变保持在特性分支中几 分钟,几天甚至几个月,等它们成熟以后再合并,而不用在乎它们建立的顺序或者进度。
现在我们来看一个实际的例子。请看图 3-20,由下往上,起先我们在 master 工作到 C1,然后开始一个新分支 iss91 尝试修复 91 号缺陷,提交到 C6 的时候,又冒出一个解决该问题的新办法,于是从之前 C4 的地方又分出一个分支iss91v2,干到 C8 的时候,又回到主干 master 中提交了 C9 和 C10,再回到 iss91v2 继续工作,提交 C11,接着,又冒出个不太确定的想法,从 master 的最新提交 C10 处开了个新的分支dumbidea 做些试验。
现在,假定两件事情:我们最终决定使用第二个解决方案,即 iss91v2 中的办法;另外,我们把 dumbidea 分支拿给同事们看了以后,发现它竟然是个天才之作。所以接下来,我们准备抛弃原来的iss91 分支(实际上会丢弃 C5 和 C6),直接在主干中并入另外两个分支。最终的提交历史将变成图 3-21 这样:
请务必牢记这些分支全部都是本地分支,这一点很重要。
当你在使用分支及合并的时候,一切都是在你自己的 Git 仓库中进行的 — 完全不涉及与服务器的交互。