解决am会话中的冲突

7

我想从一个仓库中挑选多个提交到另一个仓库。我按照这篇Stack Overflow帖子提供的说明进行操作:

/path/to/2 $ git --git-dir=/path/to/1/.git format-patch --stdout sha1^..sha1 | git am -3

接下来出现了一个冲突:

Applying: commit-name-xxx
fatal: sha1 information is lacking or useless (path/to/conflicted/file).
error: could not build fake ancestor
Patch failed at 0001 commit-name-xxx
The copy of the patch that failed is found in: .git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

我尝试通过运行以下命令解决冲突:

 git mergetool --tool=DiffMerge

但是回应是:
No files need merging

我也运行了git status,但响应是:

On branch develop
You are in the middle of an am session.
  (fix conflicts and then run "git am --continue")
  (use "git am --skip" to skip this patch)
  (use "git am --abort" to restore the original branch)

nothing to commit, working directory clean

我不确定这里发生了什么:我的第一条命令告诉我有冲突,它已经被移动到.git/rebase-apply/patch,但是无论是git mergetool还是git status都找不到这个冲突。


“git-am”似乎使用的实现方式与cherry-picking非常不同,而且根据我的经验,它的效果要差得多。难道真的不可能从原始存储库中获取并使用常规的cherry-pick吗? - max630
2个回答

5

总结

你的 Git 没有足够的信息将补丁转换为三方合并。如果你的索引中没有不完整的三方合并,那么你的 git mergetool 就无法处理任何东西。你可能需要手动应用这个补丁。

(在此,索引 也称为 暂存区缓存,它用于解决三方合并期间的冲突。在不涉及三方合并的情况下,索引仅存储你要构建下一个 git commit 的文件。在冲突合并期间,索引存储 更多 的文件。)

如果你从一个仓库(编号为 1 的仓库)中 git fetch 到另一个仓库(编号为 2 的仓库),你可以尝试使用 git cherry-pick 提取相关提交,就像 max630 在评论中提出的建议。也就是说,在仓库 2 中,你可以将仓库 1 添加为一个 远程仓库:

git remote add <name> <path-to-repo-1>

然后像从任何其他远程仓库一样,您可以使用git fetch来获取它,或者您可以使用旧的(Git 1.5风格)git fetch <path>语法暂时从repo-1获取所有可达到的对象,然后通过哈希ID挑选。

如果这仍然无法工作(但它会),或者因为某些其他原因不方便,您将不得不手动应用补丁。考虑使用git apply --reject,然后进行手动清理。

长文

这个错误信息告诉我们 - 好吧,告诉我 - 发生了什么:

fatal: sha1 information is lacking or useless (path/to/conflicted/file).

您正在使用git format-patchgit am将一个补丁1从一个Git仓库传输到另一个仓库,就像人们更典型地使用(或过去使用)git format-patch在没有其他网络连接的站点之间通过电子邮件发送补丁一样。当Git制作这样的补丁时,它在每个文件补丁上方包括一个index行:

diff --git a/Documentation/RelNotes/2.17.0.txt b/Documentation/RelNotes/2.17.0.txt
index 7001dbbf8..c828d3734 100644

这个索引行可能提供Git所需的信息来构建一个完整的三方合并(如果可能的话)。在格式化补丁选项中添加--full-index会使index行变长:
index 7001dbbf88b7ea5822eb0b798ac983505c57b3dc..c828d37345224550540a1665aaed2566d5bcb40e 100644

现在这两个哈希值要复杂得多;在某些情况下,这可能会有所帮助。但是它们到底是什么呢?
这两个哈希 ID 是存储在仓库中的文件的“blob 哈希 ID”——即“之前”和“之后”文件的实际内容。接下来的差异块提供了说明:如果您使用这些替换行更改原始 blob(文件)中的这些行,则将左侧哈希表示的原始 blob(内容)转换为右侧哈希所表示的新 blob(内容)。
当您将此 diff 提交给 `git apply` 时,可能会出现 `HEAD` 中的文件不再匹配,并且在某些部分甚至与补丁中的“原始 blob”几乎没有相似之处的情况。 在这种情况下,上下文行将无法对齐,或者“before”部分将不会出现在任何位置。直接应用补丁变得不可能。
如果您向 `git apply` 提供了 `--3way` 或 `-3` 标志 - 并且 `git am` 也会这样做 - Git 现在可以使用 `index` 行中的信息。由于第一个哈希是产生变更集的存储库中实际文件内容的 blob 哈希,因此您自己的 Git 可以查看您自己的存储库,以查看是否有具有该哈希 ID 的 blob。 如果是这样,那么您已经拥有了原始文件。Git 可以提取该文件并进行修补,以生成“补丁后”版本。
现在 Git 有了文件的全部三个版本:通过“之前”哈希 ID 获得的基本版本,并在您的存储库中找到;通过将补丁应用于基本版本获得的“theirs”版本;以及当前或 `HEAD` 提交中的文件的“ours”版本。因此,Git 现在可以将所有三个版本插入您的索引中,从而使三向合并成为可能。
另一方面,`index` 行中的 blob 哈希 ID 可能与您的存储库中的任何对象都不匹配。 在这种情况下,您没有文件的“before”版本。无法进行三向合并。或者,虽然很少见,但也有可能缩短的 blob 哈希与存储库中的多个 blob 相匹配。在这种情况下,您可能拥有文件的“before”版本,但 Git 不确定,因此不会尝试识别这些 blob 中的任何一个是否正确。
无论如何,由于你的 Git 没有足够的信息尝试三方合并,它就不会尝试,让你处于这种情况。使用 git fetchgit cherry-pick,你最终可以获得真正的三方合并。这些历史甚至不需要相关,因为 cherry-pick 强制合并基础是被挑选提交的父提交。
1这也适用于一组补丁,但格式化补丁指令表明只有一个补丁。 2请注意,git am 本质上只是一个包装器,它在每个补丁上运行 git apply,然后跟随结果进行 git commit3请记住,Git 假设因为你将补丁提供给 git am你没有其他存储库的副本。别人已经通过电子邮件向你发送了补丁。只有他们拥有那个存储库;你只有你自己的存储库。但这里不是这样的——你有两个存储库,但是Git不知道! 4机会取决于你的存储库中 blob 对象的数量和缩短哈希的长度。Git 现在有代码自动选择适当的缩写哈希长度,但这是基于生成差异的存储库中对象的数量,而不是接收存储库中对象的数量。如果接收存储库显著更大,则发送方可能不会提供足够长的哈希。旧版本的 Git 也没有这种自动计算,并且默认情况下仅无条件使用 28 位哈希;这可能太短了。

很好的答案和解释!git remote add + git fetch + cherry-pick 是正确的选择。顺便提一下,cherry-pick 允许选择多个提交,获取更多信息请参考:https://dev59.com/t3I-5IYBdhLWcg3wy7wd - MHogge

3
我找到了一个更好的解决方案,就是使用patch --merge而不是任何git工具。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接