Git分支

分支

分支是Git的杀手级特征,而且Git鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。因为Git分支非常轻量级,不像其他的版本控制,创建分支意味着要把项目完整的拷贝一份,而Git创建分支是在瞬间完成的,而与你工程的复杂程度无关。

因为在上文中已经说到,Git保存文件的最基本的对象是blob对象,Git本质上只是一棵巨大的文件树,树的每一个节点就是blob对象,而分支只是树的一个分叉。说白了,分支就是一个有名字的引用,它包含一个提交对象的的40位校验和,所以创建分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,所以自然就快了,而且与项目的复杂程度无关。

Git的默认分支是master,存储在.git\refs\heads\master文件中,假设你在master分支运行git branch dev创建了一个名字为dev的分支,那么git所做的实际操作是:

  • .git\refs\heads文件夹下新建一个文件名为dev(没有扩展名)的文本文件。
  • 将HEAD指向的当前分支(当前为master)的40位SHA-1 校验和外加一个换行符写入dev文件。
  • 结束。
    file

创建分支就是这么简单,那么切换分支呢?更简单:

  • 修改.git文件下的HEAD文件为ref: refs/heads/<分支名称>
  • 按照分支指向的提交记录将工作区的文件恢复至一模一样。
  • 结束。
    记住,HEAD文件指向当前分支的最后一次提交,同时,它也是以当前分支再次创建一个分支时,将要写入的内容。

分支合并

再来说一说合并,首先是Fast-forward,换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。比如:file注意箭头方向,因为每一次提交都有一个指向上一次提交的指针,所以箭头方向向左,更为合理

当在master分支合并dev分支时,因为他们在一条线上,这种单线的历史分支不存在任何需要解决的分歧,所以只需要master分支指向dev分支即可,所以非常快。

当分支出现分叉时,就有可能出现冲突,而这时Git就会要求你去解决冲突,比如像下面的历史:file因为master分支和dev分支不在一条线上,即v7不是v5的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(v7v5)以及它们的共同祖先(v3)进行一次简单的三方合并计算。合并之后会生成一个和并提交v8file注意:和并提交有两个祖先(v7v5)。

分支的变基rebase

把一个分支中的修改整合到另一个分支的办法有两种:mergerebase。首先mergerebase最终的结果是一样的,但 rebase能产生一个更为整洁的提交历史。仍然以上图为例,如果简单的merge,会生成一个提交对象v8,现在我们尝试使用变基合并分支,切换到dev

  1. $ git checkout dev
  2. $ git rebase master
  3. First, rewinding head to replay your work on top of it...
  4. Applying: added staged command

file

这段代码的意思是:回到两个分支最近的共同祖先v3,根据当前分支(也就是要进行变基的分支 dev)后续的历次提交对象(包括v4v5),生成一系列文件补丁,然后以基底分支(也就是主干分支 master)最后一个提交对象(v7)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成两个新的合并提交对象(v4'v5'),从而改写 dev 的提交历史,使它成为 master 分支的直接下游,如下图:file现在,就可以回到master分支进行快速合并Fast-forward了,因为master分支和dev分支在一条线上:

  1. $ git checkout master
  2. $ git merge dev

file现在的v5'对应的快照,其实和普通的三方合并,即上个例子中的 v8 对应的快照内容一模一样。虽然最后整合得到的结果没有任何区别,但变基能产生一个更为整洁的提交历史。如果视察一个变基过的分支的历史记录,看起来会更清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。