与上次合并的提交的交互式重新提交在 pick/edit 的提交列表中显示了两个额外的提交

Interactive reabse of commits with last merge shows two extra commits in list of commits to pick/edit

我试过网络搜索并找到 Rebasing a Git merge commit,其中写着:

By default, a rebase will simply drop merge commits from the todo list, and put the rebased commits into a single, linear branch.

所以现在我明白了为什么在 git rebase -i HEAD~9 上我没有在交互式变基的 edit/pick 的提交列表中看到我的合并提交。但是,我在列表中看到了另外两个较旧的提交(总共 10 个提交,没有合并)。为什么?可以吗?我做了变基,现在想知道也许我最好重做所有最近的提交。我想变基以尝试在合并后首先删除不需要的本地提交。

最后一次提交是合并,在此之前:

git status
Your branch and 'origin/devel' have diverged,
and have 6 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

所以我猜这两个是因为有 2 个来自 origin 的提交被合并了。我说得对吗?

任何提交,一旦做出,就永远无法更改。因此 git rebase 执行的工作是 copy 一些现有的提交(你在某种程度上喜欢,但不喜欢 something 关于这些提交) 到新的和改进的——好吧,无论如何,你 希望 改进——提交。 Git 然后设置 b运行ch name—b运行ch names 是你如何告诉 Git 到 find 提交——以便它找到新的和改进的提交 而不是 原始的。

执行此 copy-and-replace 操作时的主要问题在于确定 哪个提交复制 。如您所见,您正在 Git 复制提交 以及 merged-in 提交,正如您在文档中看到的,期间的默认操作这种 copy-and-switch-over 是 drop 完全合并提交(因为它们是不必要的,这反过来又是由于复制的执行方式)。

但是,您链接的问答已经很老了,此时可以追溯到大约 11 年前。 Git 最近(最近一两年内)学会了一个新的 git rebase 技巧,就是 re-perform 合并 。 rebase 代码拼写为 --rebase-merges,或简称为 -r。您现在 可以 使用 git rebase -r 来做一些您过去 不能 使用 git rebase 做的事情。

So I guess those two more is because there were 2 commits from origin that were merged. Am I correct here?

或多或少,是的。这里最棘手的部分是我们如何指定哪些提交 Git 应该 复制,哪些提交 Git 不应该 复制。

但是,正如主要关注点主要棘手部分所暗示的那样,变基还有其他棘手的部分。要理解您在做什么,您 必须 知道 Git 中的提交是如何工作的。总体上理解这一点是个好主意,而不仅仅是为了 rebase 工作。 Git 最终都是关于 提交 。它与文件无关,尽管提交包含文件并且人们非常关心文件内容。 Git 也与 b运行ches 无关,尽管 b运行ch 名称在查找提交时很重要。但是 Git 本身因为提交而存在,并且与提交一起工作。所以你需要知道提交是什么以及对你有什么作用。

每个 Git 提交都有编号:每个提交都有一个 唯一的 编号,看起来(但不是)运行dom。这个数字保证运行唯一,1不仅在这个存储库中,而且在每个 Git 存储库,过去,现在和未来。这意味着如果你记下你所做的一些提交的哈希 ID,并随时检查任何旧的 Git 存储库并发现它有一个使用这个哈希 ID 的提交,你会立即知道你正在查找的存储库在有 你的 提交。

此哈希 ID 方案是 为什么 无法更改任何提交。如果可以的话,我们可以通过使不同的提交使用相同的哈希 ID 来非常容易地破坏 Git。2 在任何情况下,因为我们期望 改进 我们的提交当我们变基时,我们将无法保留原件:我们 至少 一些 新的具有新的和不同的哈希 ID 的副本。

同时,每次提交 存储两件事:

  • 提交具有每个文件的完整快照,Git 在您或任何人进行提交时知道这些文件。这些文件以特殊的 read-only(散列!)、Git-only 格式存储,压缩并且——重要的是控制存储库大小,以及其他原因——de-duplicated 针对存储库中每次提交中的所有其他文件。

  • 提交存储 元数据: 有关提交本身的信息,例如提交人和时间。在这里您会找到您的姓名和电子邮件地址,Git 已经从您的 user.nameuser.email 设置中复制了这些地址。您会发现一个 date-and-time 邮票。您可以存储一条提交 日志消息 告诉所有人 为什么 您进行了提交。3

现在,在每次提交的元数据中,Git 都会为自己添加一些东西。在每次提交中,Git 存储 previous 提交的原始哈希 ID 列表。 Git 将这些称为提交的 parents。大多数提交只有一个 parent: the previous commit,即在 this commit.

这个 parent 哈希 ID,存储在每个提交中, Git 如何能够通过时间倒退,看看你和其他人完成了。假设我们有一些带有哈希 ID H 的提交,这是您刚刚做出的 最新 提交(在某些 b运行ch 上)。提交 H 包含快照和元数据,并且该元数据包含一些早期提交的原始哈希 ID。我们说 H 分 t 其 parent。让我们调用 parent G:

        <-G <-H

H出来的箭头,向后指向G,就是H的parent。但是 G 也是一个提交,所以它也有一个快照和一个 parent——一个向后的箭头——指向某个更早的提交 F:

... <-F <-G <-H

F 有一个箭头指向某些 still-earlier 提交,依此类推。


1Git 做出了这个承诺,但是 pigeonhole principle 告诉我们 Git 总有一天会失败。哈希 ID 的 大小 有助于将那一天推到足够远的未来,我们都将死去而不在乎。

2破损会有些局限,但总的来说是烂片

3您应该始终记录 为什么 您进行了提交。不要只说你做了什么:Git 可以将此提交的快照与任何其他提交的快照进行比较,这将显示 你做了什么。我们可以很容易地看到您更改了第 42 行。告诉我们 为什么 您进行了更改:为什么 “红球”变成了“蓝立方体” ",或者你改变了什么。它是红色的,还有一个球有什么不好?


B运行ch 名称帮助我们(和Git)找到提交

现在您已经了解了提交——您在上面看到的几乎是您需要知道的所有内容,尽管总是有更多需要学习的东西——让我们看看我们如何找到 提交。请记住,这些插图中的哈希 ID,如 HG,实际上是丑陋的 random-looking 东西。人类不可能记住它们。我们可以把它们写下来,但是等一下,我们有一台 计算机 。计算机擅长这个 s—t:让我们让 计算机 记下哈希 ID。让我们将散列 ID 存储在 名称 的 table 中,例如 b运行ch 名称和标签名称。

它们就是这样:b运行ch 名称只是 table 中存储哈希 ID 的一个条目。如果我们在table中有名字main,并且main存储了H的hash ID,我们说main指向 H:

...--G--H   <-- main

我们可能会添加一个新的 b运行ch 名称 devfeature 或其他名称并切换到使用那个 b运行ch 名称,但是那个名称 此时指向H

...--G--H   <-- dev (HEAD), main

我们(和Git)将使用特殊名称HEAD,像这样全部大写,以记住我们正在使用的名称 .然后我们将让 Git 进行新的提交——一个新的快照和元数据——与 Git 永久存储在 H 中的快照相比有一些变化。我们将调用我们的新提交 I,并且 I 将像这样指向 H

          I
         /
...--G--H

偷偷摸摸的把戏Git 是当我们把名字dev 作为我们的current b运行ch name 时,我们使用 git commit、Git 更新 b运行ch name 以使其指向新提交。其他名称,如 main,没有改变,所以它们仍然指向它们之前指向的任何提交;但是 dev 附加了 HEAD,现在指向 I:

          I   <-- dev (HEAD)
         /
...--G--H   <-- main

这就是 Git 这部分的全部内容,但我们遗漏了一些重要的重要想法,我将略微提及。

Git 的索引和您的工作树

提交中的快照是read-only和Git-only,使用那些压缩文件和de-duplicated文件。但是您的计算机程序无法读取这些类型的文件,并且您的某些程序(例如您的编辑器)需要能够写入 文件。所以提交的文件是没有用的!许多版本控制系统 (VCS) 都是如此,而不仅仅是 Git.

为了使提交的文件有用,Git 必须从提交中提取 它们,就像从存档中提取文件一样。 (事实上​​,这与从压缩存档中提取完全一样,只是存档软件不使用 Git 奇怪的内部格式,这种格式仅对 Git 本身有用。)这就是 git switchgit checkout 在其 branch-switching 模式下。4

当你使用 Git 时,你通常会使用这些文件的工作副本,Git 放入一些东西 Git 称为你的 工作树work-tree。这些文件实际上不是 in Git:最多,它们被复制 out of 一些提交,并可能最终被复制回未来的承诺。请注意,如果您损坏或销毁了这些文件,Git 可以提取其任何现有存档(提交),但无法获取您所做的任何修改。

在其他 VCS 中,我们到此为止:有提交,有工作树,VCS 从工作树进行 new 提交。但是 Git 很奇怪:Git makes new com它来自其他东西。 Git 将此其他内容称为 index,或 staging area,或(现在很少)cache。我们不会在这里详细介绍,但这个索引就是为什么你必须如此频繁地 运行 git add:你告诉 Git 复制我的工作树文件,我已经更新了,回到你的索引中准备提交。这将压缩和 de-duplicates 文件(在 git add 时间,这使得后来的 git commit 运行得更快——对于较旧的 VCS,人们可能 运行 他们的提交动词,然后喝杯咖啡或午休时间,因为很多分钟都不会发生任何事情)。

因为索引持有它自己的份文件(de-duplicated“份数”),你可以同时拥有一些文件的三个不同版本: HEAD 或 current-commit,版本在当前提交 中一直冻结 ,无论其哈希 ID 是什么。索引中有该文件的第二个副本(或“副本”,当它是 de-duplicated 时),最初与提交的副本相同,但您可以使用 git add 更改该副本;并且在您的工作树中有该文件的第三个副本,您可以随时使用任何编辑器或您喜欢的任何内容对其进行更改。

因此,重要的是要确保在 运行 git commit、Git 的 index(或临时区域)保持您希望 Git 永久冻结到新提交中的副本。这就是为什么你会想要 运行 git diffgit status 之类的:查看索引是否与 HEAD 提交匹配,或工作树,或两者或两者都不匹配,如果匹配,git diff --cachedgit diff可以告诉你不同的地方。但这就是我们在这里要说的。


4在旧的(pre-2.23)Git 版本中你只有 git checkout,所以你必须使用旧的命令;由于一个小的设计错误,它有点危险。这在 Git 2.23 中得到了纠正,但是如果你有 Git 2.23 或更高版本,你也可以开始使用 git switch


合并和合并提交

最终我们总是发现自己有合并提交,出于某种原因。当他们做你想做的事时,合并是好的。当他们不这样做时,他们很糟糕。但是因为我们 get 它们并 make 它们,所以我们需要看看它们是关于什么的以及我们是如何制作它们的。

我们首先假设我们的小存储库中有这些提交:

          I--J   <-- br1 (HEAD)
         /
...--G--H
         \
          K--L   <-- br2

也就是说,我们至少有两个b运行切,分别命名为br1br2。两个 b运行ches 都从一些 shared 提交开始,这些提交是通过 H 的提交,但在那之后有人提交了 I 和 [= br1 上的 98=],有人在 br2 上提交了 KL

我们现在想合并这两个“某人”所做的工作。为此,我们选择一个 b运行ch (br1) 作为当前 b运行ch 并提交,就像我们在这里所做的那样。然后我们 运行:

git merge br2

这里的br2实际上只是一种让Git找到我们想要合并的commit的方法。 B运行ch 名称通常用于查找 commit(或提交),这也不例外:Git 查找提交 L 因为br2 指向 L.

合并代码现在按照 Git 的趋势,从提交 J 开始——当前或 br1 上的 HEAD 提交——以及从同时提交 L。 working-back 发现提交 H 最佳共享提交 ,它 Git 调用 合并基础 .5

Git 现在执行 work-finding 步骤。为了弄清楚我们在 br1 上更改了什么,Git 只需将 H 中的快照与 J 中的快照进行比较:br1 的 tip 提交,我们在其中现在。这得到一个 diff,它有一个说明要更改特定文件的特定行的方法:

git diff --find-renames <hash-of-H> <hash-of-J>   # changes on br1

为了弄清楚他们(无论他们是谁)在 br2 上做了什么,Git 运行 是第二个差异,从 相同的合并基础 H,但是这次要L

git diff --find-renames <hash-of-H> <hash-of-L>   # changes on br2

合并过程现在是合并更改的简单问题。无论我们更改什么行,在什么文件中,Git 都会进行这些更改,但是 Git 也会对他们更改的任何文件的任何行进行 他们的 更改。 Git 将 组合 更改应用到来自提交 H 的快照——共享起点,即合并基础。

如果发生冲突,通常是因为我们和他们更改了相同的行,或者在相同的文件中更改了“接触”的行,但我们和他们做的不同那些线的东西。 Git 不知道是拿我们的,他们的,还是两者都拿。 Git 本身只是因合并冲突而停止,并让程序员 you 找出正确的 r结果。 (然后你必须完成合并。)

但是,如果没有此类冲突,Git 将继续自行进行合并提交。与任何提交一样,合并提交将使用 当前 提交作为 parent。 merge 提交的特别之处在于它将获得 second parent:我们告诉 Git 合并的提交.在这种情况下,就是提交 L:

          I--J
         /    \
...--G--H      M   <-- br1 (HEAD)
         \    /
          K--L   <-- br2

像每个提交一样,合并提交 M 有一个快照,其中包含所有文件(像往常一样压缩和 de-duplicated)。与任何提交一样,合并提交 M 具有元数据。 M 唯一特别之处在于它的元数据列出了 两个 parent 而不是通常的 6 如果存在合并冲突,M 中的快照是您,程序员,告诉 Git 制作的快照。否则它就是 Git 自己计算出来的(除非你使用 --no-commit 来调整结果,如 evil merge)。

不过这里有一个 especially-interesting 副作用。提交 K-L 过去仅“在”b运行ch br2 上。现在他们在 both b运行ches 上。提交 I-J 仍然只在 br1 上。 通过从所有 b运行ch 名称开始,找到它们指向的提交,然后向后工作,可以找到包含任何给定提交的 b运行ches 集。 如果此过程命中了您想了解的提交,则该提交包含在该 b运行ch 中。如果不止一个 b​​运行ch 名称包含提交,则不止一个 b​​运行ch 包含提交。 (这也是 Git 特有的;大多数版本控制系统不采用这种观点。)


5在插图中,我们可以在两个 b运行ches 上一次返回一步并在 H 处相遇,但实际上例如,在某些情况下,我们可能只需要在一个 b运行ch 上返回一跳,而在另一个 b运行ch 上返回许多跳。查找合并基础的实际算法是 Lowest Common Ancestor algorithm。我们将在这里跳过一些重要的细节,包括 Git 对 multiple-merge-bases 案例的处理。

6Git 中的合并提交可以列出两个以上的 parent。我们不会介绍如何让 Git 执行此操作,但它不需要我们尚未介绍的任何内容:它仍然是带有快照和元数据的提交。


遥控器和 remote-tracking 名称

Your branch and 'origin/devel' have diverged ...

名字origin/devel就是我所说的remote-tracking名字。 Git 调用这些 remote-tracking b运行ch names,但我找到 b运行ch 这里没有添加任何内容,只会造成混乱。 origin/devel 一个名称,它确实“跟踪远程的东西”,但它不是b运行ch name: 最多一个标签名就是一个b运行ch name 例如.

这里发生的事情是您的 Git 软件正在与另一个 Git 存储库通信。您的存储库有 b运行ch 名称,例如 devel 或其他名称。但是这些是你的b运行ch名字:你运行git switch devel,然后做一些工作和运行git commit,现在存储在 in 中的哈希 ID b运行ch 名称是您所做的一些新提交 I 的哈希 ID。

因此,您的 Git 软件必须小心 而不是 踩踏 您的 b运行ch 名称,只是因为他们的 Git 存储库,在 GitHub 上或任何地方,有一个 b运行ch 名称 devel。因此,您的 Git 将创建或更新名称 origin/devel,每次您使用名称 originremote,如 Git 调用它 — 让您的 Git 软件调用他们的 Git 软件并获得他们拥有而您没有的任何提交。您的 Git 看到他们有一个名为 devel 的 b运行ch,因此您的 Git 获得了他们最新的 devel 提交哈希 ID。如果您的 Git 存储库没有该提交,您的 Git 从他们那里获取该提交并将其填充到您的存储库中,连同任何需要的 parent 提交,依此类推,现在你有他们的 devel b运行ch。但是您不能将其命名为 devel,因此您的 Git 软件使用 origin/devel 代替:

          I--J--K   <-- devel (HEAD)
         /
...--G--H
         \
          L   <-- origin/devel

此设置与我们在您的存储库中有两个 b运行ches 时的设置相同。事实上,我们 do 有两个 b运行ches,如果 b运行ch 我们的意思是 有趣的提交子集 而不是 通过 b运行ch name 找到的东西。 (另请参阅 What exactly do we mean by "branch"?

我们可以合并这些:

          I--J--K
         /       \
...--G--H         M   <-- devel (HEAD)
         \ ______/
          L   <-- origin/devel

当我们这样做时,提交 L 现在在 devel

HEAD~<em>n</em>语法

你运行:

git rebase -i HEAD~9

当我们有简单的线性图时:

...--G--H--I--J--K--L   <-- main (HEAD)

波浪号 ~ 语法,例如 HEAD~5,简单地倒数五次提交:LKJI, H. So HEAD~5ormain~5means *commitH`*.向后只有一条路,我们走那条路th 并计算跳数,仅此而已。

当我们有复杂的图形时,其中有 branch-and-merge 个结构:

          G--H--I
         /       \
...--E--F         M--N   <-- devel (HEAD)
         \       /
          J--K--L

现在已经不是这样了。我们现在有 两条 条后退路径。从 HEADdevel 开始,这意味着提交 N,我们可以向后移动一跳到 M。但现在我们可以再向后移动一跳到 I L。我们应该跳哪一跳?

Git 对此的回答是将 parent 链接之一指定为 first parent。所有其他的都“不太有趣”(尽管如果你愿意,你可以用帽子或插入符号 ^ 后缀将它们挑出来)。所以 HEAD~5 这里的意思是 跟随 M 中的第一个 parent。计算 this 路径上的五次提交,我们得到 NMIHGHEAD~5 表示 提交 G.

再跳一跳,HEAD~6,意味着 提交 F。请注意,到达 G 后,只有一条返回 F 的路径。有趣的是,b运行ch 分开在“向前”的方向——方向Git使用——是Git确实使用的“向后”方向的合并,并且合并提交充当branch-apart,而不是merge-together,当我们向后工作时。由于 Git 确实 向后工作,Git 的观点与这里的普通人有很大不同。

当你想详细了解这些东西的时候,总是站在Git的角度来看:

and have 6 and 2 different commits each, respectively

这意味着 你当前的 b运行ch(大概是 devel)有六个提交 origin/devel 缺少,并且 origin/devel 有两个你的 b运行ch 没有的提交。如果我们把它画出来,我们会得到这样一张图:

       E--F--G--H--I--J   <-- devel (HEAD)
      /
...--D
      \
       K--------------L   <-- origin/devel

如果我们从 J 向后计算跳数,则需要 7 跳才能到达 D。因此,要命名提交 D,您只需要 HEAD~7——或者您可以 运行 git loggit log --decorate --graph --oneline 并剪切并粘贴提交的原始哈希 ID ,这通常比这样计算跳数更容易。

我们终于准备好变基了

如上所述,当我们使用 git rebase 时,我们是在说 我们的提交中有好东西,但我们不喜欢它们的某些东西。我们 究竟喜欢什么决定了我们运行 的变基类型。 Rebase 是一个 high-powered 工具,有点像命令的 Swiss Army chainsaw,但它的核心是复制提交。

在我们进入细节之前,我们应该注意有一个Git命令复制一个提交,使用起来更简单:git cherry-pick.要使用 git cherry-pick,我们检查/切换到一些 b运行ch:

git switch copy-goes-here

导致:

...--o--o--P--C--o--o   <-- somebranch
      \
       o--o--H   <-- copy-goes-here (HEAD)

其中 o 代表我们并不真正关心其哈希 ID 的提交。同时 commit C 是我们要“复制”的那个。它进行了一些 更改 — 即它的快照,当与它的 parent P 的快照相比时,说 更改此文件 and/or 另一个文件,它所做的更改 是我们想要 提交的更改H .提交 C 的元数据中还有提交日志消息和作者等信息,git cherry-pick 也会复制其中的大部分内容。所以我们现在 运行:

git cherry-pick <hash-of-C>

Git 找到从 PC 的差异,并将 that diff 应用到我们当前的提交 H。从技术上讲,Git 使用与合并相同的内部机制,因为它 运行s two git diff 命令,一个来自 PH 以查看“我们更改了什么”(以保留这些更改),从 PC 以查看“他们更改了什么”(以添加他们的更改)。然后它会合并更改,就像任何合并一样。不过,它所做的特殊事情是创建一个新的提交 C' ,它类似于 C 但是:

  • H作为它的parent;和
  • 我们是 提交者(与元数据中的 作者 分开)

并且它不是合并提交。它不会“记住”提交 C 的哈希 ID(尽管您可以使用 -x 将其添加到日志消息中)。

新副本 C' 获得不同的哈希 ID,我们现在有:

...--o--o--P--C--o--o   <-- somebranch
      \
       o--o--H--C'  <-- copy-goes-here (HEAD)

请注意,Git 像往常一样更新了 b运行ch 名称,因此 copy-goes-here 现在指向 C',后者又指向 H。原始提交 PC 未受干扰(必然如此:任何提交都不能更改)。

A typical rebase 用于,嗯,re-do 一些 b运行ch:

的“基础”
          A--B--C   <-- topic (HEAD)
         /
...--o--*--D--E   <-- origin/topic

关于提交 A-B-C 我们 喜欢 的是 除了 [=提交 A 的 1018=] 是提交 *。我们想在 commi 之后放置 A-B-CE:

          A--B--C   [abandoned]
         /
...--o--*--D--E   <-- origin/topic
               \
                A'-B'-C'  <-- topic (HEAD)

原始的 A-B-C 提交链无法更改,甚至无法销毁,但我们可以简单地 停止使用它们 。 Git 强制名称 topic 最终指向 C' 而不是 C。由于人类不记得哈希 ID,他们将使用名称 topic 来查找最新提交并获得 C' 而不是 C。他们太笨了,他们会认为 C' C。而这通常正是我们想要的。 (在这种情况下,这可能是您想要的,但您可以稍后再做。)

无论如何,要让 rebase 发挥作用,Git 需要知道:

  • 应该Git复制哪些提交?
  • 副本应该Git放在哪里?

git rebase 命令巧妙地从 单个参数 中获取了 两条信息 。我们 运行:

git switch topic

所以我们在右边 b运行ch 开头。然后我们 运行:

git rebase origin/topic

名字origin/topic选择提交E。这是一个提交 Git 不应该复制 ,但是 Git 也使用它通常的 scan-backwards 技巧:Git 不会复制 D,也不是 *,也不是任何较早的提交。

Git复制的提交,如果没有被“不复制”停止,是那些在当前提交处结束的提交,即,提交 CBA* 以及更早的所有内容。但是 origin/topic 告诉 Git:不要复制 * 或任何更早的东西 。因此 停止 Git 复制太多提交。

同时,放置副本的位置 是:在提交 E 之后,由名称 origin/topic 找到。所以副本在 E.

之后

A non-interactive rebase 现在只需直接检出提交 E,然后一次复制一个 ABC,因为如果有,或者字面上有,git cherry-pick。然后它将 name topic——我们所在的 b运行ch 拉到指向 last-copied 提交。

交互式变基做同样的事情,但在每个 cherry-pick.

pick 指令列表中打开编辑器

通过合并进行变基

在你的情况下,你犯了一个错误(不是一个大错误,但如果没有它会更容易):你 Git 将你的 develorigin/devel。即来自:

       E--F--G--H--I--J   <-- devel (HEAD)
      /
...--D
      \
       K--------------L   <-- origin/devel

您有 Git 运行 git merge origin/devel 或同等学历。 (你可能 运行 git pull: Git 新手通常应该 避免 git pull,在我看来,但这只是一个意见。)

结果是这样的:

             E--F--G--H--I--J
            /                \
...--B--C--D                  M   <-- devel (HEAD)
            \                /
             K--------------L   <-- origin/devel

你然后运行git rebase -i HEAD~9。向后计算 9 跳,沿着合并提交 M 的第一个 parent,到达提交 B。因此,您告诉 git rebase -i 不要 复制的提交是提交 B 或任何更早的内容。

Rebase 现在列出它应该 复制的所有提交。如您所见,默认设置是完全删除合并提交。剩余的提交列表是:CD,然后是 EK 之一,然后是该行的其余提交,7 然后沿着另一行提交。提交 M,因为它 合并,从 pick 列表中删除。

如果您不修改列表并让合并操作 运行,它会复制这组提交,一次一个。副本将在提交 B 后进行,因此我们将得到如下内容:

...--B--C'-D'-E'-...-L'  <-- devel (HEAD)
      \
       C--D--...--M   [abandoned]
           \     /
            K---L   <-- origin/devel

不过,git rebase自作聪明,可以的话,直接re-useCD;它可以,所以它会,除非你告诉它不要;但是效果和我这里画的很像。如果你改变pick行,你当然可以得到许多其他效果,我们不知道K'-L'是在D之后还是在结束之前。

如果您更喜欢合并,但是,您可以使用git rebase -i -r。当你这样做时,你会得到一个更复杂的指令 sheet,其中包含 label 命令。这有助于 rebase 代码重建合并。 只是 运行宁一系列cherry-pick命令,Git可以在工作时记住哈希ID,然后运行 git merge 命令。所以它可以产生这个:

             EFI-H'-GJ
            /         \
...--B--C--D           M'  <-- devel (HEAD)
            \         /
             K-------L   <-- origin/devel

如果你愿意,可以单独保留原来的 DK 以及 L,合并 EF 以及 I到一个新的提交中,将 H 复制到 H',将 GJ 合并到一个提交中,然后执行 git merge 合并 GJL 一起构成 M'。 (和往常一样,原件仍将全部保存在存储库中,不受干扰,只是有点难找:ORIG_HEAD 并且 reflogs 会保留它们的哈希 ID 一段时间。)

使用git rebase -i -r <hash-of-D>(或HEAD~7,但我通常发现git log和cut-and-paste在这里更容易)将是一种方法我马上。这完全取决于您想要什么作为您的最终结果,以及您尝试通过一个步骤完成它与多个单独步骤相比如何舒适table。


7Rebase 在内部使用 --topo-order 来确保这一点;没有 --topo-order 会有更多可能的顺序混合行。