git |将旧提交移至另一个分支的过去
git | move old commit to the past of another branch
我过去错误地分支了,一个提交留在了另一个分支的开头:
* 03431cb (HEAD -> bar) a2
| * d332e4d (foo) b2
| * 9b29ae3 b1
| * 4656a98 a1
|/
* 6ebca20 (master) root
如何将 a1
从 foo
移到 bar
,以便 bar
的历史记录为 root -> a1 -> a2
和 a1
不在foo
?是否可以通过一次 git 提交来完成?
这不是推送所以不用担心破坏别人的本地回购协议。
我首先想到的是对 a1
做一个 cherry pick,然后更正 a2
和 a1
之间的顺序。这样做的问题是,在我的真实案例场景中,这两个提交发生冲突,我必须在进行樱桃挑选和切换顺序时纠正冲突。
mwe 在 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
我非常喜欢语法 git rebase --onto x y z
,这意味着:
Starting at z, look back thru the parent chain until you are about to come to y and stop. Now rebase those commits, ie everything after y up to and including z, onto x.
换句话说,使用此语法,您可以清楚地说明在哪里剪断链条。另外,您不必在变基之前切换分支。语法需要一些时间来适应,但是一旦你熟练掌握它,你就会发现自己一直在使用它。
所以:
- 在a1创建一个临时分支只是给它起个名字:
git branch temp 4656a98
- 现在将 b1 和 b2 变基到根:
git rebase --onto master temp foo
- 最后将 a2 变基到 a1:
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
我相信这就是你所说的你想要的。
Is it possible to do [what I want] with one single git commit?
我假设你在这里指的是一个Git命令而不是一个Git提交。答案是:不,不可能那样做。
How can I move a1
out of foo
into bar
, so that bar
's history is root -> a1 -> a2
and a1
is not in foo
?
有关使用 git rebase --onto
执行此操作的方法,请参阅 。
但是,按照 Git 的看法可能会有所帮助。这不是 root -> a1 -> a2
。是 root <-a1 <-a2
。名称 foo
本身可以移动,但这里的三个现有提交是一成不变的:它们的任何部分都不能更改。没关系,因为正如您所说,这些提交中的 none 已发送到其他任何地方。
无论你做什么,你都必须复制 一些提交到new-and-improved 提交,使用不同的哈希ID。您可以单独保留现有的 carved-in-stone 提交,前提是您要更改 nothing,包括作为提交一部分的 backwards-pointing 箭头。
root 提交(实际上:6ebca20
)没有 backwards-pointing 箭头。这就是使它成为根提交的原因。只要您不喜欢这个提交的任何内容,您就可以不理会它——这很好,因为 root 提交很难复制。 git cherry-pick
和 git rebase
都可以 做到这一点,但这有点奇怪 special-case-y,因为 cherry-pick 或 rebase 操作有效通过将提交的快照与其 parent 的快照进行比较。 no parent 根提交的事实让它有点奇怪。1
提交 a1
(实际上:4656a98
)向后指向根提交。好像也可以。
提交 b1
,然而——这实际上是 9b29ae3
——向后指向提交 4656a98
。这是一成不变的。它不能改变。您可以创建一个新的不同的提交,您也将其称为 b1
,而不是向后指向根提交,这就是您想要做的。
使用 b1
完成此操作后,您现在需要将提交 b2
复制到 new-and-improved 提交,因为现有的 b2
(d332e4d
)指向 9b29ae3
。您需要一个进行相同更改的副本 — cherry-pick 会为您做些什么 — 但它有新的 b1
,无论它的哈希 ID 变成什么,因为它的 parent.
复制 b1
和 b2
后,您可以将名称 foo
指向 b2
的副本。 Git 中的分支名称只是 指向 一些实际的现有提交。您可以随时更改它们指向的提交;他们指向 的任何提交都是 分支中的最后一次提交。通过 last 提交中的 parent 链接向后工作,然后向后工作另一个步骤到 parent 的 parent(或 parents' parents 或其他)。
由于您必须 将b1
复制到new-and-improved 版本,这会强制您也复制b2
。 git rebase
命令 运行s git cherry-pick
重复完成上述复制,然后——一旦所有复制完成——移动 分支名称 指向最后复制的提交。所以一个 git rebase
,具有正确的选项(包括 --onto
),将完成 foo
分支的技巧。
另外,您必须将现有的a2
复制到new-and-improved版本。完成复制后,您必须移动 name bar
以指向复制的 a2
。一个 git rebase
命令也足以完成所有这些操作。
因为 git rebase
可以 运行 git checkout
给你,所以需要的最少 Git 命令数量是,在这一点上,两个。这导致了 matt 使用的一组命令:两个额外的命令是为了方便。
1cherry-pick 代码本身通过使用 faked-up“没有文件”parent 暂时处理这个问题,因此就 cherry-pick 操作而言,提交所做的“更改”是 添加所有文件 。 rebase 代码需要 --root
选项,至少在 Git 的旧版本中是这样;我不确定这里发生了什么,因为音序器现在已经学会了做以前在 shell 脚本中的交互内容。
我过去错误地分支了,一个提交留在了另一个分支的开头:
* 03431cb (HEAD -> bar) a2
| * d332e4d (foo) b2
| * 9b29ae3 b1
| * 4656a98 a1
|/
* 6ebca20 (master) root
如何将 a1
从 foo
移到 bar
,以便 bar
的历史记录为 root -> a1 -> a2
和 a1
不在foo
?是否可以通过一次 git 提交来完成?
这不是推送所以不用担心破坏别人的本地回购协议。
我首先想到的是对 a1
做一个 cherry pick,然后更正 a2
和 a1
之间的顺序。这样做的问题是,在我的真实案例场景中,这两个提交发生冲突,我必须在进行樱桃挑选和切换顺序时纠正冲突。
mwe 在 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
我非常喜欢语法 git rebase --onto x y z
,这意味着:
Starting at z, look back thru the parent chain until you are about to come to y and stop. Now rebase those commits, ie everything after y up to and including z, onto x.
换句话说,使用此语法,您可以清楚地说明在哪里剪断链条。另外,您不必在变基之前切换分支。语法需要一些时间来适应,但是一旦你熟练掌握它,你就会发现自己一直在使用它。
所以:
- 在a1创建一个临时分支只是给它起个名字:
git branch temp 4656a98
- 现在将 b1 和 b2 变基到根:
git rebase --onto master temp foo
- 最后将 a2 变基到 a1:
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
我相信这就是你所说的你想要的。
Is it possible to do [what I want] with one single git commit?
我假设你在这里指的是一个Git命令而不是一个Git提交。答案是:不,不可能那样做。
How can I move
a1
out offoo
intobar
, so thatbar
's history isroot -> a1 -> a2
anda1
is not infoo
?
有关使用 git rebase --onto
执行此操作的方法,请参阅
但是,按照 Git 的看法可能会有所帮助。这不是 root -> a1 -> a2
。是 root <-a1 <-a2
。名称 foo
本身可以移动,但这里的三个现有提交是一成不变的:它们的任何部分都不能更改。没关系,因为正如您所说,这些提交中的 none 已发送到其他任何地方。
无论你做什么,你都必须复制 一些提交到new-and-improved 提交,使用不同的哈希ID。您可以单独保留现有的 carved-in-stone 提交,前提是您要更改 nothing,包括作为提交一部分的 backwards-pointing 箭头。
root 提交(实际上:6ebca20
)没有 backwards-pointing 箭头。这就是使它成为根提交的原因。只要您不喜欢这个提交的任何内容,您就可以不理会它——这很好,因为 root 提交很难复制。 git cherry-pick
和 git rebase
都可以 做到这一点,但这有点奇怪 special-case-y,因为 cherry-pick 或 rebase 操作有效通过将提交的快照与其 parent 的快照进行比较。 no parent 根提交的事实让它有点奇怪。1
提交 a1
(实际上:4656a98
)向后指向根提交。好像也可以。
提交 b1
,然而——这实际上是 9b29ae3
——向后指向提交 4656a98
。这是一成不变的。它不能改变。您可以创建一个新的不同的提交,您也将其称为 b1
,而不是向后指向根提交,这就是您想要做的。
使用 b1
完成此操作后,您现在需要将提交 b2
复制到 new-and-improved 提交,因为现有的 b2
(d332e4d
)指向 9b29ae3
。您需要一个进行相同更改的副本 — cherry-pick 会为您做些什么 — 但它有新的 b1
,无论它的哈希 ID 变成什么,因为它的 parent.
复制 b1
和 b2
后,您可以将名称 foo
指向 b2
的副本。 Git 中的分支名称只是 指向 一些实际的现有提交。您可以随时更改它们指向的提交;他们指向 的任何提交都是 分支中的最后一次提交。通过 last 提交中的 parent 链接向后工作,然后向后工作另一个步骤到 parent 的 parent(或 parents' parents 或其他)。
由于您必须 将b1
复制到new-and-improved 版本,这会强制您也复制b2
。 git rebase
命令 运行s git cherry-pick
重复完成上述复制,然后——一旦所有复制完成——移动 分支名称 指向最后复制的提交。所以一个 git rebase
,具有正确的选项(包括 --onto
),将完成 foo
分支的技巧。
另外,您必须将现有的a2
复制到new-and-improved版本。完成复制后,您必须移动 name bar
以指向复制的 a2
。一个 git rebase
命令也足以完成所有这些操作。
因为 git rebase
可以 运行 git checkout
给你,所以需要的最少 Git 命令数量是,在这一点上,两个。这导致了 matt 使用的一组命令:两个额外的命令是为了方便。
1cherry-pick 代码本身通过使用 faked-up“没有文件”parent 暂时处理这个问题,因此就 cherry-pick 操作而言,提交所做的“更改”是 添加所有文件 。 rebase 代码需要 --root
选项,至少在 Git 的旧版本中是这样;我不确定这里发生了什么,因为音序器现在已经学会了做以前在 shell 脚本中的交互内容。