混帐|将旧提交移至另一个分支的过去

时间:2021-07-25 21:24:21

标签: git git-rebase

我过去错误地进行了分支,并且在另一个分支的开头留下了一个提交:

* 03431cb (HEAD -> bar) a2
| * d332e4d (foo) b2
| * 9b29ae3 b1
| * 4656a98 a1
|/  
* 6ebca20 (master) root

如何将 a1foo 移到 bar,以便 bar 的历史记录是 root -> a1 -> a2a1 不是在foo?是否可以通过一次 git commit 来完成?
这不会被推送,因此无需担心破坏其他人的本地存储库。

我首先考虑对 a1 进行挑选,然后更正 a2a1 之间的顺序。问题在于,这两个提交在我的真实案例中存在冲突,我必须在进行挑选和切换订单时纠正冲突。


在 bash 中:

#!/bin/bash
set -e

rm -rf .git

git init -b master
echo content > my-file
git add my-file
git commit -m root

git checkout -B foo
echo asd >> my-file
git add my-file
git commit -m a1
echo qwe >> my-file
git add my-file
git commit -m b1
echo zxc >> my-file
git add my-file
git commit -m b2

git checkout master
git checkout -B bar
echo jkl >> my-file
git add my-file
git commit -m a2

2 个答案:

答案 0 :(得分:6)

我非常喜欢语法 git rebase --onto x y z,这意味着:

<块引用>

从 z 开始,回顾父链,直到您即将到达 y 并停止。现在将这些提交(即 y 之后直到并包括 z 的所有内容)重新设置到 x 上。

换句话说,使用此语法,您可以清楚地说明在何处剪断链。另外,您不必在变基之前切换分支。语法需要一些时间来适应,但是一旦你熟练掌握它,你就会发现自己一直在使用它。

所以:

  1. 在 a1 创建一个临时分支只是为了给它一个名字:git branch temp 4656a98
  2. 现在将 b1 和 b2 重新设置为根目录:git rebase --onto master temp foo
  3. 最后将 a2 变基到 a1:git rebase --onto temp master bar
  4. 现在您可以根据需要删除临时文件:git branch -D temp

当然,我们可以节省两个步骤,只需使用 SHA 编号 4656a98 而不是名称 temp 执行 2 和 3,但名称更好。


证明。

起始位置:

* 9a97622 (HEAD -> bar) a2
| * 83638ec (foo) b2
| * 7e7cbd0 b1
| * 931632a a1
|/  
* 6976e30 (master) root

现在:

% git branch temp 931632a
% git rebase --onto master temp foo
% git rebase --onto temp master bar
% git branch -D temp

结果:

* 3a87b61 (HEAD -> bar) a2
* 931632a a1
| * bbb83d0 (foo) b2
| * 5fa70af b1
|/  
* 6976e30 (master) root

我相信这就是你所说的你想要的。

答案 1 :(得分:2)

<块引用>

是否可以通过一次 git commit 完成[我想要的]?

我假设您在这里指的是一个 Git 命令,而不是一个 Git 提交。答案是:不,这是不可能的。

<块引用>

如何将 a1foo 移到 bar,以便 bar 的历史记录是 root -> a1 -> a2a1 不是在foo

有关使用 git rebase --onto 执行此操作的方法,请参阅 matt's answer

然而,它可能有助于按照 Git 的看法来绘制它。不是root -> a1 -> a2。它是root <-a1 <-a2。名称 foo 本身可以移动,但这里现有的三个提交是一成不变的:它们的任何部分都不能更改。没关系,因为正如您所说,这些提交都没有发送到其他任何地方。

无论您做什么,您都必须复制一些提交到新的和改进的提交,使用不同的哈希 ID。您可以保留现有的一成不变的提交,只要您没有更改它们,包括作为提交一部分的向后箭头。

根提交(实际上:6ebca20)没有向后箭头。这就是使它成为根提交的原因。只要你不讨厌这次提交的任何内容,你就可以不理会它——这很好,因为根提交很难复制。 git cherry-pickgit rebase 都可以这样做,但这有点奇怪和特殊情况,因为cherry-pick 或 rebase 操作是通过比较提交的快照来工作的到其父级的快照。根提交没有父提交的事实使它有点奇怪。1

提交 a1(实际上是:4656a98)向后指向根提交。看起来也不错。

提交b1,然而——实际上是9b29ae3——指向后提交4656a98。这是一成不变的。它无法更改。您可以进行新的和不同的提交,您也可以调用 b1,而是向后指向根提交,这就是您想要做的。

使用 b1 完成此操作后,您现在需要将提交 b2 复制到新的和改进的提交,因为现有的 b2 (d332e4d) 指向回到9b29ae3。您需要一个可以进行相同更改的副本 - 樱桃挑选可以为您做的事情 - 但它具有新的 b1,无论其哈希 ID 变成什么,作为其父项。

复制 b1b2 后,您可以将名称 foo 指向 b2 的副本。在 Git 中,分支名称只是指向一些实际存在的提交。您可以随时更改它们指向的提交;他们指向的任何提交分支中的最后一次提交。由于通过最后提交中的父链接向后工作,较早的提交也在分支中,然后向后工作到父的父(或父的父或其他)的另一步骤。

由于您必须b1 复制到一个新的和改进的版本,这迫使您也复制 b2git rebase 命令重复运行 git cherry-pick 以实现上述复制,然后 - 一旦所有复制完成 - 将 分支名称 指向 最后复制的提交< /em>。因此,一个带有正确选项(包括 git rebase)的 --onto 将在 foo 分支上发挥作用。

另外,您必须将现有的 a2 复制到新的改进版本。复制后,您必须移动 name bar 以指向复制的 a2。一个 git rebase 命令也足以完成所有这些操作。

因为 git rebase 可以为您运行 git checkout,所以此时所需的最小 Git 命令数量是两个。这导致了 matt 使用的一组命令:另外两个是为了方便。


1cherry-pick 代码本身通过临时使用伪造的“没有文件”父级来处理此问题,因此就cherry-pick 操作而言,“更改" 提交是添加所有文件。 rebase 代码需要 --root 选项,至少在旧版本的 Git 中;我不确定这里发生了什么,因为音序器现在已经学会了执行以前在 shell 脚本中进行的交互操作。