【问题标题】:How to git rebase a branch with the onto command?如何使用 on 命令 git rebase 分支?
【发布时间】:2015-07-06 23:41:13
【问题描述】:

我注意到下面的两个 git 命令块有不同的行为,我不明白为什么。

我有一个 A 和一个 B 分支,它们与一个 commit 不同

---COMMIT--- (A)
\
 --- (B)

我想在最新的A 上重新设置B 分支(并在B 分支上提交)

---COMMIT--- (A)
         \
          --- (B)

如果我这样做没问题:

checkout B
rebase A

但如果我这样做:

checkout B
rebase --onto B A

它根本不起作用,没有任何反应。我不明白为什么这两种行为不同。

PhpStorm GIT 客户端使用第二种语法,因此似乎完全被破坏了,这就是我问这个语法问题的原因。

【问题讨论】:

  • 签出B后尝试git rebase --onto A A
  • 经过这么多年从这个问题开始,我认为我们面对这个论点的根本理解问题是语义上的:on 并不意味着“onto”,它意味着“on top of”

标签: git git-rebase


【解决方案1】:

tl;博士

在您的情况下,使用git rebase --ontoA 之上重新设置B 的正确语法是:

git checkout B
git rebase --onto A B^

A之上重新设置B,从B的父提交开始引用B^B~1

如果您对git rebase <branch>git rebase --onto <branch> 之间的区别感兴趣,请继续阅读。

快速:git rebase

git rebase <branch> 将在<branch>可访问的最新提交之上重新设置您当前已签出的分支,由HEAD 引用,但不是 来自HEAD.
这是最常见的变基案例,可以说是需要较少预先计划的案例。

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

在此示例中,FG 是可从 branch 访问但不能从 HEAD 访问的提交。说git rebase branch 将采用D,这是分支点之后的第一个提交,并且重新设置它(即更改其父级)在最新的可到达提交之上来自branch,但不是来自HEAD,即G

精确:带有 2 个参数的 git rebase --onto

git rebase --onto 允许您从特定提交开始。它使您可以精确控制要重新定位的内容和位置。这适用于需要精确的场景。

例如,假设我们需要从E 开始精确地在F 之上重新定义HEAD。我们只想将F 引入我们的工作分支,同时我们不想保留D,因为它包含一些不兼容的更改。

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

在这种情况下,我们会说git rebase --onto F D。这意味着:

F 之上重新设置从HEAD 的父级为D 可访问的提交。

换句话说,E 的父级D 更改为F。那么git rebase --onto 的语法就是git rebase --onto <newparent> <oldparent>

另一个派上用场的场景是,当您想快速从当前分支中删除一些提交而无需执行交互式变基

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

在此示例中,为了从序列中删除 CE,您可以说 git rebase --onto B E,或者在 B 之上重新设置 HEAD,其中旧父级是 E

外科医生:带有 3 个参数的 git rebase --onto

git rebase --onto 在精度方面可以更进一步。事实上,它允许您将任意范围的提交重新设置在另一个之上。

这是一个例子:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

在这种情况下,我们希望在F 之上重新调整确切范围E---H,忽略HEAD 当前指向的位置。我们可以通过说git rebase --onto F D H 来做到这一点,这意味着:

F 的基础上,将父级为D 的提交范围重新设置为H

带有提交范围的git rebase --onto 的语法然后变成git rebase --onto <newparent> <oldparent> <until>。这里的技巧是记住<until> 引用的提交在范围内包含,并且在rebase 完成后将成为新的HEAD

【讨论】:

  • 不错的答案。只是一般情况下的一个小补充:如果范围的两个部分位于不同的分支上,<oldparent> 名称就会分解。通常是:“包括从 <until> 可访问的每个提交,但排除从 <oldparent> 可访问的每个提交。”
  • git rebase --onto <newparent> <oldparent> 是我见过的 --onto 行为的最佳解释!
  • 谢谢!我在--onto 选项上有点挣扎,但这让它一清二楚!我什至不明白我以前是怎么理解的 :D 感谢出色的“教程”:-)
  • 虽然这个答案很好,但我觉得它并没有涵盖所有可能的情况。最后一种语法形式也可以用来表达一种更微妙的变基类型。从 Pro Git(第 2 版)中的示例开始,D 不一定必须是 H 的祖先。相反,D 和 H 也可以是具有共同祖先的提交 - 在这种情况下,Git 将找出它们的共同祖先并从该祖先重播到 H 到 F。
  • 这很有帮助。手册页根本没有解释这些论点。
【解决方案2】:

这是您了解--onto所需要知道的全部内容:

git rebase --onto <newparent> <oldparent>

您在提交上切换父级,但您没有提供提交的 sha,只提供当前(旧)父级的 sha。

【讨论】:

  • 简短易懂。实际上,意识到我必须提供我想要变基的那个的 parent 提交而不是那个提交花了我最长的时间。
  • 重要的细节是您从当前分支中选择一个 oldparent 的子项,因为一个提交可以是许多提交的父级,但是当您将自己限制在当前分支时,提交只能是一个提交的父级.换句话说,父关系在分支上是唯一的,但如果您不指定分支,则不必如此。
  • 注意:需要在分支中,或者添加分支名称作为第三个参数 git rebase --onto
  • 这个答案太棒了,在这个线程中根据需要直截了当
【解决方案3】:

简而言之,给定:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

与(因为--onto 接受一个参数)相同:

git rebase D H --onto F

表示在 F 之上的范围 (D, H] 中重新设置基准。注意该范围是左手排他的。 它是排他的,因为通过键入例如 branch 来指定第一次提交更容易让git 找到来自branch 的第一个分歧提交,即D,这导致H

OP案例

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

可以改为单个命令:

git rebase --onto B A B

这里看起来像错误的是B 的放置,这意味着“将一些提交移动到BB 之上的分支B”。问题是“一些提交”是什么。如果添加-i 标志,您将看到它是HEAD 指向的单个提交。提交被跳过,因为它已经应用于--onto 目标B,所以什么也没有发生。

在分支名称这样重复的任何情况下,该命令都是无意义的。这是因为提交范围将是一些已经在该分支中的提交,并且在 rebase 期间将跳过所有提交。

git rebase &lt;upstream&gt; &lt;branch&gt; --onto &lt;newbase&gt;的进一步解释及适用用法。

git rebase 默认值。

git rebase master

扩展为:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

变基后自动结帐。

当以标准方式使用时,例如:

git checkout branch
git rebase master

您不会注意到在 rebase gitbranch 移动到最近的 rebase 提交并执行 git checkout branch 之后(请参阅 git reflog 历史记录)。有趣的是,当第二个参数是 commit hash 而分支名称 rebase 仍然有效,但没有要移动的分支,所以你最终会进入“分离的 HEAD”而不是被检出到移动的分支。

省略主要的分歧提交。

--onto 中的 master 取自第一个 git rebase 参数。

                   git rebase master
                              /    \
         git rebase --onto master master

因此,它可以是任何其他提交或分支。这样,您可以通过获取最新的提交并保留主要的分歧提交来限制 rebase 提交的数量。

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

HEAD 指向的单个提交变基为master 并最终进入“分离的HEAD”。

避免显式结帐。

默认的 HEADcurrent_branch 参数根据上下文取自您所在的位置。这就是为什么大多数人结帐到他们想要变基的分支的原因。但是,当明确给出第二个 rebase 参数时,您不必在 rebase 之前签出以隐式方式传递它。

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

这意味着您可以从 任何 位置重新提交和分支。 因此,与 rebase 后自动结帐。 您不必在 rebase 之前或之后单独结帐 rebase 分支。

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.

【讨论】:

    【解决方案4】:

    为了更好地理解git rebasegit rebase --onto 之间的区别,最好了解这两个命令的可能行为。 git rebase 允许我们将提交移动到所选分支的顶部。喜欢这里:

    git rebase master
    

    结果是:

    Before                              After
    A---B---C---F---G (master)          A---B---C---F---G (master)
             \                                           \
              D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)
    

    git rebase --onto 更精确。它允许我们选择我们想要开始的特定提交以及我们想要完成的地方。喜欢这里:

    git rebase --onto F D
    

    结果是:

    Before                                    After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD my-branch)                E'---H'---I' (HEAD my-branch)
    

    要了解更多详细信息,我建议您查看我自己关于 git rebase --onto overview 的文章

    【讨论】:

    • @Makyen 当然,我以后会记住的:)
    • 所以,我们可以将git rebase --onto F D 读作将 D 的父级的子级设置为 F,不是吗?
    • @Prihex 是的,我想是的 :)
    • 无论是直接变基还是基于特定的提示,如果旧树已经在 'remotes/origin/branchname' 中创建,您将不得不强制推送任何内容
    【解决方案5】:

    这里的 Git 措辞有点混乱。如果你假装命令看起来像这样可能会有所帮助:

    git rebase --onto=&lt;new_base&gt; &lt;old_base&gt; [&lt;branch&gt;]

    如果我们现在在branch,可以省略:

    git rebase --onto=&lt;new_base&gt; &lt;old_base&gt;

    如果new_baseold_base相同,我们可以省略--onto参数:

    git rebase &lt;new_old_base&gt;

    这听起来可能很奇怪:如果旧基础与新基础相同,您将如何重新设置基础?但是这样想,如果你有一个功能分支foo,它已经(很可能)基于你的main 分支中的一些提交。通过“re-base”,我们只是使提交基于更新。

    (事实上,&lt;old_base&gt; 是我们比较 branch 的东西。如果它是一个分支,那么 git 会寻找一个共同的祖先(另见 --fork-point);如果它是当前分支上的提交,则提交之后使用;如果它是与当前分支没有共同祖先的提交,则使用当前分支的所有提交。&lt;new_base&gt; 也可以是提交。因此,例如,git rebase --onto HEAD~ HEAD 将在旧基 @ 之间进行提交987654335@ 和当前的HEAD 并将它们放在HEAD~ 的顶部,有效地删除最后一次提交。)

    【讨论】:

      【解决方案6】:

      简单地说,git rebase --onto 选择一系列提交并根据作为参数给出的提交重新设置它们。

      阅读git rebase 的手册页,搜索“onto”。例子很好:

      example of --onto option is to rebase part of a branch. If we have the following situation:
      
                                         H---I---J topicB
                                        /
                               E---F---G  topicA
                              /
                 A---B---C---D  master
      
         then the command
      
             git rebase --onto master topicA topicB
      
         would result in:
      
                              H'--I'--J'  topicB
                             /
                             | E---F---G  topicA
                             |/
                 A---B---C---D  master
      

      在这种情况下,您告诉 git 在master 之上将提交从topicA 重新设置为topicB

      【讨论】:

        【解决方案7】:

        对于onto,您需要两个额外的分支。使用该命令,您可以将基于 branchA 的来自 branchB 的提交应用到另一个分支,例如master。在下面的示例中,branchB 是基于 branchA 的,您希望在不应用 branchA 的更改的情况下将 branchB 的更改应用于 master

        o---o (master)
             \
              o---o---o---o (branchA)
                           \
                            o---o (branchB)
        

        通过使用命令:

        checkout branchB
        rebase --onto master branchA 
        

        您将获得以下提交层次结构。

              o'---o' (branchB)
             /
        o---o (master)
             \
              o---o---o---o (branchA)
        

        【讨论】:

        • 你能解释一下吗,如果我们想rebase到master上,它怎么会变成当前分支?如果你这样做 rebase --onto branchA branchB 会不会把整个 master 分支放在 branchA 的头上?
        • 不应该是checkout branchB: rebase --onto master branchA吗?
        • 为什么这被赞成了?这并不像它所说的那样做。
        • 我编辑并修复了答案,因此人们不必先破坏他们的 repo 分支,然后就在此之后来阅读 cmets...?
        【解决方案8】:

        还有另一种情况,git rebase --onto 难以掌握:当您基于对称差异选择器(三个点“...”)导致的提交变基时

        Git 2.24(2019 年第四季度)在管理该案例方面做得更好:

        请参阅commit 414d924commit 4effc5bcommit c0efb4ccommit 2b318aa(2019 年 8 月 27 日)和commit 793ac7ecommit 359eceb(2019 年 8 月 25 日)Denton Liu (Denton-L)
        帮助者:Eric Sunshine (sunshineco)Junio C Hamano (gitster)Ævar Arnfjörð Bjarmason (avar)Johannes Schindelin (dscho)
        请参阅commit 6330209commit c9efc21(2019 年 8 月 27 日)和commit 4336d36(2019 年 8 月 25 日)Ævar Arnfjörð Bjarmason (avar)
        帮助者:Eric Sunshine (sunshineco)Junio C Hamano (gitster)Ævar Arnfjörð Bjarmason (avar)Johannes Schindelin (dscho)
        (由 Junio C Hamano -- gitster -- 合并到 commit 640f9cd,2019 年 9 月 30 日)

        rebase:更多情况下快进--onto

        之前,当我们有下图时,

        A---B---C (master)
             \
              D (side)
        

        运行 'git rebase --onto master... master side' 将导致 D 始终被重新定位,无论如何。

        此时,阅读“What are the differences between double-dot '..' and triple-dot "..." in Git diff commit ranges?

        这里:“master...”指的是master...HEAD,即B:HEAD 是侧面HEAD(当前已签出):您正在重新定位到B
        你在重新定位什么?任何提交 not 在 master 中,并且可以从 side 分支访问:只有一个提交符合该描述:D... 已经在 B 之上!

        同样,在 Git 2.24 之前,这样的 rebase --onto 将导致 D 始终被重新定位,无论如何。

        但是,期望的行为是 rebase 应该注意到这是可快速转发的,然后改为这样做。

        这类似于 OP 的 rebase --onto B A,它什么也没做。

        将检测添加到can_fast_forward,以便可以检测到这种情况并执行快进。
        首先,重写函数使用goto,简化逻辑。
        接下来,由于

        options.upstream &&
        !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)
        

        条件已在 cmd_rebase 中删除,我们在 can_fast_forward.
        特别是,检查upstreamhead 的合并基可以修复t3416 中的失败案例。

        t3416的简图如下:

                F---G topic
               /
          A---B---C---D---E master
        

        失败的命令是

        git rebase --onto master...topic F topic
        

        之前,Git 会看到有一个合并基(Cmaster...topic 的结果),并且合并和上是相同的,因此它会错误地返回 1,表明 我们可以快进。这将导致重新定位的图形为“ABCFG” 当我们期待“ABCG”时。

        rebase --onto C F topic 表示任何提交之后 F,可通过topic HEAD 访问:仅G,而不是F 本身。
        在这种情况下,快进会在重新定位的分支中包含F,这是错误的。

        通过附加逻辑,我们检测到上游和头部的合并基础 是F。由于 on 不是 F,这意味着我们没有重新设置完整的 来自master..topic的提交。
        由于我们排除了一些提交,因此无法执行快进 ,因此我们正确返回 0。

        将“-f”添加到由于此更改而失败的测试用例,因为 他们没想到会快进,因此会强制执行变基。

        【讨论】:

          猜你喜欢
          • 2017-02-12
          • 2019-11-17
          • 1970-01-01
          • 2017-12-22
          • 1970-01-01
          • 1970-01-01
          • 2015-10-31
          • 2016-02-06
          • 2012-04-24
          相关资源
          最近更新 更多