如何交换Git提交的两个父母的顺序?

时间:2014-08-12 13:26:59

标签: git

合并提交是至少有两个父级的提交。这些父母按特定顺序排列。

如果我当前在分支master上,并且我合并了分支feature,我创建了一个新的提交,其第一个父代是master的提交,并且第二次提交是feature的提交。运行git log --first-parent

时,此顺序尤为明显
*   The merge commit
|\
| * The commit from `feature`
* | The commit from `master`

说我现在意识到订单是错误的方式:我打算通过运行master将分支feature合并到git checkout feature; git merge master。我想交换合并提交的父级的顺序,但我不想经历再次解决所有合并冲突的麻烦。我怎么能这样做?

*   The merge commit
|\
* | The commit from `feature`
| * The commit from `master`

4 个答案:

答案 0 :(得分:7)

一种方法是模拟合并。那我们怎么做呢?

让我们假设你有类似下面的提交图:

* (master) Merge branch 'feature'
|\
| * (feature) feature commit
* | master commit
. .
. .
. .

保留更改

我们希望将master合并到feature,但我们希望保留更改,因此首先我们切换到master,我们从中“手动”将我们的HEAD引用更新为点在feature,而不更改工作树。

git checkout master
git symbolic-ref HEAD refs/heads/feature

symbolic-ref命令与git checkout feature类似,但不触及工作树。因此master的所有更改都会保留。

撤消旧合并

现在我们对工作树中的合并进行了所有更改。因此,我们通过重置master继续“撤消”合并。如果您不愿意将引用丢失到合并提交中,则可以创建临时标记或分支。

(如果你想保留提交信息,现在是将它复制到某处保存的好时机。)

# Optional
git tag tmp master

git branch -f master master^

现在,您的提交树应该像合并之前一样。

假合并

这是hacky部分。我们想欺骗git相信我们目前正在合并。我们可以通过在MERGE_HEAD文件夹中手动创建一个包含我们要合并的提交哈希值的.git文件来实现这一点。
所以我们这样做:

git rev-parse master > .git/MERGE_HEAD

如果您正在使用git bash,git现在会告诉您它目前正在合并。

要完成合并,我们只需要commit

git commit
# Enter your commit message

它已经完成了。我们重新创建了合并提交但是使用了交换父项。所以你提交历史应该是这样的:

* (feature) Merge branch 'master'
|\
| * (master) master commit
* | feature commit
. .
. .
. .

如果您需要任何进一步的信息,请不要犹豫。

答案 1 :(得分:2)

更新 - 它从未如此简单,是吗?

我已经认识到我建议的原始说明中的一个缺陷:当使用路径参数执行checkout时,您可能希望从工作路径中删除一个文件,因为它不在您的提交中重新检查;但是你可能会感到惊讶......

与任何历史记录重写一样,值得注意的是,您可能不应该这样做(使用任何这些答案中的任何一种方法,包括这一方法)到您已经推送过的任何合并。那说......

之前的答案很好,但是如果你想避免使用(或需要知道)管道命令或其他git内部工作 - 如果这对你来说比拥有像Jared的解决方案更重要 - 那么这是另一种选择:

这个解决方案的整体结构类似于zeeker,但是他使用管道命令来操纵HEAD或“欺骗”git来完成合并,我们只会使用瓷器。

就像我们之前一样

* (master) Merge Commit
| \
| * (feature) fixed something
* | older commit on master

让我们开始吧:

1)标记旧合并

我们实际上将使用此标记。 (如果你想写下缩写的SHA1,那就行了;但这里的重点是让修复用户友好,所以...)

git tag badmerge master

现在我们有了

* (master) [badmerge] Merge Commit
| \
| * (feature) fixed something
* | older commit on master

2)从主人的历史中取出合并

git branch -f master master^

足够简单:

* [badmerge] Merge Commit
| \
| * (feature) fixed something
* | (master) older commit on master

3)开始新的合并

git checkout feature

如果我们现在只运行git merge master,我们知道合并将失败;但我们不想重做手动冲突解决方案。如果我们有办法将badmerge中的数据覆盖到我们的新合并提交中,我们就会全部设置......我们就这样做了!

开始合并(a)而不创建要清理的冲突状态,但(b)让合并提交保持打开状态,以便我们“修复”它:

git merge --no-commit --strategy=ours master

4)覆盖badmerge中已经解析/合并的数据

确保我们位于repo的根目录中,然后我们可以git checkout badmerge -- .(请注意,我们已经为git checkout提供了路径(。),因此它只更新了工作树和索引);但这实际上可能是一个问题。

如果合并导致文件被删除(但它们现在在我们的工作树中),那么上面的命令将不会删除它们。因此,如果我们不确定,我们有几种选择可以避免这种可能性:

我们可以先清除我们的工作树......但是我们需要注意不要擦除.git文件夹,并且可能需要对忽略文件中的任何内容进行特殊处理。

在简单的情况下 - .git是唯一的.-文件,没有被忽略 - 我们可以做

rm -rf *
git checkout badmerge -- .

或者,如果这似乎风险太大,另一种方法是跳过rm,执行git checkout badmerge -- .,然后反对badmerge以查看是否需要清理。

5)完成合并

git commit
git tag -d badmerge

答案 2 :(得分:2)

this answer的启发,我想到了这一点:

git replace -g HEAD HEAD^2 HEAD^1 && 
git commit --amend && 
git replace -d HEAD@{1}

第一个命令将两个父对象切换到一个称为“替换引用”的地方,但仅将其存储在本地,而have called it a hack人。

第二个命令创建一个新的提交。

第三个命令删除较旧的替换引用,因此它不会弄乱其他提交(取决于该提交)。

答案 3 :(得分:1)

您可能希望使用git replace --graft(例如git replace --graft HEAD HEAD^2 HEAD^1)而不是重写历史记录。它创建了一个替换提交和keeps its children unchanged

另见: