如何git重基一个分支与上命令?

我注意到以下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客户端使用第二种语法,所以看起来完全被破坏了,这就是为什么我问这个语法问题。

148769 次浏览

对于onto,你需要两个额外的分支。使用该命令,你可以将基于branchAbranchB提交应用到另一个分支,例如master。在下面的示例中,branchB是基于branchA的,你想在master上应用branchB的变化,而不应用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)

简单地说,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

博士tl;

在你的例子中,使用git rebase --ontoA之上重新构建B的正确语法是:

git checkout B
git rebase --onto A B^

从__ABC0的父对象——提交开始,将__ABC0的基赋在A之上引用B^B~1

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

快速:git改基

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,这是分支点之后的第一次提交,而变基它(即G0)位于branch但不能从HEAD到达的最新提交之上,即G

The Precise: git rebase -onto带有2个参数

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。这意味着:

将从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,或者在B之上重新建立HEAD,其中旧的父类是E

外科医生:git rebase -on有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)

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

将父类为D的提交范围重设为F之上的H

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

这就是你要理解--onto所需要知道的一切:

git rebase --onto <newparent> <oldparent>

您在提交时切换了父节点,但您没有提供提交的sha,只提供它当前(旧)父节点的sha。

简单地说,给定:

      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来指定第一次提交更容易,让gitbranch找到第一次发散的提交,即D,从而导致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 history)。有趣的是,当第二个参数是提交哈希时,分支名称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分支
(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.

还有另一种情况下,git rebase --onto很难掌握:当你将基准重基于对称差分选择器(三个点'...')所导致的提交时

Git 2.24(2019年Q4)在管理这种情况方面做得更好:

参见刘丹顿(Denton-L)提交414年d924提交4 effc5b提交c0efb4c提交2 b318aa(2019年8月27日)和提交793年ac7e提交359年eceb(2019年8月25日) 得益于:Eric Sunshine (sunshineco) 滨野朱尼奥(gitster)Ævar Arnfjörð Bjarmason (avar) Johannes Schindelin (dscho)。< br > 参见Ævar Arnfjörð Bjarmason (avar)提交6330209提交c9efc21(2019年8月27日)和提交4336年d36(2019年8月25日) 得益于:Eric Sunshine (sunshineco) 滨野朱尼奥(gitster)Ævar Arnfjörð Bjarmason (avar) Johannes Schindelin (dscho)。< br > (由Junio C Hamano—gitster in 提交640年f9cd合并,30 Sep 2019)

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

之前,当我们有下面的图表时,

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

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

此时,读取"在Git的diff提交范围中,双点“__ABC0”和三点“...”之间有什么区别?"

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

这里:"master... "指的是master...HEAD,即B: HEAD是side HEAD(当前签出):你正在基于B 你在改变什么基础?master中的任何提交,并且可以从side分支到达:只有一个提交符合此描述:D…它已经在B!< / p >

同样,在Git 2.24之前,这样的rebase --onto会导致D总是被重基,无论发生什么。

然而,期望的行为是Rebase应该注意,这是快速前进的,并这样做。

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

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

.
options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)
如果在cmd_rebase中删除了>条件,则在 can_fast_forward。< br > 特别是,检查upstreamhead的合并基底可以修复t3416中的失败情况

t3416的缩略图如下:

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

失败的命令是

git rebase --onto master...topic F topic
在此之前,Git会看到有一个merge base (C, result of master...topic),并且merge和onto是相同的,因此它会错误地返回1,表明 我们可以快进。这将导致重基图为'ABCFG' 当我们期待'ABCG'时。

A rebase --onto C F topic表示任何 F提交,可以通过topic HEAD到达:即仅是G,而不是F本身 在这种情况下,快进会在重基分支中包含F,这是错误的

使用额外的逻辑,我们检测上游和头部的合并基 F。由于onto不是F,这意味着我们没有重基全部的 从master..topic提交 由于我们排除了一些提交,因此将执行快进不能,从而正确返回0

将'-f'添加到由于此更改而失败的测试用例中 他们没想到会有一个快进,这样就会被迫调降

为了更好地理解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 - to概述

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分支中的一些提交。通过“re- based”,我们只是让它基于的提交更当前。

(事实上,<old_base>是我们用来比较branch的对象。如果它是一个分支,那么git会寻找一个共同的祖先(另见--fork-point);如果是当前分支上的提交,则使用之后的提交;如果它是一个与当前分支没有共同祖先的提交,则使用当前分支的所有提交。<new_base>也可以是一个提交。因此,例如,git rebase --onto HEAD~ HEAD将接受旧的base HEAD和当前HEAD之间的提交,并将它们放在HEAD~之上,有效地删除最后一次提交。)