如何处理已经被 rebase 过的公共代码库?

4

这个问题可能已经被问过了,但我在搜索时遇到了很多困难。

经验丰富的 git 用户会经常说 "不要对已经公开的存储库进行 rebase 操作!",这是正确的。但是我没有看到任何处理后果的信息。我知道我们不应该陷入这种情况,但当它确实发生时,我们该怎么办?

Original: A--B--C--D--E--F
My Repo:  A--B--C--D
                    \
                     X--Y--Z
Rebased:  A--B
              \
               L--M--N--O

所以我使用fetch,这应该很简单,对吗?然后会发生什么?如果我执行`pull --rebase,它应该在某个时候失败,因为缺少C--D。我将遇到哪些错误,并如何修复它们?


Git 2.0将简化我在下面的回答中描述的过程。 - VonC
2个回答

4

在git-rebase手册中有一个名为“从上游rebase恢复”的章节,对此进行了很好的描述。

获取

对于您的特定情况,让我们来看看会发生什么。如果您只是获取(fetch),那么它只是更新您的远程跟踪分支。这仍然可以正常工作 - 但它会给您一个警告

From <url>:
 + abc123...def456 master    -> origin/master  (forced update)

拉取

无论是普通的合并(merging)还是变基(rebasing),都会在内部调用fetch。因此,如果您还没有进行过获取操作,则会看到“强制更新”警告。如果您已经获取了,之前就会出现强制更新警告,并且现在没有强制更新需要警告。如果在合并后有大量的差异报告,您也可能会错过强制更新警告。在获取后,git pull 就会按照所请求的方式调用合并或变基操作。这时就会变得非常危险。就 Git 而言,提交 CD 现在只是本地提交。它不会关心这些提交曾经被发布过。因此,Git 会像平常一样进行合并/变基。

合并:

- A - B - L - M - N - O (origin/master)
   \                   \
    C - D - X - Y - Z - Q (master)

变基(Rebase):

 - A - B - L - M - N - O (origin/master) - C' - D' - X' - Y' - Z' (master)

现在,如果删除提交C和D会使后续提交不同,那么就可能会遇到冲突。 合并冲突是你唯一会注意到的其他错误。但是如果它们是孤立的更改,那么一切都会“很好”。如果用户没有注意到这一点,那么他们可能随后推送此历史记录,从而重新引入已删除的提交!有时这可能相对温和。也许L实际上是在提交消息中修正了拼写错误的C,而MNO与DEF相同,只是父级不同。因此,重新引入C和D将不会产生任何更多的影响,只会使历史记录杂乱无章。但是,也可能非常危险:也许删除C修复了某些可怕的错误,并且已经被重新引入。

避免问题

这就是为什么重写已发布历史记录是一个非常糟糕的想法。最好的情况是最终得到一些重复提交,最坏的情况是用户不知情地破坏了一切。因此,如果确实发生了这种情况,第一件事就是告诉每个人并指导他们如何恢复。下一步是警惕地监视随后发布的所有内容,以确保不会重新引入旧提交。如果您在公共存储库中获取数据,则希望您足够信任维护人员永远不会重写稳定分支,或者会告诉您他们这样做了。如果您不信任他们,那么最好的做法是先获取然后合并/变基,而不是拉取,以便您可以注意到“强制更新”消息并非常谨慎地进行操作。

本地恢复

如何恢复的详细信息因情况而异;我建议查阅前面提到的手册页。很难确定您需要做什么 - 这取决于C是否被重写为L(也许D被重写为M),或者它们是全新的提交,以及您想要变基还是合并。如果您只想变基,那么将XYZ变基到O就足够了。如果您想要合并,您需要将XYZ变基到B或M,然后合并O。


关于您发布的合并结果图,合并基应该是A-B而不仅仅是A,对吗? - clickMe

3
你应该执行以下操作(如“有人将rebase或reset推送到已发布的分支后,我该如何恢复/重新同步?”中所述):
git checkout yourBranch
git branch old-upstreamBranch D # BEFORE fetching!

           E--F (origin/upstreamBranch)
          /
A--B--C--D (old-upstreamBranch)
          \
           X--Y--Z (yourBranch)


git fetch


     L--M--N--O (origin/upstreamBranch)
    /
A--B--C--D (old-upstreamBranch)
          \
           X--Y--Z (yourBranch)

此时,你不能简单地将你的分支在重写的upstreamBranch之上进行变基:这会引入C和D,而当重写upstreamBranch时它们明确被留下。

你只需要将yourBranch变基,而不是它之前的提交。
这就是为什么在获取之前要放置一个old-upstreamBranch标签的原因:

git rebase --onto origin/upstreamBranch old-upstreamBranch yourBranch

                X--Y--Z (yourBranch)
               /
     L--M--N--O (origin/upstreamBranch)
    /
A--B--C--D (old-upstreamBranch)



git branch -D old-upstreamBranch 

                X--Y--Z (yourBranch)
               /
     L--M--N--O (origin/upstreamBranch)
    /
A--B

但是:有了Git 2.0,你只需要做的就是

fork_point=$(git merge-base --fork-point origin/upstreamBranch yourBranch)
# return D
git rebase --onto origin/upstreamBranch $fork_point yourBranch

没有必要在拉取之前创建一个分支!
如果你在执行了git fetch后才意识到问题,你仍然可以将你的分支很好地变基在重写后的上游分支之上。
自从提交ad8261dJohn Keeping (johnkeeping), git rebase 可以使用相同的新的 --fork-point 选项。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接