如何摆脱合并提交中的错误并保留正确的部分?
How to get rid of mistakes in a merge commit and keep the right parts?
有人不熟悉 git 在他的分支上提交,然后与 develop
分支进行合并提交。合并时,他:
- 通过完全重写解决了冲突
- 对几个可以合并而不会发生冲突的文件进行了更改
- 丢弃了本应自动合并的其他更改
现在我想保留1和2的部分,但要还原3rd的部分,怎么办?注意到他的分支已经被推送到远程所以我希望可以避免reset
。
我尝试过的:
git revert <commit-id> -m 1
并回到合并前的提交
- 再次尝试合并,但被告知 'Already up to date.' 并且丢弃的更改仍然存在。
我在这里期待的应该和git reset head^; git merge develop
一样,但我似乎没有正确理解revert
。
好的,我设法自己修复了它。我 post 万一幸运的人遇到类似情况的解决方案。
- 从
develop
签出一个新分支,我们称之为 fix
- 将错误的分支合并到
fix
中,挑对的部分丢弃错误的部分
- 合并
fix
到故障分支因为我想保持分支干净
看起来很容易,为什么我要花那么多时间想出解决方案...?
这个特定问题没有正确答案。只有留下几个问题的答案,和留下很多问题的答案。每个问题的严重程度取决于您的具体情况:
例如,使用 git reset
剥离合并,然后使用 git push --force
,会给使用远程克隆的其他人带来问题。但也许只有 另一个人 在使用那个克隆,并且另一个人已经知道该做什么,或者可以被指示该做什么。
在这种情况下,剥离错误的合并并重新开始的“坏处”相对较小,尤其是因为您可以保留良好的分辨率(尽管这需要手动工作和大量 Git 知识) .完成后,没有人需要再次处理错误的合并,这让一切都处于良好状态。
但也许 很多 人正在使用那个远程存储库,并且删除错误的合并会造成无法弥补的损失。在那种情况下,剥离坏合并的“坏处”是巨大的,你应该使用另一种策略。
要记住的主要事情是,Git 存储库最终只不过是 提交 的集合。存储库中的提交 是历史记录 并且 是存储库 .1 所以,无论你最终如何这样做,您将 将提交添加到存储库 。要修复错误的合并提交,您必须添加更多提交。
这些不必是合并提交。您可以保留现有的合并,只需记住它(或将其标记为 git notes
)为“错误” , 不使用”。然后您可以添加解决问题的普通(非合并)提交。
每个提交存储每个文件的完整快照。提交不包含与上一次提交的差异。因此,错误的合并提交只是一些文件内容错误的提交。随后的非合并提交可以存储具有正确内容的文件。
因此,您的问题归结为两部分:
你必须决定是否删除坏合并。这是一个价值判断,没有正确答案。
您必须提出更正的内容。这是一个机械问题:您将如何产生正确的文件?在这里,Git 可以提供帮助。
让我把脚注移开,然后描述 Git 如何提供帮助。
1这是一种温和的夸大:可能有 git notes
,尽管从技术上讲,它们无论如何都存储在提交和标签中;并且人类重视 b运行ch 名称,它们也在存储库中,但相当短暂,不应该如此依赖。
Git 如何执行真正的合并
在 Git 中,真正的合并是对 三个输入提交的操作。2 这三个提交包括您的当前提交,由您的当前 b运行ch 名称 和特殊名称HEAD
选择。你在命令行上给 Git 另一个提交:当你 运行 git merge <em>other-b运行ch-name</em>
or git merge <em>hash-id</em>
, Git用这个定位另一个b运行ch 提示 提交。有关 b运行ch tips 的工作原理以及 HEAD
的工作原理的更多信息,请参阅 Think Like (a) Git。该站点也将有助于理解下一部分。
鉴于这两个 b运行ch 提示提交,Git 现在找到第三个——或者在某种意义上,第一个——三个输入提交单独使用 提交图 。每个普通的非合并提交都向后连接到某个较早的提交。这一系列的后向连接最终必须到达某个共同起点,其中两个 b运行ches 最后共享某个特定提交。
我们可以这样画出这种情况:
I--J <-- our-branch (HEAD)
/
...--G--H
\
K--L <-- their-branch
我们最近的提交,我将其绘制为提交 J
,向后指向一些较早的提交,我将其绘制为提交 I
。他们最新的提交 L
指向一些较早的提交 K
。但是 I
和 K
向后指向某个提交——这里是 H
——它同时在 both b运行ches. Think Like (a) Git 有很多关于它是如何工作的,但为了我们的目的,我们只需要看到 Git 自己找到提交 H
,并且它在两个 b[=377 上=]ches.
当我们 运行 git merge
将提交 J
作为我们的提交时——Git 调用 --ours
或 HEAD
或 local 提交——并提交 L
作为他们的提交——Git 称其为 --theirs
或 remote提交,通常—Git 发现提交H
作为合并基础。然后它:
将 在 提交 H
中的快照与我们提交 J
中的快照进行比较。这会找出 我们 更改了哪些文件,以及我们对这些文件进行了哪些更改。
将 H
中的快照与 L
中的快照进行比较。这会找出 他们 更改了哪些文件,以及他们对这些文件做了哪些更改。
合并更改。这是艰苦工作的部分。 Git 使用简单的文本替换规则进行组合:它不知道真正应该使用哪些更改。在规则允许的情况下,Git 自行进行这些更改;如果规则声称存在冲突,Git 会将冲突传递给我们,让我们解决。在任何情况下,Git 应用 对起始提交中快照的组合更改:merge base H
。这样可以在添加他们的更改的同时保留我们的更改。
因此,如果合并顺利进行,Git 将进行新的合并提交 M
,如下所示:
I--J
/ \
...--G--H M <-- our-branch (HEAD)
\ /
K--L <-- their-branch
新提交 M
有一个快照,就像任何提交一样,还有一个日志消息和作者等等,就像任何提交一样。 M
唯一的特别之处在于它不仅链接回提交 J
——我们开始时的提交——而且还链接回提交 L
,我们告诉其哈希 ID 的提交 git merge
关于(使用原始哈希 ID,或使用名称 their-branch
)。
如果我们必须自己修复合并,我们会这样做,然后 运行 git add
然后 git commit
或 git merge --continue
,使合并提交 M
。当我们这样做时,我们可以完全控制进入 M
.
的内容
2这种合并会导致 合并提交,即有两个父项的提交。 Git 也可以执行它所谓的 快进合并 ,这根本不是合并并且不产生新的提交,或者它所谓的 octopus merge,需要三个以上的输入提交。八达通合并有一定的限制,这意味着它们不适用于这种情况。真正的合并可能涉及进行 递归 合并,这也使图片复杂化,但我将在这里忽略这种情况:复杂性与我们将要做的事情没有直接关系.
重做错误的合并
我们这里的情况是我们开始于:
I--J <-- our-branch (HEAD)
/
...--G--H
\
K--L <-- their-branch
然后有人——大概不是我们——运行 git merge their-branch
或等价物,遇到了合并冲突,并错误地解决了它们并提交:
I--J
/ \
...--G--H M <-- our-branch (HEAD)
\ /
K--L <-- their-branch
要重新执行合并,我们只需要签出/切换到提交J
:
git checkout -b repair <hash-of-J>
例如,或:
git switch -c repair <hash-of-J>
使用新的(自 Git 2.23 起)git switch
命令。然后我们运行:
git merge <hash-of-L>
要获得两个哈希 ID,我们可以在合并提交 M
上使用 git rev-parse
,使用时髦的 ^1
和 ^2
语法后缀;或者我们可以 运行 git log --graph
或类似的方法找到两个提交并直接查看它们的哈希 ID。或者,如果名称 their-branch
仍然找到提交 L
,我们可以 运行 git merge their-branch
。 Git 只需要找到正确的提交。
此时,Git 将按照完全相同的规则重复之前尝试的合并尝试。这将产生完全相同的冲突。我们现在的工作是解决这些冲突,但这一次,我们做对了。
如果我们喜欢其他人在提交 M
中做出的决议,我们可以询问 git checkout
(Git 的所有版本)或 git restore
(Git 2.23 及更高版本)提取其他人提交的已解析文件 M
:
git checkout <hash-of-M> -- <path/to/file>
例如。即使我们不喜欢整个分辨率,我们仍然可以这样做,然后修复文件和 运行 git add
;只有当我们不喜欢 任何 的决议,并且想自己完成整个修复时,我们才 必须 完成整个自己装修。
尽管如此,我们只是修复了每个文件,git add
结果告诉 Git 我们已经修复了文件。 (git checkout <em>hash</em> -- <em>path</em>
技巧使我们可以跳过git add
在某些情况下是一步,但它也不会伤害到 运行 git add
。)当我们都完成后,我们 运行 git merge --continue
或git commit
完成此合并:结果是一个新的合并提交 M2
或 N
,在我们的新 b运行ch repair
或我们在我们创建了它:
I--J-----M2 <-- repair (HEAD)
/ \ /
...--G--H M / <-- our-branch
\ /_/
K--L <-- their-branch
我们现在可以 git checkout our-branch
,这使我们能够提交 M
,并直接从 repair
:
抓取文件
git checkout our-branch
git checkout repair -- path/to/file1
git checkout repair -- path/to/file2
...
然后我们准备好 git commit
进行新的提交 N
。或者,我们可以从 M2
:
集中抓取 每个 文件
git checkout repair -- .
和运行 git status
, git diff --cached
, and/or git commit
在这一点上,取决于我们对这一切的确定程度。
以上结果为:
I--J-----M2 <-- repair
/ \ /
...--G--H M-/--N <-- our-branch (HEAD)
\ /_/
K--L <-- their-branch
我们现在可以完全删除 b运行ch name repair
:commit N
只是“神奇地修复”了。
如果我们打算保持提交M2
,我们可以使用git merge
将repair
合并到M
。我们可能想要 运行 git merge --no-commit
以便我们获得完全控制:这将阻止 git merge
进行实际提交,以便我们可以检查即将进入的快照新合并。然后最后的 git merge --continue
或 git commit
使 N
作为新的合并提交:
I--J-----M2 <-- repair
/ \ / \
...--G--H M-/----N <-- our-branch (HEAD)
\ /_/
K--L <-- their-branch
我们可以再次删除名称repair
;它不再增加任何有价值的东西。
(我通常只是自己做一个简单的非合并修复提交,而不是另一个合并。使 N
as 合并的合并基础是提交 J
和 L
,这意味着 Git 将进行递归合并,除非我们指定 -s resolve
。递归合并往往很混乱,有时会有奇怪的冲突。)
如果自错误合并以来有提交
发生在之后 bad-merge-M
的提交只需要将它们的更改转入我在上面绘制的最终提交 N
中。你如何着手实现这一目标并不是非常重要,尽管某些方法可能 Git 为你做更多的工作。这里要记住的是我之前所说的:最后,重要的是存储库 中的提交 。这包括图表——从提交到早期提交的回溯连接——和快照。该图对 Git 本身很重要,因为它是 git log
的工作方式以及 git merge
找到合并基础的方式。快照对你很重要,因为它们是Git存储你关心的内容的方式。
有人不熟悉 git 在他的分支上提交,然后与 develop
分支进行合并提交。合并时,他:
- 通过完全重写解决了冲突
- 对几个可以合并而不会发生冲突的文件进行了更改
- 丢弃了本应自动合并的其他更改
现在我想保留1和2的部分,但要还原3rd的部分,怎么办?注意到他的分支已经被推送到远程所以我希望可以避免reset
。
我尝试过的:
git revert <commit-id> -m 1
并回到合并前的提交- 再次尝试合并,但被告知 'Already up to date.' 并且丢弃的更改仍然存在。
我在这里期待的应该和git reset head^; git merge develop
一样,但我似乎没有正确理解revert
。
好的,我设法自己修复了它。我 post 万一幸运的人遇到类似情况的解决方案。
- 从
develop
签出一个新分支,我们称之为fix
- 将错误的分支合并到
fix
中,挑对的部分丢弃错误的部分 - 合并
fix
到故障分支因为我想保持分支干净
看起来很容易,为什么我要花那么多时间想出解决方案...?
这个特定问题没有正确答案。只有留下几个问题的答案,和留下很多问题的答案。每个问题的严重程度取决于您的具体情况:
例如,使用
git reset
剥离合并,然后使用git push --force
,会给使用远程克隆的其他人带来问题。但也许只有 另一个人 在使用那个克隆,并且另一个人已经知道该做什么,或者可以被指示该做什么。在这种情况下,剥离错误的合并并重新开始的“坏处”相对较小,尤其是因为您可以保留良好的分辨率(尽管这需要手动工作和大量 Git 知识) .完成后,没有人需要再次处理错误的合并,这让一切都处于良好状态。
但也许 很多 人正在使用那个远程存储库,并且删除错误的合并会造成无法弥补的损失。在那种情况下,剥离坏合并的“坏处”是巨大的,你应该使用另一种策略。
要记住的主要事情是,Git 存储库最终只不过是 提交 的集合。存储库中的提交 是历史记录 并且 是存储库 .1 所以,无论你最终如何这样做,您将 将提交添加到存储库 。要修复错误的合并提交,您必须添加更多提交。
这些不必是合并提交。您可以保留现有的合并,只需记住它(或将其标记为 git notes
)为“错误” , 不使用”。然后您可以添加解决问题的普通(非合并)提交。
每个提交存储每个文件的完整快照。提交不包含与上一次提交的差异。因此,错误的合并提交只是一些文件内容错误的提交。随后的非合并提交可以存储具有正确内容的文件。
因此,您的问题归结为两部分:
你必须决定是否删除坏合并。这是一个价值判断,没有正确答案。
您必须提出更正的内容。这是一个机械问题:您将如何产生正确的文件?在这里,Git 可以提供帮助。
让我把脚注移开,然后描述 Git 如何提供帮助。
1这是一种温和的夸大:可能有 git notes
,尽管从技术上讲,它们无论如何都存储在提交和标签中;并且人类重视 b运行ch 名称,它们也在存储库中,但相当短暂,不应该如此依赖。
Git 如何执行真正的合并
在 Git 中,真正的合并是对 三个输入提交的操作。2 这三个提交包括您的当前提交,由您的当前 b运行ch 名称 和特殊名称HEAD
选择。你在命令行上给 Git 另一个提交:当你 运行 git merge <em>other-b运行ch-name</em>
or git merge <em>hash-id</em>
, Git用这个定位另一个b运行ch 提示 提交。有关 b运行ch tips 的工作原理以及 HEAD
的工作原理的更多信息,请参阅 Think Like (a) Git。该站点也将有助于理解下一部分。
鉴于这两个 b运行ch 提示提交,Git 现在找到第三个——或者在某种意义上,第一个——三个输入提交单独使用 提交图 。每个普通的非合并提交都向后连接到某个较早的提交。这一系列的后向连接最终必须到达某个共同起点,其中两个 b运行ches 最后共享某个特定提交。
我们可以这样画出这种情况:
I--J <-- our-branch (HEAD)
/
...--G--H
\
K--L <-- their-branch
我们最近的提交,我将其绘制为提交 J
,向后指向一些较早的提交,我将其绘制为提交 I
。他们最新的提交 L
指向一些较早的提交 K
。但是 I
和 K
向后指向某个提交——这里是 H
——它同时在 both b运行ches. Think Like (a) Git 有很多关于它是如何工作的,但为了我们的目的,我们只需要看到 Git 自己找到提交 H
,并且它在两个 b[=377 上=]ches.
当我们 运行 git merge
将提交 J
作为我们的提交时——Git 调用 --ours
或 HEAD
或 local 提交——并提交 L
作为他们的提交——Git 称其为 --theirs
或 remote提交,通常—Git 发现提交H
作为合并基础。然后它:
将 在 提交
H
中的快照与我们提交J
中的快照进行比较。这会找出 我们 更改了哪些文件,以及我们对这些文件进行了哪些更改。将
H
中的快照与L
中的快照进行比较。这会找出 他们 更改了哪些文件,以及他们对这些文件做了哪些更改。合并更改。这是艰苦工作的部分。 Git 使用简单的文本替换规则进行组合:它不知道真正应该使用哪些更改。在规则允许的情况下,Git 自行进行这些更改;如果规则声称存在冲突,Git 会将冲突传递给我们,让我们解决。在任何情况下,Git 应用 对起始提交中快照的组合更改:merge base
H
。这样可以在添加他们的更改的同时保留我们的更改。
因此,如果合并顺利进行,Git 将进行新的合并提交 M
,如下所示:
I--J
/ \
...--G--H M <-- our-branch (HEAD)
\ /
K--L <-- their-branch
新提交 M
有一个快照,就像任何提交一样,还有一个日志消息和作者等等,就像任何提交一样。 M
唯一的特别之处在于它不仅链接回提交 J
——我们开始时的提交——而且还链接回提交 L
,我们告诉其哈希 ID 的提交 git merge
关于(使用原始哈希 ID,或使用名称 their-branch
)。
如果我们必须自己修复合并,我们会这样做,然后 运行 git add
然后 git commit
或 git merge --continue
,使合并提交 M
。当我们这样做时,我们可以完全控制进入 M
.
2这种合并会导致 合并提交,即有两个父项的提交。 Git 也可以执行它所谓的 快进合并 ,这根本不是合并并且不产生新的提交,或者它所谓的 octopus merge,需要三个以上的输入提交。八达通合并有一定的限制,这意味着它们不适用于这种情况。真正的合并可能涉及进行 递归 合并,这也使图片复杂化,但我将在这里忽略这种情况:复杂性与我们将要做的事情没有直接关系.
重做错误的合并
我们这里的情况是我们开始于:
I--J <-- our-branch (HEAD)
/
...--G--H
\
K--L <-- their-branch
然后有人——大概不是我们——运行 git merge their-branch
或等价物,遇到了合并冲突,并错误地解决了它们并提交:
I--J
/ \
...--G--H M <-- our-branch (HEAD)
\ /
K--L <-- their-branch
要重新执行合并,我们只需要签出/切换到提交J
:
git checkout -b repair <hash-of-J>
例如,或:
git switch -c repair <hash-of-J>
使用新的(自 Git 2.23 起)git switch
命令。然后我们运行:
git merge <hash-of-L>
要获得两个哈希 ID,我们可以在合并提交 M
上使用 git rev-parse
,使用时髦的 ^1
和 ^2
语法后缀;或者我们可以 运行 git log --graph
或类似的方法找到两个提交并直接查看它们的哈希 ID。或者,如果名称 their-branch
仍然找到提交 L
,我们可以 运行 git merge their-branch
。 Git 只需要找到正确的提交。
Git 将按照完全相同的规则重复之前尝试的合并尝试。这将产生完全相同的冲突。我们现在的工作是解决这些冲突,但这一次,我们做对了。
如果我们喜欢其他人在提交 M
中做出的决议,我们可以询问 git checkout
(Git 的所有版本)或 git restore
(Git 2.23 及更高版本)提取其他人提交的已解析文件 M
:
git checkout <hash-of-M> -- <path/to/file>
例如。即使我们不喜欢整个分辨率,我们仍然可以这样做,然后修复文件和 运行 git add
;只有当我们不喜欢 任何 的决议,并且想自己完成整个修复时,我们才 必须 完成整个自己装修。
尽管如此,我们只是修复了每个文件,git add
结果告诉 Git 我们已经修复了文件。 (git checkout <em>hash</em> -- <em>path</em>
技巧使我们可以跳过git add
在某些情况下是一步,但它也不会伤害到 运行 git add
。)当我们都完成后,我们 运行 git merge --continue
或git commit
完成此合并:结果是一个新的合并提交 M2
或 N
,在我们的新 b运行ch repair
或我们在我们创建了它:
I--J-----M2 <-- repair (HEAD)
/ \ /
...--G--H M / <-- our-branch
\ /_/
K--L <-- their-branch
我们现在可以 git checkout our-branch
,这使我们能够提交 M
,并直接从 repair
:
git checkout our-branch
git checkout repair -- path/to/file1
git checkout repair -- path/to/file2
...
然后我们准备好 git commit
进行新的提交 N
。或者,我们可以从 M2
:
git checkout repair -- .
和运行 git status
, git diff --cached
, and/or git commit
在这一点上,取决于我们对这一切的确定程度。
以上结果为:
I--J-----M2 <-- repair
/ \ /
...--G--H M-/--N <-- our-branch (HEAD)
\ /_/
K--L <-- their-branch
我们现在可以完全删除 b运行ch name repair
:commit N
只是“神奇地修复”了。
如果我们打算保持提交M2
,我们可以使用git merge
将repair
合并到M
。我们可能想要 运行 git merge --no-commit
以便我们获得完全控制:这将阻止 git merge
进行实际提交,以便我们可以检查即将进入的快照新合并。然后最后的 git merge --continue
或 git commit
使 N
作为新的合并提交:
I--J-----M2 <-- repair
/ \ / \
...--G--H M-/----N <-- our-branch (HEAD)
\ /_/
K--L <-- their-branch
我们可以再次删除名称repair
;它不再增加任何有价值的东西。
(我通常只是自己做一个简单的非合并修复提交,而不是另一个合并。使 N
as 合并的合并基础是提交 J
和 L
,这意味着 Git 将进行递归合并,除非我们指定 -s resolve
。递归合并往往很混乱,有时会有奇怪的冲突。)
如果自错误合并以来有提交
发生在之后 bad-merge-M
的提交只需要将它们的更改转入我在上面绘制的最终提交 N
中。你如何着手实现这一目标并不是非常重要,尽管某些方法可能 Git 为你做更多的工作。这里要记住的是我之前所说的:最后,重要的是存储库 中的提交 。这包括图表——从提交到早期提交的回溯连接——和快照。该图对 Git 本身很重要,因为它是 git log
的工作方式以及 git merge
找到合并基础的方式。快照对你很重要,因为它们是Git存储你关心的内容的方式。