【问题标题】:Is it correct to say that a git rebase is equivalent to a git cherry-pick of certain commits from the other direction?说 git rebase 等同于 git cherry-pick 来自另一个方向的某些提交是否正确?
【发布时间】:2021-01-06 17:58:51
【问题描述】:

我正在努力加强对 git 命令的理解(和交流)。

这样说对吗

git checkout A
git rebase B

完全等价于

git checkout B
git cherry-pick <all_commits_from_common_ancestor_of_<A>_and_<B>_to_<A>>

如果不是,它们在什么情况下会出现分歧?

【问题讨论】:

  • 这能回答你的问题吗? Git cherry pick vs rebase
  • 我确实读过那个问题,但我无法吸收足够的精力来自信地回答我将一个翻译成另一个的问题。

标签: git rebase git-rebase cherry-pick git-cherry-pick


【解决方案1】:

这不是相当正确的。

这里有几个绊脚石。首先,可能没有共同的祖先,或者可能不止一个。 (这是非常次要的:这只是意味着所有提交都被复制,或者所有共同的祖先都被忽略了。)其次,我们可能不会在这里检查 B 指定的提交。第三,一些提交可以省略,根据变基的形式,这可能会有点复杂。最后,复制发生在 detached HEAD 模式下,然后,rebase 将分支名称拉到周围(好像通过git checkout -Bgit switch -C,或git branch -f 后跟git checkout 或@987654332 @)。

要枚举的实际提交取决于 rebase 的 upstream 参数,可以这样指定:

git rebase --onto <target> <upstream>

或:

git rebase <upstream>

如果省略了--onto &lt;target&gt; 选项,则targetupstream 相同。这是被检出的提交(在 detached-HEAD 模式下)。

The rebase documentation 首先建议要枚举的提交是:

upstream..HEAD

(当然是在结帐步骤之前,因为这会移动HEAD)。这并不完全正确,因此当前文档立即进行了一些更正:

这与git log &lt;upstream&gt;..HEAD 显示的提交集相同;或git log 'fork_point'..HEAD,如果--fork-point 处于活动状态(参见下面--fork-point 的描述);或git log HEAD,如果指定了--root 选项。

稍后,它添加了以下内容:

请注意,HEAD 中引入与 HEAD.. 中的提交相同的文本更改的任何提交都将被省略(即,将跳过已在上游接受的具有不同提交消息或时间戳的补丁)。

直到很久以后它才提到完全省略了 merge commits,除非您使用(现已弃用)-p 选项或(Git 2.18 中的新)-r 选项。

不过,这里真正发生的是,Git 使用 git rev-list 的三点语法和 --left-right 模式。1 基本三点语法:

git rev-list upstream...HEAD

枚举从 either 提交可到达的所有提交,但 notboth 提交可到达。在图论方面,这就是对称差。在the gitrevisions documentation 中有简要描述。这会强制修订遍历代码检查可从 HEAD 访问但不是 upstream 可从 upstream 访问的提交但不是HEAD。在执行此操作时,Git 会在每次提交时执行 git patch-id。这让git rebase 可以找到“相同”的提交(就它们的更改而言),因此如果它们已经被挑选到上游分支,则可以忽略它们。

那么,特别是,假设您有:

...--o--*--D--E--B'--F   <-- their-branch
         \
          A--B--C   <-- your-branch (HEAD)

然后你运行git rebase their-branch 来复制你的三个A-B-C 提交到F 之后。变基代码将计算来自ABC 的补丁ID,以及来自DEB'F 的补丁ID。鉴于提交 B' 是您的提交 B 的副本,它可能2 具有相同的补丁 ID。所以 Git 会省略 B 从提交列表中复制。

--fork-point 模式的描述有些倾斜,但您应该首先注意 --fork-point 在某些情况下是默认选项,而--no-fork-point 在其他情况下是默认选项。分叉点模式的工作方式是使用您的 reflogs。有关更多信息,请参阅Git rebase - commit select in fork-point mode

有一个相对较新的--keep-base 选项可以真正进行合并基础计算。您可以使用三点语法直接调用它,也可以使用--keep-base 选项将其打开。

最后,合并提交的遗漏(-r 或您应该避免的 -p 选项除外)发生是因为 Git 从字面上 不能 复制合并。省略合并与运行git rev-list时使用--no-merges选项基本相同。当您使用 -r 选项时,Git 将枚举合并并记下它们,并将使用更新颖的交互式脚本模式来重新执行合并。也就是说,给定这样的图形片段:

...--o--*-------F   <-- their-branch
         \
          \   B
           \ / \
            A   D--E   <-- your-branch (HEAD)
             \ /
              C

git rebase -r 将产生:

                      B'
                     / \
                    A'  G--E'   <-- your-branch (HEAD)
                   / \ /
                  /   C'
                 /
...--o--*-------F   <-- their-branch
         \
          \   B
           \ / \
            A   D--E   [abandoned]
             \ /
              C

其中新的合并提交G 是通过在提交B'C' 上运行git merge 生成的。如果您使用git merge --no-commitD 设置为evil merge,则在此重新合并期间将失去邪恶。剩余的提交,标有主要后缀(A' 等),通过复制完成,使用底层的cherry-pick 机制。3


1在过去,git rebase 是几个 shell 脚本,其中一个确实像这样运行git rev-list,尽管我记得它使用了--right-only --cherry-pick。从那时起它就被用 C 重写了,现在它......更复杂了。 :-)

2它是否具有相同的patch-ID取决于是否有人在复制它时必须对其进行修改。详情请见the git patch-id documentation

3在旧版本的 Git 中,默认实际上是使用git format-patchgit am,或内部等效项。这在物理上也无法复制合并,并且错过了cherry-pick 检测到的一些重命名案例。在添加新的-r 选项期间,所有设置都设置为将默认设置切换为使用cherry-pick,并且最近(2.25ish?)成为了新的默认设置。

【讨论】:

  • 嘿,感谢您提供如此深入的回答。这里有一些概念,当我更精通 git 时,我将不得不回顾一下。根本不想过分简化您的答案,而是回覆事情以检查我的理解。我将我介绍的两种方法之间的差异分为 3 类。 (1) 先决条件和意外状态例如可能没有共同的祖先,或者结帐可能失败。说得通;但我认为这些不是典型的“基本的、快乐的道路场景”。
  • (2) 它是如何到达那里的。 我看到这两种方法采取不同的步骤来达到“终点”。对于一个只想“到达那里”的初学者来说,可能没有任何区别,除非有冲突。 (3) Options。我只是想问最基本的用法,但我发现给定高级选项,这两种方法也可能存在分歧。
  • 是的,特别奇怪的错误,如结账失败(第 1 部分)并不常见。引起人们注意的第一个常见故障是,当在其中进行合并的变基时,他们没有意识到将复制多少提交(合并的双方!)并且合并被两个“腿”删除变平了,但是您的公式涵盖了大部分内容-只是不包括“拖放合并”情况。使用 fork-point 和 patch-ID 技巧会出现一个更罕见的故障:特别是您可能会“丢失”您希望通过这种方式复制的提交。这些都是第 2 部分的一部分,即如何到达那里。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-18
  • 1970-01-01
  • 2016-10-24
  • 2012-11-05
  • 2014-07-08
  • 2018-04-17
  • 2013-03-09
相关资源
最近更新 更多