熟悉git分支的原理是掌握了git的精髓,因为git和我们常用的源码管理系统有很大的区别和优点在分支上可以体现出来,一般我们常用的源码管理系统分支都是需要创建新目录,有全新的源码copy,一般都需要创建一个源代码目录完整的副本。对应大项目来说非常的耗费时间和空间。git正式因为其优秀的分支模式可以从源码管理系统中脱颖而出。因为git的分支非常的轻量级,他的操作机会瞬间完成,在不同的分支切换也非常快速。与其他版本相比,git更加推崇使用分支管理。分支是一个git非常强大和高效的工具。熟悉使用可以大大的提高工作效率和开发效率。
1、分支
git分支是如果存储数据,我们通过git数据存储的方式可以知道,存储一些列文件快照的方式。而一般常用的源码管理软件保存的基于文件版本差异的变化保存数据。
在git提交中,会保存一个提交(commit)对象,该对象包含一个指向暂存内容快照的指针,包括本次提交的作业的相关附属信息,包含零个或多个指向该提交对象的父对象指针,首次提交时没有直接的祖先,普通提交有一个祖先,由两个或多个分支合并产生的提交有多个祖先。
假如我们的工作目录有三个文件,准备将他们暂存后提交,暂存操作会对每一个文件计算校验和(Sha1哈希字符串)。然后把当前版本文件快照保存到git仓库中。git使用blob类型对象存储这些快照。并将校验和加入暂存区。
当使用git commit 新建一个提交对象前,git会计算每一个子目录或者项目的根目录的校验和,然后在git仓库中将这些目录保存为(tree)树对象。之后git创建的提交对象,除了包含相关提交信息之外,还包含指向这个树对象(项目根目录)的指针。如此他就可以在将来需要的时候,重现此次快照的内容。
$ git add test1.txt test2.txt test3.txt
$ git commit -m " this is the firt commit"
[master (root-commit) 680e70a] this is the firt commit
3 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 test1.txt
create mode 100644 test2.txt
create mode 100644 test3.txt
$ find .git/objects/ -type f
.git/objects/15/f2ed7f3d000ee95bf0e343f40eb892f622174d
.git/objects/30/758feb48e8bdba6ace1ecadf7bf2f89b94c115
.git/objects/5b/3c0c1c42386a00a8f81854b84628305f7082c7
.git/objects/68/0e70ac4e4376992c7d90905b8351badd233850
.git/objects/97/22bcfd0dcf84bbc82ce6095055010c011f91e6
以上是我刚刚创建的一个空的仓库,添加test1.txt test2.txt test3.txt 三个文件。添加到暂存区后,在。git/objects/ 对象中会新增加三个文件快照blog 对象。暂存.git/index 会指向对应的对象。首次提交到仓库后,仓库中在.git/objecs/目录中有5个对象。比在暂存区中增加了两个对象,是因为一个是根目录树(tree)对象。和一个提交commit 的对象。根目录tree对象指向三个文件blob的索引对象和保存文件内容的名称。而commit 对象会指向树对象,同时保存对象说明及相关的附属信息。仓库对象之间的关系图可以如下图。
当连续多次提及后,每次创建一个提交commit 对象会指向上一个提交的父对象。已经提交了三个对象,可以参照如下图:
$ git log --oneline
4d38d40 the third commit
66a1a2e the secomd commit
680e70a this is the firt commit
2、 创建分支
git bran branchname 这个命令会在当前活动分支的基础上创建一个新的分支。其实是在当前活动分支对象commit 新添加一个指向该对象的分支指针,如我们在现master 分支的基础上创建一个testing 分支。git branch testing 创建分支如下关系图:
git 当前活动分支,即工作分支是是哪个呢,可以通过命令git branch 查看,*号标示的就是当前活动分支。有一个head 特别指针保存着当前活动分支。具体物理地址是保存在.git/head 文件中,他指向的是一个分支名称,分支明确refs/heads/master 指向的是当前分支指向的commit 对象。他是指向当前工作本地活动分支的指针。看如下命令,我么可以通过git checkout branchname 转换分支。如git checkout testing 后就指向了testing 分支。看如下图转变过程。
$ git branch
* master
testing
$ ls .git/refs/heads
master testing
$ cat .git/refs/heads/master
4d38d402b3873d95c03cb3e1d2c65bf9aa3b5894
$ cat .git/refs/heads/testing
4d38d402b3873d95c03cb3e1d2c65bf9aa3b5894
$ cat .git/head
ref: refs/heads/master
$ git checkout testing
Switched to branch 'testing'
$ cat .git/head
ref: refs/heads/testing
当前活动分支是在testing 分支,我们在testing 分支做修改后提交。这是testing 分支指向最近一个提交。master 分支是指向当前提交的父提交commit 对象。
现在我们切换到master 分支,并把工作目录文件换成master分支指向的快照内容,现在所做的修改时在老的版本上做修改,在master 分支上做修改。修改不同余testing 分支另一条分支提交。如下图结果。
$ git commit -a -m "testing branch change"
[testing 0c8f2de] testing branch change
1 files changed, 1 insertions(+), 0 deletions(-)
$ git checkout master
Switched to branch 'master'
$ git commit -a -m "the master branch commit "
[master 1d37642] the master branch commit
1 files changed, 2 insertions(+), 0 deletions(-)
如现在我们需要把testing 分支和 master 分支合并。合并操作命令git merge branch name ,查看分支日志。
$ git merge testing
Merge made by the 'recursive' strategy.
test2.txt | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
$ git log --oneline --graph
* ac631f6 Merge branch 'testing'
|\
| * 0c8f2de testing branch change
* | 1d37642 the master branch commit
|/
* 4d38d40 the third commit
* 66a1a2e the secomd commit
* 680e70a this is the firt commit
由于分支实际上仅仅是一个包含所指对象校验和(40个字符串长度的sha1字符串)文件。所以创建和销毁一个分支会非常的容易和廉价。实际上新建一个分支就是向一个文件写入41个字节(一个换行符)。指向一某个提交点。
正式由于这个特性和其它版本形成了特别鲜明的对比。原始的源码管理系统管理分支一般都采用备份所有项目文件到特定的目录,所以根据项目文件数量和大小不同,消耗的时间随着项目的大小时间也不同。而gti 无论项目大小不同,他的特性与项目的复杂度无关,永远可以在几毫秒内创建分支和切换分支。同时每次回记录和指向父对象。这位将来要合并对象对参照标准基础。
3、分支合并创建实例模型
分支创建和合并的例子,实际工作中工作流程
1、开发一个项目
2、为显示某个新的需求,创建一个新的分支。
3、在这个分支的开发
4、如果此时项目有bug需要立即修复需要在原先发布到生产环境的版本的分支。
5、为这次紧急bugfix 创建一个新的分支,并在其中修复问题。
6、bug修复没问题后,会到开发服务器生产分支,和修补分支合并,然后推送到生产服务器上。
7、所有一切都完成后,切换到之前新需求分支,继续工作。
接着刚刚写的范例,我们在主分支发布到生产环境后,已经推送到中心服务器,当前的状态如下图:
1、现在有新的需求需要开发,所以在当地master 分支上创建一个新的开发分支。然后再开发分支上做开发。用到的命令式git checkout -b dev ,相当于git branch dev ,git checkout dev 两条命令。当前工作活动分支是指向dev开发分支,我们在新的开发分支上做修改提交后。如下图:
$ git checkout -b dev
Switched to a new branch 'dev'
$ git commit -a -m "dev branch revise"
[dev 46324fb] dev branch revise
1 files changed, 1 insertions(+), 0 deletions(-)
现在正式的生产环境发现有bug 需要修复,现在我们不需要在创建和发布到修复服务器上花大力气做修改,现在需要做的只需要切回到master分支,然后再master 的分支上创建bugfix分支。在分支切回过程有一点需要注意的是。在暂存区和工作目录是否还没有提交的修改,如果他和你即将检出的分支产生冲突从而阻止切回分支。在切换分支前需要保持一个干净的工作区域。
$ git checkout master
Switched to branch 'master'
$ git checkout -b bugfix
Switched to a new branch 'bugfix'
$ git commit -a -m "bug fix branch commit"
[bugfix 7599941] bug fix branch commit
1 files changed, 2 insertions(+), 0 deletions(-)
在bugfix 分支上测试成功后,发现已经ok了,需要把master分支合并进来,然后推送到中心仓库服务器。先切换成到master 分支,然后git merge 合并bugfix 分支做合并,你会发现fast-forfward 合并,git只需要把master分支指针直接右移,如果顺着一个分支走下去可以到达另一个分支,那么在git 合并的时候,只会简单的把指针右移,因为这种单线历史分支不存在解决冲突和分歧。所以这种合并过程成为快进(fast forward). 当master 分支和bugfix 分支合并后,见下图,当前的bugfix 分支和master 分支会指向一个相同的提交对象。如果bugfix 分支已经没有存在的必要了,可以使用git branch -d branch 进行删除。
$ git merge bugfix
Updating ac631f6..7599941
Fast-forward
test1.txt | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
$ git log --graph --oneline
* 7599941 bug fix branch commit
* ac631f6 Merge branch 'testing'
|\
| * 0c8f2de testing branch change
* | 1d37642 the master branch commit
|/
* 4d38d40 the third commit
* 66a1a2e the secomd commit
* 680e70a this is the firt commit
$ git branch -d bugfix
Deleted branch bugfix (was 7599941).
以上bug修复完成后,可以切换到dev 分支,继续需求开发,可以将master 分支合并到dev 分支,或者等dev 版本开发完成后,再将dev分支更新到并入master.现在分支不同于hotfix 合并的方式,因为这次从历史ac631 处有分支同父对象,dev 和master 分支在进行合并的时候,会找到相同的祖先分支。ac631 和最新分支末端 46324fb 和 22ef87 进行一个三方合并运算,合并提交三个对象,git没有简单的把分支指正右移,而是三方合并结果做一个新的快照创建一个提交commit 对象1421fd5 。这个提交对象有两个祖先,有一点需要注意的是,git 可以决定哪个祖先是最佳合并的基础,这和CVS 和 SVN 不同,他们需要开发者手工合并基础,所以此特性合并操作会让git操作会比其它系统要简单许多。
$ git merge dev
Merge made by the 'recursive' strategy.
$ git log --oneline --graph
* 1421fd5 Merge branch 'dev'
|\
| * 46324fb dev branch revise
* | 22ef787 before dev merege commit
* | 7599941 bug fix branch commit
|/
* ac631f6 Merge branch 'testing'
|\
| * 0c8f2de testing branch change
* | 1d37642 the master branch commit
|/
* 4d38d40 the third commit