我无法理解git rebase --onto的行为

时间:2015-04-28 08:20:23

标签: git git-rebase

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

我有一个A和B分支,它与一个提交分歧

---BRANCH A-------COMMIT1-----
\--BRANCH B--

我想在最新的A上重新定义B分支(并且在B分支上有commit1)

---BRANCH A-------COMMIT1-----
                          \--BRANCH B--

如果我这样做没问题:

checkout B
rebase A

但如果我这样做:

checkout B
rebase --onto B A

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

Phpstorm git客户端使用第二种语法,因此在我看来完全崩溃了,这就是我要求这种语法问题的原因。

8 个答案:

答案 0 :(得分:302)

TL;博士

在您的情况下使用BA之上重新定位git rebase --onto的正确语法是:

git checkout B
git rebase --onto A B^

B之上重新定位A,从B 的父级提交开始,B^ 引用B~1或{{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)

在此示例中,FG是可以从branch但无法从HEAD访问的提交。假设git rebase branch将采用D,这是分支点之后的第一次提交,而将其(即更改其父)置于之上最新提交可以从branch但不能从HEAD到达,即G

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

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

例如,让我们想象一下,我们需要从HEAD开始精确地在F之上重新定位E。我们只对将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。这意味着:

  

HEAD的父级D可以在F之上重新启动。

换句话说,E的父D更改为Fgit rebase --onto的语法是git rebase --onto <newparent> <oldparent>

另一种方便的方法就是当你想要从当前分支中快速删除一些提交而不必进行交互式rebase 时:

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

在此示例中,为了从序列中删除CE,您需要git rebase --onto B E,或在HEAD之上重新定位B老父母是E

外科医生:git rebase - 以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来表达:

  

将父级为D的提交范围重新调整为H之上的F

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

答案 1 :(得分:34)

理解--onto

,这是您需要了解的全部内容
git rebase --onto <newparent> <oldparent>

您在提交时切换父级,但您不提供提交的sha,只提供它的当前(旧)父级的sha。

答案 2 :(得分:8)

简单地说,git rebase --onto选择一系列提交并在作为参数给出的提交中重新定位它们。

阅读git rebase的手册页,搜索&#34;到&#34;。这些例子非常好:

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之上将提交从topicB重新绑定到master

答案 3 :(得分: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 --onto overview的文章

答案 4 :(得分:5)

很快就输入:

      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)中对提交进行重新基准设置。请注意,该范围是左手排他的。这是排他性的,因为通过键入例如{{1} },让branchgit中找到第一个发散的提交,即branch导致D

OP案件

H

可以更改为单个命令:

    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 之后,将git移至最近重新构建的基础提交并进行了branch(请参阅git checkout branch的历史记录)。当第二个参数为 commit hash 时,有趣的是,分支名称重设仍然有效,但是没有分支可移动,因此您最终以“分离的HEAD”开头,而不是检出到移动的分支。

忽略主要分歧提交。

git reflog中的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并最终以“分离的HEAD”结尾。

避免显式检出。

默认的masterHEAD参数是从您所处的位置上下文中获取的。这就是为什么大多数人结帐到他们想作为基准的分支的原因。但是,当显式给出第二个rebase参数时,您不必在重新设置基数前以隐式方式传递它。

current_branch

这意味着您可以在任何位置重新建立提交和分支。 因此,与变基后自动检出一起使用。您不必在变基之前或之后分别检出变基分支。

(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.

答案 5 :(得分:3)

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

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

使用命令:

checkout master
rebase --onto branchA branchB

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

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

答案 6 :(得分:0)

在另一种情况下,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 c9efc21commit 4336d36(2019年8月27日)和Ævar Arnfjörð Bjarmason (avar)(2019年8月25日)。
帮助者: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?

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

在这里:“ master...master...HEAD,它是B:HEAD是side HEAD(当前已检出):您将重新部署到B。 /> 你在变什么master中的所有 not 提交都可以通过side分支访问:只有一个符合该描述的提交:D ...已经在{{1}的顶部}!

同样,在Git 2.24之前,这样的B将导致rebase --onto始终被重新设置基准,无论如何。

  

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

这类似于OP的D,后者什么也没做。

  

rebase --onto B A添加检测,以便可以检测到这种情况并执行快进。
  首先,使用gotos重写该函数,以简化逻辑。
  接下来,因为

can_fast_forward
     

条件已在options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) 中删除,我们在   cmd_rebase
  特别是,检查can_fast_forwardupstream的合并基础可以解决head中失败的情况。

     

t3416的缩写图如下:

t3416

失败的命令是

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

在此之前,Git会看到有一个合并基础(git rebase --onto master...topic F topic ,结果为C),并且合并和合并是相同的,因此它将错误地返回1,表明   我们可以快进。这将导致重新绘制的图形为“ master...topic”   当我们期望“ ABCFG”时。

ABCG表示在{em} rebase --onto C F topic之后的所有 F提交:仅topic,而不是G本身。
在这种情况下,快进将在重新建立基础的分支中包含F,这是错误的。

  

通过附加逻辑,我们检测到上游和头部的合并基础   是F。由于on不是F,这意味着我们不会对整个   从F提交。
  由于我们排除了某些提交,因此无法执行快进 ,因此我们正确返回了0。

     

将'master..topic'添加到测试由于此更改而失败的案例中,因为   他们并没有期待快速前进,因此不得不强制进行重新设基。

答案 7 :(得分:0)

这里的Git措辞有点令人困惑。如果您假装命令如下所示,可能会有所帮助:

git rebase --onto=<new_base> <old_base> [<branch>]

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

git rebase --onto=<new_base> <old_base>

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

git rebase <new_old_base>

这听起来很奇怪:如果旧基础与新基础相同,您如何重新定基础?但是请这样考虑,如果您有一个功能分支foo,它已经(可能)已经基于您的main分支中的某些提交。通过“重新基准化”,我们只是在基于最新消息的情况下进行提交。

(实际上,<old_base>是我们比较branch的对象。如果它是分支,则git寻找一个共同的祖先(另请参阅--fork-point);如果是提交在当前分支上,将使用此之后的提交;如果它是与当前分支没有共同祖先的提交,那么将使用当前分支的所有提交。<new_base>也可以是一个提交。因此,例如,{{ 1}}将在旧的基础git rebase --onto HEAD~ HEAD与当前的HEAD之间进行提交,并将它们放置在HEAD的顶部,从而有效地删除最后的提交。)