跨具有不同提交历史的存储库应用补丁

Applying patches across repositories with different commit histories

假设我有两个相似但提交历史不同的存储库 A 和 B。

一个示例可能是两个 Python Flask 应用程序,或者 Rails 上的两个 Ruby 应用程序共享 lot 相似文件但并不完全相同。

我对存储库 A 进行了更改,我想也将其应用于存储库 B。将其应用于 B 可能会出现一些冲突,但没关系,我想看看它们是什么,并且解决它们。

我尝试了以下从 repo A 生成补丁

> cd ~/git/repoA/
> git format-patch HEAD~
0001-My-Example-Commit.patch
> mv 0001-My-Example-Commit.patch ~/git/repoB

然后我尝试将补丁应用到 repo 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".

如图所示,结果有几个errors/conflicts。没关系,我会尝试查看冲突是什么并修复它们,就像我在常规合并中所做的那样:

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 甚至不将更改应用为 diff,因此没有 diffed/modified 文件,我什至看不到冲突是什么或尝试修复它们

有没有办法强制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"
....

没有冲突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(这是可能的)也可以)。这两个变化冲突。

一个补丁只提供一个变化。它要么适用,要么不适用。


修补的替代方案

这里有两个备选方案。从低效到高效顺序:

  • 确保 repo A 中的补丁是由 git format-patch --full-index 生成的。使用git am时,使用git am -3(或配置am.threeWaytrue)。这样,A 上的差异将包含文件父版本的完整 blob 哈希。如果那个 blob——文件的父版本——在 repo B 中可用,Git 将能够使用 blob-hash-and-patch 来重建真正合并所需的三个输入:一个公共基础版本以及两个从基础版本修改而来的版本。

  • 或者,在 repo B 中使用 git remote add 来添加对 repo A 的直接访问。选择一个远程名称,它会提醒你以后这是做什么用的(或者之后立即删除远程).然后 运行 git fetch 使用此远程名称将 repo A 中的提交带入 repo B,以便所有提交都在 B 中本地可用。现在,而不是 git format-patchgit am, 你可以 运行:

    git cherry-pick <hash>
    

    对于有问题的提交。 Git 现在将进行完整的三向合并,使用来自 repo A 的两个提交作为合并基础和它们的更改:

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

    并且您当前的提交作为您的更改:

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

    并将它们结合起来,并酌情考虑冲突。这里的第二个 diff 命令是 "what we changed" 或合并的 --ours 部分;这里的第一个 diff 命令是 "what they changed",因此是合并的 --theirs 部分。

请注意,git am -3 或设置 am.threeWay 只是一种 尝试 完全合并的方法,希望:

index a1539a7ce682f10a5aff3d1fee4e86530e058c89..98f88a28d3227c436ecf1765f75b7f4e8e336834 100755

给出一个出现在您的存储库中的哈希 ID。做 git fetch 实际上 得到 你有那个散列 ID 的 blob,如果你还没有它,那么就不需要单纯的希望和愿望。

注意,你看:

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

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

commit 80736d7 (23 Oct 2019) by Junio C Hamano (gitster)
(由 Junio C Hamano -- gitster -- in commit f1e2666 合并,2019 年 11 月 10 日)

doc: am --show-current-patch gives an entire e-mail message

The existing wording gives an impression that it only gives the contents of the $GIT_DIR/rebase-apply/patch file, i.e. the patch proper, but the option actually emits the entire e-mail message being processed (iow, one of the output files from "git mailsplit").

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