Git之(四)分支管理

当我们初始化Git仓库的时候,Git会默认创建一个名为master的主分支。在实际工作中,主分支要求是一个稳定、健壮、安全的主线,一般不允许在主分支上直接进行开发,而是拉取一个新的分支,开发、测试完成后,再将分支合并到主分支上。

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

Git 的分支模型可称为“必杀技特性”,而正是因为该特性将 Git 从版本控制系统家族里区分出来,鹤立鸡群。其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。但Git的分支是与众不同的,无论创建、切换和删除分支,Git能在瞬间完成!无论你的版本库是1个文件还是1万个文件。

Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。在实际工作中,往往修复一个bug都会使用一个分支来完成。

理解分支的概念并熟练运用后,你才会意识到为什么 Git 是一个如此强大而独特的工具,并从此真正改变你的开发方式。

分支实现原理

在第一篇文章中提到过,Git 保存的不是文件差异或者变化量,而只是一系列文件快照。

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

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

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

现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象:

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

Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作为分支的默认名字。在若干次提交后,其实已经有了一个指向最后一次提交对象的 master 分支,它在每次提交的时候都会自动向前移动。

创建分支

创建一个新的名为“testing”分支,可以使用“git branch<branchName>”命令:

git branch testing

该命令会在当前 commit 对象上新建一个指针:

那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为HEAD 的特别指针。在 Git 中,它是一个指向正在工作中的本地分支的指针(可以将 HEAD 想象为当前分支的别名)。运行Git branch 命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在 master 分支里工作。

使用不带任何参数的“git branch”命令可以查看当前的分支情况:

* master
  testing

Git显示,共有两个分支,当前工作分支为master,分支列表中的星号“*”相当于HEAD指针,标注了当前工作分支。

切换分支

命令“git checkout <branchName>”可以将当前工作分支切换到名为branchName的分支。比如,运行命令:

git checkout testing

Git会提示:

Switched to branch ‘testing‘

这样 HEAD 就指向了 testing 分支:

现在我们如果修改了工作区的文件,所有commit操作都是提交到testing分支,而非master。

现在 testing 分支向前移动了一步,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象。现在重新切换到master分支:

git checkout master


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

在mast分支上再做些修改,然后提交。现在我们的项目提交历史产生了分叉,因为刚才我们创建了一个分支testing,转换到其中进行了一些工作,然后又回到原来的master主分支进行了另外一些工作。

这些改变分别孤立在不同的分支里。我们可以在不同分支里反复切换,并在时机成熟时把它们合并到一起。

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

合并分支

模拟这样的一个场景,早上到了公司接到新任务,新建一个名为“iss53”的分支来进行开发工作。要新建并切换到该分支,运行git checkout 并加上 -b 参数:

git checkout -b iss53

这相当于执行下面这两条命令:

git branch iss53
git checkout iss53


然后不断地写代码,提交代码:

突然,接到通知,需要立即修复master分支上的一个严重bug。

第一步肯定需要切换到master。如果当前工作区与暂存区都是干净的,OK,直接切换回master即可。但是如果iss53分支上的开发还没有完成,并且不便于commit到版本库,怎么办?一旦切回到其他分支,工作区与暂存区就会被清空、覆盖。实际上,如果工作区或暂存区不是干净的,存在没有提交到版本库的更改,Git是不允许切换分支的,会提示:

error: Your local changes to the following files wouldbe overwritten by checkout:
      readme.txt
Please, commit your changes or stash them before you can switch branches.

解决这个问题的办法就是git stash命令。

该命令可以获取工作目录的中间状态——也就是修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。

运行“git stash”命令之后,iss53分支上未commit得变更就会被“储藏”起来,可以顺利地切换到master分支了。要查看现有的储藏,你可以使用 git stash list,会的到这样的一个列表:

[email protected]{0}: WIP on testing: 049d078 …
[email protected]{1}: WIP on testing: c264051 …
[email protected]{2}: WIP on testing: 21d80a5 …

列出的是该分支上所有被stash过的编号,使用命令“git stash apply”即可恢复到最新stash过的场景。如果想应用更早的储藏,可以通过名字指定它,像这样:git stash apply [email protected]{2}。如果不指明编号,Git 默认使用最近的储藏并尝试应用它。

题归正转,我们切换到master分支,拉去一个名为“hotfix”的分支来紧急修复bug。

git checkout -b ‘hotfix‘

修复好之后,commit到版本库,则现在Git的分支结构如下图所示:

经测试之后,该bug成功修复,然后需要将该分支合并到master,首先依然要切换到master,然后使用命令“git merge”合并分支:

git checkout master
git merge hotfix

Git提示:

Updating 771f6de..adea62a
Fast-forward
 …

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

现在最新的修改已经在当前master 分支所指向的提交对象中了:

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

git branch -d hotfix

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

现在回到之前未完成的 iss53分支上继续工作,完成后commit到版本库。

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

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

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

值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础,不需要开发者手工指定合并基础。此特性让Git 的合并操作比其他系统都要简单不少。

解决冲突

有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起,逻辑上说,这种问题只能由人来裁决。这时候如果合并分支就会出现下面的结果:

Auto-merging readme.txt
CONFLICT (content): Merge conflictin readme.txt
Automatic merge failed; fix conflicts and then committhe result.

Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用git status 查阅:

On branch master
You have unmerged paths.
  (fix conflictsand run "git commit")

Unmerged paths:
  (use "gitadd <file>..." to mark resolution)
      bothmodified:   readme.txt
no changes added to commit (use "git add"and/or "git commit -a")

任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:

this is my first git project
<<<<<<<HEAD
add row on master branch
add annother row on master branch
=======
add row on testing branch
add another row on testing branch
>>>>>>>testing

可以看到 ======= 隔开的上半部分,是 HEAD(即 master 分支,在运行merge 命令时所切换到的分支)中的内容,下半部分是在 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。当然,Git插入的额外标记行也需要删除。

在解决了所有文件里的所有冲突后,运行 git add将把它们标记为已解决状态(实际上就是来一次快照保存到暂存区域)。因为一旦暂存,就表示冲突已经解决。

删除分支

分支合并到master之后,如果无特殊用途,应该及时删除分支。

要从该清单中筛选出已经(或尚未)与当前分支合并的分支,可以用 “--merged”和“--no-merged” 选项。

比如用“git branch --merged” 查看哪些分支已被并入当前分支,也就是说哪些分支是当前分支的直接上游:

* master
  testing

证明testing分支已经合并到master分支当中了,可以删除:

git branch –d testing

使用“git branch --no-merge”查看还没有合并的分支:

 newTesting

如果使用命令“git branch–d newTesting”删除该分支,Git会提示:

error: The branch ‘newTesting‘ is not fully merged.
If you are sure you want to delete it, run ‘git branch -D newTesting‘.

由于这些分支中还包含着尚未合并进来的工作成果,所以简单地用 Git branch -d 删除该分支会提示错误,因为那样做会丢失数据。

不过,如果你确实想要删除该分支上的改动,可以用大写的删除选项 -D 强制执行,就像上面提示信息中给出的那样。

时间: 2024-12-06 19:42:51

Git之(四)分支管理的相关文章

Git 学习笔记&lt;分支管理&gt; (三)

分支是什么? 分支就像树分出的树枝,不同的是,它们之间可以互相合并. 将版本的推进想象成一个链表的伸长:  version 1.0 ==> version 2.0 ==>version3.0  . master是主要的分支基本上用于发布产品.你可以从master分出一个dev,在上面创建新功能,或者修bug然后调试.最后再合并到master里面.就像下面这样. master分支:  version 1.0=========>version 2.0===... \            

四、git学习之——分支管理、解决冲突

分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN. 如果两个平行宇宙互不干扰,那对现在的你也没啥影响.不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN! 分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了.如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险. 现在有了分支,就不用怕了.你

Git(3):分支管理

Git 分支管理 几乎每一种版本控制系统都以某种形式支持分支.使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作. 创建分支命令 $git branch <branch name> 切换分支命令(当你切换分支的时候,Git会用该分支的最后提交的快照替换你的工作目录的内容,所以多个分支不需要多个目录.) $git checkout <branch name> 列出分支命令 $git branch 删除分支命令 $git branch -d <branch

Git教程之分支管理之二

分支管理策略 通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息.如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息(有分支的commit id).下面我们实战一下--no-ff(ff:fast forward)方式的git merge:首先,仍然创建并切换dev分支:修改readme.txt文件,并提交一个新的commit: 现在,我们切换回master:准

Git教程之分支管理之一

分支在实际中有什么用呢? 你创建了一个属于你自己的分支,别人看不到,别人还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作. 在版本回退里,我们已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支.截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支.HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支

Git由浅入深之分支管理

几乎所有的版本控制系统都以分支的方式进行操作,分支是独立于项目主线的一条支线,我们可以在不影响主线代码的情况下,在分支下进行工作.对于传统的一些版本控制工具来说,我们通常需要花费比较多的时间拷贝主线代码,创建一个分支,并且对分支的管理效率也越来越不令人满意,而如今备受推崇的Git确实名副其实,Git中的分支非常轻量,我们可以随时随意创建任意数量的新分支,几乎感觉不到什么延时,而且对分支的操作也很高效,如,切换分支,暂存内容,分支合并,分支提交等. Git分支的与众不同 上面我们提到相对于其他大多

git学习 六 分支管理

分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN. 如果两个平行宇宙互不干扰,那对现在的你也没啥影响.不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN! 分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了.如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险. 现在有了分支,就不用怕了.你

git学习 九 分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息. 如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息. 下面我们实战一下--no-ff方式的git merge: 首先,仍然创建并切换dev分支: $ git checkout -b dev Switched to a new branch 'dev' 修改readme.txt文件,并提交一个新的commit

wghd的git代码仓库分支管理说明【转】

英文原文:http://www.nvie.com/posts/a-successful-git-branching-model/ 原文作者:Vincent Driessen 本文经Linux大棚博主总结精简而成. 1 GIT,在技术层面上,绝对是一个无中心的分布式版本控制系统,但在管理层面上,我建议你保持一个中心版本库. 2 我建议,一个中心版本库(我们叫它origin)至少包括两个分支,即“主分支(master)”和“开发分支(develop)” 3 要确保:团队成员从主分支(master)获

码云git使用四(分支的创建,使用和合并)

我们在使用git的时候,一般都不是直接在主代码中开发, 通常我们做的操作是创建一个分支,我们在分支上开发,开发完毕,在提交到主代码中. 我们现在学习创建分支,合并分支. 1.首先我们下载到本地都是在主分支上面的. 2.分支分为远程分支和远程所在的本地分支,我们通常是先提交代码到远程的本地分支,然后和远程分支进行合并,确定没有问题,就可以提交到远程了. 3.创建远程对应的本地分支. 4.本地分支表示. 5.本地分支的切换 6.目前先总结那么多,以后遇到问题,在总结