我过去错误地进行了分支,并且在另一个分支的开头留下了一个提交:
* 03431cb (HEAD -> bar) a2
| * d332e4d (foo) b2
| * 9b29ae3 b1
| * 4656a98 a1
|/
* 6ebca20 (master) root
如何将 a1
从 foo
移到 bar
,以便 bar
的历史记录是 root -> a1 -> a2
而 a1
不是在foo
?是否可以通过一次 git commit 来完成?
这不会被推送,因此无需担心破坏其他人的本地存储库。
我首先考虑对 a1
进行挑选,然后更正 a2
和 a1
之间的顺序。问题在于,这两个提交在我的真实案例中存在冲突,我必须在进行挑选和切换订单时纠正冲突。
在 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
答案 0 :(得分:6)
我非常喜欢语法 git rebase --onto x y z
,这意味着:
从 z 开始,回顾父链,直到您即将到达 y 并停止。现在将这些提交(即 y 之后直到并包括 z 的所有内容)重新设置到 x 上。
换句话说,使用此语法,您可以清楚地说明在何处剪断链。另外,您不必在变基之前切换分支。语法需要一些时间来适应,但是一旦你熟练掌握它,你就会发现自己一直在使用它。
所以:
git branch temp 4656a98
git rebase --onto master temp foo
git rebase --onto temp master bar
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 提交。答案是:不,这是不可能的。
<块引用>如何将 a1
从 foo
移到 bar
,以便 bar
的历史记录是 root -> a1 -> a2
而 a1
不是在foo
?
有关使用 git rebase --onto
执行此操作的方法,请参阅 matt's answer。
然而,它可能有助于按照 Git 的看法来绘制它。不是root -> a1 -> a2
。它是root <-a1 <-a2
。名称 foo
本身可以移动,但这里现有的三个提交是一成不变的:它们的任何部分都不能更改。没关系,因为正如您所说,这些提交都没有发送到其他任何地方。
无论您做什么,您都必须复制一些提交到新的和改进的提交,使用不同的哈希 ID。您可以保留现有的一成不变的提交,只要您没有更改它们,包括作为提交一部分的向后箭头。
根提交(实际上:6ebca20
)没有向后箭头。这就是使它成为根提交的原因。只要你不讨厌这次提交的任何内容,你就可以不理会它——这很好,因为根提交很难复制。 git cherry-pick
和 git rebase
都可以这样做,但这有点奇怪和特殊情况,因为cherry-pick 或 rebase 操作是通过比较提交的快照来工作的到其父级的快照。根提交没有父提交的事实使它有点奇怪。1
提交 a1
(实际上是:4656a98
)向后指向根提交。看起来也不错。
提交b1
,然而——实际上是9b29ae3
——指向后提交4656a98
。这是一成不变的。它无法更改。您可以进行新的和不同的提交,您也可以调用 b1
,而是向后指向根提交,这就是您想要做的。
使用 b1
完成此操作后,您现在需要将提交 b2
复制到新的和改进的提交,因为现有的 b2
(d332e4d
) 指向回到9b29ae3
。您需要一个可以进行相同更改的副本 - 樱桃挑选可以为您做的事情 - 但它具有新的 b1
,无论其哈希 ID 变成什么,作为其父项。
复制 b1
和 b2
后,您可以将名称 foo
指向 b2
的副本。在 Git 中,分支名称只是指向一些实际存在的提交。您可以随时更改它们指向的提交;他们指向的任何提交是分支中的最后一次提交。由于通过最后提交中的父链接向后工作,较早的提交也在分支中,然后向后工作到父的父(或父的父或其他)的另一步骤。
由于您必须将 b1
复制到一个新的和改进的版本,这迫使您也复制 b2
。 git 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 脚本中进行的交互操作。