git-merge.vs.rebase

git-merge.vs.rebase

原文

目录

  • 概览
  • merge方式
  • rebase方式
  • 交互式rebase
  • rebase的黄金法则
  • 强制推送
  • 工作流
  • 总结

git rebase命令以魔法巫毒著称,初学者应该远离它,但它实际上可以使开发团队在使用git时更加轻松。在本文中,我们将比较git rebase和相关的git Merge命令,并说明将rebase合并到典型的Git工作流中的所有场景。

概览

首先我们要知道git rebasegit merge解决的是同一个问题,二者都是将某个分支的更改应用到引入到另一个分支,只是使用的方式不同。

考虑这样一种场景:你在一个新建分支上开始自己的工作,与此同时,其他人在master分支上更新了新的提交。这导致了分叉式的历史,任何使用Git作为协作工具的人都应该熟悉这种场景。

a-forked-commit-history
现在,假设master中的新提交与你当前分支相关,要将新提交合并到你当前分支中,你有两个选择:mergerebase

merge方式

最简单的方式是将master分支合并到feature分支,如下:

git checkout feature
git merge master

或者将其简写成一行:

git merge feature master

这种方式会在feature分支创建一个新的merge commit,将两个分支关联在一起,现在分支结构如下图:

merging-master-into-the-feature-branch

merge操作不具有破坏性,已有分支不会被改变。这避免了rebase有可能导致的所有问题(稍候讨论)。

另一方面,每次合并时,feature分支都会产生一个额外的合并提交。如果master分支非常活跃,这会对你的分支整洁度造成相当大的影响。虽然可以使用高级的gitlog选项来缓解这个问题,但它会使其他开发人员很难理解该项目的历史。

rebase方式

作为合并的另一种选择,您可以使用以下命令将feature分支重新rebase(变基)到master分支:

git checkout feature
git rebase master

这将使整个feature分支从master分支的尖端开始,有效地将所有新提交都合并到master中。不同于merge产生一个merge commitrebase根据原commit创建新的commit来重写项目历史。

rebasing-the-feature-branch-onto-master

rebase的主要好处是你获得一个干净的历史。首先,它消除了git merge所需的不必要的合并提交。第二,正如您在上面的图表中所看到的,rebase还会生成一个完美的线性项目历史,更便于查看历史。

但对此有两点需要考虑:安全和可追溯性。如果您不遵循rebaseGolden Rule of Rebasing,重写项目历史可能会对您的协作工作流程造成潜在的灾难。而且,更重要的是,重基会丢失合并提交提供的上下文–你无法看到何时将上游更改合并到特性分支中。

交互式rebase

当启用交互式rebase时,你可以在创建新commit时修改他们。这点很有用, 因为我们可以更自由的控制分支的提交历史。通常,这用于在将特性分支合并到主分支之前清理自己混乱的历史记录。

传入-i参数启用交互式rebase:

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

这个清单清楚展示了在执行reabse之后分支的样子。通过改变pick命令或各行排列顺序,可以依你所想来定制分支的提交历史。例如,如果第二个commit是对第一个的小小修复,你可以通过fixup命令将他们合并成一个commit:

pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

保存并关闭文件,Git会分根据文件内容执行rebase,新的项目历史如下:

squashing-a-commit-with-an-interactive-rebase

清除不必要的提交可使分支历史更清晰易懂,这是git merge很难做到的。

rebase的黄金法则

一旦你明白了什么是rebase,最需要学会的是知道什么时候不能使用它。使用reabse的原则是:不要在公共分支上使用。

例如,想一下如果把master分支reabse到你的feature分支上会发生什么:

rebasing-the-master-branch

这个操作会把master分支是的commit移动到feature分支上,此时的问题是这些变化只发生在你本地,其他协同开发者仍然以远程master为参照。由于rebase生成了新的commit,Git会认为你的master分支历史和其他人的有了分叉。

同步两个master分支的唯一办法是将它们重新合并,结果就是,会产生一个额外的commit,且包含两套内容相同的commit(原始的一组,rebaser后创建的一组)。不用说,这是一个非常混乱的局面。

所以,在你运行rebase前,要先想下,“是否还有其他人使用这个分支?”,如果有,那就住手,另想他法吧(如git revert),否则,你可以随意重写历史。

强制推送

如果你尝试推送变基过的master分支到远程,git会阻止你,因为本地分支和远程分支有冲突。但是,你可以使用--force选项强制推送到远程,如下:

# Be very careful with this command!
git push --force

本地分支会覆盖远程原分支,会让组内其他人感到困惑。所以,确保你知道会发生什么,不然不要轻易使用该命令。

只有当你在本地做了一些清理,且推送到一个私人的远程分支时,才允许使用该命令。类似这种情况:”糟糕,我并不想推送原先那个版本的,用当前版本替代他吧“。再次强调,要确认没有其他人使用当前远程分支。

工作流

可以将变基合并到您现有的git工作流中,或多或少地帮助你的团队。我们将看一看在特性开发的各个阶段rebase所能带来的好处。

任何利用git rebase的工作流的第一步是为每个特性创建一个专用分支。这为您提供了安全使用变基的必要分支结构:

developing-a-feature-in-a-dedicated-branch

Local Cleanup

整合到工作流中的最佳方式之一是清理本地的、正在进行的功能。通过定期执行交互式变基,你可以确保特性中的每个提交都是有重点的和有意义的。这使你在编写代码时,不必担心将其分解为孤立的提交–你可以在事后重新整理。

执行git rebase时,确定重新开始的基础有两种方式:一个是使用特性分支的父分支(比如master),一个是当前分支的较早的commit。第一种我们之前已经讨论过,第二种用来处理只需要修复最近的几次提交的情况再好不过了。如下命令以倒数第四个commit为基点处理最后三个commit:

git checkout feature
git rebase -i HEAD~3

通过指定HEAD~3作为新基点,实际上,你并没有移动分支–只是在交互地重新编写之后的3次提交。请注意,这不会将上游更改合并到特性分支中。

rebasing-onto-HEAD~3

如果你想用这种方法来重写整个分支,git merge-base命令可以帮你找出当前分支的基点。如下命令返回原基点的commit id,然后可以将其传给git rebase

git merge-rebase feature master

这种交互式重基的使用是将git重基引入工作流的好方法,因为它只影响本地分支。其他开发人员只会看到一个干净且易于跟踪的最终版分支历史。

但再次地,一定要确保是在私人分支进行操作。如果你通过同一个特性分支与其他开发人员合作,则该分支是公开的,并且不允许重写它的历史记录。

git merge没有类似功能。

将上游更改合并到特性分支

之前我们展示了使用git mergegit rebase将master分支的更改合并到特性分支中,merge是一个安全的方式,它保存了存储库的整个历史,而rebase通过将功能分支移动到master的末端,创建了一个线性历史记录。

这种git rebase的使用类似于本地清理(并且可以同时执行),但是在这个过程中它合并了来自主分支的上游提交。

请记住,变基到远程分支而非主分支是完全合法的。这可能发生在与其他开发人员就同一特性分支进行协作时,并且你需要将他人的更改合并到你的存储库中。

例如,如果你和另一个名为John的的开发人员添加提交到特性分支,那么在从John的存储库中获取远程特性分支后,您的存储库可能如下所示:

collaborating-on-the-same-feature-branch
你可以像引入master分支上游变化一样解决这个问题:合并local featurejohn/feature,或在john/feature的基础上rebase你的local feature

merging-vs-rebasing-onto-aremote-branch
rebasing

请注意,此重基并不违反重基的黄金规则,因为只有改变了你的本地特性提交–在此之前的所有内容都是未被更改的。这就像在说,“把我的改变加到john已经做过的事上。”在大多数情况下,这比通过合并提交与远程分支同步更直观。

默认情况下,git pull命令执行merge,但通过传递--rebase选项,可以强制它将远程分支进行rebase操作。

使用Pull Request审查代码

如果你使用Pull Request作为代码审查的一部分,在创建Pull Request后,就要避免使用git rebaes。一旦你发出Pull Request,其他开发者看到了你的提交,你的分支就变成了公开的,重写分支历史会让其他人很难合并你的提交。

来自其他开发者的任何改变都只能通过git merge而非git rebase合并进来。

因此,在提交Pull Request前使用git reabse清理自己的代码是很好的做法。

集成已被认可的功能

在被团队认可后,你可以使用rebase将特性分支变基到主分支,然后再使用git merge合并到主分支。

这和合并上游变化到特性分支类似,但是不允许重写主分支的历史,你最终还是需要使用git merge合并特性分支。但通过先执行rebase,可以保证mergefast-forward,仍然会产生一个线性提交历史。这给你在pull request期间仍然能压缩整理提交的机会。

integrating-a-feature-into-master-with-and-without-a-rebase

如果你对git rebase不太放心,你可以一直在临时分支上操作,这样即使操作有问题,也可以切回到原分支,重新来过。如:

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch

总结

这就是你开始使用rebase全部所需要知道的。如果你倾向于生成一个干净、没有不必要的合并提交、线性的历史,你应该使用git rebase

相反,如果你更想保存完整的项目历史,不愿冒重写公共提交的风险,你可以坚持使用git merge。两个选择都是完全没问题的,但至少你现在有了从git rebase得益的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值