假设有这样一个分支:
若使用 git merge 合并 experiment
和 master
,会先找到这俩的最近公共祖先 C2
然后比较两者相对于 C2
的修改,冲突部分需手动解决,其余未冲突部分会自动合并,最终新建一个新的提交 C5
并推进 master
(如果当前 HEAD 指针 指向的是 master)。
还有一种方法:你可以提取在 C4
中引入的补丁和修改,然后在 C3
的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。
在这个例子中,你可以检出 experiment
分支,然后将它变基到 master
分支上:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
它的原理是首先找到这两个分支(即当前分支 experiment
、变基操作的目标基底分支 master
) 的最近共同祖先 C2
,然后对比当前分支 C4
相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3
, 最后以此将之前另存为临时文件的修改依次添加到 C3
中,形成新的 C4'
。
从结果上来看 C4'
和 C5
没有区别,只是变基相比合并省去了 C4
原来的分支提交历史。因此,当你觉得自己本地的某个分支不需要与别人协同工作,则可以直接使用变基来合并到远程分支上,让整体的分支记录更加简洁。假如别人也有这些提交,且有可能会基于这些提交进行开发,那就不要执行变基。
变基的风险
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase
命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase
命令,这样尽管不能避免伤痛,但能有所缓解。
例如你拉取了一个公共仓库的内容并在其基础上进行了一些修改:
然后,某人又向中央服务器提交了一些修改,其中还包括一次合并。 你抓取了这些在远程分支上的修改,并将其合并到你本地的开发分支,然后你的提交历史就会变成这样:
接下来,teamone 觉得合并的不好,将合并操作回滚后使用变基操作新建一个
C4'
,同时又用 git push --force
命令覆盖了服务器上的提交历史。 之后你从服务器抓取更新,会发现多出来一些新的提交。
(这里不加 —force 会不让提交,因为本地缺少了变基删掉的部分,需要先 fetch 下来)
此时如果你再进行一次 git pull
来合并服务器更改,会发现自动新建了个 C8
,而 C8
和 C7
提交的作者、日期、日志居然是一样的,会让人感到混乱。
并且此时如果你将自己的工作推送到服务器后,服务器上之前因为变基删去的
C4
和 C6
又会重新出现。
用变基解决变基
如果你 真的 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。 如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。
实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——即 “patch-id”。
若你遇到了有人推送了经过变基的提交,并且自己本地开发还没提交上去时,即:
如果我们不是执行合并,而是执行 git rebase teamone/master
, Git 将会:
- 检查哪些提交是我们的分支上独有的(C2,C3,C4,C6,C7)
- 检查其中哪些提交不是合并操作的结果(C2,C3,C4)
- 检查哪些提交在对方覆盖更新时并没有被纳入目标分支(只有 C2 和 C3,因为 C4 其实就是 C4’)
- 把查到的这些提交应用在
teamone/master
上面
此时,你自己的修改 C2
、C3
会整理出来并应用到 C4'
上,而 C7
由于只是 C6
和 C3
的合并,没有新的修改,故不会保留。
此操作只有当对方的 C4'
跟你本地仓库的 C4
相同,即对方变基时没有对 C4
进行修改时才有效。否则 Git 无法识别变基操作,并新建另一个类似 C4
的补丁。
除了先将远程服务器的东西拉取 git fetch
后执行 git rebase teamone/master
来合并,也可以直接使用 git pull --rebase
来实现相同效果。假如想默认开启 --rebase
选项,可以修改默认配置,即执行 这条语句 git config --global pull.rebase true
。