为什么 rebased commit ids 不同于 cherry-picked ids?

Why do rebased commit ids differ from cherry-picked ids?

这个问题源于一个令人讨厌的小合并冲突,当我不小心从我的跟踪分支中挑选到我的跟踪分支而不是重新定位它时,我自己陷入了这个冲突。修复它很容易,但我仍然想弄明白为什么它首先会被发布。

假设我有以下分支(tracking 基于 tracked),其中包含一系列在括号中带有哈希的提交,箭头指向父提交。

tracked: a(123) <- b(234) <- c(345)

tracking: a(123) <- b(234) <- c(345)

假设一个新的提交 d 提交 ID 456 进入 tracked 分支状态如下:

tracked: a(123) <- b(234) <- c(345) <- d(456)

tracking: a(123) <- b(234) <- c(345)

我现在 cherry-pick 456tracking 导致以下跟踪状态:

tracking: a(123) <- b(234) <- c(345) <- d(somethingnot456)

但是,如果我只是执行一个 git rebase tracked,它会是:

tracking: a(123) <- b(234) <- c(345) <- d(456)

为什么上面的 id 不同?

我已经看到很多关于 rebasecherry-pick 的问题,但我还没有找到这个特定问题的答案。谢谢。

git rebase will "reapply commits on top of another base tip" while git cherry-pick 将 "apply the changes introduced by some existing commits".

换句话说,rebase 将直接在当前分支上应用 commits,而 cherry picking 将应用来自提交的 changes到您当前的分支(然后进行新的提交)。


rebase 文档甚至在概要之后明确说明了这一点:

If is specified, git rebase will perform an automatic git checkout <branch> before doing anything else. Otherwise it remains on the current branch.

因此,当使用 git rebase tracked 时,您实际上只是执行了 git checkout tracked 并快速转发了您的 tracking 分支...

Rebase 和(重复的)cherry-pick 本质上是同一件事,但它们并非 100% 完全 同一件事。在这种特殊情况下,关键是被复制的东西,嗯,什么都没有。

让我按照我更喜欢表达 Git 图形片段的方式重新绘制您的示例。而不是:

tracked: a(123) <- b(234) <- c(345)

tracking: a(123) <- b(234) <- c(345)

让我们把它画成:

A(123) <- B(234) <- C(345)   <-- tracking, tracked

因为毕竟每个提交都是独一无二的:只有一份 A,一份 B,一份 C,很快就会成为 D。同时,两个标签(trackingtracked)都指向提交 C,其哈希为 345whatever.

现在您将新提交 D(456) 添加到 tracked(因此 tracking 仍然指向 C(345):

A(123) <- B(234) <- C(345)          <-- tracking
                          \
                           D(456)   <-- tracked

Cherry-pick 总是复制

git cherry-pick <commit> 的本质是:

  1. 将给定的提交与其父提交进行比较(因此,D vs C
  2. 在当前分支(tracking)上应用相同的更改,然后
  3. 使用相同的 消息 进行 new 提交,但 ID 不同。

这当然是你以前见过的。您当前的分支 (tracking) 获取新提交 D'D 的副本,但编号不同。

Rebase 发现哪些提交需要被复制

另一方面,

Rebase 的工作方式是获取当前分支 (tracking) 和 <upstream> 分支 (tracked) 所做的所有提交的列表不是。具体来说,这些是 git rev-list 将列出的提交:

$ git rev-list tracked..tracking
$ 

没有这样的提交,从图中很容易看出。我们甚至不需要哈希:

A <- B <- C     <-- tracking
           \
            D   <-- tracked

tracking 开始,我们沿着标记提交的箭头向左移动,但是从 tracked 开始,我们再次沿着箭头向左移动,un标记提交。由于 D 返回到 C,这会取消标记所有内容,我们根本不会复制任何内容。

如果我们在 tracking 上提交了 未被 跟踪:

A--B--C--E   <-- tracking
       \
        D    <-- tracked

然后 rebase 将复制 E,进行新的(不同 ID)提交 E'E 的副本将在 D 之后,像这样:

A--B--C--E   <-- tracking
       \
        D    <-- tracked
         \
          E' [rebase in progress]

然后,rebase 移动分支标签

一旦 git rebase 完成所有复制,它会记下它停止的位置 — 在 D,如果没有要复制的东西;在 E',或者甚至 F'G' 或任何其他地方,如果 提交复制——然后它剥离旧的分支标签(tracking) 关闭并将其粘贴到新点:

A--B--C--E   [abandoned]
       \
        D    <-- tracked
         \
          E' <-- tracking

当没有 E 可复制时,我们改为:

A--B--C
       \
        D   <-- tracked, tracking

即,两个分支标签现在都指向提交 D,根本没有被复制。 (也没有理由保留图中向下的小腿,也没有要放弃的提交——放弃 E 不会放弃 C,因为 C 是可找到的来自 D.)