我可以压缩与其前任之一的合并提交吗?
Can I squash a merge commit with one of its predecessors?
假设我从这样的历史开始:
A---B---C
\
\-D---E
然后,我合并了两个分支,解决了过程中的一些冲突:
A---B---C---F
\ /
\-D---E-/
但之后,我想重写历史,使其看起来像这样:
A---B---C---EF
\ /
\-D-----/
换句话说,我想将更改从分支提交 E
移动到合并提交 F
中。就好像我在 E
中进行了更改,同时手动解决了将 D
合并到 C
的冲突。
(我的实际原因是 E
只是一个合并准备提交,使预期的冲突更容易解决。它本身没有太大价值,我不想它污染了历史。我不想将 E 压缩到 它的 前身,因为它的变化在逻辑上是分开的。)
我试过了:
git rebase -i --rebase-merges HEAD~10
但这给了我:
...
pick 1069500 ...
merge -C 44c0a69 ...
# s, squash <commit> = use commit, but meld into previous commit
# ...
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
所以看起来提交可以是合并提交,或 git rebase -i
可以将其压缩到其前身,但不能同时合并。而且 git rebase
会 make me reapply my manual conflict resolutions,无论如何,这有点臭。
还有其他方法吗?
我会这样做:
git checkout $( git commit-tree -p C -p D -m "some merge" F^{tree} )
你最终会在一个修订版上以分离的 HEAD 结束,就像你所请求的那样(检查 git 日志或 gitk,检查文件)......如果你喜欢它:
git branch-f some-branch
git checkout some-branch
TL;DR
使用 git commit-tree
与 F
的快照和您选择的父级进行新的合并提交,然后强制您的分支名称指向那里:
newcommit=$(git commit-tree -p HEAD^ -p HEAD^2^ HEAD^{tree} -F /tmp/msg)
其中 /tmp/msg
包含您要使用的提交消息。然后使用 git reset
将当前分支/提交移动到 $newcommit
,检查此提交是否符合您的要求(例如 git log --graph --oneline $newcommit
)。
(这假设一个类 Unix shell,shell 变量设置为 var=value
,$(...)
到 运行 一个命令,等等.)
长
提交不存储更改。他们存储快照。
合并提交也是如此:它们的快照是快照。他们以每个文件的合并形式 保存每个文件的完整副本 。
所以假设你有:
A--B--C--F
\ /
D----E
(这是您的原始绘图,我只是将提交内容稍微推到我更喜欢的样式)并且想要:
A--B--C--G
\ /
D----'
(我只是在这里使用了一个全新的合并字母G
,但这和你的EF
是一样的)。合并提交 G
的 快照 必须与合并提交 F
的现有快照完全匹配。 merge commit G
的第一个 parent 必须是 commit C
——与现有 commit F
的第一个 parent 相同——第二个 parent 必须是 commit D
,跳过commit E
,但最重要的是,G
的 snapshot 应该与您已经使用 merge commit F
创建的快照完全相同。
现在,您可以轻松生成这个图:
___
/ \
A--B--C--F G
\ / /
D----E /
\_____/
根据您已有的图表,通过进行新提交 G
。 git commit-tree
命令正是这样做的。
您必须向 git commit-tree
提供三样东西:
一组父哈希 ID,或解析为哈希 ID 的名称。您需要提交的 ID C
和 D
,您可以分别使用 HEAD^1
和 HEAD^2^1
拼写它们。每个 1
都可以省略。这些是 -p
个参数。
新提交的树哈希 ID。因为 HEAD
是提交 F
并且它的树是你想要的,所以 HEAD^{tree}
指定。
提交日志消息,带有 -m
或 -F
或来自标准输入。
提交树程序写出新的提交对象并打印其哈希 ID,我们希望使用 shell 变量以人类可读的形式捕获它。
一旦完成,我们只需要更新当前的分支名称。由于此分支当前已检出,我们将使用 git reset
:
git reset $newcommit
我们可以 git reset --soft
避免更改当前索引,或 git reset --hard
更改索引和工作树,但如果当前索引和工作树与现有合并提交中的快照匹配F
,合并 G
中的快照无论如何都完全相同,因此实际上不需要任何一个标志。默认 --mixed
重置将重置索引,使工作树不受干扰。 (不过,如果你仔细安排了一些东西,你可能想要 --soft
。)
提交 F
将继续存在,但没有 name 找到它,您将看不到它——最终,一旦保留它的 reflog在过期前后,Git 的垃圾收集器将丢弃提交的 F
和 E
.
假设我从这样的历史开始:
A---B---C
\
\-D---E
然后,我合并了两个分支,解决了过程中的一些冲突:
A---B---C---F
\ /
\-D---E-/
但之后,我想重写历史,使其看起来像这样:
A---B---C---EF
\ /
\-D-----/
换句话说,我想将更改从分支提交 E
移动到合并提交 F
中。就好像我在 E
中进行了更改,同时手动解决了将 D
合并到 C
的冲突。
(我的实际原因是 E
只是一个合并准备提交,使预期的冲突更容易解决。它本身没有太大价值,我不想它污染了历史。我不想将 E 压缩到 它的 前身,因为它的变化在逻辑上是分开的。)
我试过了:
git rebase -i --rebase-merges HEAD~10
但这给了我:
...
pick 1069500 ...
merge -C 44c0a69 ...
# s, squash <commit> = use commit, but meld into previous commit
# ...
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
所以看起来提交可以是合并提交,或 git rebase -i
可以将其压缩到其前身,但不能同时合并。而且 git rebase
会 make me reapply my manual conflict resolutions,无论如何,这有点臭。
还有其他方法吗?
我会这样做:
git checkout $( git commit-tree -p C -p D -m "some merge" F^{tree} )
你最终会在一个修订版上以分离的 HEAD 结束,就像你所请求的那样(检查 git 日志或 gitk,检查文件)......如果你喜欢它:
git branch-f some-branch
git checkout some-branch
TL;DR
使用 git commit-tree
与 F
的快照和您选择的父级进行新的合并提交,然后强制您的分支名称指向那里:
newcommit=$(git commit-tree -p HEAD^ -p HEAD^2^ HEAD^{tree} -F /tmp/msg)
其中 /tmp/msg
包含您要使用的提交消息。然后使用 git reset
将当前分支/提交移动到 $newcommit
,检查此提交是否符合您的要求(例如 git log --graph --oneline $newcommit
)。
(这假设一个类 Unix shell,shell 变量设置为 var=value
,$(...)
到 运行 一个命令,等等.)
长
提交不存储更改。他们存储快照。
合并提交也是如此:它们的快照是快照。他们以每个文件的合并形式 保存每个文件的完整副本 。
所以假设你有:
A--B--C--F
\ /
D----E
(这是您的原始绘图,我只是将提交内容稍微推到我更喜欢的样式)并且想要:
A--B--C--G
\ /
D----'
(我只是在这里使用了一个全新的合并字母G
,但这和你的EF
是一样的)。合并提交 G
的 快照 必须与合并提交 F
的现有快照完全匹配。 merge commit G
的第一个 parent 必须是 commit C
——与现有 commit F
的第一个 parent 相同——第二个 parent 必须是 commit D
,跳过commit E
,但最重要的是,G
的 snapshot 应该与您已经使用 merge commit F
创建的快照完全相同。
现在,您可以轻松生成这个图:
___
/ \
A--B--C--F G
\ / /
D----E /
\_____/
根据您已有的图表,通过进行新提交 G
。 git commit-tree
命令正是这样做的。
您必须向 git commit-tree
提供三样东西:
一组父哈希 ID,或解析为哈希 ID 的名称。您需要提交的 ID
C
和D
,您可以分别使用HEAD^1
和HEAD^2^1
拼写它们。每个1
都可以省略。这些是-p
个参数。新提交的树哈希 ID。因为
HEAD
是提交F
并且它的树是你想要的,所以HEAD^{tree}
指定。提交日志消息,带有
-m
或-F
或来自标准输入。
提交树程序写出新的提交对象并打印其哈希 ID,我们希望使用 shell 变量以人类可读的形式捕获它。
一旦完成,我们只需要更新当前的分支名称。由于此分支当前已检出,我们将使用 git reset
:
git reset $newcommit
我们可以 git reset --soft
避免更改当前索引,或 git reset --hard
更改索引和工作树,但如果当前索引和工作树与现有合并提交中的快照匹配F
,合并 G
中的快照无论如何都完全相同,因此实际上不需要任何一个标志。默认 --mixed
重置将重置索引,使工作树不受干扰。 (不过,如果你仔细安排了一些东西,你可能想要 --soft
。)
提交 F
将继续存在,但没有 name 找到它,您将看不到它——最终,一旦保留它的 reflog在过期前后,Git 的垃圾收集器将丢弃提交的 F
和 E
.