如何在没有与冲突相关的错误的情况下修改线性历史记录中的快照(提交)?

时间:2014-09-29 18:03:39

标签: git rebase

在交互式rebase期间,可能会出现如下错误消息:

% git rebase --continue
error: could not apply be3679b... shrink the kids

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply be3679bd31a31bd3869e17a66dc0b1b908282d94... shrink the kids

动词“apply”(在could not apply be3679b...中)明确暗示git将提交视为补丁。这与git记录"snapshots, not differences"的常见断言完全矛盾(因为补丁只是两个状态之间的差异)。

例如,假设我在master上有以下历史记录:

                       HEAD
...---A-----B-----C-----D
                      master

...我运行git rebase -i A。假设我选择编辑B,没有别的。我发现即使我对B所做的更改不涉及删除或添加文件,我仍然会收到上述类型的错误(“无法应用......”)。

为了说明为什么这些错误对我来说没有任何意义,这里有一个更费力,但(至关重要的)无冲突的方式来产生我试图通过上面的交互式rebase实现的结果:

% git branch grmbl A
% git checkout grmbl
% git checkout B -- .
% # modify files in working directory to my heart's content
% git commit -a -m foo
% git checkout C -- .
% git commit -m bar
% git checkout D -- .
% git commit -m baz

(为了使它等同于原始git rebase -i,我们假设我使用的提交消息是BC和{{1}的原始提交消息}。)

此时D将没有显示任何差异。这也是所希望的,因为我想要做的就是沿着一系列快照更改一个快照,而保持所有其他快照不变。

关键点在于上面的序列执行我想要的 git diff grmbl master报告任何冲突解决错误。我只是检查该提交的内容(使用git)而不是“应用”提交(就好像它是 patch )。这不产生冲突的事实使得git checkout <commit> -- .在看似等效的交互式rebase操作期间报告的错误更难以理解。

是否有一种不那么费力但仍无冲突的方式来实现上述序列所取得的效果?

2 个答案:

答案 0 :(得分:1)

正如Jubobs所说,git以快照格式存储历史记录。但是,当您使用git rebase命令重新创建提交时,它使用diff / apply重新创建提交。

如果你真的想做你提出的建议,这很简单,但要求你自己编写脚本来替换rebase。

所以当前发生的事情(简化)是rebase在其待办事项列表中生成ORIG_SHA列表,然后为每个SHA调用git diff SHA^1,并尝试应用该差异。你不希望这种行为。你想要的是简单地用ORIG_SHA ^ {tree}调用提交树。您可以通过手动创建提交,或者执行原始树的git read-tree并丢弃现有索引来完成此操作。

答案 1 :(得分:1)

  

动词&#34;适用&#34; (在could not apply be3679b...中)明确暗示git将提交视为补丁。这与git记录"snapshots, not differences"的经常断言完全矛盾(因为补丁只是两个状态之间的差异)。

我同意这句话

could not apply <commit>

具有误导性。您应用的实际上是一个补丁,它对应于<commit>与其父级之间的差异(或者更常见的是,其父级的一个)。因此,以下信息会更准确,也许更有意义:

could not apply patch from <commit-A> to <commit-B>

然而,它只是滥用语言,随着你习惯它,它变得无害。在英语中,你可以说像

这样的东西
  鲍勃独自喝了一整瓶葡萄酒!

并且每个人都会承诺,您要报告 Bob 的葡萄酒消费情况,而不是声称他自己摄取了容器(瓶子)。

你还没被骗过; Git 确实记录了快照/修订/提交/状态。差异/补丁仅在需要时从提交中生成;例如,使用git diffgit cherry-pick时。


  

[...]这些错误对我来说毫无意义[...]   事实上,这不会产生冲突,这使得git在看似等效的交互式rebase操作期间报告的错误更加难以理解。

为了解释为什么交互式rebase方法会引发冲突,而你的方法却没有,让我们考虑一个简单的例子:让我们假设你的repo中唯一被跟踪的文件是一个名为的文件README,历史记录如下。

enter image description here

(提交上方/下方的纸张表示相关提交中记录的README文件的内容。)

您的无冲突方法

如果你跑

git branch grmbl A
git checkout grmbl
git checkout B -- .
# replace 'foo' by 'FOO'
printf "FOO\n" > README
git commit -a -m foo
git checkout C -- .
git commit -m bar
git checkout D -- .
git commit -m baz

你最终会得到这个:

enter image description here

请注意,B中记录的版本在运行时在工作树中检出

git checkout C -- .

只会被commit C中记录的版本覆盖。这里没有贴片可以应用到任何地方;只涉及破坏性的结账;因此,没有冲突。

另外,正如您所说,在这种情况下,

git diff grmbl master

确实不会返回任何内容,因为那些分支mastergrmbl指向提交DD'的提交(READMEgit rebase -i A )。{{1文件。

Interactive-rebase方法

运行

时会发生什么
B'

非常不同,因为它 涉及应用补丁。到目前为止,一切都很艰难,

enter image description here

即。在手动更换&#34; foo&#34;之后by&#34; FOO&#34;并创建提交C

然而,在此之后,事情就会出现问题:交互式rebase尝试在提交B -> C之上挑选提交B'(换句话说,应用修补程序bar),但它找不到&#34;锚&#34;。更具体地说,它尝试在foo行之后添加README行,但是,当然,后者无处可寻,因为在此阶段,FOO行的签出版本{ {1}}文件只包含{{1}}行。因此,出现了冲突:

enter image description here


因此...

  

如何修改线性历史记录中的快照(提交)而没有与冲突相关的错误?

好吧,尽管交互式反馈可能引起冲突,但它更可取,因为它更加强大和自动化。您的方法可能没有冲突,但也很繁琐(所有那些手动检查!)并且容易出错。