一个rebase在合并之前通常是一个好主意,因为这个想法是你在你的分支 Y 中集成你将要合并的分支 B 的工作 . 但是,再次,在合并之前,你解决你的分支中的任何冲突(即:"rebase",如“从分支的最近点开始重播我在我的分支中的工作 B ”) 如果正确完成,从分支到分支 B 的后续合并可以快进 .
合并直接影响目标分支 B ,这意味着合并更好是琐碎的,否则分支 B 可以很长时间恢复到稳定状态(时间为你解决所有冲突)
重组后的合并点?
在我描述的情况下,我将 B 改为我的分支,只是为了有机会从最近的一点重播我的工作,但是留在我的分支机构 . 在这种情况下,仍然需要合并才能将"replayed"工作带到 B 上 .
另一种情况(例如described in Git Ready),是通过一个rebase直接将你的工作带入 B (这确实保留了你所有的好提交,甚至让你有机会通过交互式rebase重新排序它们) . 在那种情况下(你在B分支中进行重组),你是对的:不需要进一步合并:
A git tree at default when we have not merged nor rebased
we get by rebasing:
第二种情况就是:如何将新功能重新带入主服务器 .
我的观点是,通过描述第一个rebase场景,是为了提醒大家,一个rebase也可以作为一个初步的步骤(即"get new-feature back into master") . 您可以使用rebase首先为master -662483_带来新功能分支:rebase将重播来自 HEAD master 的新功能提交,但仍然在新功能分支中,有效地将您的分支起点从旧的主提交移动到 HEAD-master . 这允许您解决分支中的任何冲突(意味着,孤立地,如果您的冲突解决阶段花费太长时间,则允许master继续并行发展) . 然后你可以切换到master并合并 new-feature (如果你想保留在 new-feature 分支中完成的提交,请将 new-feature 重新绑定到 master ) .
所以:
可以将
"rebase vs. merge"视为导入作品的两种方式,例如 master .
但是"rebase then merge"可以是一个有效的工作流程,可以首先单独解决冲突,然后恢复工作 .
310
这里有很多答案说合并将所有提交转换为一个,因此建议使用rebase来保留你的提交 . This is incorrect. And a bad idea if you have pushed your commits already .
更新3: Does one still need to merge after a successful rebase? 是的,你这样做 . 原因是rebase基本上涉及"shifting"提交 . 在完成rebase之后我正在重新调整 . 你在rebase之前有一个分支 . 之后你会有一个相同长度的分支 . 在发布更改之前,您仍需要合并 . 换句话说,可以根据需要多次重新设置(再次,只有在您没有将更改推送到上游时) . 只有在你变基之后合并 .
在最后一步中,现在重新应用分支-A上的新提交 - 但是在新位置上,在分支-B的集成提交之上(它们是重新基础的) . The result looks like development had happened in a straight line. Instead of a merge commit that contains all the combined changes, the original commit structure was preserved.
最后,你得到一个干净的分支 branch-A ,没有不需要的和自动生成的提交 .
Note: 取自git-tower的惊人post . rebase 的缺点在同一篇文章中也是一个很好的阅读 .
14 回答
短版
Merge接受一个分支中的所有更改,并将它们合并到一个提交中的另一个分支中 .
Rebase说我想要分支的点移动到一个新的起点
那么你什么时候使用其中任何一个?
合并
Rebase
这很简单,你说使用另一个分支作为你工作的新的 base .
例如,如果你有一个分支
master
并且你创建了一个分支来实现一个新功能,那就说你将其命名为cool-feature
,当然主分支是你新功能的基础 .现在,您希望添加在
master
分支中实现的新功能 . 你可以切换到master
并合并cool-feature
分支:但是这样就添加了一个新的虚拟提交,如果你想避免意大利面条历史,你可以 rebase :
然后将其合并到
master
中:这一次,由于主题分支具有相同的master提交以及具有新功能的提交,因此合并将只是一个快进 .
补充my own answer提及by TSamper,
一个rebase在合并之前通常是一个好主意,因为这个想法是你在你的分支
Y
中集成你将要合并的分支B
的工作 .但是,再次,在合并之前,你解决你的分支中的任何冲突(即:"rebase",如“从分支的最近点开始重播我在我的分支中的工作
B
”)如果正确完成,从分支到分支
B
的后续合并可以快进 .合并直接影响目标分支
B
,这意味着合并更好是琐碎的,否则分支B
可以很长时间恢复到稳定状态(时间为你解决所有冲突)在我描述的情况下,我将
B
改为我的分支,只是为了有机会从最近的一点重播我的工作,但是留在我的分支机构 .在这种情况下,仍然需要合并才能将"replayed"工作带到
B
上 .另一种情况(例如described in Git Ready),是通过一个rebase直接将你的工作带入
B
(这确实保留了你所有的好提交,甚至让你有机会通过交互式rebase重新排序它们) .在那种情况下(你在B分支中进行重组),你是对的:不需要进一步合并:
A git tree at default when we have not merged nor rebased
we get by rebasing:
第二种情况就是:如何将新功能重新带入主服务器 .
我的观点是,通过描述第一个rebase场景,是为了提醒大家,一个rebase也可以作为一个初步的步骤(即"get new-feature back into master") .
您可以使用rebase首先为master -662483_带来新功能分支:rebase将重播来自
HEAD master
的新功能提交,但仍然在新功能分支中,有效地将您的分支起点从旧的主提交移动到HEAD-master
.这允许您解决分支中的任何冲突(意味着,孤立地,如果您的冲突解决阶段花费太长时间,则允许master继续并行发展) .
然后你可以切换到master并合并
new-feature
(如果你想保留在new-feature
分支中完成的提交,请将new-feature
重新绑定到master
) .所以:
可以将
"rebase vs. merge"视为导入作品的两种方式,例如
master
.但是"rebase then merge"可以是一个有效的工作流程,可以首先单独解决冲突,然后恢复工作 .
这里有很多答案说合并将所有提交转换为一个,因此建议使用rebase来保留你的提交 . This is incorrect. And a bad idea if you have pushed your commits already .
合并不会消除您的提交 . 合并保留了历史! (只看gitk)Rebase重写了历史,这是你推后的坏事 .
Use merge -- not rebase 无论何时你已经推了 .
Here is Linus' (author of git) take on it . 这是一个非常好的阅读 . 或者您可以在下面阅读我自己的相同想法版本 .
在master上重新分支:
提供了如何创建提交的错误想法
使用一堆可能未经过良好测试的中间提交来污染master
实际上可能会在这些中间提交中引入构建中断,因为在原始主题分支之间进行了更改创建和何时重新定位 .
让在主人身上找到好位置很难结账 .
使提交的时间戳与树中的时间顺序不一致 . 所以你会看到提交A在master中提交B之前,但是提交B首先被创作 . (什么?!)
产生更多冲突,因为主题分支中的各个提交都可能涉及必须单独解决的合并冲突(进一步包含在每次提交中发生的事件的历史记录中) .
是历史的重写 . 如果被重新分支的分支已被推送到任何地方(与除您之外的任何人共享),那么您重写了历史记录 .
相反,将主题分支合并为master:
保留创建主题分支的历史记录,包括从主分支到主题分支的任何合并,以帮助保持最新 . 您真正了解开发人员在构建时使用的代码 .
master是一个主要由合并组成的分支,并且每个合并提交在历史记录中通常都是'good points',可以安全地检出,因为这是主题分支准备好集成的地方 .
保留主题分支的所有单独提交,包括它们位于主题分支中的事实,因此隔离这些更改是很自然的,您可以在需要的位置钻取 .
合并冲突只需要解析一次(在合并时),因此不必单独解决在主题分支中进行的中间提交更改 .
可以顺利完成多次 . 如果您定期将主题分支集成到master,那么人们可以继续构建主题分支,并且可以独立地合并 .
TL; DR
如果您有任何疑问,请使用合并 .
简答
rebase和merge之间的唯一区别是:
历史的结果树结构(通常只在查看提交图时才会显着)是不同的(一个将具有分支,另一个将不具有) .
合并通常会创建一个额外的提交(例如树中的节点) .
合并和rebase将以不同方式处理冲突 . Rebase将一次提交一个提交的冲突,其中merge将同时呈现它们 .
所以简短的回答是根据您希望历史记录的样子选择rebase或merge .
答案很长
在选择要使用的操作时,您应该考虑几个因素 .
您所获得的分支是否与团队外部的其他开发人员共享(例如开源,公共)?
如果是这样,请不要改变 . Rebase会破坏分支,这些开发人员将拥有破坏/不一致的存储库,除非他们使用
git pull --rebase
. 这是快速打乱其他开发人员的好方法 .您的开发团队技术娴熟吗?
Rebase是一种破坏性的操作 . 这意味着,如果您没有正确应用它, you could lose committed work and/or break the consistency of other developer's repositories.
我曾经在团队中工作,开发人员都来自公司可以负担得起专职人员来处理分支和合并的时候 . 那些开发人员对Git了解不多,也不想了解太多 . 在这些团队中,我不会冒任何理由推荐变基 .
分支本身是否代表有用的信息
一些团队使用每个功能分支模型,其中每个分支代表一个功能(或错误修复或子功能等) . 在此模型中,分支有助于识别相关提交的集合 . 例如,可以通过恢复该分支的合并来快速恢复特征(公平地说,这是一种罕见的操作) . 或者通过比较两个分支(更常见)来区分特征 . Rebase将破坏分支,这不会是直截了当的 .
我还参与了使用每个开发人员分支模型的团队(我们都在那里) . 在这种情况下,分支本身不传达任何其他信息(提交已经有作者) . 变基不会有任何伤害 .
您是否想以任何理由还原合并?
与恢复合并相比,恢复(如在撤消中)相当困难和/或不可能(如果rebase存在冲突) . 如果您认为有可能需要恢复,请使用合并 .
你在团队工作吗?如果是这样,你愿意在这个分支上采取全有或全无的方法吗?
需要使用相应的
git pull --rebase
来拉动Rebase操作 . 如果您自己工作,您可能能够记住在适当的时候应该使用哪些 . 如果您在团队中工作,那么协调将非常困难 . 这就是为什么大多数rebase工作流建议为所有合并使用rebase(和所有pulls的git pull --rebase
) .常见的神话
合并破坏历史(南瓜提交)
假设您有以下合并:
有些人会说合并“破坏”提交历史记录,因为如果你只查看主分支(A - D)的日志,你会错过重要的提交消息包含在B和C中 .
如果这是真的,我们就不会questions like this . 基本上,你会看到B和C,除非你明确要求不要看到它们(使用--first-parent) . 这很容易为自己尝试 .
Rebase允许更安全/更简单的合并
这两种方法的合并方式不同,但目前尚不清楚一种方法总是比另一方更好,而且可能取决于开发人员的工作流程 . 例如,如果开发人员倾向于定期提交(例如,他们可能每天提交两次,因为他们从工作转移到家庭),那么对于给定的分支可能会有很多提交 . 许多提交可能看起来不像最终产品(我倾向于每个功能重构一次或两次我的方法) . 如果其他人正在处理相关的代码区域并且他们试图改变我的更改,那么这可能是一项相当繁琐的操作 .
Rebase更酷/更性感/更专业
如果您想将
rm
别名为rm -rf
至"save time",那么也许rebase适合您 .我的两分钱
我总是认为有一天我会遇到一个场景,git rebase是解决问题的绝佳工具 . 就像我想我会遇到一个场景,git reflog是一个很棒的工具,可以解决我的问题 . 我已经和git合作了五年多了 . 它没有发生 .
对我来说,凌乱的历史从未真正成为问题 . 我永远不会像一部令人兴奋的小说那样阅读我的提交历史 . 大多数时候我需要一个历史,我将使用git blame或git bisect . 在这种情况下,合并提交对我来说实际上是有用的,因为如果合并引入了对我来说有意义的信息的问题 .
更新(4/2017)
虽然我的一般建议仍然存在,但我觉得有义务提到我个人已经软化了使用rebase . 我最近与Angular 2 Material项目进行了很多交流 . 他们使用rebase来保持非常干净的提交历史 . 这使我能够非常轻松地查看修复给定缺陷的提交内容以及该提交是否包含在发布中 . 它是正确使用rebase的一个很好的例子 .
合并意味着:创建一个新的提交,将我的更改合并到目标中 .
Rebase意味着:使用我当前的提交集作为提示创建一系列全新的提交 . 换句话说,如果我已经从我正在变相的那一点开始制作它们,那么计算我的变化会是什么样子 . 因此,在rebase之后,您可能需要重新测试您的更改,并且在rebase期间,您可能会遇到一些冲突 .
鉴于此,你为什么要变基?只是为了保持发展历史的清晰 . 假设您正在使用功能X,当您完成后,您将合并您的更改 . 目标将现在有一个提交,可以说“添加功能X” . 现在,如果您重新组合然后合并,则目标开发历史记录将包含单个逻辑进程中的所有单个提交,而不是合并 . 这使得稍后审查更改变得更加容易 . 想象一下,如果有50位开发人员一直在合并各种功能,那么查看开发历史有多难 .
也就是说,如果你已经推动了你正在上游工作的分支机构,你不应该改变,而是合并 . 对于尚未被推送到上游的分支,rebase,test和merge .
你可能想要重新定义的另一个时间是你想要在推送上游之前从你的分支中删除提交 . 例如:承诺尽早引入一些调试代码,并进一步提交清除代码的其他提交 . 唯一的方法是执行交互式rebase:
git rebase -i <branch/commit/tag>
更新:当您使用Git连接到不支持非线性历史记录的版本控制系统(例如subversion)时,您还想使用rebase . 使用git-svn桥时,非常重要的是,您合并回subversion的更改是在trunk中最新更改之上的连续变化列表 . 只有两种方法可以做到这一点:(1)手动重新创建更改和(2)使用rebase命令,这要快得多 .
UPDATE2:另一种思考rebase的方法是,它允许从您的开发样式到您承诺的存储库中接受的样式的一种映射 . 假设您喜欢用小块的小块进行操作 . 你有一个提交修复错误,一个提交摆脱未使用的代码等等 . 当你完成了你需要做的事情时,你需要进行一系列的提交 . 现在让我们说你承诺鼓励大量提交的存储库,所以对于你正在做的工作,人们会期待一个或两个提交 . 你如何处理你的提交并将它们压缩到预期的位置?您将使用交互式rebase并将您的小提交压缩为更少的更大块 . 如果需要反向,情况也是如此 - 如果你的风格是一些大的提交,但回购要求很长小提交字符串 . 您也可以使用rebase来做到这一点 . 如果您已合并,则现在已将提交样式移植到主存储库中 . 如果有很多开发人员,你可以想象在一段时间后跟踪具有几种不同提交样式的历史记录是多么困难 .
更新3:
Does one still need to merge after a successful rebase?
是的,你这样做 . 原因是rebase基本上涉及"shifting"提交 . 在完成rebase之后我正在重新调整 . 你在rebase之前有一个分支 . 之后你会有一个相同长度的分支 . 在发布更改之前,您仍需要合并 . 换句话说,可以根据需要多次重新设置(再次,只有在您没有将更改推送到上游时) . 只有在你变基之后合并 .合并/ rebase之前:
在
git merge master
之后:在
git rebase master
之后:(A,B,C,D,E和F是提交)
这个例子以及关于git的更好的图解信息可以在这里找到:http://excess.org/article/2008/07/ogre-git-tutorial/
虽然合并绝对是集成更改的最简单和最常用的方法,但它并不是唯一的方法: Rebase 是一种替代的集成方式 .
Understanding Merge a Little Better
当Git执行合并时,它会查找三个提交:
(1)共同祖先提交如果您遵循项目中两个分支的历史记录,它们总是至少有一个共同的提交:此时,两个分支具有相同的内容,然后以不同的方式进化 .
(2)(3)每个分支的 endpoints 集成的目标是组合两个分支的当前状态 . 因此,他们各自的最新修订特别感兴趣 . 结合这三个提交将导致我们的目标集成 .
Fast-Forward or Merge Commit
在非常简单的情况下,自分支发生以来,两个分支中的一个没有任何新的提交 - 它的最新提交仍然是共同的祖先 .
在这种情况下,执行集成非常简单:Git可以在共同的祖先提交之上添加另一个分支的所有提交 . 在Git中,这种最简单的集成形式称为“快进”合并 . 然后两个分支共享完全相同的历史 .
然而,在很多情况下,两个分支都是单独前进的 .
要进行集成,Git必须创建一个包含它们之间差异的新提交 - 合并提交 .
Human Commits & Merge Commits
通常,提交是由人类仔细创建的 . 它是一个有意义的单元,仅包含相关更改并使用注释对其进行注释 .
合并提交有点不同:它不是由开发人员创建的,而是由Git自动创建的 . 而不是包装一组相关的更改,其目的是连接两个分支,就像一个结 . 如果您想稍后了解合并操作,则需要查看两个分支的历史记录和相应的提交图 .
Integrating with Rebase
有些人喜欢没有这样的自动合并提交 . 相反,他们希望项目的历史看起来好像是在一条直线上演变而来 . 没有迹象表明它在某些时候被分成了多个分支 .
让我们一步一步地完成一个rebase操作 . 该场景与前面的示例相同:我们希望将branch-B中的更改集成到branch-A中,但现在使用rebase .
我们将分三步完成
git rebase branch-A // syncs the history with branch-A
git checkout branch-A // change the current branch to branch-A
git merge branch-B // merge/take the changes from branch-B to branch-A
首先,Git将“撤消”在行开始分支之后发生的分支-A上的所有提交(在共同的祖先提交之后) . 但是,当然,它不会丢弃它们:相反,您可以将这些提交视为“暂时保存” .
接下来,它应用我们想要集成的branch-B的提交 . 此时,两个分支看起来完全相同 .
在最后一步中,现在重新应用分支-A上的新提交 - 但是在新位置上,在分支-B的集成提交之上(它们是重新基础的) . The result looks like development had happened in a straight line. Instead of a merge commit that contains all the combined changes, the original commit structure was preserved.
最后,你得到一个干净的分支 branch-A ,没有不需要的和自动生成的提交 .
Note: 取自git-tower的惊人post .
rebase
的缺点在同一篇文章中也是一个很好的阅读 .这句话得到了:
资料来源:http://www.git-scm.com/book/en/v2/Git-Branching-Rebasing#Rebase-vs.-Merge
亲git书是对rebasing page的一个非常好的解释 .
基本上合并将需要2次提交并将它们组合在一起 .
rebase将转到2上的共同祖先,并逐步将更改应用于彼此之上 . 这使得“更清洁”和更线性的历史 .
但是,当你重新放弃以前的提交并创建新的提交时 . 所以你永远不应该改变一个公开的回购 . 在回购工作的其他人将恨你 .
仅仅因为这个原因我几乎完全合并 . 99%的时间我的分支没有那么大的差异,所以如果有冲突,它只在一两个地方 .
这个答案广泛针对Git Flow . 这些表是用漂亮的ASCII Table Generator生成的,历史树是用这个精彩的命令生成的(aliased as
git lg
):表格按时间倒序排列,与历史树木更加一致 . 另请参阅
git merge
和git merge --no-ff
之间的区别(您通常希望使用git merge --no-ff
,因为它会使您的历史更贴近现实):git merge
命令:
结果:
git merge --no-ff
命令:
结果:
git merge vs git rebase
第一点: always merge features into develop, never rebase develop from features . 这是Golden Rule of Rebasing的结果:
In other words:
我个人补充说:除非它是一个功能分支,否则你和你的团队都会意识到后果 .
所以
git merge
vsgit rebase
的问题几乎只适用于特征分支(在以下示例中,合并时始终使用--no-ff
) . 请注意,因为我是一个更好的解决方案(a debate exists),所以我只提供两个命令的行为方式 . 在我的情况下,我更喜欢使用git rebase
,因为它产生了一个更好的历史树:)功能分支之间
git merge
命令:
结果:
git rebase
命令:
结果:
从开发到功能分支
git merge
命令:
结果:
git rebase
命令:
结果:
附注
git cherry-pick
当你只需要一个特定的提交时,
git cherry-pick
是一个很好的解决方案(-x
选项会在原始提交消息体中附加一行“(从提交中挑选出来的樱桃)”),因此使用它通常是个好主意 -git log <commit_sha1>
看到它):命令:
结果:
git pull --rebase
不确定我能比_662577更好地解释... ...基本上,使用
git pull --rebase
而不是git pull
:)文章中缺少的是you can enable it by default:git rerere
再次,很好地解释了here . 但简单来说,如果启用它,则不必再多次解决相同的冲突 .
Git rebase用于使历史记录中的分支路径更清晰,存储库结构是线性的 .
它还用于保持您创建的分支是私有的,因为在重新定位并将更改推送到服务器之后,如果删除了分支,则不会有您已经处理过的分支的证据 . 所以你的分公司现在是你当地的关注点 .
在做了rebase后,我们还摆脱了额外的提交,我们曾经看过它们是否正常合并 .
并且在成功的rebase之后仍然需要进行合并,因为rebase命令只是将你的工作放在你在rebase中提到的分支的顶部说master并且使你的分支的第一次提交作为master分支的直接后代 . 这意味着我们现在可以进行快进合并,以便将更改从此分支转移到主分支 .
一些实际的例子,与大规模开发有些联系,其中gerrit用于审查和交付集成 .
当我将我的功能分支提升到一个新的远程主控时,我合并 . 这提供了最小的提升工作,并且很容易跟踪功能开发的历史,例如gitk .
我在准备交付提交时合并 .
当我的交付提交由于某种原因导致集成失败时,我会重新定义,我需要将其更新为新的远程主服务器 .
我什么时候使用
git rebase
?几乎从来没有,因为它改写了历史 .git merge
几乎总是最好的选择,因为它尊重项目中实际发生的事情 .