如何使用 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
命令的工作原理是:
- 在某处保存当前分支名称。
- 正在枚举要复制的提交的原始哈希 ID。
- 使用 Git 的 分离 HEAD 模式 select 某些特定目标 (
--onto
) 提交。这是新副本的去向。
- 复制提交,一次一个,就像通过
git cherry-pick
一样(相当于 Mercurial hg graft
除了分支名称无关紧要:提交永远不会绑定到任何分支)。
- 最后,拉出在步骤 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 main
或 git switch main
使提交 CCCCC
成为当前(即使不需要也可以这样做,这只是一个那就不要操作了)。那我们运行git rebase origin/main
.
Git 现在从这里(HEAD
或 CCCCC
)向后列出所有哈希 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
而且空行也不再有任何理由,所以它消失了。
这是我本地仓库的状态。我在 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
命令的工作原理是:
- 在某处保存当前分支名称。
- 正在枚举要复制的提交的原始哈希 ID。
- 使用 Git 的 分离 HEAD 模式 select 某些特定目标 (
--onto
) 提交。这是新副本的去向。 - 复制提交,一次一个,就像通过
git cherry-pick
一样(相当于 Mercurialhg graft
除了分支名称无关紧要:提交永远不会绑定到任何分支)。 - 最后,拉出在步骤 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 main
或 git switch main
使提交 CCCCC
成为当前(即使不需要也可以这样做,这只是一个那就不要操作了)。那我们运行git rebase origin/main
.
Git 现在从这里(HEAD
或 CCCCC
)向后列出所有哈希 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
而且空行也不再有任何理由,所以它消失了。