版本管理工具Git的使用(三)-Git分支

几乎全部的版本控制系统都以某种形式支持分支。 使用分支意味着你能够把你的工做从开发主线上分离开来,以避免影响开发主线。 在不少版本控制系统中,这是一个略微低效的过程——经常须要彻底建立一个源代码目录的副本。对于大项目来讲,这样的过程会耗费不少时间。
有人把 Git 的分支模型称为它的“必杀技特性”,也正由于这一特性,使得 Git 从众多版本控制系统中脱颖而出。 为什么 Git 的分支模型如此出众呢? Git 处理分支的方式可谓是难以置信的轻量,建立新分支这一操做几乎能在瞬间完成,而且在不一样分支之间的切换操做也是同样便捷。 与许多其它版本控制系统不一样,Git 鼓励在工做流程中频繁地使用分支与合并,哪怕一天以内进行许屡次。 理解和精通这一特性,你便会意识到Git 是如此的强大而又独特,而且今后真正改变你的开发方式。
在介绍Git分支,先将下Git是如何保存数据的?html

1、Git是如何保存数据的

直接记录快照,而非比较差别

Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差异在于 Git 对待数据的方法。 概念上来区分,其它大部分系统以文件变动列表的方式存储信息。 这类系统(CVS、Subversion、Perforce、Bazaar 等等)将它们保存的信息看做是一组基本文件和每一个文件随时间逐步累积的差别。
image.png
Git 不按照以上方式对待或保存数据。 反之,Git 更像是把数据看做是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的所有文件制做一个快照并保存这个快照的索引。 为了高效,若是文件没有修改,Git 再也不从新存储该文件,而是只保留一个连接指向以前存储的文件。 Git 对待数据更像是一个 快照流。
image.png
这是 Git 与几乎全部其它版本控制系统的重要区别。
在进行提交操做时,Git 会保存一个提交对象(commit object)。知道了 Git 保存数据的方式,咱们能够很天然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不只仅是这样,该提交对象还包含了做者的姓
名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操做产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象,为了更加形象地说明,咱们假设如今有一个工做目录,里面包含了三个将要被暂存和提交的文件。 暂存操做会为每个文件计算校验,而后会把当前版本的文件快照保存到Git 仓库中,最终将校验和加入到暂存区域等待提交:git

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

当使用 git commit 进行提交操做时,Git 会先计算每个子目录的校验和,而后在Git 仓库中这些校验和保存为树对象。 随后,Git 便会建立一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就能够在须要的时候重现这次保存的快照。
如今,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和全部提交信息)。
image.png数据库

作些修改后再次提交,那么此次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。服务器

image.png

(以上内容参考于progit)
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在屡次提交操做以后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操做中自动向前动。
(Git 的 “master” 分支并非一个特殊分支。 它就跟其它分支彻底没有区别。 之因此几乎每个仓库都有 master 分支,是由于 git init 命令默认建立它)
image.png
2、分支相关的概念及操做
了解了上面的概念,接下来假设下,如今须要两我的为同一个项目开发不一样的功能,在开发过程当中,如何保证不相互影响了?---分支就能够解决这种影响网络

2.1分支的新建合并

来看一个简单的分支新建与分支合并的例子,实际工做中你可能会用到相似的工做流。 你将经历以下步
骤:工具

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

正在此时,你忽然接到一个电话说有个很严重的问题须要紧急修补。 你将按照以下方式来处理:测试

  1. 切换到你的线上分支(production branch)。
  2. 为这个紧急任务新建一个分支,并在其中修复它。
  3. 在测试经过以后,切换回线上分支,而后合并这个修补分支,最后将改动推送到线上分支。
  4. 切换回你最初工做的分支上,继续工做。

新建分支
首先,假设正在你的项目上master分支工做,而且已经有一些提交。
如今须要修复线上的一个问题(issuse 52),这时须要新建一个修复该问题的分支,并切换到该分支
这时使用命令:fetch

git checkout -b issuse52

它是下面两条命令的简写:网站

git branch issuse52
git checkout issuse52

注意:在切换分支时,要摆正暂存区是干净的,负责切换分支会失败。
你花了一些时间将该issuse修复,并将修复的代码推送到远程库。
这时,你能够切换到原来的master分支继续以前的工做了
请牢记:当你切换分支的时候,Git 会重置你的工做目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工做目录和这个分支最后一次提交时的样子如出一辙。
你能够运行你的测试,确保你的修改是正确的,而后将其合并回你的master 分支来部署到线上。 你可使用git merge 命令来达到上述目的:

git checkout master
git merge issuse52
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

在合并的时候,你应该注意到了"快进(fast-forward)"这个词。 因为当前 master 分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,因此 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,若是顺着一个分支走下去可以到达另外一个分支,那么 Git 在合并二者的时候,只会简单的将指针向前推动(指针右移),由于这种状况下的合并操做没有须要解决的分歧——这就叫作 “快进(fast-forward)”。如今,最新的修改已经在 master 分支所指向的提交快照中,你能够着手发布该修复了。
关于这个紧急问题的解决方案发布以后,你准备回到被打断以前时的工做中。 然而,你应该先删除 hotfix 分
支,由于你已经再也不须要它了 —— master 分支已经指向了同一个位置。 你可使用带 -d 选项的 git
branch 命令来删除分支:

git branch -d issuse52
Deleted branch issuse52 (3a0874c).

2.2遇到冲突时的分支合并

有时候合并操做不会如此顺利。 若是你在两个不一样的分支中,对同一个文件的同一个部分进行了不一样的修改,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 作了合并,可是没有自动地建立一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。
你能够在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:

git status
On branch master
You have unmerged paths.
 (fix conflicts and run "git commit")
Unmerged paths:
 (use "git add <file>..." to mark resolution)
 both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")

任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你能够打开这些包含冲突的文件而后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

这表示 HEAD 所指示的版本(也就是你的 master 分支所在的位置,由于你在运行 merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在======= 的
下半部分。 为了解决冲突,你必须选择使用由 =======
分割的两部分中的一个,或者你也能够自行合并这些内容。 例如,你能够经过把这段内容换成下面的样子来解
决冲突:

<div id="footer">
please contact us at email.support@github.com
</div>

上述的冲突解决方案仅保留了其中一个分支的修改,而且 <<<<<<< , ======= , 和 >>>>>>> 这些行被彻底删除了。 在你解决了全部文件里的冲突以后,对每一个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些本来有冲突的文件,Git 就会将它们标记为冲突已解决。

2.3分支开发工做流

接下,介绍一些常见的利用分支进行开发的工做流程。而正是因为分支管理的便捷,才衍生出这些典型的工做模式,你能够根据项目实际状况选择一种用用看。
长期分支
由于 Git 使用简单的三方合并,因此就算在一段较长的时间内,反复把一个分支合并入另外一个分支,也不是什么难事。 也就是说,在整个项目开发周期的不一样阶段,你能够同时拥有多个开放的分支;你能够按期地把某些特性分支合并入其余分支中。许多使用 Git 的开发者都喜欢使用这种方式来工做,好比只在 master 分支上保留彻底稳定的代码——有可能仅
仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来作后续开发或者测试稳定性——这些分支没必要保持绝对稳定,可是一旦达到稳定状态,它们就能够被合并入 master 分支了。 这样,在确保这些已完成的特性分支(短时间分支,好比以前的 iss53 分支)可以经过全部测试,而且不会引入更多 bug 以后,就能够合并入主干分支中,等待下一次的发布。事实上咱们刚才讨论的,是随着你的提交而不断右移的指针。 稳定分支的指针老是在提交历史中落后一大截,而前沿分支的指针每每比较靠前。
一般把他们想象成流水线(work silos)可能更好理解一点,那些通过测试考验的提交会被遴选到更加稳定的流水线上去。
image.png
特性分支
特性分支对任何规模的项目都适用。 特性分支是一种短时间分支,它被用来实现单一特性或其相关工做。在 Git 中一天以内屡次建立、使用、合并、删除分支都很常见。
你已经在上一节中你建立的 iss53 和 hotfix 特性分支中看到过这种用法。 你在上一节用到的特性分支(iss52 )中提交了一些更新,而且在它们合并入主干分支以后,你又删除了它们。 这项技术能使你快速而且完整地进行上下文切换(context-switch)——由于你的工做被分散到不一样的流水线中,在不一样的流水线中每一个分支都仅与其目标特性相关,所以,在作代码审查之类的工做的时候就能更加容易地看出你作了哪些改动。 你能够把作出的改动在特性分支中保留几分钟、几天甚至几个月,等它们成熟以后再合并,而不用在意它们创建的顺序或工做进度。
请牢记,当你作这么多操做的时候,这些分支所有都存于本地。
当你新建和合并分支的时候,全部这一切都只发生在你本地的 Git 版本库中 —— 没有与服务器发生交互。

2.4远程分支

远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你能够经过 git ls-remote (remote) 来显式地得到远程引用的完整列表,或者经过 git remote show (remote) 得到远程分支的更多信息。 然而,一个更常见的作法是利用远程跟踪分支。
远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你作任何网络通讯操做时,它们会自动移动。 远程跟踪分支像是你上次链接到远程仓库时,那些分支所处状态的书签。

它们以 (remote)/(branch) 形式命名。 例如,若是你想要看你最后一次与远程仓库 origin 通讯时 master分支的状态,你能够查看origin/master 分支。 你与同事合做解决一个问题而且他们推送了一个 iss52 分支,你可能有本身的本地 iss53 分支;可是在服务器上的分支会指向 origin/iss53 的提交。
这可能有一点儿难以理解,举一个例子。 假设你的网络里有一个在 git.ourcompany.com 的 Git 服务器。 若是你从这里克隆,Git 的 clone 命令会为你自动将其命名为 origin,拉取它的全部数据,建立一个指向它的 master 分支的指针,而且在本地将其命名为origin/master。 Git 也会给你一个与 origin 的 master分支在指向同一个地方的本地 master 分支,这样你就有工做的基础。
若是你在本地的 master 分支作了一些工做,与此同时,其余人推送提交到 git.ourcompany.com 并更新了它的 master 分支,那么你的提交历史将向不一样的方向前进。 另外,只要你不与 origin 服务器链接,你的
origin/master 指针就不会移动。
若是要同步你的工做,运行 git fetch origin 命令。 这个命令查找 “origin” 是哪个服务器(在本例中,它是 git.ourcompany.com),从中抓取本地没有的数据,而且更新本地数据库,移动 origin/master
指针指向新的、更新后的位置。
推送
当你想要公开分享一个分支时,须要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。
若是但愿和别人一块儿在名为 serverfix 的分支上工做,你能够像推送第一个分支那样推送它。 运行 git push (remote) (branch):

git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
 * [new branch] serverfix -> serverfix

下一次其余协做者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/serverfix,指向服务器的 serverfix 分支的引用:

git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
 * [new branch] serverfix -> origin/serverfix

要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话
说,这种状况下,不会有一个新的 serverfix 分支——只有一个不能够修改的 origin/serverfix 指针。
能够运行 git merge origin/serverfix 将这些工做合并到当前所在的分支。 若是想要在本身的
serverfix 分支上工做,能够将其创建在远程跟踪分支之上:

git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

这会给你一个用于工做的本地分支,而且起点位于 origin/serverfix。
跟踪分支
从一个远程跟踪分支检出一个本地分支会自动建立所谓的“跟踪分支”(它跟踪的分支叫作“上游分支”)。
跟踪分支是与远程分支有直接关系的本地分支。 若是在一个跟踪分支上输入 git pull,Git 能自动地识别去哪一个服务器上抓取、合并到哪一个分支。
当克隆一个仓库时,它一般会自动地建立一个跟踪 origin/master 的 master 分支。

总结:

上面介绍了如何建并切换至新分支、在不一样分支之间切换以及合并本地分支以及向Git的服务器推送本地分支的内容以及获取远程分支的最新内容、使用共享分支与他人协做。

相关文章
相关标签/搜索