TL;DR:您可以使用 git merge-base 和 reflog 信息找到提交 E。
您的绘图是正确的,因为 rebase 所做的是 copy 提交(对新的、略有不同的提交)。
原始提交会在存储库中保留一段时间(默认情况下通常为 30 天):它们只是被放置在视野之外。有可能(使用 reflogs 和/或 ORIG_HEAD 找到原始的 C 并从那里找到合并基础提交 E。
如果这种事情经常发生,我建议不要直接使用git pull。事实上,我建议不要从git pull 开始,尽管使用这两个独立的底层步骤需要更多的工作和更多的输入。这里的想法是让 Git 在做什么很明显,从而更容易插入有用的自动化。 (之后,根据您自己的自动化,您可以返回使用git pull,有或没有--rebase,如果您发现运行一个命令而不是两个命令更方便。)
git pull 所做的是运行git fetch,然后运行git merge 或git rebase。虽然pull 有一些额外的花里胡哨,但这确实是其行动的核心。单独执行这两个操作的好处是它们可以让您检查fetch 之后和merge-或-rebase 之前的状态。
(这对于merge 的必要性要小得多,因为“合并前”状态很明显:只需剥离合并提交,您就有了合并前状态。rebase 更有用,因为“之前”合并”状态让您可以根据需要分别查看分叉开发的双方,然后再允许 Git 做疯狂而疯狂的事情,制作提交副本。)
让我们在这里重现您的“之前”状态,然后展示 rebase 的真正作用。这是运行git fetch 之后、运行git rebase 之前的“之前”状态:
A---B---C <-- HEAD -> branch
/
D---E---F---G <-- origin/branch
我添加了名称 HEAD 指向您当前的分支,并将分支重命名为更典型。
在git rebase 步骤之后,我们有这个:
A---B---C <-- ORIG_HEAD, branch@{1}
/
D---E---F---G <-- origin/branch
\
A'--B'--C' <-- HEAD -> branch
通常,查看者(git log、gitk --all 等)会跳过特殊引用,例如 ORIG_HEAD、CHERRY_PICK_HEAD 等。他们还会忽略 reflogs (branch@{1}) 的内容。使用git reflog 或git log -g,或在命令行中添加ORIG_HEAD 之类的名称,您可以指示这些查看者向您展示原件。
如果您将这两个步骤分开,则可以轻松保存任何特定提交的信息或添加标记(例如临时git tags)。比如git fetch之后,可以标记commit E:找到branch和origin/branch的1合并基,并标记:
$ git tag temp-marker $(git merge-base branch origin/branch)
(假设 sh 或 bash 或类似的;请注意,为了概括这一点,您可以使用 HEAD 和 @{u} 代替 branch 和 origin/branch)。
如果您不想混淆他人(“为什么在我们所有的存储库中都有一个名为 temp-marker 的标签?!”),请确保删除 temp-marker 标签(或至少避免推送它),一次你已经完成了。
即使您确实使用 git pull --rebase 将它们组合在一起,之后您也可以使用 reflog 和/或 ORIG_HEAD 来做同样的事情。 (这里的主要问题是 reflog 中的数字——上面的 branch@{1} 和 ORIG_HEAD 本身都会更新。如果你做另一个 rebase,ORIG_HEAD 现在跟踪 that rebase 而不是一个你仍然关心的。同时如果你对branch进行任何更新,数字(@{1})会增加:现在你需要branch@{2},然后是branch@{3},等等。(你也可以使用@987654378 @,它检查记录到 branch 的每个更改的日期戳,并使用来自“昨天”的最新日期戳。有关详细信息,请参阅 the git reflog documentation 及其返回到 git log 的链接以及 gitrevisions .)
换句话说,一旦你在这个特定的pickle中,检查branch@{1}是否是正确的参考(或者ORIG_HEAD仍然是好的)。如果没有,请使用git reflog 或类似名称来查找正确的参考。无论如何,一旦你找到它:
git merge-base branch@{1} origin/branch
将定位提交E。
1这假设只有一个合并基础提交。对于我们在这里考虑的所有情况,这应该是正确的。只有在存在“交叉合并”(必须手工制作)的情况下,您才会获得多个合并基础:
...--o--*---o--o <-- branch1
\ /
X
/ \
...--o--*---o--o <-- branch2
这里,branch1 和 branch2 之间没有单一的合并基础:* 提交都是合适的合并基础。这不会发生在具有单个上游分支的正常工作流程中(因为您无法签出 origin/branch 并对其进行提交)。您必须自己做一些疯狂而疯狂的事情(使用临时分支、两次显式合并和至少一次推送)才能实现。