我想合并我的分支(让我们称之为my_branch
)来掌握。它有一个文件在我的github.com上的pull请求中显示为合并冲突。在重新定位之前我想要压缩我的提交(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'
任何人都可以向我解释这是什么问题吗?我知道我不能因为挤压而无法改变,因为每一次提交都需要解决..
答案 0 :(得分:5)
你说:
在变基之前我想压缩我的提交(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
的说明,但所有的南瓜都会在 rebase期间发生,也就是说,在重新定位到之前你不会压缩来自origin/master
的新提示。
鉴于即将发生的冲突,它将在您复制的11次提交期间的某个地方发生。它似乎恰好发生在第一个:
当我保存并退出时,我收到错误 -
error: could not apply e7ce468... 'commit1 message'
这个早期合并冲突的好处是它比后来的合并冲突更容易解释,尽管此时你可以做的事情在这里与其他任何地方。
该错误告诉您Git无法完成第一次提交的副本。
您可以停止正在进行的rebase,使用以下内容将所有内容恢复原状:
git rebase --abort
继续阅读以决定是否要这样做并尝试不同的方法。
使用Git时,您需要保留"提交图表"记住,如果只是作为一种背景,阴暗,不祥的迫在眉睫的东西:-)大部分时间。实际上,提交图虽然可能令人生畏,但意味着有用。它经常非常大而且很常见。" 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个字符的哈希值。圆形o
节点代表了我不需要说些什么的更多提交,因此它们很无聊。 *
是一个提交,其ID我不知道,并且没有任何名称,但它的非常有趣,所以我给了它是明星。
(较旧的提交位于左侧,分支名称指向每个分支的 tip 提交。最后一点是Git如何工作:分支名称指向提交提交,每个提交向后指向它早期的父提交。内部向后箭头有点令人分心和烦人,很难用ASCII绘制,所以我只是将它们画成线条。)
请注意,A
到K
是11个提交,而L
是origin/master
点提示(在git fetch
es带来你的Git之后)最新的origin
上的Git存储库。我只是随机猜测有多少普通的无聊o
提交,但这并不重要。
当您运行git rebase origin/master
(有或没有-i
)时,Git将尝试复制所有11次提交,副本将会出现&#34;后&#34;提交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]
(当你将其设为交互式rebase并使用squash
时,Git会简单地修改复制过程,使其首先像往常一样A'
,然后从{{B'
中取出A' + B
1}}并链接到L
,然后从C'
中取出B' + C
,依此类推。这里的关键是Git一次只做一步。)< / p>
这里的问题是每个步骤都可能导致合并冲突。我们不清楚每个步骤是否 - 我们知道第一个步骤是什么,但我们对其余步骤知之甚少。
如果只有一种方法可以压缩所有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
,重新加入*
。也就是说,将链从另一条链上的链条复制到现在的位置。
我们已基于*
,但如果我们&#34; rebase&#34;在我们现在的位置,我们可以轻松地压制一切:没有冲突,南瓜就会起作用。我们只需要以某种方式命名提交*
。
如何找到提交*
?一种方法是使用git log
,它会显示所有提交ID。剪切并粘贴您关注的提交的ID:
$ git rebase -i <id-of-commit-*>
像以前一样压制壁纸,Git会做你想做的事情:压扁之前压扁。 (嗯,不完全是&#34;之前&#34;,但作为一个更小的,压扁的基础,在大基础之前。)
查找提交*
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
指向提交*
),但将文件保留在工作树和索引中因为它们在提交K
中,然后使用当前索引进行新提交。因此,这会使 tree 与K
的提交匹配,但其父提交为*
。 (您必须编写一个全新的提交消息,而压缩则允许您编辑原始11提交的消息。)
无论如何,您最终都希望将新的AK
提交或原始A--B--...--J--K
链重新绑定到提交L
。这将需要解决合并冲突。
使用AK
执行此操作的优点是,您只需解决一次,而不是每次冲突提交一次。缺点是,在一次解决一个较小的冲突时,可能更清楚该怎么做:A
可能存在明显的冲突,而J
有一个不同且也很明显的,K
有一个与A
类似的但仍然很明显......但是当你将AK
组合在一起时曾经,A
和K
之间的冲突加上J
的分心,使得很难看到如何解决它。
因此,首先挤压的好处是可能发生冲突的提交次数减少。首先压缩的负数是可能冲突的提交次数减少。这取决于你尝试哪一个。
无论你做什么,请记住Git始终通过添加新提交来工作。旧的,即使他们已经放弃&#34;,仍在您的存储库中。默认情况下,它们会至少保留30天,并且他们的ID会保存在Git&#34; reflogs&#34;中。如果您想要恢复旧提交,只需使用保存的ID创建新的分支或标记名称。
HEAD
有一个reflog,每个分支有一个。 my_branch
reflog更容易用于从rebase中恢复。