我不明白git rebase --onto的行为


169

我注意到以下git命令的两个块具有不同的行为,我不理解为什么。

我有一个分支A与一个B分支commit

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

我想以B最新的分支为基础A(并在分支上提交B

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

如果我做的话没问题:

checkout B
rebase A

但是,如果我这样做:

checkout B
rebase --onto B A

它根本不起作用,什么也没发生。我不明白为什么两种行为是不同的。

phpstorm git client使用第二种语法,对我来说似乎完全坏了,这就是为什么我要这个语法问题。


Answers:


412

tl; dr

在您的情况下B基于A使用的正确语法git rebase --onto是:

git checkout B
git rebase --onto A B^

底垫B的顶部A从起始提交即父B与引用的B^B~1

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

快速:git rebase

git rebase <branch>是要重订当前已签出的分支,通过引用HEAD,在上面的最新承诺是可达的距离<branch>,但HEAD
这是重新部署的最常见情况,并且可以说是需要较少的预先计划的情况。

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

在这个例子中 F and G是可从branch但无法访问的提交HEAD。说git rebase branch将采取D,那是分支点后的第一个承诺,和重订它(即改变其母公司)上的最新顶级提交从可达branch但不从HEAD,即G

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

git rebase --onto允许您从特定的提交开始进行基础调整。它使您可以精确控制要重新设置的内容和位置。这适用于需要精确的情况。

例如,让我们假设我们需要从开始HEAD精确地重新建立基础。我们只想加入我们的工作分支,而与此同时,我们不想保留它,因为它包含一些不兼容的更改。FEFD

          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。这表示:

重新确定HEAD其父级D位于之上的可达的提交F

换一种说法, 将的父E更改DFgit 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,或重订HEAD之上B的老父母在何处E

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

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)

在这种情况下,我们要变基的确切范围E---H之上F,忽略了其中HEAD目前是指向。我们可以这样说git rebase --onto F D H,这意味着:

调整基线提交的,其父母为范围DH之上F

然后,git rebase --onto带有一定范围的提交的语法变为git rebase --onto <newparent> <oldparent> <until>。这里的窍门是要记住,引用的提交<until>包含在范围内,并且在完成基准调整后将成为新的提交HEAD


1
好答案。对于一般情况,这只是一个很小的补充:<oldparent>如果范围的两个部分在不同的分支上,则名称会分解。通常是:“包括可从中到达的<until>每个提交,但排除可从中到达的每个提交<oldparent>。”
musiKk 2015年

50
git rebase --onto <newparent> <oldparent>是我见过的行为的最佳解释!
朗科特

4
谢谢!我在这个--onto选项上有点挣扎,但这使它变得非常清晰!我什至不明白我以前怎么也听不懂:D感谢出色的“教程” :-)
grongor

3
虽然这个答案很好,但我认为它并没有涵盖所有可能的情况。最后一种语法形式也可以用来表示更细微的rebase类型。以Pro Git(第二版)中的示例为例,D不必一定是H的祖先。相反,D和H也可以是一个共同祖先的提交-在这种情况下,Git会找出他们的共同祖先并从那个祖先重播到H,然后回到
F。– Pastafarian

1
这很有帮助。手册页根本没有解释参数。
a544jh

61

这就是您需要了解的所有内容--onto

git rebase --onto <newparent> <oldparent>

您要在提交上切换父级,但不提供提交的影子,而仅提供其当前(旧)父代的影子。


4
简短易行。实际上,意识到我必须提供我想作为基准的那个的提交而不是那个提交,这花费了我最长的时间。
Antoniossss

1
重要的细节是您从当前分支中选择一个老父母的孩子,因为一个提交可以成为许多提交的父项,但是当您将自己限制为当前分支时,则提交只能是一个提交的父项。换句话说,父关系船在分支上是唯一的,但是如果您未指定分支,则不必是唯一的。
Trismegistos

2
注意:您需要在分支中,或将分支名称添加为第三个参数git rebase --onto <newparent> <oldparent> <feature-branch>
Jason Portnoy

1
这个答案是惊人的,直截了当地
John Culviner19年

13

简短地说,鉴于:

      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找到第一个不同的提交,branchD导致H

OP案

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

git checkout B
git rebase --onto B A

可以更改为单个命令:

git rebase --onto B A B

这里看起来像错误的地方是它的位置B,意思是“移动一些提交,从而导致分支B在顶部B”。问题是什么是“某些提交”。如果添加-i标志,您将看到它是指向的单次提交HEAD。由于已将提交应用于--onto目标B,因此跳过该提交,因此没有任何反应。

在这样重复分支名称的情况下,该命令都是无意义的。这是因为提交范围将是该分支中已经存在的某些提交,并且在重新设置基准期间将全部跳过这些提交。

的进一步解释和适用git rebase <upstream> <branch> --onto <newbase>

git rebase 默认值。

git rebase master

扩展为:

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

变基后自动结帐。

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

git checkout branch
git rebase master

您不会注意到,在gitbranch基准迁移到最近的基于基准的提交之后,确实会这样做git checkout branch(请参阅git reflog历史记录)。当第二个参数是commit hash而不是分支名称rebase时,仍然很有趣,但是没有分支可移动,因此您最终以“ detached HEAD”结尾,而不是检出到移动的分支。

忽略主要分歧提交。

master--onto从第一个拍摄git rebase参数。

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

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

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

HEAD重新master建立指向的单个提交,并以“分离的HEAD”结尾。

避免显式检出。

默认HEADcurrent_branch参数是从上下文中从您所处的位置获取的。这就是为什么大多数人结帐到他们想作为基准的分支的原因。但是,当显式给出第二个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.

这意味着您可以在任何地方重新建立提交和分支。因此,与重新设置基准点后的自动结帐一起在重新设置基准之前或之后,您不必分别检出已重新定义的分支。

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

8

简单的说, 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将提交的基础从topicA更改topicBmaster


8

为了更好地理解两者之间的区别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的文章-概述


@Makyen当然,我会在未来牢记这一点:)
womanonrails

所以,我们可以看到git rebase --onto F D作为德的母公司为F的一组孩子,我们不能?
Prihex

2

因为onto您需要另外两个分支。使用该命令,您可以将branchB基于的提交应用于branchA另一个分支,例如master。在下面的示例中,您branchB基于,branchA而您想要应用branchBon master的更改而不应用的更改branchA

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)

1
您能否再解释一下,如果我们想基于master,它将如何成为当前分支?如果这样做rebase --onto branchA branchB,那么会将整个master分支放在branchA的头上吗?
聚合酶

8
这不是checkout branchB: rebase --onto master branchA吗?
goldenratio

4
为什么对此表示反对?这并没有做到它所说的。
De Novo

我编辑和固定的答案,所以人们不必先打破他们的回购分支机构和刚刚之后来读取评论...🙄
Kamafeather

0

还有一种情况git rebase --onto很难理解:当您基于对称差异选择器(三个点' ...')产生的提交时

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

请参阅Denton Liu(提交414d924提交4effc5b提交c0efb4c提交2b318aa(2019年8月27日)和提交793ac7e提交359eceb(2019年8月25日。 帮助按:埃里克阳光(JUNIOÇ滨野(ÆvarArnfjörðBjarmason( )约翰内斯Schindelin( ) 犯6330209提交c9efc21(2019年8月27日),并提交4336d36通过(2019年8月25日)ÆvarArnfjörðBjarmason( Denton-L
sunshinecogitsteravardscho
avar
帮助按:JUNIOÇ滨野(ÆvarArnfjörðBjarmason( )约翰内斯Schindelin( ) (通过合并JUNIOÇ滨野- -提交640f9cd,2019年9月30日)埃里克阳光(sunshinecogitsteravardscho
gitster

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

以前,当我们有以下图形时,

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

无论如何,运行' git rebase --onto master... master side'都会导致D始终重新设置基础。

此时,请阅读“ Git diff提交范围中的双点..'和三点之间有什么区别...

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

在这里:“ master...”是指master...HEAD,它是B:HEAD是侧面HEAD(当前已检出):您正在重新定位到B
你在变什么 任何提交都不 master中并且可以从side分支访问的提交:只有一个适合该描述的提交:D...已经在B!的顶部了。

同样,在Git 2.24之前, rebase --onto无论如何都会导致D始终重新设置基础。

但是,所需的行为是 变基应该注意这是可以快速转发的,而应该这样做。

这类似于 rebase --onto B A OP,没有执行任何操作。

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

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会看到有一个合并基(C,的结果master...topic),并且合并和合并是相同的,因此它将错误地返回1,表明我们可以快进。ABCFG当我们期望' ABCG' 时,这将导致重定基图为' '。

A rebase --onto C F topic表示HEAD 之后 F可到达的任何提交topicG仅此而已,而不是F其本身。
在这种情况下,快进将包含F在重新创建的分支中,这是错误的。

使用附加逻辑,我们检测到上游和头的合并基为F。由于进入不是F,这意味着我们不会基于进行全部提交master..topic
由于我们排除了某些提交,因此快进不能执行,因此我们正确地返回0。

将' -f' 添加到由于此更改而失败的测试用例中,因为它们不希望快速前进,因此必须进行重新设置。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.