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

如何交换Git提交的两个父级的顺序?,git,Git,合并提交是至少有两个父级的提交。这些父母是按特定顺序排列的 如果我当前在分支主机,并且我合并到分支功能,我将创建一个新的提交,其第一个父级是来自主机的提交,第二个提交是来自功能的提交。通过运行git log--first parent,这个顺序尤其明显 * The merge commit |\ | * The commit from `feature` * | The commit from `master` 假设我现在意识到顺序是错误的:我打算通过运行git checkout功能将分支

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

如果我当前在分支
主机
,并且我合并到分支
功能
,我将创建一个新的提交,其第一个父级是来自
主机
的提交,第二个提交是来自
功能
的提交。通过运行
git log--first parent
,这个顺序尤其明显

*   The merge commit
|\
| * The commit from `feature`
* | The commit from `master`
假设我现在意识到顺序是错误的:我打算通过运行
git checkout功能将分支
master
合并到
feature
;git合并主机
。我想交换合并提交的父级顺序,但我不想再次经历解决所有合并冲突的麻烦。我该怎么做

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

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

假设您有如下提交图:

* (master) Merge branch 'feature'
|\
| * (feature) feature commit
* | master commit
. .
. .
. .
不用找了 我们想将
master
合并到
feature
中,但我们想保留更改,因此首先我们切换到
master
,从中“手动”更新头部参考,以指向
feature
,同时不更改工作树

git checkout master
git symbolic-ref HEAD refs/heads/feature
symbolic ref
命令类似于
git签出功能
,但不触及工作树。因此,
master
中的所有更改都将保留

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

(如果要保留提交消息,现在是将其复制到保存位置的好时机。)

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

假装合并 这是令人讨厌的部分。我们想骗git相信我们正在合并。我们可以通过在
.git
文件夹中手动创建一个
MERGE\u HEAD
文件来实现这一点,该文件包含要合并的提交的哈希值。
所以我们这样做:

git rev-parse master > .git/MERGE_HEAD
如果您使用的是GitBash,git现在会告诉您它当前正在合并

要完成合并,我们只需提交

git commit
# Enter your commit message
一切都结束了。我们重新创建了合并提交,但使用了交换的父级。因此,您提交的历史现在应该如下所示:

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

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

事实上,我最近学到了一个非常酷的命令,它可以完全满足您的需要:

git commit-tree -p HEAD^2 -p HEAD^1 -m "Commit message" "HEAD^{tree}" 
这将基于当前的HEAD创建一个新的commit,但假设它的父对象是HEAD^2,HEAD^1(注意,这是相反的顺序)

git commit tree将新版本打印为输出,因此您可以将其与git重设结合使用:

git reset --hard $(git commit-tree -p HEAD^2 -p HEAD^1 -m "New commit message" "HEAD^{tree}")

更新-从来没有那么容易,是吗

我发现我建议的原始指令中存在一个缺陷:当使用路径参数执行
签出时,您可能希望从工作路径中删除一个文件,因为它不在要签出的提交中;但是你可能会感到惊讶


与任何历史重写一样,值得注意的是,您可能不应该这样做(使用这些答案中的任何一种方法,包括这一种)来进行您已经推动的任何合并。也就是说

前面的答案很好,但如果您希望避免使用(或需要知道)管道命令或其他git内部工作-如果这比使用Jared的解决方案那样的一行程序更重要-那么这里有一个替代方案:

这个解决方案的整体结构与zeeker的类似,但当他使用管道命令操纵HEAD或“技巧”git完成合并时,我们将只使用瓷器

就像以前一样

* (master) Merge Commit
| \
| * (feature) fixed something
* | older commit on master
让我们开始:

1)标记旧合并

git checkout feature
git commit
git tag -d badmerge
我们实际上会使用这个标签。(如果你想写下缩写的SHA1,那就行了;但这里的要点是让修复程序更友好,所以…)

现在我们有了

* (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 commit
git tag -d badmerge
如果我们现在运行
git merge master
,我们知道合并将失败;但我们不想重做手动冲突解决。如果我们有办法将badmerge中的数据覆盖到新的合并提交上,那么我们就都准备好了。。。而我们做到了

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

4)覆盖来自badmerge的已解析/合并数据

git checkout feature
git commit
git tag -d badmerge
确保我们在repo的根目录中,然后我们可以执行
git checkout badmerge--.
(注意,我们提供了一个路径(.)到
git checkout
,因此它只更新工作树和索引);但这实际上可能是个问题

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

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

在简单的情况下-.git是唯一的。-文件,没有任何内容被忽略-我们可以这样做

rm -rf *
git checkout badmerge -- .
或者,如果这看起来太危险,另一种方法是跳过
rm
,执行
git checkout-badmerge--.
,然后与badmerge进行比较,看看是否有什么需要清理

5)完成合并

git checkout feature
git commit
git tag -d badmerge
您可能希望使用(例如,
git replace--graft HEAD ^2 HEAD ^1
)而不是重写历史记录。它创建一个替换并提交

编辑:<