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

How do I use git rebase to move a commit?

这是我本地仓库的状态。我在 AAAAA 并提交了 CCCCC。我做了一个 git pull 并且它提取了提交并将 BBBBB 自动(大概)合并到 CCCCC 中并制作了 DDDDD。我不想这样,所以我用 git 重置杀死了 DDDDD。

$ git tree --all

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

我不想合并,而是想将 CCCCC 移动到 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 中也是如此。但是,对于 Mercurial 新手来说,用于变基(“嫁接”)和历史编辑(hg histedit)的 Mercurial 界面比 Git 的变基对 Git 新手来说要清晰得多。 (这是 Mercurial-vs-Git 中的通用主题。)

在 Git 中,提交从未真正绑定到任何特定分支。相反,Git 查找 从一些 分支名称 开始提交——这实际上更像是 Mercurial 的“书签”——然后向后工作。这个向后工作部分达到的提交集被称为“在分支上”。

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

git rebase 命令的工作原理是:

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

要处理第 2 步和第 3 步——“要复制的提交”列表和“目标”提交——Git 通常使用 单个参数 。该单个参数通常是目标分支名称。因此,要复制的提交的来源是 当前分支 (每个步骤 1)。这就是我们开始的原因:

git switch main

或同等学历。

要列出要复制的提交,Git 然后使用粗略的等效项:

git log target..HEAD

这意味着 找到可以从 HEAD 而不是 target 的提交。 (Mercurial 也有这个,使用 target::.,除了 Mercurial 的 :: 图形运算符包括区间的两端,而 Git 是半开间隔:它总是排除最左边的提交。)

detached-HEAD-checkout 目标实际上就是这种形式的 target 参数。

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

git rebase --onto target upstream

步骤 2 中的提交列表现在是 <em>upstream</em>..HEAD 而不是 <em>目标</em>..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 现在从这里(HEADCCCCC)向后列出所有哈希 ID:先是 CCCCC,然后是 AAAAA,然后是下面的任何内容那里。从这个列表中,Git 剥离 BBBBB(它不在那里,但这只会使剥离速度很快!),然后是 AAAAA,然后是下面的任何东西——只留下 CCCCC.

现在 Git 执行 BBBBB 的分离头检查。然后它会以正确的顺序为保存的列表中的所有提交发出 cherry-picks。该列表中只有一个提交: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 将 name 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 不会 显示 :

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

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