git | move old commit to the past of another branch展示了一种情况,在这种情况下,可以方便地将 * 不复制什么 * 和 * 复制到哪里 * 作为两个单独的东西。 1实际上,git rebase * 只能 * 移动当前的分支(从最新的Git版本2.32开始,但可能还需要很长一段时间)--所以如果你提供一个分支名称,git rebase * 通过 * 使用 * git checkout或git switch * 开始。参见脚注2。 2您可以为命令行命令提供一个分支名称。如果您这样做,则该命令当前实际运行或已在其中内置了必要的 checkout /切换操作。当变基完成时,即使您不在变基开始之前,您也在所选的分支上。也就是说,在:
git checkout main # puts us on `main`
git rebase origin/foo foo
2条答案
按热度按时间pod7payv1#
【不要接受这个答案,这只是对托雷克的回答的一种评论。】
在我看来,看待这个问题的方法是理解
git rebase
的完整形式是onto
和三个参数:字符串
要读到这一点,请在脑海中将
y
和z
组合在一起,并将--onto x
交换到最后(因为这是直接宾语和介词短语的自然英语顺序),因此整个过程将解析如下内容(伪代码):型
在这个伪代码中,表达式
(y z]
表示“从y
之后开始,一直到z
”。Git通过从z
向后计算“从y
之后开始“的含义,而不是从y
向前计算,但效果大致相同。所以
git rebase --onto x y z
的意思是:“抓取所有从y
之后开始一直到z
的提交,并将它们附加到x
。”很好。这是
git rebase
的 * 完整 * 形式。当你省略任何参数时,Git会为你填充它们。它这样做的方式令人惊讶。这就是你看到的结果的原因。让我们举一个真实的例子。这是我们的起始位置:
型
请仔细观察图表。我们在
dev
上。我们有一个远程origin
,并且我们比远程跟踪分支origin/dev
领先一个分支。dev
在a
处从main
分离,之后它继续型
同时,
main
型
现在试试
型
我们在
dev
上,所以这意味着型
这意味着“抓取从
main
之后开始并继续到dev
的提交-即x
y
z
-并将它们附加到main
。”型
是的,就像我说的。我想,这是大多数人在使用单参数
git rebase
时所期望的。好,现在重新开始。这次我们说
型
正如torek的回答告诉你的那样
型
所以这意味着“抓取从
origin/dev
到dev
的所有内容--也就是z
--并将其附加到main
上。这非常令人惊讶!我们从来没有提到origin/dev
,但这就是Git在我们变基时将要剪切我们的分支的地方。我们开始了...这就是我们得到的:型
这可能就是发生在你身上的事情(OP)。很容易看出为什么你会觉得惊讶!
所以在我看来,主要的收获是,如果你遗漏了这三个参数中的任何一个,你可能会对Git为它们选择的东西感到惊讶。因此,在我看来,你不应该遗漏它们中的任何一个!你只是不知道如果你这样做会发生什么。
最后一点:好吧,我撒了一点谎。还记得第一个结果吗?
我在图中省略了
origin/dev
。实际上,这就是我们现在所拥有的:注意
x
和y
提交的 * 重复 *。这就是git rebase
所做的:如果我们打算推送dev
,这是一个棘手的情况,因为我们将要求远程origin
忘记origin/dev
当前指向的y
和x
,它不会高兴的ktca8awb2#
编辑:我忘记了最后一点或第一点,我将在这里首先插入。
git rebase
的 * 用法 * 非常简化:字符串
方括号
[...]
表示每个参数都是 * 可选的 *。尖括号<...>
表示 * 您在这里填写某些内容 。--onto *newbase*
选项使用一个标志;newbase
* 是给定的(由您,用户)当且仅当它前面有关键字--onto
,用双连字符拼写。类似地,当且仅当您给出 *upstream
* 参数时,它才被给予。因此:型
给出
master
的一个参数an *upstream
*;git rebase --onto master
给出 master 的一个参数 *newbase
。如果你不给出 *upstream
* 参数,git rebase
会自己找到一个。如果你不给出 *newbase
* 参数,git rebase
会自己找到一个。如果你只给出一个参数,而不给出另一个参数,git rebase
* 仍然 * 自己找到另一个。作为一个一行式的回答,那么:**
git rebase master
选择master
作为 * 目标和上游 ,但是git rebase --onto master
选择master
作为目标,并带有 default 上游,不管它是当前分支的默认值。您可以看到当前分支的默认值:型
如果当前分支是
dev
,其上游是origin/dev
,那么git rebase master
意味着git rebase --onto master master
,但git rebase --onto master
意味着git rebase --onto master origin/dev
。各种参数的含义
要执行它的任务--也就是说,复制一些提交,然后移动一个分支名称--
git rebase
需要知道三件事:最后一个默认为 * 当前分支 *。1因此,您只需首先运行
git checkout
或git switch
,以选择正确的分支。2Git的作者巧妙地将这三件事中剩下的两件塞进了
git rebase
的一个参数中,the documentation称之为 *upstream
* 参数。然而,有时候,你真的需要这两件事是 * 分开 *.
--onto
标志允许你分开他们:型
将提交 * 复制到 * 提供的 *
newbase
*,而不是将它们 * 复制到 * 提供的 *upstream
*。奇怪的不是 *
newbase
*,这很简单,而是 *upstream
* 参数是如何使用的 *。它的使用方式很复杂,但为了简化它,Git运行:型
(在执行初始
git checkout
或git switch
之后,如果您提供 *branch
* 参数)。因此,*upstream
* 指定的不是 * 要复制的内容 *,而是 * 不**要复制的内容 *。**rebase命令 * 将 * 复制一些提交集。**这是一个给定的,因为
git rebase
的目标是以某种方式或形式将一些不太好的现有提交转化为 * 改进的 * 提交-但是一旦提交完成,就不可能 * 更改 * 任何提交。由于现有提交 * 不能 * 更改,git rebase
所能做的最好的事情就是将它们 * 复制 * 到新的和改进的 * 副本 * 中,然后开始使用副本来代替原始文件。“使用副本而不是原始文件”这一步是需要移动一个分支名称的原因。Git使用分支名称来查找提交。分支名称一直在移动,通常是以一种简单的、一步一步的、容易遵循的方式。但是因为名称可以移动,
git reset
和其他Git命令可以移动它们,甚至可能是暴力地移动,一次可以移动许多提交,把他们从中国的家乡绑架到澳大利亚内陆或其他地方。😀在git rebase
的情况下,rebase代码首先将选定的旧的和糟糕的提交复制到他们的新家,进行修改,我们可以改进它们,或者至少让它们适应它们的新家。然后它移动分支名称,这样我们就可以找到 * 副本 * 而不是原始的-然后变基完成。upstream
* 参数指定了 * 不复制 * 什么,有时候--实际上,非常频繁!--这个 * 相同的 * 说明符 * 可以被用作 * 要复制的提交应该去的地方。但是当它 * 不能 * 以这种方式使用时,--onto
参数让你指定 * 复制到哪里 * 和 * 不复制什么 *,作为两个独立的东西。git | move old commit to the past of another branch展示了一种情况,在这种情况下,可以方便地将 * 不复制什么 * 和 * 复制到哪里 * 作为两个单独的东西。
1实际上,
git rebase
* 只能 * 移动当前的分支(从最新的Git版本2.32开始,但可能还需要很长一段时间)--所以如果你提供一个分支名称,git rebase
* 通过 * 使用 *git checkout
或git switch
* 开始。参见脚注2。2您可以为命令行命令提供一个分支名称。如果您这样做,则该命令当前实际运行或已在其中内置了必要的 checkout /切换操作。当变基完成时,即使您不在变基开始之前,您也在所选的分支上。也就是说,在:
型
我们还不如跑:
型
无论如何,因为我们最终使用的是
foo
,而不是main
。但这确实意味着如果我们必须运行git checkout
foo,我们可以运行:型
或者:
型
让
git rebase
帮我们做git checkout