git pull --rebase --preserve-merges 重写未修改父项的合并提交
git pull --rebase --preserve-merges rewrites merge commit with unmodified parents
回顾历史
D origin/master
/
| C master
| |\
| | B
\|/|
A :
|
:
在 git pull --rebase --preserve-merges
之后,我希望历史是
C' master
|\
D |
| B
|/|
A :
|
:
但结果是
C' master
|\
| B'
|/|
D :
|
A
|
:
换句话说,B 提交被重写为 B',即使不需要它,因为两个父项都未修改。我知道我正在变基到 D,但是这是有问题的,因为必须再次解决 B 合并中的所有冲突。
当前功能有什么好处吗?
有没有办法获得我想要的功能?
我无法真正回答 "benefit" 问题,但我可以描述为什么 rebase 会按原样运行,以及如何获得您想要的结果。
无论交互与否,Rebase 都采用 "three branch-or-commit-like" 个参数,它调用 newbase
、upstream
, 和 branch
:
git rebase
[-i
| --interactive
] [options
] [--exec <em>cmd</em>
] [--onto <em>newbase</em>
]
[upstream
[branch
]]
如果您省略其中的部分或全部,rebase 仍会使用它们,它只是自行找到它们:
branch
默认为当前分支,或者HEAD
(包括分离的头)。
upstream
默认为当前分支的上游,由 git branch --set-upstream-to
或类似设置。无论您在此处指定什么,或默认值,都会提供给 git rev-list
以使其 stop 接受提交,即,将重新定位的提交是由 [=117= 打印的提交]git rev-list upstream..HEAD.
newbase
默认为与 upstream
. 相同的提交 ID
(如果您在 git 中提供 --fork-point
足以拥有 --fork-point
,这些都会有所修改,但这里的总体思路仍然适用:要重新设置基数的提交是默认情况下,根据 "after" 上游的那些选择,直到并包括 HEAD。)
当你让 git pull
运行 git rebase
为你提供时,它提供,作为 upstream
,当前分支的实际上游(同样,由 fork 修改-point 计算在较新的 gits 中,但是如果您从中获取的实际上游没有完成自己的变基,这应该没有影响)。 (--onto
通常也是相同的上游。在这种情况下,它等于 git rebase [options] --onto origin/master origin/master master
。)
如您所见(通过运行宁git rev-list origin/master..master
),这意味着"please rebase commits B
and C
onto D
"。添加 --preserve-merges
仅使用交互式机制并按要求保留合并,在按要求重新定位两个提交之后。
如果你只是 运行 git fetch
,你将像往常一样获得提交 D
并且将你绘制的图形作为第一个:
D origin/master
/
| C master
| |\
| | B
\|/|
A :
|
:
您现在可以 尝试 到 运行 git rebase -p
手动变基提交 C
,指定 B
作为其 "upstream" 以便 rebase 不会复制提交 B
,并将 D
指定为 --onto
提交,以便 C
重新基于 D
:
$ git rebase -p --onto origin/master master^2 master
不幸的是,这会产生一个新的合并提交,其两个父项是 origin/master
(提交 D
)和原始的 master^
(提交 A
)。 (虽然这显然与内部交互保留模式的工作方式有关,但原因并不明显。)
因此,本例中的技巧是进行您自己的合并:
$ git branch -m master old-master
$ git checkout master
(这使得一个新的 master
指向提交 D
),然后:
$ git merge old-master^2
(这会重新合并未修改的 B
)。
不过,您还提到:
... it is problematic because all conflicts in the B merge have to be resolved again.
不幸的是,您无法真正避免这种情况,因为 D
可能与 A
有很大不同,因此实际的合并冲突解决方案应该 也 不同。
如果您确定它们(分辨率)不应该(不同),您也可以通过从旧提交 C
中获取合并结果来回避这个问题。例如,假设冲突发生在 dir1/file1
和 dir2/file2
中,而 A
和 D
之间的差异全部在 README.txt
或类似的地方。然后,当您执行上述合并时,您可以 "resolve" 非常简单地解决冲突:
$ git checkout old-master -- dir1/file1 dir2/file2
提取这些文件的提交 C
版本,更新您的索引和工作树以恢复您以前的合并解决方案。您的冲突现已解决(与以前完全一样),您可以 git commit
合并结果。
回顾历史
D origin/master
/
| C master
| |\
| | B
\|/|
A :
|
:
在 git pull --rebase --preserve-merges
之后,我希望历史是
C' master
|\
D |
| B
|/|
A :
|
:
但结果是
C' master
|\
| B'
|/|
D :
|
A
|
:
换句话说,B 提交被重写为 B',即使不需要它,因为两个父项都未修改。我知道我正在变基到 D,但是这是有问题的,因为必须再次解决 B 合并中的所有冲突。
当前功能有什么好处吗?
有没有办法获得我想要的功能?
我无法真正回答 "benefit" 问题,但我可以描述为什么 rebase 会按原样运行,以及如何获得您想要的结果。
无论交互与否,Rebase 都采用 "three branch-or-commit-like" 个参数,它调用 newbase
、upstream
, 和 branch
:
git rebase
[-i
|--interactive
] [options
] [--exec <em>cmd</em>
] [--onto <em>newbase</em>
]
[upstream
[branch
]]
如果您省略其中的部分或全部,rebase 仍会使用它们,它只是自行找到它们:
branch
默认为当前分支,或者HEAD
(包括分离的头)。upstream
默认为当前分支的上游,由git branch --set-upstream-to
或类似设置。无论您在此处指定什么,或默认值,都会提供给git rev-list
以使其 stop 接受提交,即,将重新定位的提交是由 [=117= 打印的提交]git rev-list upstream..HEAD.newbase
默认为与upstream
. 相同的提交 ID
(如果您在 git 中提供 --fork-point
足以拥有 --fork-point
,这些都会有所修改,但这里的总体思路仍然适用:要重新设置基数的提交是默认情况下,根据 "after" 上游的那些选择,直到并包括 HEAD。)
当你让 git pull
运行 git rebase
为你提供时,它提供,作为 upstream
,当前分支的实际上游(同样,由 fork 修改-point 计算在较新的 gits 中,但是如果您从中获取的实际上游没有完成自己的变基,这应该没有影响)。 (--onto
通常也是相同的上游。在这种情况下,它等于 git rebase [options] --onto origin/master origin/master master
。)
如您所见(通过运行宁git rev-list origin/master..master
),这意味着"please rebase commits B
and C
onto D
"。添加 --preserve-merges
仅使用交互式机制并按要求保留合并,在按要求重新定位两个提交之后。
如果你只是 运行 git fetch
,你将像往常一样获得提交 D
并且将你绘制的图形作为第一个:
D origin/master
/
| C master
| |\
| | B
\|/|
A :
|
:
您现在可以 尝试 到 运行 git rebase -p
手动变基提交 C
,指定 B
作为其 "upstream" 以便 rebase 不会复制提交 B
,并将 D
指定为 --onto
提交,以便 C
重新基于 D
:
$ git rebase -p --onto origin/master master^2 master
不幸的是,这会产生一个新的合并提交,其两个父项是 origin/master
(提交 D
)和原始的 master^
(提交 A
)。 (虽然这显然与内部交互保留模式的工作方式有关,但原因并不明显。)
因此,本例中的技巧是进行您自己的合并:
$ git branch -m master old-master
$ git checkout master
(这使得一个新的 master
指向提交 D
),然后:
$ git merge old-master^2
(这会重新合并未修改的 B
)。
不过,您还提到:
... it is problematic because all conflicts in the B merge have to be resolved again.
不幸的是,您无法真正避免这种情况,因为 D
可能与 A
有很大不同,因此实际的合并冲突解决方案应该 也 不同。
如果您确定它们(分辨率)不应该(不同),您也可以通过从旧提交 C
中获取合并结果来回避这个问题。例如,假设冲突发生在 dir1/file1
和 dir2/file2
中,而 A
和 D
之间的差异全部在 README.txt
或类似的地方。然后,当您执行上述合并时,您可以 "resolve" 非常简单地解决冲突:
$ git checkout old-master -- dir1/file1 dir2/file2
提取这些文件的提交 C
版本,更新您的索引和工作树以恢复您以前的合并解决方案。您的冲突现已解决(与以前完全一样),您可以 git commit
合并结果。