分支介绍
使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线
与其他版本控制系统不同,git的分支模型轻量而且很快,git鼓励在工作流程总频繁地使用分支与合并,哪怕一天内进行许多次
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。在进行提交操作时,Git 会保存一个提交对象(commit object)。该提交对象会包含一个指向暂存内容快照的指针。但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象
举例说明:我们假设现在有一个工作目录,里面包含了将要被暂存和提交的文件,暂存操作会为每一个文件计算校验和(SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针
Git的分支,其实本质上仅仅是指向提交对象的可变指针。GIt的默认分支名字是master。在多次提交操作之后,你其实就有一个指向最后那个提交对象的master分支。它在每次的提交操作中自动向前移动。
分支创建
创建分支只是创建了一个可以移动的新的指针。比如创建一个testing分支,
这会在当前所在的提交对象上创建一个指针
那么,git又是怎么知道当前在哪一个分支上呢,它有一个名为HEAD的特殊指针,它指向当前所在的本地分支
可以简单用git log命令查看各个分支当前所指的对象。提供这一功能的参数时--decorate
分支切换
这样HEAD就指向了testing 分支
这样做有什么好处,不妨再提交一次
HEAD分支随着提交操作自动向前移动
如图,testing分支向前移动,但是master分支却没有,它仍然指向运行git checkout时所指向的对象
现在切换回master分支看下
这条命令做了两件事,一是HEAD指回master分支,二是将工作目录恢复成master分支指向的快照内容
分支切换会改变工作目录中的文件
如果再进行修改并提交
这个项目的提交历史已经产生了分叉
可以简单实用git log命令查看分叉历史
由于git的分支实质上仅是包含说指对象校验和(长度为40的SHA-1值字符串)的文件,所以它的创建和销毁都异常高效。创建一个新分支就像是往一个文件中写入41个字节(40个字符和一个换行符),如此的简单能不快吗
分支的新建和合并
使用一个例子说明,假设你正在项目上工作,并已经有一些提交
现在要解决#53问题,想要新建一个分支并同时切换到那个分支上,可以运行一个带有-b参数的git checkout命令
它是以下两条命令的简写
你继续在#53问题上工作,并做了一些提交,在此过程中,iss53分支在不断的向前推进
现在又接到一个电话,有一个紧急问题等待你解决,只是我们只需要切换到master分支,注意在切换分支之前,保持好一个干净的状态,有一些方法可以绕过这个问题(保存进度stashing和修补提交)
假设你已经把你的修改全部提交了,这时可以切换回master分支
这个时候工作目录就和修复#53问题之前一模一样,牢记:当你切换分支的时候,git会重置你的工作目录,使其看起来像回到了你在哪个分支上最后一次提交的样子。git会自动添加,删除,修改文件依确保此时你的工作目录和这个分支最后一次提交时的样子一模一样
接下来,要修复这个紧急问题,我们建立一个针对该紧急问题的分支hotfix branch,在该分支上工作直到问题解决
运行测试确保修改是正确的,然后将其合并回你的master分支来部署到线上。可以使用git merge来达到上述目的
在合并的时候,你应该注意到了"快进(fast-forward)"这个词。由于当前 master 分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
这个紧急问题解决并发布后准备回到被打断之前的额工作中,可以先删除hotfix分支
现在可以切换回你正在工作的分支继续你的工作,也就是针对#53问题的那个分支
分支的合并
假设已经修正了#53问题,并且打算将你的工作合并入master分支。为此需要合并iss53分支到master分支
这和之前合并hotfix分支的时候不一样,开发历史是从一个更早的地方开始分叉开来的,因为master分支所在的提交并不是iss53分支所在提交的直接祖先,git不得不做一些额外的工作,需要做一个三方合并
和之前的将分支指针向前推进所不同的是,git将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他不止一个父提交
合并后就不再需要iss53分支了。现在就可以再任务追踪系统中关闭此项任务,并删除这个分支
遇到冲突时的分支合并
如果在两个不同的分支中,对同一个文件的额同一部分进行了不同的修改,git就没法干净的合并它们,会引起冲突
此时git做了合并,但是美欧自动地创建一个新的合并提交,git会暂停下俩,等待你去解决黑帮产生的冲突。
通过git status来查看那些因包含冲突而处于未合并状态的文件:
git会在右冲突的文件汇总加入标砖的冲突解决标记,这样可以打开这些包含冲突的文件然后手动解决冲突。
出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:
HEAD所指示的版本(也就是master的状态)在这个区段的上部分(======的上部分)而iss53分支说只是的版本再=====的下半部分,
为了解决冲突,必须选择使用由====风格的两部分中的一个,或者可以执行合并这些内容
只保留你需要的内容,<<<<< ===== >>>>>全部删除
在解决冲突之后,对每个文件使用git add来标记为冲突已解决,一旦暂存这些原本有冲突的文件,git就会将它们标记为冲突已解决
可以继续运行git status确认所有合并冲突都已被解决
如果你对结果满意,并且确定之前有冲突的文件都已经咱村里,这时可以输入git commit来完成合并提交
分支管理
查看已有分支 git branch
查看每个分支的最后一次提交 git branch -v
查看哪些分支已经合并到当前分支 git branch --merged 然后没有*号的分支可以进行删除 git branch -d
查看未合并的分支 git branch --no-merged
要是想删除未合并的分支,用git branch -d 命令会失败, 如果真的想要删除分支并丢掉那些工作可以使用-D 强制删除它
分支开发工作流
这里介绍一些常用的利用分支进行开发的工作流程
长期分支
许多使用git的开发者都喜欢使用这种方式来工作,比如只在master分支上保留完全稳定地代码---有可能仅仅是已经发布或即将发布的代码。
还有一些名为develop或者next的平行分支,被用来做后续开发或者测试稳定性--这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入master分支了,这样在确保这些已完成的特性分支能够通过所有测试,并且不会引入更多bug之后,即可以合并入主干分支中,等待下一次的发布。
稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前
特性分支
特性分支是一种短期分支,它被用来实现单一特性或相关工作。一般这种都是在合并后就立刻删除
举个例子,你在master分支工作到C1,为了解决一个问题新建iss91并工作到C4,然而这是对那个问题由有了新的想法,于是再新建一个iss91v2分支试图用另一种方法解决那个问题,接着又回到master分支工作了一会儿,又冒出了一个不太确定的想法,在C10上新建一个dumbidea分支,并在上面做些实验。提价历史如下
这里假设两件事,你决定使用第二个方案来解决那个问题,即使用在iss91v2分支中的方案;另外dumbidea是惊人之举,这时可以抛弃iss91分支(即丢弃C5和C6提交),然后把另外两个分支合并入主干分支。
请牢记:当你做这么多操作的时候,这些分支全部都存在本地,当你新建和合并分支的时候,所有这一切都只发生在你本地的git版本库中--没有与服务器发生交互
远程分支
远程仓库名字“origin”与分支名字“master”一样,在git中并没有任何特别的含义。同时“master”是你运行git init时默认的起始分支名字,“origin”是当你运行git clone时默认的远程仓库名字。如果你运行git clone -o booyah,那么你默认的远程分支名字将会是booyah/master
如果你在本地的master分支做了一些工作,然而在同一时间,其他人推送提交到git.ourcompany.com并更新了它的master分支,那么你的提交历史将向不同的方向前进,也许,只要你不与origin服务器连接,你的origin、master指针就不会移动
如果要同步你的工作,运行git fetch origin命令。这个命令查找“origin”是哪一个服务器(在本例子中,它是git.ourcompany.com),从中抓取本地没有的数据,并更新本地数据库,移动origin/master指针指向新的,更新后的位置。
为了演示多个远程仓库与远程分支的情况,我们假定有另一个内部git服务器,仅用于你的sprint小组的开发工作,这个服务器位于git.team1.ourcompany.com,可以运行git remote add命令添加一个新的远程仓库引用到当前的项目
现在,可以运行git fetch teamone来抓取远程仓库teamone有而本地没有的数据,因为那台服务器上现有的额数据时origin服务器上的一个子集,所以git并不会抓取数据而是会设置远程跟踪分支teamone/master指向teamone的master分支
推送
当你想公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。本地的分支并不会自动与远程仓库同步,你必须显式地推送想要分享的分支。这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人写作的内容推送到公开分支。
如果希望和别人一起在名为serverfix的分支上工作,你可以像推送第一个分支那样推送它。运行git push (remote) (branch):
以上为推送本地的serverfix分支来更新远程仓库上的serverfix分支
同时用命令 git push origin serverfix:serverfix也是做同样的事,相当于说,“推送本地的serverfix分支,将其作为远程仓库的serverfix分支”,可以通过这种格式来推送本地分支到一个命名不相同的远程分支。
如果并不想让远程仓库的分支叫做serverfix,可以运行 git push origin serverfix:awesomebranch来讲本地的serverfix分支推送到远程仓库上的awesomebranch分支
如果不想每次推送都输入用户名和密码,可以设置一个“credential cache”
下一次其他协作者从服务器上抓取数据时,他们就会在本地生成一个远程分支origin/serverfix,指向服务器的serverfix分支的引用,
注意的是,抓取到新的serverfix分支,本地不会自动生成一份可编辑的副本。换句话说,不会有一个新的serverfix分支,只有一个不可以修改的origin/serverfix指针
可以运行git merge origin/serverfix将这些工作合并到当前所在的分支。如果想要在自己的serverfix分支上工作,可以将其建立在远程跟踪分支之上
这会给你一个用于工作的本地分支,并且起点位于origin/serverfix
跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建一个叫做“跟踪分支”,跟踪分支是与远程分支有直接关系的本地分支。如果在一个跟踪分支上输入git pull,git能自动地识别去哪个服务器上抓取,合并到哪个分支
当克隆一个仓库是,它通常会自动地创建一个跟踪origin/master的master分支。然而,如果你愿意的话可以设置其他的跟踪分支