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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29914052/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 03:08:36  来源:igfitidea点击:

I can't understand the behaviour of git rebase --onto

gitgit-rebase

提问by Xmanoux

I have noticed that the two blocks of following git commands have different behaviours and I don't understand why.

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

I have a Aand a Bbranch that diverge with one commit

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

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

I want to rebase Bbranch on the lastest A(and have the commit on the Bbranch)

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

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

No problem if I do:

如果我这样做没问题:

checkout B
rebase A

But if I do:

但如果我这样做:

checkout B
rebase --onto B A

It doesn't work at all, nothing happens. I don't understand why the two behaviours are different.

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

Phpstorm git client use the second syntax, and so seems to me completely broken, that's why I ask for this syntax issue.

Phpstorm git 客户端使用第二种语法,所以在我看来完全坏了,这就是我问这个语法问题的原因。

回答by Enrico Campidoglio

tl;dr

tl;博士

The correct syntax to rebase Bon top of Ausing git rebase --ontoin your case is:

正确的语法变基B之上A使用git rebase --onto你的情况是:

git checkout B
git rebase --onto A B^

or rebase Bon top of Astarting from the commit that is the parent of Breferenced with B^or B~1.

从作为引用的父级的提交开始的基础变基BABB^B~1

If you're interested in the difference between git rebase <branch>and git rebase --onto <branch>read on.

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

The Quick: git rebase

快速:git rebase

git rebase <branch>is going to rebase the branch you currently have checked out, referenced by HEAD, on top of the latest commit that is reachablefrom <branch>but notfrom HEAD.
This is the most common case of rebasing and arguably the one that requires less planning up front.

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)

In this example, Fand Gare commits that are reachable from branchbut not from HEAD. Saying git rebase branchwill take D, that is the first commit after the branching point, and rebase it(i.e. change its parent) on top of the latest commit reachable from branchbut not from HEAD, that is G.

在这个例子中,FG是可从branch但不能从访问的提交HEAD。说git rebase branch将采取D,这是分支点之后的第一次提交,并将其变基(即更改其父项)基于可从branch但不能从到达的最新提交HEAD,即G

The Precise: git rebase --onto with 2 arguments

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

git rebase --ontoallows you to rebase starting from a specific commit. It grants you exact control over what is being rebased and where. This is for scenarios where you need to be precise.

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

For example, let's imagine that we need to rebase HEADprecisely on top of Fstarting from E. We're only interested in bringing Finto our working branch while, at the same time, we don't want to keep Dbecause it contains some incompatible changes.

例如,让我们想象一下,我们需要HEADFE. 我们只对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)

In this case, we would say git rebase --onto F D. This means:

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

Rebase the commit reachable from HEADwhose parent is Don top of F.

重新确定可从HEAD其父级DF.

In other words, change the parentof Efrom Dto F. The syntax of git rebase --ontois then git rebase --onto <newparent> <oldparent>.

换句话说,改变父母E,从DF。的语法git rebase --onto是 then git rebase --onto <newparent> <oldparent>

Another scenario where this comes in handy is when you want to quickly remove some commits from the current branch without having to do an interactive rebase:

另一个派上用场的情况是,当您想快速从当前分支中删除一些提交而无需进行交互式 rebase 时

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

In this example, in order to remove Cand Efrom the sequence you would say git rebase --onto B E, or rebase HEADon top of Bwhere the old parent was E.

在这个例子中,为了去除CE来自顺序,你会说git rebase --onto B E,或重订HEAD之上B的老父母在何处E

The Surgeon: git rebase --onto with 3 arguments

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

git rebase --ontocan go one step further in terms of precision. In fact, it allows you to rebase an arbitrary range of commitson top of another one.

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

Here's an example:

下面是一个例子:

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

In this case, we want to rebase the exact range E---Hon top of F, ignoring where HEADis currently pointing to. We can do that by saying git rebase --onto F D H, which means:

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

Rebase the range of commits whose parent is Dup to Hon top of F.

将父级D最高的提交范围重新H设置为F.

The syntax of git rebase --ontowith a range of commitsthen becomes git rebase --onto <newparent> <oldparent> <until>. The trick here is remembering that the commit referenced by <until>is includedin the range and will become the new HEADafter the rebase is complete.

git rebase --ontowith a range of commits的语法就变成了git rebase --onto <newparent> <oldparent> <until>. 这里的技巧是要记住的是,提交通过引用<until>包含在范围,将成为新的HEAD底垫完成后。

回答by sEver

This is all you need to know to understand --onto:

这是您需要了解的全部内容--onto

git rebase --onto <newparent> <oldparent>

You're switching a parent on a commit, but you're not providing the sha of the commit, only the sha of it's current (old) parent.

您在提交时切换父项,但没有提供提交的 sha,仅提供其当前(旧)父项的 sha。

回答by WloHu

Put shortly, given:

简而言之,给定:

      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

Which is the same as (because --ontotakes one argument):

这与(因为--onto需要一个参数)相同:

git rebase D H --onto F

Means rebase commits in range (D, H] on top of F. Notice the range is left-hand exclusive.It's exclusive because it's easier to specify 1st commit by typing e.g. branchto let gitfind the 1st diverged commit from branchi.e. Dwhich leads to H.

表示在 F 之上的范围 (D, H] 内的 rebase 提交。请注意,该范围是左手独占的。它是独占的,因为通过键入 egbranch来指定第 1 次提交更容易让我们git找到与branchie不同的第 1 次提交,D这导致H.

OP case

操作案例

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

git checkout B
git rebase --onto B A

Can be changed to single command:

可以改为单个命令:

git rebase --onto B A B

What looks like error here is placement of Bwhich means "move some commits which lead to branch Bon top of B". The questions is what "some commits" are. If you add -iflag you will see it is single commit pointed by HEAD. The commit is skipped because it is already applied to --ontotarget Band so nothing happens.

什么样子的错误这里是放置的B意思是“将一些提交从而导致分支B之上B”。问题是“一些提交”是什么。如果添加-i标志,您将看到它是由HEAD. 提交被跳过,因为它已经应用于--onto目标B,所以没有任何反应。

The command is nonsense in any case where branch name is repeated like that. This is because the range of commits will be some commits which are already in that branch and during rebase all of them will be skipped.

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

Further explanation and applicable usage of git rebase <upstream> <branch> --onto <newbase>.

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

git rebasedefaults.

git rebase默认值。

git rebase master

Expands to either :

扩展为:

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

Automatic checkout after rebase.

变基后自动结帐。

When used in standard way, like:

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

git checkout branch
git rebase master

You won't notice that after rebase gitmoves branchto most recently rebased commit and does git checkout branch(see git refloghistory). What is interesting when 2nd argument is commit hashinstead branch name rebase still works but there is no branch to move so you end up in "detached HEAD" instead being checked out to moved branch.

你不会注意到在 rebasegit移动branch到最近 rebase 提交之后git checkout branch(见git reflog历史)。当第二个参数是提交哈希而不是分支名称 rebase时,有趣的是没有分支可以移动,因此您最终会进入“分离的 HEAD”,而不是被检出到移动的分支。

Omit primary diverged commits.

省略主要的分歧提交。

The masterin --ontois taken from 1st git rebaseargument.

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

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

So practicaly it can be any other commit or branch. This way you can limit number of rebase commits by taking the latest ones and leaving primary diverged commits.

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

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

Will rebase single commit pointed by HEADto masterand end up in "detached HEAD".

将重订单提交尖HEADmaster和“分离的头”而告终。

Avoid explicit checkouts.

避免显式结帐。

The default HEADor current_branchargument is contextually taken from place you're in. This is why most people checkout to branch which they want to rebase. But when 2nd rebase argument is given explicitly you don't have to checkout before rebase to pass it in implicit way.

默认值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.

This means you can rebase commits and branches from anyplace. So together with Automatic checkout after rebase.you don't have to separately checkout rebased branch before or after rebase.

这意味着您可以从任何地方重新设置提交和分支。因此,与变基后的自动结帐一起使用您不必在 rebase 之前或之后单独检出 rebase 分支。

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

回答by Gauthier

Simply put, git rebase --ontoselects a range of commits and rebases them on the commit given as parameter.

简而言之,git rebase --onto选择一系列提交并根据作为参数给出的提交将它们变基。

Read the man pages for git rebase, search for "onto". The examples are very good:

阅读 的手册页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

In this case you tell git to rebase the commits from topicAto topicBon top of master.

在这种情况下,您告诉 git 将提交从topicA到 重新设置为topicB基于master.

回答by womanonrails

To better understand difference between git rebaseand git rebase --ontoit is good to know what are the possible behaviors for both commands. git rebaseallow us to move our commits on top of the selected branch. Like here:

为了更好地理解之间的区别git rebasegit rebase --onto这是好事,知道什么是两个命令的可能行为。git rebase允许我们将提交移动到所选分支的顶部。像这儿:

git rebase master

and the result is:

结果是:

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 --ontois more precises. It allows us to choose specific commit where we want to start and also where we want to finish. Like here:

git rebase --onto更精确。它允许我们选择我们想要开始和结束的特定提交。像这儿:

git rebase --onto F D

and the result is:

结果是:

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)

To get more details I recommend you to check out my own article about git rebase --onto overview

要获得更多详细信息,我建议您查看我自己关于git rebase --onto 概述的文章

回答by Michael Mairegger

For ontoyou need two additional branches. With that command you can apply commits from branchBthat are based on branchAonto another branch e.g. master. In the sample below branchBis based on branchAand you want to apply the changes of branchBon masterwithout applying the changes of branchA.

因为onto你需要两个额外的分支。使用该命令,您可以将branchB基于的提交应用branchA到另一个分支上,例如master. 在下面的示例branchB是基于branchA和要应用的改变branchBmaster,而不应用的变化branchA

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

by using the commands:

通过使用命令:

checkout branchB
rebase --onto master branchA 

you will get following commit hierarchy.

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

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

回答by VonC

There is another case where git rebase --ontois hard to grasp: when you rebase onto a commit resulting of a symmetric difference selector (the three dots '...')

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

Git 2.24 (Q4 2019) does a better job of managing that case:

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

See commit 414d924, commit 4effc5b, commit c0efb4c, commit 2b318aa(27 Aug 2019), and commit 793ac7e, commit 359eceb(25 Aug 2019) by Denton Liu (Denton-L).
Helped-by: Eric Sunshine (sunshineco), Junio C Hamano (gitster), ?var Arnfj?re Bjarmason (avar), and Johannes Schindelin (dscho).
See commit 6330209, commit c9efc21(27 Aug 2019), and commit 4336d36(25 Aug 2019) by ?var Arnfj?re Bjarmason (avar).
Helped-by: Eric Sunshine (sunshineco), Junio C Hamano (gitster), ?var Arnfj?re Bjarmason (avar), and Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster--in commit 640f9cd, 30 Sep 2019)

请参阅提交 414d924提交 4effc5b提交 c0efb4c提交 2b318aa(2019 年 8 月 27 日)和提交 793ac7e提交 359eceb(2019 年 8 月 25 日)由Denton Liu ( Denton-L)
帮助-通过:埃里克·阳光(sunshinecoJUNIOÇ滨野(gitsterVAR Arnfj重新Bjarmason(? )avar以及约翰内斯Schindelin( )dscho
请参阅提交 6330209提交 c9efc21(2019 年 8 月 27 日)和提交 4336d36(2019 年 8 月 25 日),作者是?var Arnfj?re Bjarmason ( avar).
帮助-通过:埃里克·阳光(sunshinecoJUNIOÇ滨野(gitsterVAR Arnfj重新Bjarmason(? )avar以及约翰内斯Schindelin( )dscho
(由Junio C gitsterHamano合并-- --提交 640f9cd,2019 年 9 月 30 日)

rebase: fast-forward --ontoin more cases

Before, when we had the following graph,

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

running 'git rebase --onto master... master side' would result in Dbeing always rebased, no matter what.

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

之前,当我们有下图时,

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

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

At this point, read "What are the differences between double-dot '..' and triple-dot "..." in Git diff commit ranges?"

此时,请阅读“ Git 差异提交范围中的双点 ' ..' 和三点之间的区别是什么” ...“?

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

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

Here: "master..." refers to master...HEAD, which is B: HEAD is side HEAD (currently checked out): you are rebasing onto B.
What are you rebasing? Any commit notin master, and reachable from sidebranch: there is only one commit fitting that description: D... which is already on top of B!

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

Again, before Git 2.24, such a rebase --ontowould result in Dbeing always rebased, no matter what.

同样,在 Git 2.24 之前,无论发生什么,这样的操作rebase --onto都会导致D总是重新定位。

However, the desired behavior is that rebase should notice that this is fast-forwardableand do that instead.

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

That is akin to the rebase --onto B Aof the OP, which did nothing.

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

Add detection to can_fast_forwardso that this case can be detected and a fast-forward will be performed.
First of all, rewrite the function to use gotos which simplifies the logic.
Next, since the

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

conditions were removed in cmd_rebase, we reintroduce a substitute in can_fast_forward.
In particular, checking the merge bases of upstreamand headfixes a failing case in t3416.

The abbreviated graph for t3416 is as follows:

添加检测到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

and the failing command was

失败的命令是

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

Before, Git would see that there was one merge base (C, result of master...topic), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'.

之前,Git 会看到有一个合并基数 ( C, result of master...topic),并且合并和 on 是相同的,所以它会错误地返回 1,表明我们可以快进。ABCFG当我们期望“ ABCG”时,这将导致重新定位的图为“ ”。

A rebase --onto C F topicmeans any commit afterF, reachable by topicHEAD: that is Gonly, not Fitself.
Fast-forwarding in this case would include Fin the rebased branch, which is wrong.

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

With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic.
Since we're excluding some commits, a fast-forward cannotbe performed and so we correctly return 0.

Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced.

通过附加逻辑,我们检测到 upstream 和 head 的合并基数是F。由于Fon不是,这意味着我们不会从master..topic.
由于我们排除了一些提交,因此无法执行快进,因此我们正确返回 0。

将“ -f”添加到由于此更改而失败的测试用例,因为它们不期望快进,因此强制变基。