2,版本控制git --分支

 

有人把 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 initial commit of my project'

当使用 git commit 进行提交操做时,Git 会先计算每个子目录(本例中只有项目根目录)的校验和,而后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会建立一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就能够在须要的时候重现这次保存的快照。web

如今,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和全部提交信息)。算法

你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来讲不是指向提交,而是指向mastermaster才是指向提交的,因此,HEAD指向的就是当前分支。shell

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能肯定当前分支,以及当前分支的提交点:vim

git-br-initial

分支建立

Git 是怎么建立新分支的呢? 很简单,它只是为你建立了一个能够移动的新的指针。 好比,建立一个 testing 分支, 你须要使用 git branch 命令:bash

$ git branch testing

分支切换

要切换到一个已存在的分支,你须要使用 git checkout 命令。 咱们如今切换到新建立的 testing 分支去:app

$ 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
Switched 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>

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

如今已经建立、合并、删除了一些分支,让咱们看看一些经常使用的分支管理工具。

git branch 命令不仅是能够建立与删除分支。 若是不加任何参数运行它,会获得当前全部分支的一个列表: 

$ git branch
  iss53
* master
  testing

注意 master 分支前的 * 字符:它表明如今检出的那一个分支(也就是说,当前 HEAD 指针所指向的分支)。 这意味着若是在这时候提交,master 分支将会随着新的工做向前移动。 若是须要查看每个分支的最后一次提交.

Bug分支

软件开发中,bug就像屡见不鲜同样。有了bug就须要修复,在Git中,因为分支是如此的强大,因此,每一个bug均可以经过一个新的临时分支来修复,修复后,合并分支,而后将临时分支删除。

当你接到一个修复一个代号101的bug的任务时,很天然地,你想建立一个分支issue-101来修复它,可是,等等,当前正在dev上进行的工做尚未提交:

$ git status
On branch dev
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   hello.py

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   readme.txt

并非你不想提交,而是工做只进行到一半,还无法提交,预计完成还需1天时间。可是,必须在两个小时内修复该bug,怎么办?

幸亏,Git还提供了一个stash功能,能够把当前工做现场“储藏”起来,等之后恢复现场后继续工做:

$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge

如今,用git status查看工做区,就是干净的(除非有没有被Git管理的文件),所以能够放心地建立分支来修复bug。

首先肯定要在哪一个分支上修复bug,假定须要在master分支上修复,就从master建立临时分支:

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git checkout -b issue-101
Switched to a new branch 'issue-101'

如今修复bug,须要把“Git is free software ...”改成“Git is a free software ...”,而后提交:

$ git add readme.txt 
$ git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
 readme.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

太棒了,原计划两个小时的bug修复只花了5分钟!如今,是时候接着回到dev分支干活了!

$ git checkout dev
Switched to branch 'dev'

$ git status
On branch dev
nothing to commit, working tree clean

工做区是干净的,刚才的工做现场存到哪去了?用git stash list命令看看:

$ git stash list
stash@{0}: WIP on dev: f52c633 add merge

工做现场还在,Git把stash内容存在某个地方了,可是须要恢复一下,有两个办法:

一是用git stash apply恢复,可是恢复后,stash内容并不删除,你须要用git stash drop来删除;

另外一种方式是用git stash pop,恢复的同时把stash内容也删了:

$ git stash pop
On branch dev
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   hello.py

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   readme.txt

Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)

再用git stash list查看,就看不到任何stash内容了:

$ git stash list

你能够屡次stash,恢复的时候,先用git stash list查看,而后恢复指定的stash,用命令:

$ git stash apply stash@{0}

dev分支

软件开发中,总有无穷无尽的新的功能要不断添加进来。

添加一个新功能时,你确定不但愿由于一些实验性质的代码,把主分支搞乱了,因此,每添加一个新功能,最好新建一个dev分支,在上面开发,完成后,合并,最后,删除该dev分支。

如今,你终于接到了一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船。

因而准备开发:

$ git checkout -b dev-vulcan
Switched to a new branch 'dev-vulcan'

5分钟后,开发完毕:

$ git add vulcan.py

$ git status
On branch dev-vulcan
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   vulcan.py

$ git commit -m "add feature vulcan"
[feature-vulcan 287773e] add feature vulcan
 1 file changed, 2 insertions(+)
 create mode 100644 vulcan.py

切回dev,准备合并:

$ git checkout dev

一切顺利的话,feature分支和bug分支是相似的,合并,而后删除。

可是!

就在此时,接到上级命令,因经费不足,新功能必须取消!

虽然白干了,可是这个包含机密资料的分支仍是必须就地销毁:

$ git branch -d  dev-vulcan
error: The branch 'dev-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D dev-vulcan'.

销毁失败。Git友情提醒,dev-vulcan分支尚未被合并,若是删除,将丢失掉修改,若是要强行删除,须要使用大写的-D参数。。

如今咱们强行删除:

$ git branch -D dev-vulcan
Deleted branch dev-vulcan (was 287773e).

终于删除成功!

渐进稳定分支的工做流(“silo”)视图。

 

 

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
相关文章
相关标签/搜索