我们首先来回顾下SVN的分支是什么样的。假如说我们现在要开发一个新的功能,需要用到svn的分支,与主分支进行并行开发,我们是怎么做的呢?我们首先要创建一个分支,然后checkout分支,checkout完成后我们会发现分支branch在我们本地其实就是对trunk的一个完全拷贝。我们本地会多出一个branch的文件夹,目录结构跟trunk一模一样。那么怎么样来进行并行开发呢?试想一下,分支在开发的同时,trunk也在开发,最后trunk开发完成需要合并到主分支时,可能会存在一堆的冲突。这个怎么避免呢?是这样,在并行开发的同时,我们也要时常更新本地trunk并且把trunk合并到branch上,保持分支与仓库的实时更新,这样才不会产生大量冲突。等功能开发和测试完成,就可以把branch内容合并到trunk中,然后删除branch,提交trunk。这样才是一个完整的svn分支开发流程。我们注意到,本地的branch其实是对trunk的一个完全拷贝。会发现,使用SVN的分支开发还是挺麻烦的。
git分支与SVN分支显然大不一样。git创建一个本地分支只需要在初始化一个本地git仓库后执行 git branch
,就可以创建一个名字为branchName的本地分支。使用checkout命令可以切换分支,这个在上篇已经介绍过了。我们会发现,创建分支过后,本地仓库并没有多出分支文件夹,也就是说git创建分支并没有对主分支进行完全拷贝,这个跟svn是大便不相同的。在git分支上进行的操作,提交过后,可以合并到主分支上,合并完成后,创建的git分支即可删除。
在这里多说一下,使用git作为版本控制,每开发一个新功能或者修复一个bug都应该新建一个分支来进行操作。不然几个模块并行开发的时候,你都在主分支上操作,到时候有一个功能好了,需要提交测试,你应该怎么提交?需要人工识别哪些文件能提交,哪些文件不能提交。这显然不是一种好的做法。好的做法是每个功能模块使用分支进行开发,功能好了过后在分支提交然后往主分支master上合并,这样不用纠结那些文件能有提交推送哪些不能。
要使用好git分支,首先要弄明白分支原理。svn的分支是在本地进行一个完全拷贝,git分支则不是。下面介绍下git分支原理。
在介绍原理之前需要介绍几个概念。
HEAD:我们使用git的时候发现好多命令例如git reset - - hard HEAD^(会退到上一个版本)里面都含有HEAD, 那么HEAD是什么呢?HEAD指向的是当前分支。另外在./.git/HEAD文件中可以看到当前分支:
➜ gitlecture git:(test) cat .git/HEAD
ref: refs/heads/test
➜ gitlecture git:(test)
可以看到HEAD文件是一个指向所在分支的引用标识符,该文件内部并不包含SHA-1值,而是指向另外一个引用的指针。
另外,凡是修改了HEAD指向的操作,都会在git reflog中被记录下来。
d70e644 HEAD@{0}: checkout: moving from master to test
d70e644 HEAD@{1}: checkout: moving from test to master
d70e644 HEAD@{2}: checkout: moving from master to test
d70e644 HEAD@{3}: checkout: moving from test to master
d70e644 HEAD@{4}: checkout: moving from develop to test
d70e644 HEAD@{5}: checkout: moving from test to develop
d70e644 HEAD@{6}: checkout: moving from master to test
提交:当执行git commit命令时,git会创建一个commit对象,并且将这个commit对象的parent指针设置为HEAD所指向的引用的SHA-1值。也就是说除了第一个提交,每一个提交都有一个parent指针指向它的上一次提交,可以理解为一种链式结构。因此我们使用git log命令就可以查看到完整的提交信息。
分支:分支指向的是当前分支上的最新一次提交。分支指向的是提交。
下面通过一组图来解释git 分支提交合并原理。
讲dev合并到master。由于master没有进行任何提交,此次合并相对与master而言是进行了一次快进操作(fast-forword)。
如果分支在合并到master之前,master也做了提交,这种合并就要重新一个提交要记录合并后的状态。如果有冲突的话,还需要解决冲突。
以上就是分支提交合并的原理。
:在本地创建一个名字为branchName的分支。
:切换到名字为branchName的本地分支。
:创建一个名字为branchName的分支并且切换到改分支。
:删除一个名字为branchName的分支。如果该分支有提交未进行合并,则会删除失败。
:强制删除一个名字为branchName 的分支。如果该分支有提交未进行合并,也会删除成功。
:将名字为branchName的分支合并到当前分支。我们在本地新建一个分支develop,然后执行git push命令,会发现推送失败。
➜ gitlecture git:(develop) git push
fatal: The current branch develop has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin develop
可以看出错误提示,当前分支没有上游分支,因此推送失败。那么怎么创建一个上游分支呢,可以使用git提示对推荐的命令。
➜ gitlecture git:(develop) git push --set-upstream origin develop
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 820 bytes | 0 bytes/s, done.
Total 8 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/aronykl/gitlecture.git
* [new branch] develop -> develop
Branch develop set up to track remote branch develop from origin.
看提示,创建了一个新的分支,从本地的develop分支到远程的develop分支,分支develop设置追踪远程分支develop。
这里讲远程分支,其实涉及到了一个refspec的概念。我们想远程仓库进行推送的时候,执行的是git push命令。其实这个只是一个简写的命令,完整的命令是:
$ git push origin :
其中,
代表的是本地分支;
代表的是远程服务器的分支,意思是讲本地
到远程分支
。
git pull 也是一样。
$ git pull origin :
意思是讲远程的
分支拉取到本地
分支,并且进行合并。注意:git pull拉取实际是两个操作:首先将远程分支的更新fetch到本地的远程分支上(一般remotes/origin/开头,可用过git brach -av命令查看本地远程分支),然后再把本地远程分支合并到本地分支。而且我们是不能直接修改本地本地远程分支的内容的,只能由git修改。
再来说refspec。refspec其实是git的一个引用规范。如果服务器上有一个 master 分支,我们可以在本地通过下面这种方式来访问该分支上的提交记录:
$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master
上面的三个命令作用相同,因为 Git 会把它们都扩展成refs/remotes/origin/master。
若要将远程的 master 分支拉到本地的 origin/mymaster 分支,可以运行:
$ git fetch origin master:refs/remotes/origin/mymaster
其实也可以写成:
$ git fetch origin master:origin/mymaster
$ git fetch origin master:remotes/origin/mymaster
最终git都会把origin/mymaster 分支扩展成refs/remotes/origin/mymaster。
关于更多的refspec内容,可参考:git refspec
:
:
。这里的
其实是本地远程分支。
,意思是将本地空分支推送到远程分支,即可删除远程分支。
。