Git rebase 交互错误

Git rebase interactive error

我想将我的分支(我们称之为 my_branch)合并到 master。它有 1 个文件显示为我在 github.com 上的拉取请求的合并冲突。在变基之前,我想压缩我的提交(11 次提交)。所以我做了这样的事情:

# On master
git pull
git checkout my_branch
# on my_branch
git fetch
git rebase -i origin/master

这打开了一个包含我所有提交的 vim 编辑器 - 我将第一个保留为 pick 并将其余的更改为 s (squash)

pick commit1
s commit2
s commit3
.
.
.
s commit 11

当我保存并退出时出现错误 - error: could not apply e7ce468... 'commit1 message'

谁能给我解释一下这是什么问题?我知道我不能在没有压缩的情况下变基,因为每次提交都需要解决..

你说:

Before rebasing I want to squash my commits (11 commits)

(强调我的)。但是很快,你 运行 一个 git rebase 将在 变基期间或之后进行压缩 ,这将是一个问题。我们稍后再谈。

So I did something like this ...

说 "I did something like <fill in the blank>" 总是很可疑,因为那样我们就不知道你 实际上 做了什么,这使得很难猜测到底发生了什么。 :-) 幸运的是,这些都很有意义:

# On master
git pull

这在技术上还不是必需的,但让我们介绍一下它的作用。 git pull 命令只是 git fetch 后跟第二个 Git 命令的便捷快捷方式。第二个 Git 命令默认为 git merge,但您可以将其配置为 git rebase。现在让我们假设第二个命令只是 git merge and/or 它对我们没有做任何特别重要的事情,这可能是真的。

git fetch 部分总是安全的,没有伤害到任何东西。)

git checkout my_branch
# on my_branch
git fetch
git rebase -i origin/master

除非上游变化很快,否则这第二个 git fetch——第一个在 git pull——是不必要的,但一如既往,额外的抓取是无害的,可能是一个好习惯。问题在于 git rebase -i origin/master,它开始变基。然后,您编辑了执行 squash 的说明,但所有挤压都将在 期间 变基期间发生,即,您不会在 之前 挤压] 从 origin/master.

重新定位到新的提示提交

鉴于即将发生冲突,它将在您复制的 11 次提交期间的某个地方发生。它似乎发生在第一个:

When I save and quit I get an error - error: could not apply e7ce468... 'commit1 message'

这个早期合并冲突的好处是它比后来的合并冲突更容易解释,尽管在这一点上你可以关于它的事情在这里是一样的和其他地方一样。

错误告诉您 Git 无法 完成 第一次提交的副本。

您可以停止正在进行的变基,将一切恢复到开始之前的状态,使用:

git rebase --abort

继续阅读以决定是否要这样做并尝试不同的方法。

Rebase 通过复制提交来工作

在使用 Git 时,您需要牢记 "commit graph",即使大多数时候只是作为一种背景、阴暗、不祥的若隐若现的东西 :-)。实际上,提交图虽然可能令人生畏,但意味着会有帮助。它通常非常大并且 "loom-ish"。 (也许可以把它想象成一只非常大但友好的狗。)

提交图是我们可以绘制的,如果我们这样做,它看起来像这样:

...--o--o--*--o--o--o--o--L        <-- origin/master
           |
           |
            \
             A--B--C--D--E--F--G--H--I--J--K   <-- my_branch

这些单字母名称中的每一个都是实际提交 ID 的缩写(像 7c56b20857837de401f79db236651a1bd886fbbb 这样丑陋的 40 字符散列之一)。 round o 节点代表更多的提交,我不需要说什么,所以它们很无聊。 * 是一个我不知道其 ID 的提交,也没有任何一个的名称,但它 非常有趣 ,所以我给了它一颗星。

(较旧的提交在左侧,b运行ch 名称指向每个 b运行ch 的 tip 提交。最后bit 是 Git 真正工作的方式:b运行ch names points to tip commits, and each commit points backwards, to its earlier parent commit. 内部向后箭头有点让人分心和烦人,而且很难用 ASCII 来画,所以我只是把它们画成线条。)

请注意,AK 是 11 次提交,Lorigin/master 指向的尖端提交(在你的 git fetches 带来之后您的 Git 与 origin 上的 Git 存储库保持同步)。我只是猜测 运行domly 有多少普通无聊的 o 提交,但这并不重要。

当你运行git rebase origin/master(带或不带-i)时,Git将尝试复制所有您的 11 次提交,副本 "after" 提交 L。也就是说,它想要将图形更改为如下所示:

...--o--o--*--o--o--o--o--L        <-- origin/master
           |               \
           |                A'-B'-C'-...-J'-K'   <-- my_branch
            \
             A--B--C--D--E--F--G--H--I--J--K   [abandoned]

(当您将其设为交互式变基并使用 squash 时,Git 只需修改复制过程,使其首先像往常一样制作 A',然后制作 B'A' + B 链接到 L,然后从 B' + C 链接到 C',依此类推。这里的关键是 Git 仍然是一次做所有事情。)

这里的问题是每一步都可能导致合并冲突。目前尚不清楚每一步是否 — 我们知道第一步会,但我们对其余步骤知之甚少。

如果有一种方法可以压缩所有 11 个提交,同时 将它们附加到我标记为 * 的提交上。也就是说,不是一次复制每个提交,而是将它们添加到 L,如果我们可以让 Git 只创建一个新的提交 AK 来表示 AK 的总和呢? =55=]?我们可能会 绘制 这个不错的结果,如下所示:

...--o--o--*--o--o--o--o--L        <-- origin/master
           |\
           | AK   <-- my_branch
            \
             A--B--C--D--E--F--G--H--I--J--K   [abandoned]

现在我们可以对 AK 做一个简单的变基(这仍然会与 L 冲突,但我们只需要解决一次问题)。

Rebase:选择你的目标

嗯,事实证明这真的很容易。我们需要的是像以前一样运行git rebase -i,但是告诉它:不要变基到L,变基到*。也就是说,将链从它离开另一条链的地方复制到它现在所在的位置。

我们已经基于*,但如果我们"rebase"到现在的位置,我们可以轻松地压缩一切:会有没有冲突,南瓜就可以了。我们只需要以某种方式命名提交 *

如何找到提交 *?一种方法是使用 git log,它会显示所有提交 ID。剪切并粘贴您关心的提交的 ID:

$ git rebase -i <id-of-commit-*>

和之前一样压扁,Git 会做你想做的事:在变基之前压扁。 (好吧,不完全是 "before",而是在 big 变基之前的更小的 squash-y 变基。)

另一种查找提交 ID * 的方法是使用 git merge-base:

$ git merge-base my-branch origin/master

这会打印出 ID,准备好进行剪切和粘贴(或者您可以使用 shell 语法将 ID 直接插入到命令中,或者设置一个变量,等等)。

也有捷径

尽管忽略这个快捷方式,但您可以:

$ git checkout my_branch
$ git reset --soft <id-of-commit-*>
$ git commit

这样做是放弃 A--B--...--J--K 链(使 my_branch 指向提交 *),但 将文件保留在工作树中并且index 因为它们在提交 K 中,然后使用当前索引进行新的提交。因此,这会产生一个提交,其 treeK 匹配,但其父项是提交 *。 (您必须编写一个全新的提交消息,而压缩让您可以编辑来自原始 11 次提交的消息。)

或者,直接解决冲突

无论如何,您最终会想要将新的 AK 提交或原始 A--B--...--J--K 链变基到提交 L 上。这将需要解决合并冲突。

使用 AK 的好处是您只需解决一次,而不是每次冲突提交都解决一次。缺点是在一次解决一个较小的冲突时可能更清楚该怎么做:也许A有一个明显的冲突,而J有一个这是不同的,也很明显,K 有一个类似于 A 但仍然很明显的......但是当你一次重新设置组合的 AK 时,[=35= 之间的冲突] 和 K,加上 J 的干扰,让人很难看清如何解决。

因此,先压缩的好处是可能会发生冲突的提交更少。首先压缩的缺点是可能冲突的提交更少。由你决定尝试哪一个。

一些最后的想法

无论您做什么,请记住 Git 总是通过 添加新提交 来工作。旧的,即使它们是 "abandoned",仍在您的存储库中。默认情况下,它们至少会停留 30 天,并且它们的 ID 保存在 Git 的 "reflogs" 中。如果你想取回旧的提交,你可以简单地使用保存的 ID 创建一个新的 b运行ch 或标签名称。

HEAD 有一个 reflog,每个 b运行ch 有一个。 my_branch reflog 更容易用于从 rebases 中恢复。