据我所知,他们都帮助我们得到一个线性的历史。
根据我的实验,rebase 一直都有效。但是合并—— ff-只在可以快进的场景中有效。
我还注意到,git merge 创建了一个合并提交,但是如果我们使用—— ff-only,它会提供一个线性历史记录,基本上等同于 git rebase。所以,只会扼杀 Git 合并的目的,对吧?
那么它们之间的实际区别是什么呢?
是的,有区别。如果不能快进,git merge --ff-only将中止,并接受一个提交(通常是一个分支)来合并。它只会在不能快进的情况下创建一个合并提交(也就是说,在 --ff-only中永远不会这样做)。
git merge --ff-only
--ff-only
git rebase在当前分支上重写历史记录,或者可以用于将现有分支重新基于现有分支。在这种情况下,它不会创建合并提交,因为它是重新定基,而不是合并。
git rebase
是的,在普通 git merge失败的情况下,--ff-only总是会失败,而在普通 git merge成功的情况下,--ff-only可能会失败。这就是问题的关键——如果你试图保持一个线性的历史,并且合并不能这样做,你 想要它失败了。
git merge
将故障情况添加到命令中的选项并非毫无用处; 它是验证先决条件的一种方法,因此,如果系统的当前状态与您预期的不同,那么就不会使问题变得更糟。
请注意,git rebase与 git merge有不同的 工作(有或没有 --ff-only)。rebase所做的就是接受现有的提交并对其进行 收到。例如,假设您在 branch1上,并且两次提交了 A和 B:
rebase
branch1
A
B
...-o--o--A--B <-- HEAD=branch1 \ o--C <-- branch2
然后决定将这两个提交放在 branch2上。您可以:
branch2
A'
B'
有一个 git 命令可以为您执行 diff-and-then-copy-and-commit 操作: git cherry-pick:
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 reset
--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 也需要将其复制到 new 和略有不同的提交)。
换句话说,虽然 git cherry-pick是 一提交的自动差分和重做,但 git rebase是重做 多个提交的自动过程,最后移动标签来“忘记”或隐藏原始内容。
上面的例子说明了如何将提交从一个本地分支 branch1移动到另一个本地分支 branch2,但是当执行 git fetch(包括 fetch,这是 git pull的第一步)时,当远程跟踪分支获得一些新的提交时,git 使用 完全相同的过程来移动提交。您可以从 feature分支开始,它拥有 origin/feature的上游,然后您可以自己提交一些内容:
git fetch
fetch
git pull
feature
origin/feature
...-o <-- origin/feature \ A--B <-- HEAD=feature
但是之后你决定你应该看看上游发生了什么,所以你运行 git fetch,2,啊哈,上游有人写了一个提交 C:
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:
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,来进行合并或失败。
Git 实际上将原件保存了一段时间(在这种情况下通常是一个月) ,但它将原件隐藏了起来。找到它们最简单的方法是使用 git 的“ relog”,它保存每个分支指向的位置和 HEAD指向的位置的历史记录,在每次更新分支和/或 HEAD之前。
HEAD
最终,reflog 历史记录条目过期,此时这些提交将符合 垃圾回收的条件。
2 或者,您可以再次使用 git pull,这是一个方便的脚本,从运行 git fetch开始。一旦获取完成,方便脚本将运行 git merge或 git rebase,这取决于您如何配置和运行它。