0

我想将我的分支(让我们称之为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'

谁能向我解释问题是什么?我知道我不能在不压缩的情况下重新设置基准,因为每个提交都需要被解决..

4

1 回答 1

7

你说:

在变基之前,我想压缩我的提交(11 次提交)

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

所以我做了这样的事情......

说“我做了类似<填空>之类的事情”总是很可疑,因为那时我们不知道你实际做了什么,这使得很难猜测到底发生了什么。:-) 幸运的是,这些都很有意义:

# On master
git pull

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

(这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 次提交期间的某个地方。它似乎发生在第一个:

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

这个早期合并冲突的好处是它比后来的合并冲突更容易解释,尽管此时您可以对它的事情与其他任何地方一样。

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

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

git rebase --abort

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

Rebase 通过复制提交来工作

使用 Git 时,您需要牢记“提交图”,即使只是作为一种背景、阴暗、不祥的隐现事物 :-) 大多数时候。实际上,提交图虽然可能令人生畏,但它是帮助的。它通常非常大并且“隐约可见”。(也许可以把它想象成一只非常大但友好的狗。)

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

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

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

(较早的提交在左侧,分支名称指向每个分支的提示提交。最后一点是 Git 的实际工作方式:分支名称指向提示提交,每个提交向后指向其较早的提交。内部的向后箭头有点让人分心和烦人,而且很难用 ASCII 画出来,所以我只是把它们画成线。)

请注意,A通过K是 11 次提交,并且L是提示提交到哪些origin/master点(在您的git fetches 使您的 Git 更新到 Git 存储库后origin)。我只是随机猜测要吸引多少普通无聊o的提交,但这并不重要。

当你运行时git rebase origin/master(有或没有-i),Git 会尝试复制你的所有 11 个提交,副本在“提交之后” 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,然后制作C'B' + C,依此类推。这里的关键是 Git仍在一步一步地做所有事情。)

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

如果只有一种方法可以压缩所有 11 个提交,同时它们附加到我标记的提交上*。也就是说,不是一次复制每个提交,而是将它们添加到 中L,如果我们可以让 Git 只创建一个AK代表 总和的新提交A+B+C+...+J+K呢?我们可能会画出这样的漂亮结果:

...--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,但我们只需要解决一次问题)。

变基:选择你的目标

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

我们已经基于*,但是如果我们“重新定位”到现在的位置,我们可以轻松地压缩所有内容:不会有冲突,并且压缩会正常工作。我们只需要以*某种方式命名 commit。

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

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

和 squash 像以前一样,Git 会做你想做的事:在变基之前 squash。(嗯,不完全是“之前”,而是在大变基之前作为一个更小的,squash-y rebase。)

另一种查找提交 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指向 commit *),但将文件保留在工作树和索引中,就像它们在 commit 中一样K,然后使用当前索引进行新的提交。所以这会产生一个提交,其与 的 匹配K,但其父级是 commit *。(你必须编写一个全新的提交消息,而压缩让你可以编辑来自原始 11 个提交的消息。)

或者,只是解决冲突

无论如何,您最终都希望将新AK提交或原始A--B--...--J--K链重新设置为 commit L。这将需要解决合并冲突。

这样做的好处AK是您只需解决一次,而不是每次冲突提交一次。缺点是每次解决一个较小的冲突时可能会更清楚该怎么做:可能A有一个很明显的冲突,J有一个不同但也很明显的冲突,K还有一个相似A但仍然很明显的冲突......但是当您一次重新组合所有组合,和AK之间的冲突,加上 的分心,很难看出如何解决它。AKJ

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

最后的一些想法

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

每个分支都有一个 reflogHEAD和一个。reflogmy_branch更容易用于从变基中恢复。

于 2016-10-25T00:45:19.947 回答