什么时候推荐使用 Git rebase vs. Git merge?
成功变基后我还需要合并吗?
那么你什么时候使用其中之一呢?
这很简单。使用 rebase,您可以说使用另一个分支作为您工作的新基础。
例如,如果你有一个分支master
,你创建一个分支来实现一个新特性,并说你命名它cool-feature
,当然,主分支是你的新特性的基础。
现在,在某个时刻,您想要添加您在master
分支中实现的新功能。您可以切换到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
这一次,由于主题分支具有相同的 master 提交以及具有新功能的提交,因此合并将只是一个快进。
如果您有任何疑问,请使用合并。
变基和合并之间的唯一区别是:
所以简短的回答是根据你希望你的历史看起来像什么来选择变基或合并。
在选择要使用的操作时,您应该考虑几个因素。
如果是这样,请不要变基。Rebase 会破坏分支,除非他们使用git pull --rebase
. 这是快速让其他开发人员感到不安的好方法。
Rebase 是一种破坏性操作。这意味着,如果您没有正确应用它,您可能会丢失已提交的工作和/或破坏其他开发人员存储库的一致性。
我曾在开发人员都来自公司能够负担得起专门人员来处理分支和合并的时代工作过。那些开发人员对 Git 了解不多,也不想知道太多。在这些团队中,我不会冒险以任何理由推荐 rebase。
一些团队使用每个功能分支模型,其中每个分支代表一个功能(或错误修复或子功能等)。在此模型中,分支有助于识别相关提交集。例如,可以通过恢复该分支的合并来快速恢复功能(公平地说,这是一种罕见的操作)。或者通过比较两个分支来区分一个特征(更常见)。变基会破坏分支,这并不简单。
我还曾在使用每个开发人员分支模型的团队工作过(我们都去过那里)。在这种情况下,分支本身不会传达任何其他信息(提交已经有作者)。重新定位不会有任何害处。
与还原合并相比,还原(如撤消)变基相当困难和/或不可能(如果变基有冲突)。如果您认为有可能想要还原,请使用合并。
Rebase 操作需要使用相应的git pull --rebase
. 如果您自己工作,您可能会记住在适当的时候应该使用哪个。如果你在一个团队中工作,这将很难协调。这就是为什么大多数 rebase 工作流程建议对所有合并(以及git pull --rebase
所有拉取)使用 rebase。
假设您有以下合并:
B -- C
/ \
A--------D
有些人会说合并“破坏”了提交历史,因为如果你只查看主分支(A-D)的日志,你会错过 B 和 C 中包含的重要提交消息。
如果这是真的,我们就不会有这样的问题了。基本上,您会看到 B 和 C,除非您明确要求不要看到它们(使用 --first-parent)。这很容易自己尝试。
这两种方法以不同的方式合并,但尚不清楚一种总是比另一种更好,它可能取决于开发人员的工作流程。例如,如果开发人员倾向于定期提交(例如,他们可能每天提交两次,因为他们从工作过渡到家),那么给定分支可能会有很多提交。其中许多提交可能看起来不像最终产品(我倾向于对每个功能重构我的方法一次或两次)。如果其他人正在处理相关的代码区域并且他们试图重新调整我的更改,这可能是一个相当乏味的操作。
如果您喜欢别名rm
为rm -rf
“节省时间”,那么 rebase 可能适合您。
我一直认为总有一天我会遇到这样一个场景,即 Git rebase 是解决问题的绝佳工具。就像我想我会遇到这样一个场景,Git reflog 是一个很棒的工具,可以解决我的问题。我已经使用 Git 工作了五年多了。它没有发生。
凌乱的历史对我来说从来都不是问题。我从来不会像阅读一本激动人心的小说一样阅读我的提交历史。大多数时候我需要一个历史记录,无论如何我都会使用 Git blame 或 Git bisect。在这种情况下,合并提交实际上对我有用,因为如果合并引入了问题,那对我来说是有意义的信息。
我觉得有义务提到我个人对使用 rebase 的态度有所缓和,尽管我的一般建议仍然有效。我最近与Angular 2 Material项目进行了很多互动。他们使用 rebase 来保持非常干净的提交历史。这让我可以很容易地看到哪些提交修复了给定的缺陷,以及该提交是否包含在发布中。它是正确使用 rebase 的一个很好的例子。
在合并之前做一个 rebase 通常是一个好主意,因为这个想法是你在你的分支中集成你将要合并的分支Y
的工作。
但同样,在合并之前,您解决分支中的任何冲突(即:“rebase”,如“从分支的最近点开始在我的分支中重播我的工作)。
如果正确完成,则从分支的后续合并到分支可以快进。B
B
B
合并直接影响目标分支B
,这意味着合并最好是微不足道的,否则该分支B
可能很长时间才能恢复到稳定状态(解决所有冲突的时间)
变基后的合并点?
在我描述的情况下,我重新定位B
到我的分支,只是为了有机会从最近的时间点重播我的工作B
,但同时留在我的分支中。
在这种情况下,仍然需要合并才能将我的“重播”作品带到B
.
另一种情况(例如在 Git Ready中描述的)是B
通过变基直接将您的工作带入(这确实保留了所有不错的提交,甚至让您有机会通过交互式变基重新排序它们)。
在那种情况下(你在 B 分支中变基),你是对的:不需要进一步的合并:
当我们没有合并也没有重新定位时,默认的 Git 树
我们通过变基得到:
第二种情况是关于:我如何让新功能回到主控。
我的意思是,通过描述第一个 rebase 场景,是为了提醒大家,rebase 也可以用作一个初步步骤(即“将新功能恢复为 master”)。
您可以使用 rebase 首先将 master “引入”新功能分支:rebase 将重播来自 的新功能提交HEAD master
,但仍在新功能分支中,从而有效地将您的分支起点从旧的 master 提交移动到HEAD-master
.
这使您可以解决分支中的任何冲突(这意味着,如果您的冲突解决阶段花费的时间太长,则允许 master 继续并行发展)。然后你可以切换到
master 并合并new-feature
(或者如果你想保留在你的new-feature
master
new-feature
分支)。
所以:
master
.这里的很多答案都说合并会将您的所有提交合二为一,因此建议使用 rebase 来保留您的提交。这是不正确的。如果你已经推送了你的提交,那是个坏主意。
合并不会消除您的提交。合并保留历史!(只看 gitk) Rebase 重写历史,在你推送它之后这是一件坏事。
使用合并——当你已经推送时不要变基。
这是 Linus (Git 的作者)对它的看法(现在托管在我自己的博客上,由 Wayback Machine 恢复)。这真是一本好书。
或者你可以在下面阅读我自己版本的相同想法。
在 master 上重新建立一个分支:
相反,将主题分支合并到主分支:
我刚刚用我自己的话为我的团队创建了一个常见问题解答,它回答了这个问题。让我分享:
merge
?提交,将不同分支的所有更改组合到当前。
rebase
?将当前分支的所有提交重新提交到不同的基本提交。
merge
和之间的主要区别是rebase
什么?merge
只执行一个新的提交。rebase
通常执行多个(当前分支中的提交数)。merge
产生一个新的生成提交(所谓的合并提交)。rebase
只移动现有的提交。merge
?merge
每当您想将分支分支的更改添加回基本分支时使用。
通常,您可以通过单击 Pull/Merge Requests 上的“Merge”按钮来执行此操作,例如在 GitHub 上。
rebase
?rebase
每当您要将基本分支的更改添加回分支分支时使用。
通常,feature
只要分支发生变化,您就会在分支中执行此操作main
。
merge
将基础分支中的更改合并到功能分支中呢?git 历史将包括许多不必要的合并提交。如果在一个特性分支中需要多次合并,那么特性分支甚至可能比实际提交包含更多的合并提交!
这会创建一个循环,破坏 Git 设计的心智模型,这会导致 Git 历史的任何可视化出现问题。
想象有一条河(例如“尼罗河”)。水流向一个方向(Git 历史中的时间方向)。时不时地,想象那条河有一个分支,并假设这些分支中的大多数会汇回河流中。这就是河流的自然流动。这说得通。
但是再想象一下那条河的一个小支流。然后,由于某种原因,河流汇入分支,分支从那里继续。从技术上讲,这条河现在已经消失了,它现在在分支中。但随后,不知何故神奇地,那根树枝又汇回了河里。你问哪条河?我不知道。河流现在实际上应该在分支中,但不知何故它仍然存在,我可以将分支合并回河流中。所以,河在河中。有点没有意义。
这正是当您merge
将基本分支合并到一个feature
分支时发生的情况,然后当feature
分支完成后,您再次将其合并回基本分支。心智模式被打破。正因为如此,您最终会得到一个不是很有帮助的分支可视化。
merge
:请注意以 . 开头的许多提交Merge branch 'main' into ...
。如果您变基,它们甚至都不存在(在那里,您只会有拉取请求合并提交)。还有许多视觉分支合并循环(main
into feature
into main
)。
rebase
:更清晰的 Git 历史记录,更少的合并提交,也没有杂乱的视觉分支合并循环。
rebase
吗?是的:
rebase
移动提交(技术上重新执行它们),所有移动提交的提交日期将是变基的时间,并且git 历史失去了初始提交时间。因此,如果出于某种原因需要提交的确切日期,那么merge
是更好的选择。但通常情况下,干净的 git 历史记录比准确的提交日期有用得多。rebase
:TLDR:这取决于什么是最重要的——整洁的历史或发展顺序的真实表现
如果整洁的历史记录是最重要的,那么您将首先变基,然后合并您的更改,这样新代码到底是什么就很清楚了。如果你已经推送了你的分支,除非你能处理后果,否则不要变基。
如果序列的真实表示是最重要的,那么您将在没有变基的情况下合并。
合并意味着:创建一个新的提交,将我的更改合并到目标中。注意:这个新提交将有两个父提交 - 您的提交字符串中的最新提交和您正在合并的另一个分支的最新提交。
Rebase 意味着:使用我当前的一组提交作为提示,创建一个全新的提交系列。换句话说,计算如果我从我要重新定位的点开始进行更改,我的更改会是什么样子。因此,在 rebase 之后,您可能需要重新测试您的更改,并且在 rebase 期间,您可能会遇到一些冲突。
鉴于此,你为什么要变基?只是为了保持发展历史清晰。假设您正在处理功能 X,当您完成后,您将更改合并到其中。目标现在将有一个提交,它会说出类似于“添加的功能 X”的内容。现在,如果您重新定位然后合并,而不是合并,目标开发历史将包含单个逻辑进程中的所有单个提交。这使得以后审查更改变得更加容易。想象一下,如果 50 位开发人员一直在合并各种功能,您会发现回顾开发历史是多么困难。
也就是说,如果你已经将你正在处理的分支推送到上游,你不应该变基,而是合并。对于尚未推送到上游的分支,进行 rebase、test 和 merge。
另一个你可能想要变基的时候是当你想在向上游推送之前从你的分支中删除提交。例如:在早期引入一些调试代码的提交和在清理该代码时进一步的其他提交。唯一的方法是执行交互式变基:git rebase -i <branch/commit/tag>
更新:当您使用 Git 连接到不支持非线性历史的版本控制系统(例如Subversion )时,您还想使用 rebase 。使用 git-svn 桥时,合并回 Subversion 的更改是主干中最新更改之上的更改顺序列表,这一点非常重要。只有两种方法可以做到这一点:(1)手动重新创建更改和(2)使用 rebase 命令,这要快得多。
更新 2:考虑变基的另一种方法是,它可以实现从您的开发风格到您正在提交的存储库中接受的风格的一种映射。假设你喜欢以小块的方式提交。你有一个提交来修复一个错字,一个提交来摆脱未使用的代码等等。当你完成你需要做的事情时,你有一系列的提交。现在假设您提交的存储库鼓励大型提交,因此对于您正在做的工作,人们会期望进行一次或两次提交。你如何接受你的提交字符串并将它们压缩到预期的值?您将使用交互式 rebase 并将您的微小提交压缩成更少的大块。如果需要相反的情况也是如此 - 如果您的风格是一些大型提交,但是存储库需要一长串的小提交。你也可以使用变基来做到这一点。如果您已经合并,那么您现在已经将您的提交样式移植到主存储库中。如果有很多开发人员,您可以想象一段时间后要跟踪具有多种不同提交样式的历史是多么困难。
更新 3:Does one still need to merge after a successful rebase?
是的,你知道。原因是变基本质上涉及提交的“转移”。正如我上面所说,这些提交是计算出来的,但是如果你从分支点开始有 14 次提交,那么假设你的变基没有问题,那么你将提前 14 个提交(在你变基之后)变基完成。在变基之前你有一个分支。之后你将有一个相同长度的分支。在发布更改之前,您仍然需要合并。换句话说,根据需要多次变基(同样,仅当您没有将更改推送到上游时)。只有在你变基后才合并。
虽然合并绝对是集成更改的最简单和最常见的方式,但它并不是唯一的:Rebase是一种替代的集成方式。
更好地理解合并
当 Git 执行合并时,它会查找三个提交:
快进或合并提交
在非常简单的情况下,两个分支之一在分支发生后没有任何新的提交——它的最新提交仍然是共同的祖先。
在这种情况下,执行集成非常简单:Git 可以将其他分支的所有提交添加到共同祖先提交之上。在 Git 中,这种最简单的集成形式称为“快进”合并。然后,两个分支共享完全相同的历史记录。
然而,在很多情况下,两个分支都各自向前发展。
为了进行集成,Git 必须创建一个包含它们之间差异的新提交——合并提交。
人工提交和合并提交
通常,提交是由人类精心创建的。这是一个有意义的单元,它只包装相关的更改并用注释对其进行注释。
合并提交有点不同:它不是由开发人员创建的,而是由 Git 自动创建的。而不是包装一组相关的变化,它的目的是连接两个分支,就像一个结。如果你以后想了解一个合并操作,你需要看一下两个分支的历史和相应的提交图。
与 Rebase 集成
有些人更喜欢不使用这种自动合并提交。相反,他们希望项目的历史看起来好像是在一条直线上发展的。没有迹象表明它在某个时候被分成了多个分支。
让我们一步一步地完成一个 rebase 操作。场景与前面的示例相同:我们希望将分支 B 的更改集成到分支 A,但现在使用 rebase。
我们将分三步完成
git rebase branch-A // Synchronises 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 上在行开始分支之后(在共同祖先提交之后)发生的所有提交。但是,当然,它不会丢弃它们:相反,您可以将这些提交视为“暂时保存”。
接下来,它应用来自我们想要集成的分支 B 的提交。此时,两个分支看起来完全一样。
在最后一步中,现在重新应用分支 A 上的新提交 - 但在新位置上,在来自分支 B 的集成提交之上(它们是重新基于的)。
结果看起来就像直线发展一样。保留了原始提交结构,而不是包含所有组合更改的合并提交。
最后,你得到一个干净的分支分支-A,没有不需要的和自动生成的提交。
在合并/变基之前:
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 The Basics Tutorial中找到这个示例以及更多关于 Git 的详细说明信息。
这个答案广泛面向Git Flow。这些表是用漂亮的ASCII Table Generator 生成的,历史树是用这个美妙的命令(别名为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 merge
另请参阅和first之间的区别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
对比git rebase
第一点:总是将 features 合并到 develop 中,永远不要 rebase development from features。这是变基黄金法则的结果:
的黄金法则
git rebase
是永远不要在公共分支上使用它。
换句话说:
永远不要改变你推到某个地方的任何东西。
我会亲自补充:除非它是一个功能分支并且您和您的团队都知道后果。
所以git merge
vs的问题git rebase
几乎只适用于特征分支(在下面的例子中,--no-ff
一直在合并时使用)。请注意,由于我不确定是否有更好的解决方案(存在争议),我将只提供这两个命令的行为方式。就我而言,我更喜欢使用它,git rebase
因为它会产生更好的历史树:)
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
develop
到功能分支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 develop
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 develop
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
当您只需要一个特定的提交时,git cherry-pick
这是一个不错的解决方案(该-x
选项会在原始提交消息正文中附加一行“ (从提交中挑选的樱桃...) ”,因此使用它通常是一个好主意 -git log <commit_sha1>
查看它):
命令:
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
我不确定我能不能比Derek Gourlay更好地解释它......基本上,使用git pull --rebase
而不是git pull
:) 虽然文章中缺少的是,您可以默认启用它:
git config --global pull.rebase true
git rerere
再次,很好地解释here。但简而言之,如果启用它,您将不再需要多次解决相同的冲突。
这句话明白了:
一般来说,两全其美的方法是在你推送它们以清理你的故事之前,对你所做但尚未共享的本地更改进行 rebase,但永远不要对你推送的任何内容进行 rebase .
Git rebase 用于使历史中的分支路径更清晰和存储库结构线性。
它还用于保持您创建的分支的私密性,因为在重新设置基础并将更改推送到服务器之后,如果您删除了您的分支,则不会有您曾经处理过的分支的证据。因此,您的分支机构现在是您当地的关注点。
在做 rebase 之后,我们还摆脱了一个额外的提交,我们曾经用它来查看我们是否进行了正常的合并。
是的,一个成功的 rebase 后仍然需要进行合并,因为 rebase 命令只是将你的工作放在你在 rebase 期间提到的分支之上,比如 master,并将你的分支的第一次提交作为 master 分支的直接后代. 这意味着我们现在可以进行快进合并,将更改从这个分支带到主分支。
一些实际示例,在某种程度上与使用Gerrit进行审查和交付集成的大规模开发有关:
当我将我的功能分支提升到一个新的远程主控时,我会合并。这提供了最小的提升工作,并且很容易遵循例如gitk中的功能开发历史。
git fetch
git checkout origin/my_feature
git merge origin/master
git commit
git push origin HEAD:refs/for/my_feature
我在准备交付提交时合并。
git fetch
git checkout origin/master
git merge --squash origin/my_feature
git commit
git push origin HEAD:refs/for/master
当我的交付提交由于某种原因无法集成时,我会重新设置基准,并且我需要将其更新为新的远程主机。
git fetch
git fetch <gerrit link>
git checkout FETCH_HEAD
git rebase origin/master
git push origin HEAD:refs/for/master
多次解释了什么是变基和什么是合并,但是什么时候应该使用呢?
什么时候应该使用变基?
变基“解除”您的更改并将变基分支的所有更改放入您当前的分支,然后将您的更改放在它之上。因此,它会更改您分支的历史记录。
我说“您希望在一个地方查看所有更改”,因为有时合并操作会将您的所有更改放在一个提交中(一些:合并自...消息)。Rebase 使您的更改看起来像您一个接一个地提交,中间没有其他人做任何事情。这使您更容易看到您对功能所做的更改。
但请确保您使用 git merge feature-branch --ff-only 来确保在将功能合并回开发/主时不会创建单个提交。
什么时候应该使用合并?
(*) 您可以通过首先将开发分支合并到您的功能然后将您的功能合并回开发来避免您的功能仅获得一个“合并的..”提交。这仍然为您提供“合并的 ..”提交,但至少您的功能的所有提交仍然可见。
我什么时候使用git rebase
?几乎从来没有,因为它改写了历史。git merge
几乎总是更可取的选择,因为它尊重您项目中实际发生的情况。