git rebase 和 git merge --ff-only 有区别吗
Is there a difference between git rebase and git merge --ff-only
根据我的阅读,它们都帮助我们获得了线性历史。
根据我的实验,变基一直有效。但是merge --ff-only只在可以快进的场景才有效
我也注意到,git merge 创建了一个合并提交,但是如果我们使用 --ff-only,它给出了一个线性历史,这基本上等于 git 变基。所以 --ff-only 扼杀了 git 合并的目的,对吧?
那么它们之间的实际区别是什么?
是的,有区别。 git merge --ff-only
如果不能快进,将中止,并进行提交(通常是分支)合并。如果不能快进,它只会创建一个合并提交(即永远不会这样做 --ff-only
).
git rebase
重写当前分支的历史记录,或者可用于将现有分支变基到现有分支。在那种情况下,它不会创建合并提交,因为它是变基,而不是合并。
是的,--ff-only
总是会在普通 git merge
失败的地方失败,并且可能会在普通 git merge
成功的地方失败。这就是重点 - 如果您试图保持线性历史记录,并且不能以这种方式进行合并,那么您希望它失败。
将失败案例添加到命令的选项并非无用;这是一种验证先决条件的方法,因此如果系统的当前状态不是您所期望的,您不会使问题变得更糟。
请注意 git rebase
的 工作 与 git merge
不同(有或没有 --ff-only
)。 rebase
所做的是获取现有提交并 复制 它们。例如,假设您在 branch1
上并进行了两次提交 A
和 B
:
...-o--o--A--B <-- HEAD=branch1
\
o--C <-- branch2
并且您决定宁愿将这两个提交放在 branch2
上。您可以:
- 获取您在
A
中所做更改的列表(A
与其父项的差异)
- 获取您在
B
中所做更改的列表(差异 B
与 A
)
- 切换到
branch2
- 进行与
A
相同的更改并提交,从 A
复制您的提交消息;我们称此提交为 A'
- 然后进行与
B
中相同的更改并提交它们,从 B
复制您的提交消息;我们称之为 B'
.
有一个 git 命令可以为您执行此差异然后复制并提交:git cherry-pick
。所以:
git checkout branch2 # switch HEAD to branch2 (commit C)
git cherry-pick branch1^ # this copies A to A'
git cherry-pick branch1 # and this copies B to B'
现在你有这个:
...-o--o--A--B <-- branch1
\
o--C--A'-B' <-- HEAD=branch2
现在您可以切换回 branch1
并使用 git reset
删除原来的 A
和 B
(我在这里使用 --hard
,这样更方便,因为它也清理了工作树):
git checkout branch1
git reset --hard HEAD~2
这删除了原来的 A
和 B
,1 所以现在你有:
...-o--o <-- HEAD=branch1
\
o--C--A'-B' <-- branch2
现在您只需重新签出 branch2
即可继续在那里工作。
这就是 git rebase
所做的:它“移动”提交(虽然不是通过实际移动它们,因为它不能:在 git 中,提交永远无法更改,所以即使只是更改父 ID 需要将其复制到新的且略有不同的提交)。
换句话说,git cherry-pick
是 one 提交的自动差异重做,git rebase
是重做 multiple commits, plus, at the end, 移动标签以“忘记”或隐藏原件。
上面说明了将提交从一个本地分支 branch1
移动到另一个本地分支 branch2
,但是 git 使用 完全相同的过程 当您有一个远程跟踪分支时移动提交,该分支在您执行 git fetch
时获取一些新提交(包括 fetch
,这是 git pull
的第一步)。您可以从分支 feature
开始,该分支具有 origin/feature
的上游,并进行一些您自己的提交:
...-o <-- origin/feature
\
A--B <-- HEAD=feature
但是你决定你应该看看上游发生了什么,所以你 运行 git fetch
,2 啊哈,上游有人写了一个提交C
:
...-o--C <-- origin/feature
\
A--B <-- HEAD=feature
此时您可以简单地将 feature
的 A
和 B
变基到 C
,给出:
...-o--C <-- origin/feature
\
A'-B' <-- HEAD=feature
这些是原件 A
和 B
的副本,副本完成后原件将被丢弃(但请参阅脚注 1)。
有时没有什么可以变基的,也就是说,没有你自己做的工作。也就是说,fetch
之前的图形如下所示:
...-o <-- origin/feature
`-- HEAD=feature
如果你然后 git fetch
和提交 C
进来,但是,你剩下 你的 feature
分支指向旧提交,而 origin/feature
已向前推进:
...-o--C <-- origin/feature
`---- <-- HEAD=feature
这就是 git merge --ff-only
的用武之地:如果您要求将当前分支 feature
与 origin/feature
合并,git 会发现只需滑动箭头即可向前,这样 feature
直接指向提交 C
。不需要实际合并。
如果您有自己的提交 A
和 B
,并且您要求将它们与 C
合并,git 将进行真正的合并,使一个新的合并提交 M
:
...-o--C <-- origin/feature
\ `-_
A--B--M <-- feature
到这里,--ff-only
会停下来给你报错。另一方面,Rebase 可以将 A
和 B
复制到 A'
和 B'
,然后隐藏原来的 A
和 B
。
所以,简而言之(好吧,太晚了:-)),他们只是做了不同的事情。有时结果相同,有时则不同。如果可以复制A
和B
,可以使用git rebase
;但如果有充分的理由 不 复制它们,您可以使用 git merge
,也许与 --ff-only
一起使用,以适当地合并或失败。
1Git 实际上会保留原件一段时间——在这种情况下通常是一个月——但它会将它们隐藏起来。找到它们的最简单方法是使用 git 的“reflogs”,它保留了每个分支指向的位置的历史记录,以及 HEAD
指向的位置,在更新分支 and/or 的每个更改之前HEAD
.
reflog 历史条目最终会过期,此时这些提交有资格获得 garbage collection。
2或者,同样,您可以使用 git pull
,这是一个以 运行ning git fetch
开头的便捷脚本。获取完成后,便捷脚本 运行 会 git merge
或 git rebase
,具体取决于您如何配置和 运行 它。
根据我的阅读,它们都帮助我们获得了线性历史。
根据我的实验,变基一直有效。但是merge --ff-only只在可以快进的场景才有效
我也注意到,git merge 创建了一个合并提交,但是如果我们使用 --ff-only,它给出了一个线性历史,这基本上等于 git 变基。所以 --ff-only 扼杀了 git 合并的目的,对吧?
那么它们之间的实际区别是什么?
是的,有区别。 git merge --ff-only
如果不能快进,将中止,并进行提交(通常是分支)合并。如果不能快进,它只会创建一个合并提交(即永远不会这样做 --ff-only
).
git rebase
重写当前分支的历史记录,或者可用于将现有分支变基到现有分支。在那种情况下,它不会创建合并提交,因为它是变基,而不是合并。
是的,--ff-only
总是会在普通 git merge
失败的地方失败,并且可能会在普通 git merge
成功的地方失败。这就是重点 - 如果您试图保持线性历史记录,并且不能以这种方式进行合并,那么您希望它失败。
将失败案例添加到命令的选项并非无用;这是一种验证先决条件的方法,因此如果系统的当前状态不是您所期望的,您不会使问题变得更糟。
请注意 git rebase
的 工作 与 git merge
不同(有或没有 --ff-only
)。 rebase
所做的是获取现有提交并 复制 它们。例如,假设您在 branch1
上并进行了两次提交 A
和 B
:
...-o--o--A--B <-- HEAD=branch1
\
o--C <-- branch2
并且您决定宁愿将这两个提交放在 branch2
上。您可以:
- 获取您在
A
中所做更改的列表(A
与其父项的差异) - 获取您在
B
中所做更改的列表(差异B
与A
) - 切换到
branch2
- 进行与
A
相同的更改并提交,从A
复制您的提交消息;我们称此提交为A'
- 然后进行与
B
中相同的更改并提交它们,从B
复制您的提交消息;我们称之为B'
.
有一个 git 命令可以为您执行此差异然后复制并提交:git cherry-pick
。所以:
git checkout branch2 # switch HEAD to branch2 (commit C)
git cherry-pick branch1^ # this copies A to A'
git cherry-pick branch1 # and this copies B to B'
现在你有这个:
...-o--o--A--B <-- branch1
\
o--C--A'-B' <-- HEAD=branch2
现在您可以切换回 branch1
并使用 git reset
删除原来的 A
和 B
(我在这里使用 --hard
,这样更方便,因为它也清理了工作树):
git checkout branch1
git reset --hard HEAD~2
这删除了原来的 A
和 B
,1 所以现在你有:
...-o--o <-- HEAD=branch1
\
o--C--A'-B' <-- branch2
现在您只需重新签出 branch2
即可继续在那里工作。
这就是 git rebase
所做的:它“移动”提交(虽然不是通过实际移动它们,因为它不能:在 git 中,提交永远无法更改,所以即使只是更改父 ID 需要将其复制到新的且略有不同的提交)。
换句话说,git cherry-pick
是 one 提交的自动差异重做,git rebase
是重做 multiple commits, plus, at the end, 移动标签以“忘记”或隐藏原件。
上面说明了将提交从一个本地分支 branch1
移动到另一个本地分支 branch2
,但是 git 使用 完全相同的过程 当您有一个远程跟踪分支时移动提交,该分支在您执行 git fetch
时获取一些新提交(包括 fetch
,这是 git pull
的第一步)。您可以从分支 feature
开始,该分支具有 origin/feature
的上游,并进行一些您自己的提交:
...-o <-- origin/feature
\
A--B <-- HEAD=feature
但是你决定你应该看看上游发生了什么,所以你 运行 git fetch
,2 啊哈,上游有人写了一个提交C
:
...-o--C <-- origin/feature
\
A--B <-- HEAD=feature
此时您可以简单地将 feature
的 A
和 B
变基到 C
,给出:
...-o--C <-- origin/feature
\
A'-B' <-- HEAD=feature
这些是原件 A
和 B
的副本,副本完成后原件将被丢弃(但请参阅脚注 1)。
有时没有什么可以变基的,也就是说,没有你自己做的工作。也就是说,fetch
之前的图形如下所示:
...-o <-- origin/feature
`-- HEAD=feature
如果你然后 git fetch
和提交 C
进来,但是,你剩下 你的 feature
分支指向旧提交,而 origin/feature
已向前推进:
...-o--C <-- origin/feature
`---- <-- HEAD=feature
这就是 git merge --ff-only
的用武之地:如果您要求将当前分支 feature
与 origin/feature
合并,git 会发现只需滑动箭头即可向前,这样 feature
直接指向提交 C
。不需要实际合并。
如果您有自己的提交 A
和 B
,并且您要求将它们与 C
合并,git 将进行真正的合并,使一个新的合并提交 M
:
...-o--C <-- origin/feature
\ `-_
A--B--M <-- feature
到这里,--ff-only
会停下来给你报错。另一方面,Rebase 可以将 A
和 B
复制到 A'
和 B'
,然后隐藏原来的 A
和 B
。
所以,简而言之(好吧,太晚了:-)),他们只是做了不同的事情。有时结果相同,有时则不同。如果可以复制A
和B
,可以使用git rebase
;但如果有充分的理由 不 复制它们,您可以使用 git merge
,也许与 --ff-only
一起使用,以适当地合并或失败。
1Git 实际上会保留原件一段时间——在这种情况下通常是一个月——但它会将它们隐藏起来。找到它们的最简单方法是使用 git 的“reflogs”,它保留了每个分支指向的位置的历史记录,以及 HEAD
指向的位置,在更新分支 and/or 的每个更改之前HEAD
.
reflog 历史条目最终会过期,此时这些提交有资格获得 garbage collection。
2或者,同样,您可以使用 git pull
,这是一个以 运行ning git fetch
开头的便捷脚本。获取完成后,便捷脚本 运行 会 git merge
或 git rebase
,具体取决于您如何配置和 运行 它。