本篇博客主要讲的是『Git 详细教程之三: Git 分支操作』。如果您是第一次学习 Git ,请先阅读博主的前几篇文章:
- Git 详细教程之一: Git 简介及其基本工作原理
- Git 详细教程之二: Git 安装配置及其常用命令
一、Git 分支的理解及好处
几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。 在很多版本控制系统中,这是一个略微低效的过程——常常需要完全创建一个源代码目录的副本。对于大项目来说,这样的过程会耗费很多时间。
Git 的分支模型称为它的“必杀技特性”,其处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。
1.1 Git 处理分支的方式(理解分支)
为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。简单来说,Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照 。
在进行提交操作时,Git 会保存一个提交对象(commit object),该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。
注意: 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象,
为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和( SHA-1 哈希算法),然后会把当前版本的 文件快照 保存到 Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit.'
当使用
git commit
进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。
现在,Git 仓库中有五个对象:
- 三个 blob 对象(保存着文件快照);
- 一个 树 对象 (记录着目录结构和 blob 对象索引);
- 一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
以上就是 Git 保存数据的方式。回到 Git 的分支,其实本质上仅仅是指向提交对象的可变指针。Git 的默认分支名字是
master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的
master 分支
。
master 分支
会在每次提交时自动向前移动。下图可以帮助我们很好地理解 Git 分支的运行原理。
1.2 Git 分支的好处
同时并行推进多个功能开发,提高开发效率。
各个分支在开发过程中,如果某一分支开发失败,不会对其他分支造成任何影响。失败的分支删除重新开始即可。
二、Git 分支操作
2.1 查看分支
分支在 Git 里称之为
branch
,查看分支的基本语法为:
git branch -v
执行上述命令后,我们可以看到,继上篇博文的操作,目前项目中只存在一个
master
分支:
2.2 创建分支
创建分支的本质只是创建了一个可以移动的新的指针,其基本语法为:
git branch 分支名
例如,创建一个
testing
分支,我们需要执行
git branch testing
命令:
这会在当前所在的提交对象上创建一个新的指针
testing
。
那么,Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为
HEAD
的特殊指针,指向当前所在的本地分支,可以将
HEAD
想象为当前分支的别名。 在本例中,你仍然在
master
分支上。 因为 **
git branch
命令仅仅创建 一个新分支,并不会自动切换到新分支中去**。
可以简单地使用
git log --decorate
命令查看各个分支当前所指的对象,可以看到此时,
HEAD
指针同时指向了两个分支指针
master
、
testing
,且指向的是第二次提交的提交对象。
2.3 切换分支
当我们项目存在多个分支时,如何进行分支间的切换?基本语法为:
git checkout 分支名
如果我们执行
git checkout testing
:
此时,
HEAD
指针就指向
testing 分支
了。
注意: 如果需要新建一个新的分支,随后立即切换到该分支,我们可以简写成:
git checkout -b new_branch
它与下面两条命令等价:
git branch new_branch
git checkout new_branch
为了更好地理解分支的意义,我们对
test.cpp
做一次修改再次提交:
可以发现,提交过后,我们仍处于
testing
分支,指向的是第三次提交的提交对象;而
master
分支指向的是第二次提交的对象。下图可以帮助我们更好地理解:
如图所示,
testing 分支
向前移动了,但是
master 分支
却没有。由此,我们再切换回
master 分支
,可以发现,执行
git checkout master
后,
HEAD
指针指回
master 分支
,同时工作目录恢复成
master 分支
所指向的快照内容。**本质上来讲,这就产生了多条开发路线,另外的开发人员可以直接进入到
testing 分支
进行开发,互不影响。这样就实现了多条开发路线的并行工作。**
2.4 合并分支
在实际工作中你可能会碰到以下类似的工作流:
- 开发某个网站,为实现某个新的用户需求,创建一个分支,并在这个分支上开展工作;
- 突然,接到一个电话说有个很严重的问题需要紧急修补;
- 切换到你的线上分支(production branch);
- 为这个紧急任务新建一个分支,并在其中修复它;
- 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支;
- 切换回你最初工作的分支上,继续工作。
在开发过程中,以上的情况经常出现,这时就需要我们对多个分支进行合并。合并分支的基本语法为:
git merge 分支名
该命令实现的是将某一分支合并到现有分支上。继『2.3 切换分支』例子后,我们有两个分支:
master
分支和在
master
分支基础上进行修改后的
testing
分支,且项目正处于
master
分支上,当我们执行
git merge testing
后:
可以看到,合并后,
test.cpp
文件出现了更新,且提示
Fast-forward
,当我们再次查看分支信息时,发现现处的
master
分支其实就是
testing
分支,指向的也就是我们第三次提交的内容。这是因为我们第三次提交时,只在第二次提交的基础上添加了一行内容,当把
testing
分支合并到
master
分支后,也就是将改行加上而已,符合合并逻辑。
注意:
Fast-forward
的提示信息指的是快进的意思,由于想要合并的分支
testing
所指向的提交是
master
分支的直接后继, 因此 Git 会直接将指针向前移动到该次提交。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
此时,一般而言,合并后应该删除
testing
分支,因为你已经不再需要它了(
master
分支已经指向了同一个位置)。我们可以使用带
-d
选项的
git branch
命令来删除分支:
git branch -d testing
2.5 冲突合并
有时候合并操作不会如此顺利,会遇到冲突。原因在于:在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改。此时Git 就没法干净的合并它们,在合并它们的时候就会产生合并冲突,必须人为决定新代码内容:
此时 Git 会暂停下来,等待你去解决合并产生的冲突。此时使用
git status
命令可以查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:
有上面可以看出,任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,如下图所示类似:
这表示在
<<<<<<<
和
>>>>>>>
之间的内容出现了合并冲突,且现处分支
HEAD
的内容和待合并分支
testing
的内容由
=======
隔开。
上述的冲突文件会一直等待人为解决。当我们执行
vim test.cpp
命令,解决了文件里的冲突且
<<<<<<<
,
=======
和
>>>>>>>
这些行被完全删除,再对
test.cpp
文件使用
git add test.cpp
命令可以将其标记为冲突已解决状态。
注意:只有暂存了这些原本有冲突的文件,Git 才会将它们标记为冲突已解决。
此后,再次执行
git commit
命令来完成合并提交。
2.6 分支管理
git branch
命令不只是可以创建与删除分支(
-d
选项)。 如果不加任何参数运行它,会得到当前所有分支的一个列表:
注意:
master
分支前的
*
字符:它代表的是现处分支(也就是当前
HEAD
指针所指向的分支)。这意味着如果在这时候提交,master 分支将会随着新的工作向前移动。
如果需要查看每一个分支的最后一次提交,可以运行
git branch -v
命令:
--merged
与
--no-merged
这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。
git branch --merged
命令表示查看哪些分支已经合并到当前分支:git branch --no-merged
命令表示查看所有包含未合并工作的分支:
版权归原作者 PanyCG_pc 所有, 如有侵权,请联系我们删除。