如何在 Git 中应用使用 format-patch 获取的多个补丁?

How to apply multiple patches taken with format-patch in Git?

我忘记从我的分支中取出一个分支并在其上完成提交。所以我不得不将这些提交移动到新分支。

经过一番调查,我采用了这种方法

-- git format-patch HEAD ~ {n} (n is 4 in this case)
-- git reset HEAD~{n} (reached master or parent branch) 
-- git checkout -- .
-- git checkout -b <new-branch-name>
-- git am (or git am *.patch) (which is git apply -r C*.patch in this case)

在这个问题中建议:git - Forgot to create new branch. How to transfer changes to new branch - Stack Overflow

我有 3 个提交要移动,其中最旧的一个是合并提交,其他是我的核心开发。因为merge commit有很多变化和冲突,当我发布format-patch时,它已经创建了70多个补丁文件,这些文件有这个约定0001-mybranch.patch0071-mybranch.patch

为了应用这些补丁,我发布了 git am (or git am *.patch) (which is git apply -r C*.patch in this case) 实际上只有

$ git am *.patch 只有这个是 运行 但给出了这个错误:

Patch failed at 0004 mybranch
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 format-patch HEAD~3 采取的所有这些补丁?是顺序的问题吗?

I had forgotten to take a branch from my branch and done commits on it. So I had to move these commits to the new branch.

有一种比您一直在尝试的方法更容易的方法。请参见 this answer to the question you linked above(但现在您有 运行 git reset HEAD~{n},您需要一种稍微不同的方法;请参见下文)。

I had 3 commits which I wanted to move and oldest one of them was a merge commit ...

git format-patch命令不能保留合并提交。它只是完全丢弃它们。它还产生了太多补丁。这些加起来就是导致您出现问题的原因。

鉴于您已经 运行 git reset HEAD~{n},现在的技巧是让您的新 b运行ch 指向 last您尝试使用 git format-patch 复制的现有提交。为此,请从以下内容开始:

git reflog

它将打印出所有各种 HEAD@{<em>number</em>} 值和您所做的各种提交的哈希 ID .找到您不小心在 wrong b运行ch 上做出的 last 提交并获取其哈希 ID(使用鼠标剪切并- 粘贴它,或写下来,或其他东西,以便您随时可以使用)。有关更多选项,请参见下文。

然后,运行:

git log <hash-value>

使用那个散列值。如果这看起来正确——如果这些是你想要的提交——创建一个 b运行ch 名称来记住它们:

git branch newbranch <hash-value>

如果不是,请尝试另一个 HEAD@{<em>number</em>} 哈希值,直到找到正确的哈希值。

Long:这是怎么回事

在内部,Git 根本不是关于 b运行ch names 的。 Git 实际上就是 提交 。 B运行ch 名称只是帮助你(和 Git)找到 一些特定的提交。

提交内容

Git 中的每个提交都包含每个文件的完整快照。在这些提交中,这些文件以特殊的、只读的、Git-only、冻结、压缩和去重格式存储。这意味着因为几乎每个提交都只是重用了一些早期提交的大部分文件,新的提交几乎不会占用任何 space,因为去重文件占用 no space 并且新的和不同的文件压缩得很好。在 this 提交中重新使用全新文件的后续提交可以共享此提交的副本,因为该副本一直被冻结。

(其中的None 与你现在正在做的事情特别相关,但在未来请记住:所有这些提交的重点是让你“回到过去”以前版本的文件,所以 commits 永远保存所有内容,或者至少,只要提交存在。这使它们成为只读的,这意味着您实际看到和工作的文件不是Git中的文件!)

每个提交都有一个唯一的编号。不过,这些数字不是简单的顺序计数,例如“commit #1”,然后是“commit #2”和“commit #3”等等,而是 运行dom-looking 哈希 ID 。它们实际上根本不是 运行dom:它们是完整提交内容的加密校验和。这样,每个地方的每个 Git 都会同意某个特定的提交获得 that 哈希 ID,因为它们都以相同的方式计算校验和。为他们提供构成提交的相同位集,他们会为 that 提交获得相同的提交编号。

除了保存的快照外,每个提交还包含一些关于提交本身的元数据:信息,例如提交人——姓名和电子邮件地址——时间和原因(提交中的日志消息)。此元数据的一部分专门针对 Git 本身,即 上一个 提交的提交编号 - 哈希 ID。

Git 向后工作

这意味着我们可以 绘制 提交,使用大写字母代表真正的哈希 ID(虽然我们会 运行 出字母很快,这就是 Git 使用如此大的“数字”的原因)。例如,想象一个在其历史早期只有三个提交的存储库:

A <-B <-C

最后 提交是提交 C。在提交 C 中有所有文件的完整快照, 加上 提交编号——早期提交 B 的哈希 ID。在提交 B 中,Git 存储了早期提交 A.

的哈希 ID

Git 可以通过其哈希 ID 查找任何提交。因此,如果我们知道 last 提交的哈希 ID C,Git 可以为我们查找。然后 Git 可以使用它来查找提交 B 的哈希 ID,并查找它,等等。这让 Git 工作 向后 。这就是 Git 的工作方式:向后,从 上次 提交,返回。

(提交 A 是第一次提交,因此 Git 故意省略了“早期提交”部分,这就是 Git 知道如何停止倒退的方式。)

B运行ch 名称包含 last commit

的哈希 ID

现在,我们——and/or Git——此时剩下的一个问题是:我们如何找到 的哈希 ID last commit? Git 可以从那里向后工作,但是找到某个实际最后一次提交的 运行dom-looking 哈希 ID 的快速方法是什么?

这就是 b运行ch 名称 的用武之地。一个 b运行ch 名称只包含 一个提交哈希 ID,它持有的是那个 b运行ch.

中的 last

让我们通过添加一个提交 D 让我们的图表更长一点。假设 name master selects 提交 C 作为 last 提交,并且 name develop selects commit D as the last commit:

A--B--C   <-- master
       \
        D   <-- develop

我们现在有 b运行ches!提交 D 指向提交 C,后者指向 B,后者指向 A。但是现在 Clast 提交 master 并且 Dlast 提交 develop.

如果我们现在 git checkout developgit switch develop,然后进行 new 提交,Git *自动更新名称 develop 指向我们的新提交:

A--B--C   <-- master
       \
        D--E   <-- develop (HEAD)

名称develop,这是我们的当前b运行ch名称——这就是我在这里添加(HEAD)的原因——现在select 是我们的新提交 E。提交 E 指向现有提交 D,后者指向现有提交 C,依此类推。

这是你最初做错的地方

你有一套 b运行ches。此图可能不准确——您可以使用 git log --graph,也许以 git log --all --decorate --oneline --graph 的形式,以获得更准确(但垂直)的实际提交图图——但这只是为了说明什么你做到了:

       H   <-- somebranch
      /
...--G
      \
       I--J   <-- anotherbranch

打算添加一个新名称并将其用作当前的 b运行ch,如下所示:

       H   <-- somebranch, newbranch (HEAD)
      /
...--G
      \
       I--J   <-- anotherbranch

请注意,在这种情况下,两个不同的 names select 相同 last commit。这在 Git 中是完全正常的。许多提交同时在多个 b运行ch 上。在这种情况下,commit Gall b运行ches 上,commit H 在两个 b运行ches 上,而 I-J 只在一个 b运行ch.

如果你当时 运行 git merge anotherbranch 你会得到这个:

       H   <-- somebranch
      / \___
...--G      K   <-- newbranch (HEAD)
      \    /
       I--J   <-- anotherbranch

然后您可以继续进行更多提交;我会做一个,L,在这个插图中:

       H   <-- somebranch
      / \___
...--G      K--L   <-- newbranch (HEAD)
      \    /
       I--J   <-- anotherbranch

但是您忘记创建新名称newbranch。因此,您当时使用的名称是 somebranch

       H   <-- somebranch (HEAD)
      /
...--G
      \
       I--J   <-- anotherbranch

您仍然提交了 KL,但是没有 Git 拖动 new b运行ch名称向前,您 Git 将 old b运行ch 名称向前拖动:

       H
      / \___
...--G      K--L   <-- somebranch (HEAD)
      \    /
       I--J   <-- anotherbranch

这是您尝试修复它的方法

为了解决这个问题,您使用 git format-patch 尝试将提交 KL 转换为可用于重新创建提交的可通过电子邮件发送的补丁 KL。但是 git format-patch 不能 重现合并提交,所以它完全离开了合并提交,并且由于合并提交,它包含了一堆你不是故意的提交包括。所以这部分并没有真正起作用。

忽略补丁文件(我们不会使用),然后您使用 git reset 使 name somebranch 指向返回位置它曾经指向。这确实奏效了,结果是:

       H   <-- somebranch (HEAD)
      / \___
...--G      K--L   ???
      \    /
       I--J   <-- anotherbranch

请注意,现在没有 name——无论如何,没有 b运行ch 名称——你可以用来 find 最后 提交 L。如果您使用了我上面链接的答案,您将首先创建名称以记住提交 L 的哈希 ID,然后重置名称 somebranch.

这是您修复问题的方法

幸运的是,Git 有这些叫做 reflogs 的东西。 HEAD reflog 在某处包含现有提交 L 的哈希 ID。问题是,它包含 每个 提交的哈希 ID,HEAD 在过去 30 到 90 天或更长时间内命名。因此,当您 运行 git reflog.

时,就会有一大堆提交,其中包含 运行dom-looking 哈希 ID

您想要的提交将接近 reflog 输出的顶部,因为它是最近的 HEAD 提交(在您提交时)。您还可以查看提交消息主题行,因为 git reflog 会打印这些主题行。

通过剪切和粘贴获取哈希 ID——或 HEAD@{3} 数字,或其他任何东西,你可以在上面 运行 git log,这将向你显示具体的提交,然后是它的父级,等等,git log 通常的方式。找到正确的哈希 ID 后,命令:

git branch <name> <hash-ID>

将创建一个新名称,指向正确的哈希 ID:

       H   <-- somebranch (HEAD)
      / \___
...--G      K--L   <-- newname
      \    /
       I--J   <-- anotherbranch

然后您可以git checkout newname得到:

       H   <-- somebranch
      / \___
...--G      K--L   <-- newname (HEAD)
      \    /
       I--J   <-- anotherbranch

(注意 HEAD 是如何移动的:现在我们正在处理来自提交 L 的文件,而不是来自提交 H 的文件)让一切看起来都像你在您打算创建新的 b运行ch 名称时,而不是在意识到您的错误之后。