在具有不同提交历史记录的存储库之间应用补丁

4
假设我有两个仓库 A 和 B,它们很相似但提交历史不同。比如说,可能是两个 Python Flask 应用程序或两个 Ruby on Rails 应用程序,它们共享许多相似的文件,但并非完全相同。
我对仓库 A 做了一些更改,现在我也想将这些更改应用到仓库 B 中。在将更改应用到 B 时可能会遇到一些冲突,但没关系,我想看看这些冲突并逐个解决。
我尝试了以下步骤来从仓库 A 生成补丁:
> cd ~/git/repoA/
> git format-patch HEAD~
0001-My-Example-Commit.patch
> mv 0001-My-Example-Commit.patch ~/git/repoB

然后我试着将补丁应用到B仓库

> cd ~/git/repoB
> git am 0001-My-Example-Commit.patch
Applying: My Example Commit
error: patch failed: Gemfile:20
error: Gemfile: patch does not apply
error: patch failed: Gemfile.lock:125
error: Gemfile.lock: patch does not apply
error: patch failed: app/assets/stylesheets/application.scss:29
error: app/assets/stylesheets/application.scss: patch does not apply
....
....
error: patch failed: spec/views/application/_mobile_navigation.html.erb_spec.rb:44
error: spec/views/application/_mobile_navigation.html.erb_spec.rb: patch does not apply
Patch failed at 0001 Using Devise for authentication
hint: Use 'git am --show-current-patch' to see the failed 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 status
On branch test-git-apply
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 tree clean

Git不会将更改应用为差异,因此没有差异/修改的文件,我甚至无法看到冲突是什么或尝试解决它们。

有没有一种方法可以强制git显示冲突?

谢谢!

编辑:

我知道--directory选项存在,但我认为它在这里并不适用,因为我的补丁文件已经相对于同一根目录生成了。例如:

diff --git a/Gemfile b/Gemfile
index c970c34..ffc812d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,12 +20,17 @@ gem "webpacker", "~> 3.5", ">= 3.5.5"
 #
 gem "bcrypt", "~> 3.1", ">= 3.1.10"
 gem "cancancan", "~> 3.0"
....
2个回答

1

没有发生冲突1。补丁只是未能应用。例如,补丁可能会说:第87行读取"foo"。在随后的第88行,将"bar"改为"baz"。第89行读取"quux"。但是你的第87到89行并没有读取"foo"、"bar"和"quux",也没有附近包含该序列的行。作为程序员,你需要自己想办法处理这个补丁。

hint: Use 'git am --show-current-patch' to see the failed patch

因此,请使用它。阅读补丁。检查您的文件并决定如何处理此补丁。


1这里不可能有冲突,因为补丁本身并不代表对同一行进行两组不同的更改。当你有两个差异时,就会发生冲突:一个说要将第88行的bar更改为baz(这是可能的),另一个说要将第88行的bar更改为rab(这也是可能的)。这两个更改发生冲突。

补丁只提供一次更改。它要么应用,要么不应用。


不使用补丁的替代方案

这里有两个替代选项,按照效果从低到高排列:

  • Be sure the patch from repo A was generated by a git format-patch --full-index. When using git am, use git am -3 (or configure am.threeWay to true). That way, the diff on A will include the full blob hash of the parent version of the file. If that blob—the parent's version of the file—is available in repo B, Git will be able to use the blob-hash-and-patch to reconstruct the three inputs that a real merge requires: a common base version and both modified-from-base versions.

  • Or, use git remote add in repo B to add direct access to repo A. Pick a remote-name that will remind you what this is for later (or delete the remote immediately afterward). Then run git fetch with this remote-name to bring the commits from repo A into repo B, so that all of the commits are available locally within B. Now, instead of git format-patch and git am, you can just run:

    git cherry-pick <hash>
    

    for the commit in question. Git will now do a full three-way merge, using the two commits from repo A as merge-base and their-change:

    git diff <parent of hash> <hash>   # what they changed
    

    and your current commit as your-change:

    git diff <parent of hash> HEAD     # what we changed
    

    and combine them, with conflicts as appropriate. The second diff command here is "what we changed" or the --ours part of the merge; the first diff command here is "what they changed" and hence the --theirs part of the merge.

请注意,git am -3或设置am.threeWay仅仅是一种尝试通过希望以下方式来进行完整合并的方法:希望
index a1539a7ce682f10a5aff3d1fee4e86530e058c89..98f88a28d3227c436ecf1765f75b7f4e8e336834 100755

提供了在你的代码库中出现的哈希ID。执行git fetch实际上会获取具有该哈希ID的blob,如果你还没有它,因此不需要仅仅是希望和愿望。


我明白你的意思,但在你的例子中,对于第87-89行的更改,git仍然无法尝试并强制执行与已有内容(使用>>>>>><<<<<<)冲突。而我作为作者,则需要手动接受其中之一。尽管如此,感谢您的见解,谢谢! - user2490003
问题在于,为了构建一个正确的冲突,Git 必须找到第三个文件,即共同祖先,它是你要打补丁的文件和你正在应用的补丁都有变更的文件。也就是说,你正在应用的补丁说“将这些更改应用到第 87-89 行”,因此 Git 需要找到具有这些行作为第 87-89 行的文件,以便它可以找到你的文件中对应的哪些行。由于其 index 行包含原始文件的哈希 ID,Git 可以使用某些 Git 补丁来完成此操作。 - torek
如果您的补丁有“索引”行,并且您有该原始文件(作为存储在您的存储库中的 blob),并且您使用git apply --3waygit am --3way,Git 将尝试执行此操作。如果您使用git format-patch生成补丁,则它们将具有索引行(并且您可以使用--full-index来防止可能使它们模糊不清的缩写),但是这些行可能不对应于存储库中的实际 blob。 - torek
请注意,如果您可以直接访问存储库A并希望将其中某些提交挑选到不相关的存储库B中,则可以在存储库B中使用git fetch获取A的提交,然后运行git cherry-pick。这种策略执行三方合并并提供所需的冲突行。 - torek
非常感谢您的更新和详细的概述。那个解释很有道理,添加另一个“remote”并执行“git fetch”以合并更改的方法正是我所需要的。 - user2490003

0

注意,你看到的是:

hint: Use 'git am --show-current-patch' to see the failed patch

但是,正如 Git 2.25(2020 年第一季度)中所记录的那样,这并不完全正确。

请参见 提交 80736d7(2019 年 10 月 23 日),由 Junio C Hamano(gitster 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 f1e2666,2019 年 11 月 10 日)

doc: am --show-current-patch 可以显示整个电子邮件消息

现有的措辞给人的印象是它只会提供$GIT_DIR/rebase-apply/patch文件的内容,即补丁本身,但该选项实际上会发出正在处理的整个电子邮件消息(即“git mailsplit”生成的输出文件之一)

因此,您可能需要从该电子邮件中提取导致冲突的确切部分。


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