Git :有些提交不是我创建的。如何摆脱那些

Git : there are commits I did not create. How to get rid of those

我总是执行 git pull --rebase 即使我没有一个待处理的提交。

我刚刚拉了一下。

突然,当我 运行 git 状态时,我看到我的本地主机比远程主机提前 11 次提交。

我不知道他们从哪里来,想摆脱他们。

我该怎么办?

I dont know where they came from

当您在远程 master 上变基分支时,您正在使用该远程分支并在远程分支之上重播所有本地提交。因此,本地分支领先于远程是完全正常的状态。

and want to get rid of them

这些提交很可能是您自己的工作。不要摆脱它们!

这是一张显示变基如何工作的图表,因此您可以了解您的案例中可能发生的情况。考虑到您的本地 masterorigin/master 都从同一个提交开始:

origin: A
master: A

现在,你说 3 次提交,而同时其他人向远程进行 3 次提交:

origin: A -- E -- F -- G
master: A -- B -- F -- D

如果您现在执行 git pull --rebase origin master 您将得到以下内容:

origin: A -- E -- F -- G
master: A -- E -- F -- G -- B' -- F' -- D'
                            ^    ^^     ^^ these are your commits

换句话说,在 rebase 之后,您的本地分支将看起来就像您刚刚克隆了远程分支并在其上重新提交了所有工作一样。在示例中,变基使您的本地分支比远程分支提前 3 次提交。

使用 git pull --rebase 你可以从其他人推送的远程分支(master)获得提交。但是,如果您有未推送到远程主机的提交,那么 git 的状态将表示您的本地分支(主机)在这些提交中领先于远程主机。

很明显,您是通常所说的 "upstream rebase" 的受害者。您可以直接跳到下面的解决方案,但这个背景非常重要。

背景

这就是我讨厌 git pull 的原因。这不是 git pull 错误 ,真的,但是因为 git pull 是一个 便利 命令,它只是 运行s git fetch 后跟第二个命令,它最终对新 Git 用户隐藏了两个基础 Git 命令。事实上,有两个 不同的 第二个命令,它们都可以以不同的方式搞砸事情 ,这是非常无益的。

幸运的是,第一部分 - git fetch 步骤 - 几乎总是有效。1 这是第二步,如果一切顺利,每个人都说 "oh that was easy" 并认为 git pull 很棒,然后当它爆炸时,就像它最终总是那样,他们有麻烦了,因为他们从来没有处理过失败的第二部分。现在是紧急情况,他们不得不花费大量时间学习一堆复杂的东西:从故障中恢复的方法,以及合并和变基如何与 git fetch 步骤交互。

不管怎样,这次是你被困在了混乱的局面中,所以让我们继续努力吧。 git pull 所做的后半部分是 git mergegit rebase。它在什么时候执行的命令超出了这个答案的范围,除了我们可以肯定地说如果你添加 --rebase,它 总是 使用 git rebase。所以你只有一个复杂的 Git 命令可以快速学习。 (耶?:-))

因此,作为 ,这些新的神秘提交来自使用 git rebase。所以现在是时候学习关于变基的所有2 它如何与git fetch[=236=交互].


1它仍然有几种可能出错的方式,但通常实际发生的是 "the network is down and thus we can't get anything from origin",这非常明显并且使一切都停止在刚开始,等网络修好了再试。

2好吧,我们不要那么。 :-)


当 fetch-and-rebase 顺利进行时

Rebase 可能很复杂,但我们可以非常简单地总结一下:git rebase 复制提交。

就是这样。 Rebase 复制提交。这实际上一点也不坏,是吗?好吧,当它们是 你的 提交时还不错,但你只是让它复制了 其他人的提交 。这里棘手的部分是精确定义 哪个 提交被复制,何时以及如何复制。这是 git fetch 再次出现的地方。

Tim 在此处的绘图涵盖了更正常的情况,即 已做出一些提交。不过我打算自己画。假设有一堆提交,当您开始时以一系列 F--G--H 结尾。也就是说,您的 masterorigin/master 都指向提交 H,后者又指向 G,后者又指向 F,依此类推。当你最初的 git clone 或最后的 git pull --rebase 或其他任何东西时,你就得到了所有这些:你如何得到它们并不重要,重要的是你确实拥有它们。

...--F--G--H      <-- master, origin/master

现在,通常情况下,您可以添加自己的提交,比如 IJ:

             I--J   <-- master
            /
...--F--G--H        <-- origin/master

与此同时,其他人使用他们自己的 Git 添加了他们自己的 不同的 提交。你现在没有它们。然后他们发布这些提交,所以现在 origin 也有它们。你还没有!但是 now 你 运行 git fetchnow 你明白了,这就是 你的 存储库。假设他们只进行了一次提交,我们将其标记为 K:

             I--J   <-- master
            /
...--F--G--H
            \
             K      <-- origin/master

请注意标签 origin/master 如何从 HK 有 "moved forward"。与此同时,你自己的 master 已经从 H 向前移动到 J:你的指向你的提交 J,它指向你的提交 I,它指向回到 H.

这是您通常引入 git rebase 的地方。您要求它将您的提交 IJ 复制到 "just as good" 但基于 提交 K:

             I--J   <-- master
            /
...--F--G--H   I'-J'
            \ /
             K      <-- origin/master

假设复制进行顺利,没有合并冲突等问题。您现在有新的提交 就像 您的原始提交,除了他们的新 "base" 提交 K。那是变基!

你的 Git 现在所要做的就是将你的 master 标签从旧的 J 上剥离下来,并使其指向新复制的 J':

             I--J   [abandoned]
            /
...--F--G--H    I'-J'   <-- master
             \ /
              K      <-- origin/master

现在正式放弃了旧的提交,我们可以理顺整个画面:

                I'-J'   <-- master
               /
...--F--G--H--K         <-- origin/master

当变基不顺利时

git rebase 有多种方式出错。让我们忽略其中的一些,只关注那些咬你的。

这次让我们画出更多F--G--H之前的内容。另外,你没有自己的提交(你说的,我相信你)。

A--B--C--D--E--F--G--H   <-- master, origin/master

现在,其他人在 "rebase" 的上游做了一些事情,即 复制 一些提交。我不知道 确切地 他们做了什么,但假设他们发现提交 DD 有效的,甚至可能被删除它完全。他们现在有:

        D--E--F--G--H   [abandoned]
       /
A--B--C
       \
        E'-F'-G'-H'     <-- master

现在你 运行 你的 git fetch。这样做的目的是拿起他们的 master 并强行调整您的 origin/master 以匹配他们的。所以 仍然像往常一样有 A-...-H,你的 master 指向那个,但现在你有 origin/master 指向 H'

        D--E--F--G--H   <-- master
       /
A--B--C
       \
        E'-F'-G'-H'     <-- origin/master

这是您的 git fetch 步骤。现在你的git pull 运行s git rebase。 rebase 所做或试图做的是 copy 提交。它应该复制哪些提交?这里的答案可能会变得复杂,但为了简化事情很多,它会认为,在这一点上,D--E--F--G--H你的提交。这些是明显的副本候选者:它们是 您的 主控上的提交,而不是 origin/master.

上的提交

重要的是 E'-F'-G'-H' 本身就是副本。但是让我们忽略它。无论您的Git是否将它们检测为副本,您的Git至少恢复提交D,认为这是您的工作,而不是被剥离的东西。在任何情况下,因为您看到 11 次提交,所以您的 Git 已经以某种方式恢复或复活或复制了 11 次提交。让我们画出所有 D-through-H 因为我们的解决方案无论哪种方式都是一样的。

你的 git rebase 复制 "your" 提交(不是你的,它只是认为它们是你的)并移动你的 master 标签,你现在有这个:

        D--E--F--G--H   [abandoned]
       /
A--B--C             D'-E"-F"-G"-H"   <-- master
       \           /
        E'-F'-G'-H'     origin/master

突然你有五个提交 "ahead of" origin/master.

正在修复

在这种情况下,因为你确定你真的没有自己的提交,你需要做的是告诉您 Git 完全放弃所有这些新副本。

为您完成这一切的命令是 git reset --hardgit reset --hard的一点是确实让你丢掉一堆自己的作品,所以用的时候一定要小心。

这种特殊情况下,但是,您承诺您在这里没有自己的提交。记住,这只是这个特殊情况:没有你自己的提交,工作树中没有未提交的工作,等等。所以擦掉东西是安全的。

所以,在这种情况下你想做的是告诉你的Git移动你的master指向origin/master 相同 提交,放弃你的 git rebase 制作的所有副本:

        D--E--F--G--H   [abandoned]
       /
A--B--C             D'-E"-F"-G"-H"   [abandoned]
       \           /
        E'-F'-G'-H'     <-- master, origin/master

如果我们现在停止绘制被放弃的提交,您会看到您的图表现在只是:

A--B--C--E'-F'-G'-H'   <-- master, origin/master

这就是你想要的:你不再是 "ahead of" origin/master

如果您有自己的提交

假设,在所有这些混乱中,您确实确实 有一些提交要保留。在这种情况下,有很多方法可以解决这个问题,但最简单的方法可能是使用 git rebase -i(交互式变基:复制 selected 提交)。您可以 git rebase -i 将 Git 认为 属于您的 11 个提交放入一组指令中。然后,您从指令 sheet 中删除除真正 之外的所有指令都是 您的指令,并且您的 Git 再次复制这些指令,并放弃未复制的一个。

既然你保证没有这样的提交,我们就可以使用更简单的 git reset --hard。 (根据您的 Git 版本,我们可能 必须 使用 reset。Git 的所有版本都拒绝让您删除 指令 sheet 中的所有内容 。较新的版本允许您放入 "no-op" 指令,这样即使没有复制指令,也至少还剩下一条指令。)