有人把 Git 的分支模型称为它的`‘必杀技特性’',也正由于这一特性,使得 Git 从众多版本控制系统中脱颖而出。 为什么 Git 的分支模型如此出众呢? Git 处理分支的方式可谓是难以置信的轻量,建立新分支这一操做几乎能在瞬间完成,而且在不一样分支之间的切换操做也是同样便捷。 与许多其它版本控制系统不一样,Git 鼓励在工做流程中频繁地使用分支与合并,哪怕一天以内进行许屡次。 理解和精通这一特性,你便会意识到 Git 是如此的强大而又独特,而且今后真正改变你的开发方式。html
在进行提交操做时,Git 会保存一个提交对象(commit object)。知道了 Git 保存数据的方式,咱们能够很天然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不只仅是这样,该提交对象还包含了做者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操做产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象,git
为了更加形象地说明,咱们假设如今有一个工做目录,里面包含了三个将要被暂存和提交的文件。 暂存操做会为每个文件计算校验和(使用咱们在 起步 中提到的 SHA-1 哈希算法),而后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:github
git add README test.rb LICENSE
git commit -m 'The inittial commit of my project'
当使用 git commit
进行提交操做时,Git 会先计算每个子目录(本例中只有项目根目录)的校验和,而后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会建立一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就能够在须要的时候重现这次保存的快照。算法
如今,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和全部提交信息)。vim
你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master
分支。HEAD
严格来讲不是指向提交,而是指向master
,master
才是指向提交的,因此,HEAD
指向的就是当前分支。工具
一开始的时候,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能肯定当前分支,以及当前分支的提交点:ui
Git 是怎么建立新分支的呢? 很简单,它只是为你建立了一个能够移动的新的指针。 好比,建立一个 testing 分支, 你须要使用 git branch
命令:spa
git branch testing
要切换到一个已存在的分支, 你须要使用 git checkout 命令, 咱们如今切换到新建立的 testing 分支去:版本控制
git checkout testing
那么,这样的实现方式会给咱们带来什么好处呢? 如今不妨再提交一次:指针
vim test.rb
git commit -a -m 'made a change'
首先, 咱们建立dev 分支, 而后切换到 dev 分支:
git checkout -b dev
Switched to a new branch 'dev'
git checkout 命令加上 -b 参数表示建立切换, 至关于如下两条命令:
git branch dev
git checkout dev
witched to branch 'dev'
而后, 用 git branch 命令查看当前分支:
git branch * dev master
git branch 命令会列出全部分支, 当前分支前面会标一个 * 号.
而后, 咱们就能够在dev 分支上正常提交, 好比对 readme.txt 作个修改, 加上一行:
Creating a new branch is quick.
你可使用带 -d
选项的 git branch
命令来删除分支:
git branch -d hotfix
Deleted branch hotfix (3a0874c).
假设你已经修正了 #53 问题,而且打算将你的工做合并入 master
分支。 为此,你须要合并 iss53
分支到 master
分支,这和以前你合并 hotfix
分支所作的工做差很少。 你只须要检出到你想合并入的分支,而后运行 git merge
命令:
git checkout master
Switched to branch 'master'
git merge iss53
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)
这和你以前合并 hotfix
分支的时候看起来有一点不同。 在这种状况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 由于,master
分支所在提交并非 iss53
分支所在提交的直接祖先,Git 不得不作一些额外的工做。 出现这种状况的时候,Git 会使用两个分支的末端所指的快照(C4
和 C5
)以及这两个分支的工做祖先(C2
),作一个简单的三方合并。
有时候合并操做不会如此顺利, 若是你在两个不一样的分支中, 对同一个文件的同一个部分进行了不一样的修改, 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 就会将它们标记为冲突已解决。
如今已经建立、合并、删除了一些分支,让咱们看看一些经常使用的分支管理工具。
git branch
命令不仅是能够建立与删除分支。 若是不加任何参数运行它,会获得当前全部分支的一个列表:
git branch iss53 * master testing
注意 master
分支前的 *
字符:它表明如今检出的那一个分支(也就是说,当前 HEAD
指针所指向的分支)。 这意味着若是在这时候提交,master
分支将会随着新的工做向前移动。 若是须要查看每个分支的最后一次提交.