Git: 当中间有一个合并时,如何变基为 1 个提交?

Git: How to rebase into 1 commit when there's a merge in the middle?

我在 feature-1 的功能分支上,它位于 master

之外

我的提交历史是:

`Adding rehX project`,                     // <-- my first commit
`Adding factory resets for rehX `,                      // <-- my second commit
`Setting regulatory values for rehX `,                  // <-- my third commit
'Merging in master and resolving conflicts'  / / <--- my fourth commit was merging in `master`
'Adding last minute request from QA'.        // <-- my unexpected 5th commit

当我不合并 master 时,我通常使用 git rebase -interactive HEAD~(number of commits) 这样我就可以将所有提交压缩到初始提交中。

虽然我合并了 master,但事情变得复杂了。我在这里有什么选择吗?

TL;DR

只需使用git rebase --interactive master。原因见下文。

编辑:我没有注意到,在主题行中,您只想获得 一个 提交,这意味着还有一种方法。

多头

您有多种选择。如果有的话,你有太多选项:

  • 您可以创建一个新分支,一次选择一个提交。
  • 您可以创建一个新分支并使用 -n 挑选提交,以便它们被预先压缩。
  • 您可以使用 git rebase -i 并使用 --onto.
  • 小心地将“提交到 cherry pick 的内容”部分与“放置副本的位置”部分分开
  • 您可以使用 git rebase -i 而无需那种小心,并删除无关的 pick 命令,如果有的话(但这里不会)。
  • 或者,要将所有内容变成 单个 提交,请使用 git reset --softgit commit

请注意,这些选项中的前四个最终以相同的方式工作,所以这实际上只是个人喜好的问题。无论您使用哪种方法,都不会复制现有的合并。事实上,变基 不能 复制合并,尽管新奇的 --rebase-merges (-r) 选项将提供/声称这样做。它没有:它只是 运行s git merge 再次进行 new 合并。如果你想自己做,你可以在使用前两种(手动模式,手动操作的 cherry-pick)方法时自己做。

让我们从您所拥有的绘图开始:

          ----------------M--N   <-- feature-1 (HEAD)
         /               /
...--H--I   <-- master  /
         \             /
          J-----K-----L

大写字母代表提交哈希 ID。 名称 feature-1 是您的 当前分支名称 ,如括号中所附的 HEAD 所示,并提交 N 是您的 当前提交 ,即主题行为 Adding last minute request from QA.

的提交

(练习:以更简单的方式重新绘制此图。在纸上或白板上,它可以非常简单。不过,请检查您是否在 master 上提交了我没有绘制的内容。如果您确实有这样的提交,请尝试所有这些。)

您需要在提交 I 之后构建一些新的提交系列——提交 Ilast 在 [=30 上的提交=]—使用现有提交 J-K-LN 的内容,但不使用 M 的内容,因为我们计划完全放弃合并。

对于单个替换提交的特殊情况

要使 仅保留提交 N 的现有状态的新提交,我们需要做的就是使用 git reset 移动 当前分支名称返回。请注意,我们想要一个 git reset --soft,它使 Git 的索引和当前工作树状态都不受干扰。结果是:

          ----------------M--N   [abandoned]
         /               /
...--H--I   <-- master, / feature-1 (HEAD)
         \             /
          J-----K-----L

也就是说,现在两个分支名称 select 现有提交 I.

我们现在可以 运行 git commit。这会根据 Git 索引中的任何内容进行新提交,正如 git status 应该显示的那样 - 应该与 当前提交相匹配: 我们将有文件标记为 已暂存提交 ,并且没有正在调用的文件 未暂存提交 。 (如果我们确实有一些这样的文件,如果需要,我们可以 git add 它们。)

git commit 的一个缺点是我们必须重新构建整个提交消息。如果这是一个问题,可以先保存每个其他提交消息(例如,git log 重定向到一个文件)。但是,与变基方法不同,没有内置的简单方法来组合现有的提交消息。

最终结果是一个新的提交,原来的一系列提交很难找到;我们可以像这样绘制一个新的提交:

...--H--I   <-- master
         \
          O   <-- feature-1 (HEAD)

请注意,这仅适用于“进行一次新提交”的情况。如果您想将两个提交合并为一个,但将其他提交分开,则不能使用此快捷方式。

对于所有变基案例(包括手动选择“变基”)

git rebase需要的是:

  • 副本所在位置的哈希 ID:--onto master 将提供;和
  • 提交的哈希 ID 复制。这是来自非--onto论点。

Git 将 不会 复制此提交,也不会从此提交开始并向后工作(向左,在上图中;git log --graph 绘制了一个图表,其中较新的提交位于顶部,而不是右侧,因此使用 git log --graph 您将跳过“向下”连接的提交。

我们需要 Git 而不是 复制的提交是提交 IH 以及它们之前的所有内容。所以我们可以为 git rebase 提供哈希 ID I,或者名称 master:

git rebase --onto master master

每当 --onto 和剩余的参数名称 相同的提交 ,我们实际上并不需要 --onto,所以这简化为:

git rebase master

无论是否交互,这种变基——没有 -r 选项——drops 完全合并提交。因此,要复制的提交列表将依次为 JKLN

如果没有 --interactive,Git 将尝试自己复制每个提交。使用 --interactive,Git 提出一条指令 sheet,然后您可以对其进行编辑,将一些 pick 更改为 squash 或其他内容。

Git 然后关闭指令 sheet:对于每个 pick,Git 运行s git cherry-pick。对于每个 squash、Git 运行 一个 git commit --amend 和一个精心选择的参数集。如果你自己使用单独的 git cherry-pick 命令来执行此操作,你可以提前计划并 运行 其中一些使用 -n:这稍微提高了计算机效率,但主要是浪费时间,因为计算机资源很便宜,人力很贵。

所以,对于这个特殊的变基,你所需要的只是你通常会做的相同的交互式变基:而不是试图计算HEAD的提交~ <em>数</em>,直接用分支名master找对点就行了。如果你想计算提交,这很棘手,因为合并提交:有五个提交一个路径,两个 提交 - 只是 NM - 另一个。 ~走哪条路? (这是学生的另一项练习。)