我注意到以下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客户端使用第二种语法,因此在我看来完全崩溃了,这就是我要求这种语法问题的原因。
答案 0 :(得分:302)
在您的情况下使用B
在A
之上重新定位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 <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
和G
是可以从branch
但无法从HEAD
访问的提交。假设git rebase branch
将采用D
,这是分支点之后的第一次提交,而将其(即更改其父)置于之上最新提交可以从branch
但不能从HEAD
到达,即G
。
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
更改为F
。 git rebase --onto
的语法是git rebase --onto <newparent> <oldparent>
。
另一种方便的方法就是当你想要从当前分支中快速删除一些提交而不必进行交互式rebase 时:
Before After
A---B---C---E---F (HEAD) A---B---F (HEAD)
在此示例中,为了从序列中删除C
和E
,您需要git rebase --onto B E
,或在HEAD
之上重新定位B
老父母是E
。
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 rebase
和git 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} },让branch
从git
中找到第一个发散的提交,即branch
导致D
。
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”结尾。
默认的master
或HEAD
参数是从您所处的位置上下文中获取的。这就是为什么大多数人结帐到他们想作为基准的分支的原因。但是,当显式给出第二个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 414d924,commit 4effc5b,commit c0efb4c,commit 2b318aa(2019年8月27日)和commit 793ac7e,commit 359eceb(2019年8月25日)由Denton Liu (Denton-L
)。
帮助者:Eric Sunshine (sunshineco
),Junio C Hamano (gitster
),Ævar Arnfjörð Bjarmason (avar
)和Johannes Schindelin (dscho
)。
请参见commit 6330209的commit c9efc21,commit 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?”
在这里:“ 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_forward
和upstream
的合并基础可以解决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_base
与old_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
的顶部,从而有效地删除最后的提交。)