你什么时候使用git rebase而不是git merge?

什么时候推荐使用git rebasegit merge

成功重组后,还需要合并吗?

短版

  • 合并将一个分支中的所有更改合并到一个提交中的另一个分支中。
  • 雷根说,我希望我分支的点,以移动到一个新的起点

那么你什么时候使用任何一个?

合并

  • 假设您已经创build了一个用于开发单个function的分支。 当你想把这些变化带回主人,你可能想要合并 (你不关心维护所有的临时提交)。

变基

  • 第二种情况是,如果你开始做一些开发,然后另一个开发者做了一个不相关的变化。 你可能想拉,然后重新基地从回购的当前版本的变化。

为了补充TSamper提到的 我自己的答案 ,

  • 在合并之前进行重新分配通常是一个好主意,因为这个想法是,你将分支B的工作集成到你的分支Y上。
    但是再次合并之前,你可以解决你的分支中的任何冲突(即:“rebase”,就像“从分支B的最近点开始在我的分支中重放我的工作)
    如果正确完成,从分支到分支B的后续合并可以快进。

  • 合并直接影响到目标分支B ,这意味着合并最好是微不足道的,否则分支B可以长时间回到稳定状态(解决所有冲突的时间)


重组后的合并点?

在我描述的情况下,我把B重新分配到我的分支上,只是为了有机会从B最近点重放我的作品,但留在我的分支。
在这种情况下,合并仍然需要将我的“重播”作品带到B

另一个场景(例如Git Ready中描述的 )是通过一个rebase(直接将你的工作直接带到B ),这样做可以节省你所有漂亮的提交,甚至给你机会通过交互式rebase重新sorting。
在这种情况下(你在B分支的时候进行重新分配),你是对的:不需要进一步合并:

默认情况下,我们还没有合并,也没有重新devise的git树

rebase1

我们通过rebasing得到:

rebase3

第二种情况是关于如何获得新的function回到主。

我的观点是,通过描述第一个转型的情景,就是提醒大家,转型也可以作为一个初步的步骤(即“把新特征带回主”)。
您可以使用rebase来首先将master放在新function分支中:rebase将重放HEAD master新特性提交,但仍然在新特性分支中,有效地将分支起点从旧的主提交给HEAD-master
这样可以解决分支中的任何冲突(也就是说,如果冲突解决阶段时间过长,则允许主控并行地继续进化)。
然后,您可以切换到掌握并合并new-feature (或者如果要保留在new-feature分支中完成的提交,则将new-feature绑定到master )。

所以:

  • “重组与合并”可以被看作是两种方式来导入一个工作,比如说, master
  • 但是“重新合并然后合并”可以成为一个有效的工作stream程,首先解决冲突的孤立,然后把你的工作。

很简单,你可以用另一个分支作为你工作的新基地

如果你有例如一个分支master并且你创build了一个分支来实现一个新的function,比如说你把它命名为cool-feature ,那么master分支当然是你的新function的基础。

现在在某个时候,你想添加你在master分支中实现的新function。 你可以切换到master和合并cool-feature分支:

 $git checkout master $git merge cool-feature 

但是这样一个新的虚拟提交被添加,如果你想避免意大利面历史,你可以rebase

 $git checkout cool-feature $git rebase master 

然后将其合并到master

 $git checkout master $git merge cool-feature 

这一次,由于主题分支具有相同的主要提交以及提交新function的提交,所以合并将仅仅是快进。

这里的很多答案都说合并将所有的提交合并为一个,因此build议使用rebase来保存提交。 这是不正确的。 如果你已经推动了你的提交,那么这个主意不好

合并不会消除你的提交。 合并保留历史! (只要看一下gitk)Rebase重写历史logging,这是一个坏的事情后,你已经

使用合并 – 无需重新绑定,只要您已经推送。

这里是Linus(git的作者)采取的 。 这是一个非常好的阅读。 或者你可以在下面阅读我自己的版本。

重新设置主分支:

  • 提供了如何创build提交的错误概念
  • 污染了大量的中间提交,可能没有经过很好的testing
  • 实际上可以在这些中间提交中引入构build中断,因为在创build原始主题分支和重新分配基本分支之间进行了更改。
  • 使得在主人find好的地方难以收银。
  • 导致提交上的时间戳与树中的时间顺序不一致。 所以你会看到提交A在master中的提交B之前,但是提交B是首先被创作的。 (什么?!)
  • 产生更多的冲突,因为主题分支中的个别提交每个都可能涉及必须单独解决的合并冲突(进一步说明每次提交中发生的事情的历史)。
  • 是历史的重写。 如果被重新devise的分支已经被推到任何地方(与自己以外的任何人分享),那么你已经搞乱了所有拥有该分支的人,因为你已经改写了历史。

相比之下,将主题分支合并为主:

  • 保留创build主题分支的历史logging,包括从主分支到主题分支的任何合并,以帮助保持最新状态。 您真正了解开发人员在构build时正在使用哪些代码。
  • master是一个主要由合并组成的分支,而且这些合并提交中的每一个都是历史上的“好点”,因此可以安全地检查出来,因为这是主题分支已经准备好被集成的地方。
  • 保留了主题分支的所有单独提交,包括它们在主题分支中的事实,因此隔离这些更改是很自然的,您可以根据需要进行钻取。
  • 合并冲突只需要解决一次(在合并时),因此在主题分支中进行的中间提交更改不必独立解决。
  • 可以顺利进行多次。 如果您将主题分支与主要分支进行定期集成,那么人们可以在主题分支上继续构build,并可以独立地保持合并。

TL; DR

如果您有任何疑问,请使用合并。

简答

rebase和merge之间唯一的区别是:

  • 由此产生的历史树形结构(通常只有在查看提交图时才会显着)是不同的(一个会有分支,另一个则不会)。
  • 合并通常会创build一个额外的提交(例如树中的节点)。
  • 合并和重组将处理不同的冲突。 Rebase将在一次合并将一次呈现所有的冲突的同时呈现冲突。

所以最简单的答案就是根据你希望你的历史logging来selectrebase或merge

长答案

select要使用的操作时,应考虑几个因素。

你是从你的团队之外的其他开发者共享的分支(例如开源,公共)?

如果是这样,不要重新分配。 Rebase破坏分支,那些开发者将破坏/不一致的存储库,除非他们使用git pull --rebase 。 这是快速打乱其他开发者的好方法。

你的开发团队有多熟练?

Rebase是一个破坏性的操作。 这意味着,如果您没有正确应用, 您可能会丢失承诺的工作和/或打破其他开发人员存储库的一致性。

我曾经在一些团队中工作过,那时开发人员都来自公司负责分支和合并的专职人员。 那些开发人员对Git知之甚less,不想知道多less。 在这些团队中,我不会冒任何理由冒险推荐重组。

分支本身是否代表有用的信息

有些团队使用每个分支代表一个function(或bug修复,或子function等)的分支function模型。在这个模型中,分支有助于识别相关的提交集合。 例如,可以通过恢复该分支的合并(公平地说,这是一种罕见的操作)来快速恢复特征。 或通过比较两个分支来比较特征(更常见)。 重build会破坏分支,这不会是简单的。

我也曾经使用过每个开发者模式的团队(我们都在那里)。 在这种情况下,分支本身不会传达任何附加信息(提交已经有作者)。 重新贷款不会有什么坏处。

您是否想要因任何原因恢复合并?

与恢复合并相比,恢复(如撤销)重组是相当困难和/或不可能的(如果重组发生冲突)。 如果你认为有机会,你会想要恢复,然后使用合并。

你在一个团队工作吗? 如果是这样,你是否愿意在这个分支上采取全部或全部的方法?

需要使用相应的git pull --rebase来拉取Rebase操作。 如果你自己工作,你可能会记得你应该在适当的时候使用。 如果你在一个团队工作,这将是非常困难的协调。 这就是为什么大多数rebase工作stream推荐使用rebase来进行所有的合并(以及git pull --rebase for all pulls)。

常见的神话

合并破坏历史(南瓜提交)

假设您有以下合并:

  B -- C / \ A--------D 

有些人会说,合并“破坏”了提交历史,因为如果你只查看主分支(A – D)的日志,你会错过B和C中包含的重要提交信息。

如果这是真的,我们不会有这样的问题 。 基本上,你会看到B和C,除非你明确要求不要看到它们(使用 – first-parent)。 这是很容易尝试自己。

Rebase允许更安全/更简单的合并

这两种方法合并方式不同,但不清楚总是比另一种更好,这可能取决于开发人员的工作stream程。 例如,如果开发人员倾向于定期提交(例如,他们在工作过渡到家时可能每天执行两次),那么对于给定的分支可能会有很多提交。 许多这些提交可能不像最终产品(我倾向于重构我的方法一次或两次,每个function)。 如果其他人正在研究相关的代码领域,他们试图改变我的改变,这可能是一个相当乏味的操作。

Rebase更酷/更性感/更专业

如果你rm rm -rf别名为rm -rf来“节省时间”,那么rebase就是为你准备的。

我的两分钱

我一直认为有一天我会碰到一个场景,git rebase是解决问题的好工具。 就像我想我会碰到一个场景,git reflog是一个很好的工具,可以解决我的问题。 我已经使用GIT五年多了。 这没有发生。

凌乱的历史对我来说从来都不是一个问题。 我从来没有像读过一本令人兴奋的小说一样阅读我的提交历史。 大多数情况下,我需要一个历史logging,我将要使用git blame或git bisect。 在这种情况下,合并提交对我来说实际上是有用的,因为如果合并引入了对我有意义的信息的问题。

更新(4/2017)

我觉得有义务提到,虽然我的一般build议仍然存在,但我个人使用rebase软化。 我最近与Angular 2 Material项目进行了很多互动。 他们用rebase来保持一个非常干净的提交历史logging。 这使我很容易地看到什么提交固定了一个缺陷,以及提交是否包含在发布中。 这是正确使用rebase的一个很好的例子。

合并意味着:创build一个新的提交,将我的更改合并到目标中。

重新表示:创build一系列全新的提交,使用我当前提交的一组提交作为提示。 换句话说,计算一下我的变化,如果我已经开始制定这些变化的话,我将会重新考虑。 因此,重新绑定之后,您可能需要重新testing您的更改,在重新绑定期间,您可能会遇到一些冲突。

鉴于此,你为什么要重组? 只是为了保持发展的历史。 假设您正在使用functionX,当您完成后,您将合并您的更改。目的地现在将有一个单独的提交,可以说“添加functionX”的行。 现在,如果您重新整合并合并,而不是合并,目标开发历史将包含所有单个逻辑进程中的单个提交。 这使得稍后审阅更改变得更容易。 想象一下,如果50位开发人员总是合并各种function,那么您是多么难以审查开发历史。

也就是说,如果你已经推动你正在上游的分支,你不应该重新分配,而是合并。 对于尚未推上游的分行,进行重新组合,testing和合并。

另一次你可能想要rebase是当你想摆脱你的分支承诺之前推上游。 例如:提交一些debugging代码的早期介绍和其他提交进一步干净的代码。 唯一的方法是通过执行一个交互式的rebase: git rebase -i <branch/commit/tag>

更新:当你使用Git来连接不支持非线性历史的版本控制系统时,你也想使用rebase(例如subversion)。 当使用git-svn桥接时,重新合并到subversion中的更改是在最新的trunk变更之上的一个顺序列表。 只有两种方法可以做到这一点:(1)手动重新创build更改;(2)使用rebase命令,这个命令速度要快得多。

UPDATE2:另一种思考rebase的方法是,它能够实现从开发风格到所承诺的存储库中接受的风格的映射。 假设你喜欢小块,小块。 你有一个提交来修复一个错字,一个提交去除未使用的代码等等。 当你完成你需要做的事情时,你有一系列的提交。 现在让我们说你正在提交的库鼓励大的提交,所以对于你正在做的工作,人们会期望一个或者两个提交。 你如何把你的提交string压缩到预期的状态? 你会使用一个交互式的rebase,把你的小小的提交压缩成更小的块。 如果需要相反的话,情况也是如此 – 如果你的风格是一些大的提交,但是回购需要很长的一小段提交。 你也可以用rebase来做到这一点。 如果你已经合并了,你现在已经把你的提交风格嫁接到主存储库。 如果有很多的开发者,你可以想象在一段时间之后,用几种不同的提交风格来追踪历史是多么的困难。

UPDATE3: Does one still need to merge after a successful rebase? 是的你是。 原因在于基本上涉及到“提交”的“转移”。 正如我上面所说的,这些提交是计算的,但是如果你从分支的angular度来看有14个提交,那么假设你的rebase没什么问题,你会在提交之后提前14分钟基地完成了。 你之前有一个分支。 之后你将有一个相同长度的分支。 在发布更改之前,您仍然需要合并。 换句话说,可以根据需要多次重新署名(同样,只有在没有向上游推送更改时)。 只有在你重新绑定之后才合并。

合并/重组之前:

 A <- B <- C [master] ^ \ D <- E [branch] 

git merge master

 A <- B <- C ^ ^ \ \ D <- E <- F 

git rebase master

 A <- B <- C <- D' <- E' 

(A,B,C,D,E和F是提交)

这个例子和更好地说明有关git的信息可以在这里find: http : //excess.org/article/2008/07/ogre-git-tutorial/

这句话得到它:

一般来说,两全其美的做法是重新分配你已经做出但尚未分享的局部变化,然后再推动它们来清理你的故事,但不要重新设定你推到的任何地方。

资料来源: http : //www.git-scm.com/book/en/v2/Git-Branching-Rebasing#Rebase-vs.-Merge

亲们的书,作为一个很好的解释,在重塑的页面 。

基本上合并将需要2次提交并将其合并。

2将发生在共同的祖先上,并逐渐将变化应用于彼此之上。 这使得“更清洁”和更线性的历史。

但是,如果你放弃以前的提交并创build新的提交。 所以你应该永远不要重新公开一个回购。 其他人在回购工作会恨你。

仅仅因为这个原因,我几乎完全合并。 99%的时间我的分支没有太大的差别,所以如果有冲突的话,只能在一两个地方。

这个答案是围绕Git Flow广泛定位的。 这些表格已经用漂亮的ASCII表生成器 ,以及用这个奇妙命令生成的历史树( 别名为git lg ):

 git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' 

表格按时间倒序排列,与历史树木更为一致。 另请参阅git mergegit merge --no-ff之间的区别git merge --no-ff首先(通常您想使用git merge --no-ff因为它会让您的历史更接近现实):

git merge

命令:

 Time Branch "develop" Branch "features/foo" ------- ------------------------------ ------------------------------- 15:04 git merge features/foo 15:03 git commit -m "Third commit" 15:02 git commit -m "Second commit" 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

结果:

 * 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo) | Third commit - Christophe * 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago) | Second commit - Christophe * 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git merge --no-ff

命令:

 Time Branch "develop" Branch "features/foo" ------- -------------------------------- ------------------------------- 15:04 git merge --no-ff features/foo 15:03 git commit -m "Third commit" 15:02 git commit -m "Second commit" 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

结果:

 * 1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/foo' - Christophe | * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago) |/ Second commit - Christophe * c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git merge vs git rebase

第一点: 总是把function融入到开发中,永远不要从function上发展 。 这是重新起步的黄金法则的结果 :

git rebase的黄金法则是永远不要在公共分支上使用它。

换句话说 :

永远不要放弃任何你推到的地方。

我会亲自补充说: 除非它是一个function分支,你和你的团队都知道这个后果

所以git merge vs git rebase的问题几乎只适用于特性分支(在下面的例子中,合并时总是使用--no-ff )。 请注意,由于我不确定是否有更好的解决scheme( 存在争议 ),我只会提供这两个命令的行为方式。 在我的情况下,我更喜欢使用git rebase因为它会产生一个更好的历史树:)

在function分支之间

git merge

命令:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- -------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git merge --no-ff features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

结果:

 * c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago) | |\ Merge branch 'features/foo' into features/bar - Christophe | * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago) | | | Fifth commit - Christophe | * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago) | | | Fourth commit - Christophe * | | 98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ \ Merge branch 'features/foo' - Christophe | |/ / |/| / | |/ | * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git rebase

命令:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git rebase features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

结果:

 * 7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago) | | Fourth commit - Christophe * | 189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | |/ | * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

From develop to a feature branch

git merge

命令:

 Time Branch “develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git commit -m “Sixth commit" 15:08 git merge --no-ff development 15:07 git merge --no-ff features/foo 15:06 git commit -m “Fifth commit" 15:05 git commit -m “Fourth commit" 15:04 git commit -m “Third commit" 15:03 git commit -m “Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m “First commit" 

结果:

 * 9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago) | |\ Merge branch 'develop' into features/bar - Christophe | |/ |/| * | 5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | | Third commit - Christophe | * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ / Second commit - Christophe | * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago) |/ Fourth commit - Christophe * 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git rebase

命令:

 Time Branch “develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git commit -m “Sixth commit" 15:08 git rebase development 15:07 git merge --no-ff features/foo 15:06 git commit -m “Fifth commit" 15:05 git commit -m “Fourth commit" 15:04 git commit -m “Third commit" 15:03 git commit -m “Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m “First commit" 

结果:

 * b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago) |/ Fourth commit - Christophe * 856433e - YYYY-MM-DD 15:07:00 (XX minutes ago) |\ Merge branch 'features/foo' - Christophe | * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

旁注

git cherry-pick

When you just need one specific commit, git cherry-pick is a nice solution (the -x option appends a line that says " (cherry picked from commit…) " to the original commit message body, so it's usually a good idea to use it – git log <commit_sha1> to see it):

命令:

 Time Branch “develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ----------------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m “Sixth commit" 15:07 git cherry-pick -x <second_commit_sha1> 15:06 git commit -m “Fifth commit" 15:05 git commit -m “Fourth commit" 15:04 git commit -m “Third commit" 15:03 git commit -m “Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m “First commit" 

结果:

 * 50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago) | | Second commit - Christophe | * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago) | | Fourth commit - Christophe * | 1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | |/ |/| | * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git pull --rebase

Not sure I can explain it better than Derek Gourlay … Basically, use git pull --rebase instead of git pull 🙂 What's missing in the article though, is that you can enable it by default :

 git config --global pull.rebase true 

git rerere

Again, nicely explained here . But put simple, if you enable it, you won't have to resolve the same conflict multiple times anymore.

Git rebase is used to make the branching paths in history cleaner and repository structure linear.

It is also used to keep the branches created by you private, as after rebasing and pushing the changes to server, if you delete your branch, there will be no evidence of branch you have worked upon. So your branch is now your local concern.

After doing rebase we also get rid of an extra commit which we used to see if we do normal merge.

And yes one still needs to do merge after a successful rebase as rebase command just puts your work on top of the branch you mentioned during rebase say master and makes the first commit of your branch as a direct descendant of the master branch. This means we can now do a fast forward merge to bring changes from this branch to master branch.

Some practical examples, somewhat connected to large scale development where gerrit is used for review and delivery integration.

I merge when i uplift my feature branch to a fresh remote master. This gives minimal uplift work and it's easy to follow the history of the feature development in for example gitk.

 git fetch git checkout origin/my_feature git merge origin/master git commit git push origin HEAD:refs/for/my_feature 

I merge when I prepare a delivery commit.

 git fetch git checkout origin/master git merge --squash origin/my_feature git commit git push origin HEAD:refs/for/master 

I rebase when my delivery commit fails integration for whatever reason and I need to update it towards a fresh remote master.

 git fetch git fetch <gerrit link> git checkout FETCH_HEAD git rebase origin/master git push origin HEAD:refs/for/master 

While merging is definitely the easiest and most common way to integrate changes, it's not the only one: Rebase is an alternative means of integration.

Understanding Merge a Little Better

When Git performs a merge, it looks for three commits:

  • (1) Common ancestor commit If you follow the history of two branches in a project, they always have at least one commit in common: at this point in time, both branches had the same content and then evolved differently.
  • (2) + (3) Endpoints of each branch The goal of an integration is to combine the current states of two branches. Therefore, their respective latest revisions are of special interest. Combining these three commits will result in the integration we're aiming for.

Fast-Forward or Merge Commit

In very simple cases, one of the two branches doesn't have any new commits since the branching happened – its latest commit is still the common ancestor.

在这里输入图像描述

In this case, performing the integration is dead simple: Git can just add all the commits of the other branch on top of the common ancestor commit. In Git, this simplest form of integration is called a "fast-forward" merge. Both branches then share the exact same history.

在这里输入图像描述

In a lot of cases, however, both branches moved forward individually. 在这里输入图像描述

To make an integration, Git will have to create a new commit that contains the differences between them – the merge commit.

在这里输入图像描述

Human Commits & Merge Commits

Normally, a commit is carefully created by a human being. It's a meaningful unit that wraps only related changes and annotates them with a comment.

A merge commit is a bit different: instead of being created by a developer, it gets created automatically by Git. And instead of wrapping a set of related changes, its purpose is to connect two branches, just like a knot. If you want to understand a merge operation later, you need to take a look at the history of both branches and the corresponding commit graph.

Integrating with Rebase

Some people prefer to go without such automatic merge commits. Instead, they want the project's history to look as if it had evolved in a single, straight line. No indication remains that it had been split into multiple branches at some point.

在这里输入图像描述

Let's walk through a rebase operation step by step. The scenario is the same as in the previous examples: we want to integrate the changes from branch-B into branch-A, but now by using rebase.

在这里输入图像描述

We will do this in three steps

  1. git rebase branch-A // syncs the history with branch-A
  2. git checkout branch-A // change the current branch to branch-A
  3. git merge branch-B // merge/take the changes from branch-B to branch-A

First, Git will "undo" all commits on branch-A that happened after the lines began to branch out (after the common ancestor commit). However, of course, it won't discard them: instead you can think of those commits as being "saved away temporarily".

在这里输入图像描述

Next, it applies the commits from branch-B that we want to integrate. At this point, both branches look exactly the same.

在这里输入图像描述

In the final step, the new commits on branch-A are now reapplied – but on a new position, on top of the integrated commits from branch-B (they are re-based). 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.

在这里输入图像描述

Finally you get a clean branch branch-A with no unwanted and auto generated commits.

Note: Taken from the awesome post by git-tower . The disadvantages of rebase is also a good read in the same post.