你的位置:首页 > 软件开发 > Java > Git分支

Git分支

发布时间:2017-04-06 12:00:36
前面的话  几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着可以从开发主线上分离开来,然后在不影响主线的同时继续工作。在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间  有人把Git的分支模型称为&ldq ...

Git分支

前面的话

  几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着可以从开发主线上分离开来,然后在不影响主线的同时继续工作。在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间

  有人把Git的分支模型称为“必杀技特性”,而正是因为它,将Git从版本控制系统家族里区分出来。Git有何特别之处呢?Git的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。和许多其他版本控制系统不同,Git鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。理解分支的概念并熟练运用后,才会意识到为什么Git是一个如此强大而独特的工具,并从此真正改变开发方式。本文将详细介绍Git分支

 

定义

  为了理解Git分支的实现方式,我们需要回顾一下Git是如何储存数据的。Git保存的不是文件差异或者变化量,而只是一系列文件快照

  在Git中提交时,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包含本次提交的作者等相关附属信息,包含零个或多个指向该提交对象的父对象指针:首次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先

  为直观起见,我们假设工作目录中有三个文件,准备将它们暂存后提交。暂存操作会对每一个文件计算校验和,然后把当前版本文件快照保存到Git仓库(Git使用blob类型的对象存储这些快照),并将校验和加入暂存区域

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

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

Git分支

  那么,Git又是如何创建一个新的分支的呢?答案很简单,创建一个新的分支指针。比如新建一个testing分支,可以使用git branch命令:

$ git branch testing

  那么,Git是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为HEAD的特别指针。请注意它和你熟知的许多其他版本控制系统(比如Subversion或CVS)里的HEAD概念大不相同。在Git中,它是一个指向你正在工作中的本地分支的指针(可以将HEAD想象为当前分支的别名)。运行gitbranch命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在master分支里工作

Git分支

  这样的实现方式会给我们带来什么好处呢?好吧,现在不妨再提交一次:

$ git commit -a -m 'made a change'

  非常有趣,现在testing分支向前移动了一格,而master分支仍然指向原先git checkout时所在的commit对象。现在我们回到master分支看看

$ git checkout master

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

$ git commit -a -m 'made other changes'

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

  这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而Git的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(即parent对象),将来要合并分支时,寻找恰当的合并基础(即共同祖先)的工作其实已经自然而然地摆在那里了,所以实现起来非常容易。Git鼓励开发者频繁使用分支,正是因为有着这些特性作保障

  接下来看看,我们为什么应该频繁使用分支

 

新建与合并

  现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

  1、开发某个网站

  2、为实现某个新的需求,创建一个分支

  3、在这个分支上开展工作

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

  1、返回到原先已经发布到生产服务器上的分支

  2、为这次紧急修补建立一个新分支,并在其中修复问题

  3、通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。

  4、切换到之前实现新需求的分支,继续工作

【新建与切换】

  假设你正在项目中愉快地工作,并且已经提交了几次更新

Git分支

  接着开始尝试修复问题,在提交了若干次更新后,iss53分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的HEAD指针正指向iss53)

$ git commit -a -m 'added a new footer [issue 53]'

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

  不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止Git为你切换分支。切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法(分别叫做stashing和commitamending)。目前已经提交了所有的修改,所以接下来可以正常转换到master分支:

$ git checkout masterSwitched to branch 'master'

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

$ git checkout master$ git merge hotfixUpdating f42c576..3a0874cFast-forward README | 1 - 1 file changed, 1 deletion(-)

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

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

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

【分支的合并】

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

$ git checkout master$ git merge iss53Auto-merging READMEMerge made by the 'recursive' strategy. README | 1 + 1 file changed, 1 insertion(+)

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

  值得一提的是Git可以自己裁决哪个共同祖先才是最佳合并基础;这和CVS或Subversion(1.5以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让Git的合并操作比其他系统都要简单不少

Git分支

  或者把它们想象成工作流水线,或许更好理解一些,经过测试的提交对象集合被遴选到更稳定的流水线

Git分支

  现在,假定两件事情:我们最终决定使用第二个解决方案,即iss91v2中的办法;另外,我们把dumbidea分支拿给同事们看了以后,发现它竟然是个天才之作。所以接下来,我们准备抛弃原来的iss91分支(实际上会丢弃C5和C6),直接在主干中并入另外两个分支。最终的提交历史将变成如下图所示

Git分支

  如果你在本地master分支做了些改动,与此同时,其他人向git.ourcompany.com推送了他们的更新,那么服务器上的master分支就会向前推进,而与此同时,你在本地的提交历史正朝向不同方向发展。不过只要你不和服务器通讯,你的origin/master指针仍然保持原位不会移动

Git分支

  为了演示拥有多个远程分支(在不同的远程服务器上)的项目是如何工作的,我们假设你还有另一个仅供你的敏捷开发小组使用的内部服务器git.team1.ourcompany.com。可以用git remote add命令把它加为当前项目的远程分支之一。我们把它命名为teamone,以便代替完整的Git URL以方便使用

Git分支

【推送本地分支】

  要想和其他人分享某个本地分支,你需要把它推送到一个你拥有写权限的远程仓库。你创建的本地分支不会因为你的写入操作而被自动同步到你引入的远程服务器上,你需要明确地执行推送分支的操作。换句话说,对于无意分享的分支,你尽管保留为私人分支好了,而只推送那些协同工作要用到的特性分支

  如果你有个叫serverfix的分支需要和他人一起开发,可以运行git push (远程仓库名) (分支名):

$ git push origin serverfixCounting objects: 20, done.Compressing objects: 100% (14/14), done.Writing objects: 100% (15/15), 1.74 KiB, done.Total 15 (delta 5), reused 0 (delta 0)To git@github.com:schacon/simplegit.git * [new branch]   serverfix -> serverfix

  最容易的整合分支的方法是merge命令,它会把两个分支最新的快照(C3和C4)以及二者最新的共同祖先(C2)进行三方合并,合并的结果是产生一个新的提交对象(C5)

Git分支

  现在回到master分支,进行一次快进合并

Git分支

  假设在接下来的一次软件发布中,我们决定先把客户端的修改并到主线中,而暂缓并入服务端软件的修改(因为还需要进一步测试)。这个时候,我们就可以把基于client分支而非server分支的改变(即C8和C9),跳过server直接放到master分支中重演一遍,但这需要用gitrebase的--onto选项指定新的基底分支master

$ git rebase --onto master server client

  现在可以快进master分支了

$ git checkout master$ git merge client

  现在我们决定把server分支的变化也包含进来。我们可以直接把server分支衍合到master,而不用手工切换到server分支后再执行衍合操作——git rebase [主分支] [特性分支]命令会先取出特性分支server,然后在主分支master上重演

$ git rebase master server

  然后就可以快进主干分支master了

$ git checkout master$ git merge server

【衍合的风险】

  要用衍合得遵守一条准则:一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作

  在进行衍合的时候,实际上抛弃了一些现存的提交对象而创造了一些类似但不同的新的提交对象。如果你把原来分支中的提交对象发布出去,并且其他人更新下载后在其基础上开展工作,而稍后你又用git rebase抛弃这些提交对象,把新的重演后的提交对象发布出去的话,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容时,提交历史就会变得一团糟

  下面我们用一个实际例子来说明为什么公开的衍合会带来问题。假设你从一个中央服务器克隆然后在它的基础上搞了一些开发,提交历史如下图所示

Git分支

  接下来,那个推送C6上来的人决定用衍合取代之前的合并操作;继而又用git push --force覆盖了服务器上的历史,得到C4'。而之后当你再从服务器上下载最新提交后,会得到

Git分支

  C8这一步的合并是迟早会发生的,因为只有这样你才能和其他协作者提交的内容保持同步。而在C8之后,你的提交历史里就会同时包含C4和C4',两者有着不同的SHA-1校验值,如果用git log查看历史,会看到两个提交拥有相同的作者日期与说明,令人费解。而更糟的是,当你把这样的历史推送到服务器后,会再次把这些衍合后的提交引入到中央服务器,进一步困扰其他人

  这个例子中,出问题的责任方是那个发布了C6后又用衍合发布C4'的人,其他人会因此反馈双重历史到共享主干,从而混淆大家的视听

  如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些尚未公开的提交对象,就没问题。如果衍合那些已经公开的提交对象,并且已经有人基于这些提交对象开展了后续开发工作的话,就会出现叫人沮丧的麻烦

 


 

海外公司注册、海外银行开户、跨境平台代入驻、VAT、EPR等知识和在线办理:https://www.xlkjsw.com

原标题:Git分支

关键词:Git

Git
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。