如何使用git-rebase来移动提交



这是我的本地回购的状态。我在AAAAA,并承诺CCCC。我做了一个git pull,它提取了提交,并将BBBBB自动(ish)合并到CCCC中,并制作了DDDDD。我不想那样,所以我用一个数字重置杀死了DDDDD。

$git树--所有

CCCCC (HEAD, main) foobar issue 666
| * BBBBB (tag: fubar, origin/main, origin/HEAD) fubar issue #69
|/  
* AAAAA foo

与其合并,我想把CCCC转移到BBBBB。我该如何重新设定基准?我需要先切换或结账到BBBBB吗?

* CCCCC (HEAD, main) foobar issue 666
* BBBBB (tag: fubar, origin/main, origin/HEAD) fubar issue #69
* AAAAA foo

回扣是正确的:

git fetch
git switch main
git rebase origin/main

你可能只是说git pull --rebase,但我不是一个冒险的人。

从技术上讲,您实际上并没有移动提交。相反,您可以将其复制到一个新的改进提交中(使用不同的哈希ID)。Mercurial也是如此。然而,与Git的rebase相比,Mercurial的rebase("移植")和历史编辑(hg histedit)界面对Mercurial新手来说要清晰得多。(这是Mercurial与Git的总主题。)

在Git中,提交从来没有实际绑定到任何特定的分支。相反,Git通过从某个分支名称开始查找提交——这实际上更像是一个Mercurial"书签"--然后反向工作。这个向后工作部分所达到的提交集合被称为"提交";在分支上";。

因此,当两个或多个分支名称识别出相同的最终提交时——就好像你在Mercurial中有两个或更多指向同一提交的书签一样——这些提交在同一个分支上,但一旦你在Git中移动了其中一个或两个分支名称,那两个完全不变的提交可能就不在同一分支上了。

git rebase命令通过以下方式工作:

  1. 将当前分支名称保存到某个位置
  2. 枚举要复制的提交的原始哈希ID
  3. 使用Git的分离HEAD模式来选择一些特定的目标(--onto)提交。这就是新副本的去向
  4. 复制提交,一次一个,就像通过git cherry-pick一样(相当于Mercurialhg graft,只是分支名称无关紧要:提交永远不会绑定到任何分支)
  5. 最后,猛拉在步骤1中保存的分支名称以指向最后复制的提交,并退出"提交";"分离的头";模式返回到您在步骤1中使用的分支上

为了处理步骤2和3;其承诺复制";列表和";目标";commit——Git通常使用单个参数。这个参数通常是一个目标分支名称。因此,要复制的提交的来源是当前分支(根据步骤1)。这就是为什么我们从开始

git switch main

或等效物。

为了列出要复制的提交,Git使用大致等效的:

git log target..HEAD

这意味着发现可从CCD_ 8访问但不能从target访问的提交。(Mercurial也有这一点,使用target::.,除了Mercurial的::图操作符包括区间的两端,而Git的是半开放区间:它总是排除最左边的提交。)

分离的HEAD签出目标实际上是这种形式的target参数。

在某些情况下,不可能以这种方式生成正确的提交列表,因此git rebase具有--onto语法,这有点奇怪:

git rebase --onto target upstream

步骤2中的提交列表现在是upstream..HEAD,而不是target..HEAD。步骤3中的签出仍然是target

给定:

CCCCC (HEAD -> main) foobar issue 666
| * BBBBB (tag: fubar, origin/main, origin/HEAD) fubar issue #69
|/  
* AAAAA foo

您想要复制的提交哈希ID的列表只有一个元素长,列出了CCCCC。您希望副本所在的位置是提交BBBBB。因此,如果需要的话——HEAD -> main说不是——我们git checkout maingit switch main使提交CCCCC成为当前的(即使不需要也可以这样做,那只是不操作)。然后我们运行git rebase origin/main

Git现在向后列出了这里的所有哈希ID(HEADCCCCC):这是CCCCC,然后是AAAAA,然后是下面的任何东西。从这个列表中,Git去掉了BBBBB(它不在那里,但这只会让剥离速度很快!),然后是AAAAA,然后是下面的任何东西——只剩下CCCCC

现在Git对BBBBB执行分离的HEAD检查。然后,它按照正确的顺序为保存的列表中的所有提交发出cherry pick。列表中只有一个提交:CCCCC。所以Git做了一个新的提交,也许是CCCCD,就像CCCCC一样,做同样的事情,但这是在BBBBB:之后

CCCCC (main) foobar issue 666
| * CCCCD (HEAD) foobar issue 666
| * BBBBB (tag: fubar, origin/main, origin/HEAD) fubar issue #69
|/  
* AAAAA foo

现在所有的复制都完成了,Git将名称main拉到这里——拉到CCCCD——并重新附加HEAD,这样我们就有了:

CCCCC foobar issue 666
| * CCCCD (HEAD -> main) foobar issue 666
| * BBBBB (tag: fubar, origin/main, origin/HEAD) fubar issue #69
|/  
* AAAAA foo

提交CCCCC无法找到,因此git log不会显示it:

* CCCCD (HEAD -> main) foobar issue 666
* BBBBB (tag: fubar, origin/main, origin/HEAD) fubar issue #69

* AAAAA foo

空白行也不再有任何理由了,所以它就消失了。