几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间。
有人把 Git 的分支模型称为 “必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来。Git 有何特别之处呢?Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。和许多其他版本控制系统不同,Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。理解分支的概念并熟练运用后,你才会意识到为什么 Git 是一个如此强大而独特的工具,并从此真正改变你的开发方式。
由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。
这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而 Git 的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(译注:即 parent 对象),所以以后要合并分支时,寻找恰当的合并基础(译注:即共同祖先)的工作其实已经完成了一大半,实现起来非常容易。Git 鼓励开发者频繁使用分支,正是因为有着这些特性作保障。
$ git branch testing #在当前 commit 对象上新建一个分支指针,但不会自动切换到这个分支中去
$ git checkout testing #切换到其他分支
$ git checkout -b testing #创建分支并切换,相当于上面两个命令
$ git checkout master
$ git merge hotfix #把hotfix分支合并到master
$ git branch -d hotfix #删除hotfix分支
$ git branch -D hotfix #强制删除hotfix分支
$ git branch #当前所有分支的清单
$ git branch -v #查看各个分支最后一次 commit 信息
$ git branch --merged #查看哪些分支已被并入当前分支
$ git branch --no-merged #查看尚未合并的工作
$ git fetch origin
会把远程分支中有本地没有的数据同步下来,同时会移动远程分支的指向,但本地获得的只是一个不可移动的指针,需要使用merge才能进行合并
$ git checkout -b sf origin/serverfix #本地分支 sf 会自动向 origin/serverfix
$ git push origin :serverfix #删除远程分支serverfix
把一个分支整合到另一个分支的办法有两种: merge(合并) 和 rebase(衍合)。
rebase
至少带一个参数,这个参数可以是一个分支名称,也可以是一次有效的 commit。
把在一个分支里提交的改变在另一个分支里重放一遍。
$ git checkout experiment
$ git rebase master
更有趣的衍合
$ git rebase --onto master server client
检出 client 分支,找出 client 分支和 server 分支的共同祖先之后的变化,然后把它们在 master 上重演一遍
永远不要衍合那些已经推送到公共仓库的更新。
git rebase 是对 commit history 的改写。当你要改写的 commit history 还没有被提交到远程 repo 的时候,也就是说,还没有与他人共享之前,commit history 是你私人所有的,那么想怎么改写都可以。
而一旦被提交到远程后,这时如果再改写 history,那么势必和他人的 history 长的就不一样了。git push 的时候,git 会比较 commit history,如果不一致,commit 动作会被拒绝,唯一的办法就是带上 -f 参数,强制要求 commit,这时 git 会以 committer 的 history 覆写远程 repo,从而完成代码的提交。虽然代码提交上去了,但是这样可能会造成别人工作成果的丢失,所以使用 -f 参数要慎重。