看到国外论坛上这篇文章讲的很好,翻译过来学习学习
原文地址:https://www.atlassian.com/git/tutorials/merging-vs-rebasing
关于git rebase的指令一直有个说法,这是一个神奇的git指令,但是新手要远离它。实际上当小心使用它的时候,可以让一个开发团队的工作简单得多。在这篇文章中,我们将比较git rebase与git merge指令并寻找一切可以将rebasing整合到典型的git工作流的机会。
概念性回顾
关于git rebase需要理解的第一点是它解决的是和git merge一样的问题。这两个指令都是设计来将代码的变化从一个分支整合到另一个分支,但是两者用了非常不同的方法来做这件事。
回想一下当你在一个独立的feature分支上时,另一个团队成员更新了master分支,这时会发生什么。对于任何将git作为一个协作工作的人来说,很显然这件事会导致分岔的history出现。
现在,假设master分支中的新commits与你正在做的需求息息相关。想把新的commits整合到你的feature分支上,你有两种选择:merging或者rebasing。
Merge 选项
最简单的选择就是把master分支merge到feature分支,指令如下:
git checkout feature git merge master
或者可以将上面两条整合到一条指令:
git merge feature master
这会在feature分支创建一个新的merge commit,将所有分支的历史都连结到一起,会给你一个如下所示的分支结构:
Merging很优秀,因为它是一种非破坏性的操作。现存的分支不会以任何形式被改变,这一点避免了rebasing所有的潜在陷阱。
在另一方面,这同样意味着,每次当你需要整合上游的改变时,feature分支会有一个额外的merge commit。如果master分支很活跃,这会将你feature分支的历史污染的很严重。当需要用到git log的时候,这些历史会使其他开发者很难理解项目的历史。
Rebase 选项
作为merging的替代品,你也可以以下指令在master分支上rebase feature分支:
git checkout feature git rebase master
这段指令将整个feature分支挪到了master分支的后面,有效地将所有新commits整合到master分支中。但是,和使用merge commit不同的是,rebasing通过为每个原始分支中的commit创建全新的commit,重写了项目的历史。
rebasing的主要好处是你可以得到一个更干净的项目历史。首先,它消除了git merge引发的不必要的merge commit。其次,你可以从上面的图像中看出,rebasing还造就了完美的线性项目历史。你可以避开任何的分岔顺着feature的流程走到项目的开端。这使你用git log,git bisect,gitk来导航项目变的极其简单。
但是对于这个崭新的commit历史来说有两件需要权衡的事:安全性和可追溯性。如果你不遵循rebasing的黄金定律,重写项目历史可能会是你协作工作流程的潜在灾难。另外不那么重要的是,rebasing失去了merge commit提供的context,你不能看到上游改变时什么时候被合并到feature分支中。
交互性 Rebasing
当commits被移到新的分支时,交互性Rebasing给了你机会去改变它们。这一点甚至比自动化的rebase更加给力,因为它提供了对于分支commit历史的完全控制。很典型的情景是,这一点被用于在将一个feature分支合并到master分支前,将混乱的历史清理干净。
开始一个交互性的rebasing,加入一个i的字段
git checkout feature git rebase -i master
这会打开一个文本编辑器,将所有需要移动的commit都列出来:
pick 33d5b7a Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3
这个列表就决定了之后合并后的branch的log会长什么样。改变pick指令或者重新排列这些条目,你就可以将分支的历史做成你想要的样子。举个例子,如果第二个commit修改了第一个commit中的一个小问题,你可以通过fixup指令将他们合并到一个单独的commit:
pick 33d5b7a Message for commit #1 fixup 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3
当你保存并关掉这个文件时,git会根据你的指令来执行rebase。然后项目历史就会看起来如下:
像上面这样忽略掉一些非必要的commit可以使你的分支历史看起来好理解的多。这是git merge不太能做到的。
Rebasing的黄金法则
当你了解rebasing是什么之后,最重要的事情就是去了解什么时候不能用它。git rebase的黄金法则就是永远不要在公共分支上用这个指令。
举个例子,想一下当你将master分支rebase到你的feature分支上,会发生什么:
rebase指令会将所有master分支的commit接到feature分支的末端。问题是这件事仅仅出现在你的仓库中。其他的所有开发者仍然在原来的master分支上工作。因为rebasing创造了新的commit,git会认为你的master分支的历史与其他人的会发生分岔。
唯一使两个master分支同步的办法是merge,这会导致一个额外的merge commit和两堆含有相同改变的commit(一堆是原始的,一堆是从rebase分支合并过来的)。这真的是个很令人费解的情景。
所以,在你执行git rebase前,多考虑考虑团队其他人。尽量使用非破坏性的方式去改变,比如git revert。
强制push
如果你试图将rebase过的master分支push到远程仓库,git将会阻止你这么做。因为这会和远程的master分支发生冲突。但是,你可以通过传递一个--force指令强制push:
# Be very careful with this command! git push --force
这会重写远程的master分支来匹配你仓库中rebase过的分支,这会让团队的其他人非常的迷惑。所以,只有当你真的知道自己在做什么的时候,再小心使用这条指令。
为数不多你必须使用这条指令的情景是当你在push了一个私有的feature分支到远程仓库上,你需要做一个本地的清理并将还原提交到远程仓库。同样要注意的是,没有人在feature分支的原始版本上改动是很重要的。
工作流预排
rebasing可以以尽可能小影响的方式合成到你已有的git工作流中,这样会让你的团队很舒服。这这一部分,我们会看看rebasing在多种情景下提供的优势。
任何工作流中利用git rebase的第一步是去为每个feature创建一个独立的分支。这会给你必要的分支结构来安全地利用rebasing:
本地清理
将rebasing合成到工作流中最好的方式之一是清除本地正在运行中的feature。定期执行交互性的rebase,你可以确信你的feature中每个commit都是专一且有意义的。这会让你不用担心在写代码的时候会破坏掉commit的结构。
当使用git rebase的时候,你有两个新的base可以选择:一个是feature的父分支,或者你的feature中一个早前的commit。我们在交互性rebasing章节有展示过一个第一种选择的例子。后面的一种选择当你只需要修改最后的几个commit时是很不错的。举个例子,下面的指令开启了一个只有三个commit的可交互性rebase:
git checkout feature git rebase -i HEAD~3
通过指定HEAD~3作为新的base,你实际上并没有移动分支,而是交互性的重写了3个随后的commit。注意这不会将上游的改变融入到feature分支中。
如果你想用这个方法重写整个feature,git merge指令在找到feature分支的原始base上是有效的。接下来会返回原始base的commit ID,你可以利用这些ID来git rebase:
git merge-base feature master
这种交互性的使用方式是一种将git rebase融入到你的工作流很好的方式,因为它只会改变本地的分支。其他的开发者只会看到你完成了一个有着清爽,简单易懂的feature分支历史的项目。
再一次说明的是,这只会在私有的feature分支上起作用,如果你通过相同的feature分支与其他人协作开发。公共的feature分支历史改写是不被允许的。对于使用交互性rebase清理本地commit,git merge的选项是行不通的。
将上游的改变融入一个feature
在概念回顾部分,我们讲述了如果利用git merge或git rebase将上游master分支中的变化融入到feature分支中。merging是一个安全的选择,它保留了你仓库完整的历史,而rebasing通过将feature分支移动到master的末端来创造一个线性的历史。
git rebase的这种用法类似于本地的清理,但是在这个过程中,它将这些上游的commit融合了。要记得在远程分支上rebase而非是在master分支上rebase,当你与其他的开发者在相同的feature分支上开发时,你需要将他们的变化融入到你的仓库中。
你可以解决这个分岔正如同你可以将master上游的变化融入进来,或者将john/feature merge到你的本地feature分支,或者将你的本地feature分支rebase到john/feature的末端。
注意这个rebase没有违反rebasing的黄金法则,因为只有你本地的feature的commit被移动了,前面任何事都没有被改变。说起来就是把你的改变加在John已经做好的部分上。在大多数情况下,这比用merge commit来同步远程分支要直观的多。
git pull默认使用了一个merge指令,但是你可以强行将远程分支利用--rebase选项进行强行融合。
利用Pull Request复审一个feature
如果你将pull request作为你代码复审流程的一部分,你需要避免在创建PR之后使用git rebase指令。当你一开始用PR,其他的开发者就会看到你的commit,意味着这是一个公共的分支。重写它的历史对于git来说是不可能的,而且你的团队成员可以追踪到任何加在feature后的commit。
将其他开发者的代码改变必须利用git merge而非git rebase融入。因此,在提交PR之前经常使用交互性rebase清理你的代码是一个好主意。
与审批过的feature交互
当一个feature被你的团队审批过后,你可以在使用git merge将feature融入到主代码base前,选择将feature rebase到master分支的末端。
这和将上游改变融入到feature分支有些相似,但是你不被允许重写master分支中的commit,你必须使用git merge去融入feature。然后通过在merge前使用rebase,你可以确信merge是快速推进的,创建一个完美的线性历史。这也给了你在PR的时候将后续的commit去除的机会。
如果你对git rebase感觉不是很舒服,你也可以在暂时的分支中使用git rebase。在这种方式下,如果你意外弄乱了你的feature的历史,你可以切换到原始分支再试一次,如下:
git checkout feature git checkout -b temporary-branch git rebase -i master # [Clean up the history] git checkout master git merge temporary-branch
总结
以上就是在开始使用rebase分支之前你需要了解的东西。如果你更倾向于一个干净,线性的历史,不被非必要的merge commit拖累,你必须在从另一个分支融入变化时采用git rebase而非git merge。
在另一方面,如果你想保存你项目的完整历史并且避免重写公共commit的风险,你可以依旧坚持使用git merge。两种选择都是完美有效的,但是至少你现在可以选择去衡量git rebase的好处。
原文地址:https://www.cnblogs.com/dieFreiheit/p/11603711.html