Git分支

Git分支详解

几乎每一种版本控制系统都以某种形式支持分支。使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。在很多版本控制系统中,这是个昂贵的过程,常常需要创建一个源代码目录的完整副本,对大型项目来说会花费很长时间。

有人把 Git 的分支模型称为 “必杀技特性”,而正是因为它,将 Git 从版本控制系统家族里区分出来。Git 有何特别之处呢?Git 的分支可谓是难以置信的轻量级,它的新建操作几乎可以在瞬间完成,并且在不同分支间切换起来也差不多一样快。和许多其他版本控制系统不同,Git 鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。理解分支的概念并熟练运用后,你才会意识到为什么 Git 是一个如此强大而独特的工具,并从此真正改变你的开发方式。

Git分支优点

由于 Git 中的分支实际上仅是一个包含所指对象校验和(40 个字符长度 SHA-1 字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,当然也就很快了。

这和大多数版本控制系统形成了鲜明对比,它们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而 Git 的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息(译注:即 parent 对象),所以以后要合并分支时,寻找恰当的合并基础(译注:即共同祖先)的工作其实已经完成了一大半,实现起来非常容易。Git 鼓励开发者频繁使用分支,正是因为有着这些特性作保障。

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 上重演一遍

衍合的风险

永远不要衍合那些已经推送到公共仓库的更新。

  • 实际上抛弃了一些现存的 commit 而创造了一些类似但不同的新 commit。如果你把 commit 推送到某处然后其他人下载并在其基础上工作,然后你用 git rebase 重写了这些 commit 再推送一次,你的合作者就不得不重新合并他们的工作,这样当你再次从他们那里获取内容的时候事情就会变得一团糟。
  • 如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些永远不会公开的 commit,那就不会有任何问题。如果衍合那些已经公开的 commit,而与此同时其他人已经用这些 commit 进行了后续的开发工作,那你有得麻烦了。

衍合的风险解释

git rebase 是对 commit history 的改写。当你要改写的 commit history 还没有被提交到远程 repo 的时候,也就是说,还没有与他人共享之前,commit history 是你私人所有的,那么想怎么改写都可以。

而一旦被提交到远程后,这时如果再改写 history,那么势必和他人的 history 长的就不一样了。git push 的时候,git 会比较 commit history,如果不一致,commit 动作会被拒绝,唯一的办法就是带上 -f 参数,强制要求 commit,这时 git 会以 committer 的 history 覆写远程 repo,从而完成代码的提交。虽然代码提交上去了,但是这样可能会造成别人工作成果的丢失,所以使用 -f 参数要慎重。