微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何消除合并提交中的错误并保留正确的部分? Git 如何执行真正的合并重做错误的合并如果自错误合并以来有提交

如何解决如何消除合并提交中的错误并保留正确的部分? Git 如何执行真正的合并重做错误的合并如果自错误合并以来有提交

不熟悉git的人在他的分支上提交了,然后在develop分支上做了一个合并提交。合并时,他:

  1. 通过完全重写来解决冲突
  2. 对可以合并而不会发生冲突的几个文件进行了更改
  3. 放弃了本应自动合并的其他更改

现在我想保留第 1 和第 2 部分,但还原第 3rd 部分,我该怎么办?注意到他的分支已经推送到远程所以我希望可以避免reset

我尝试过的:

  1. git revert <commit-id> -m 1 并在合并前返回提交
  2. 再次尝试合并,但被告知“已经是最新的”。并且丢弃的更改仍然消失了。

在这里所期望的应该与 git reset head^; git merge develop 相同,但似乎我没有正确理解 revert

解决方法

对于这个特定的问题没有正确的答案。只有答案会留下一些问题,而答案会留下许多问题。每个问题的严重程度取决于您的具体情况:

  • 例如,使用 git reset 剥离合并,然后使用 git push --force,会给使用远程克隆的其他人带来问题。但也许只有另一个人在使用该克隆,而另一个人已经知道该做什么,或者可以被指示该做什么。

    在这种情况下,剥离坏合并并重新开始的“坏处”相对较小,特别是因为您可以保持良好的分辨率(尽管这需要手动工作和大量 Git 知识)。完成后,没有人需要再次处理错误的合并,从而使事情处于良好状态。

  • 但也许许多人正在使用该远程存储库,并且剥离错误的合并会造成无法弥补的损害。在这种情况下,剥离坏合并的“坏处”是巨大的,您应该使用另一种策略。

要记住的主要事情是,Git 存储库归根结底只是提交 的集合。存储库中的提交是历史是存储库1所以,无论你最终做什么,你都会添加提交到存储库。要修复错误的合并提交,您必须添加更多提交。

这些不一定是合并提交。您可以保留现有的合并,只需将其记住(或将其标记为 - 请参阅 git notes)为“不好,不要使用” .然后,您可以添加解决问题的普通(非合并)提交。

每次提交都存储每个文件的完整快照。 提交包含与前一次提交的差异。因此,错误的合并提交只是一些文件内容错误的提交。随后的非合并提交可以存储具有正确内容的文件。

因此您的问题可以归结为两部分:

  • 您必须决定是否删除错误的合并。这是一种价值判断,没有正确答案。

  • 您必须提出更正的内容。这是一个机械问题:您将如何生成正确的文件?在这里,Git 可以提供帮助。

让我先放一个脚注,然后描述 Git 如何提供帮助。


1这有点夸大其词:可能有git notes,尽管从技术上讲,它们无论如何都存储在提交和标签中;并且人类重视分支名称,它们也在存储库中,但相当短暂,不应该被如此严重地依赖。


Git 如何执行真正的合并

在 Git 中,真正的合并是对 三个输入提交的操作。2 三个提交包括您选择的当前提交通过您的当前分支名称和特殊名称 HEAD。您在命令行上给 Git 另一个提交:当您运行 git merge other-branch-namegit merge hash-id 时,Git 使用它来定位另一个分支提示提交。有关分支提示如何工作以及HEAD如何工作的更多信息,请参阅Think Like (a) Git。此站点也将有助于理解下一部分。

鉴于这两个分支提示提交,Git 现在使用提交图自行查找三个输入提交中的第三个——或者在某种意义上,第一个 .每个普通的非合并提交向后连接到某个较早的提交。这一系列的向后连接最终必须到达某个共同的起点,在那里两个分支最后共享某个特定的提交。

我们可以把这种情况画成这样:

          I--J   <-- our-branch (HEAD)
         /
...--G--H
         \
          K--L   <-- their-branch

我们最近的提交,我画成提交 J,向后指向一些早期的提交,我画成提交 I。他们最近的提交 L 向后指向某个较早的提交 K。但是 IK 向后指向某个提交——这里是 H——同时在两个分支Think Like (a) Git 有很多关于它是如何工作的,但为了我们在这里的目的,我们只需要看到 Git 自己找到提交 H,并且它在两个分支上。

当我们运行 git merge 时,提交 J 作为我们的提交——Git 调用 --oursHEADlocal 提交——并提交L 作为他们的提交——Git 称其为 --theirs远程 提交,通常——Git 发现提交 H 作为合并基础。然后它:

  1. 提交H中的快照与我们提交J中的快照进行比较。这会找出我们更改了哪些文件,以及我们对这些文件进行了哪些更改。

  2. H 中的快照与 L 中的快照进行比较。这会找出他们更改了哪些文件,以及他们对这些文件进行了哪些更改。

  3. 合并更改。这是辛苦的部分。 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 {1}} about(使用原始哈希 ID,或使用名称 their-branch)。

如果我们必须自己修复合并,我们会这样做并运行 git add,然后运行 ​​git commitgit merge --continue,以使合并提交 M。当我们这样做时,我们可以完全控制进入 M 的内容。


2这种合并会导致合并提交,即有两个父项的提交。 Git 还可以执行它所谓的快进合并,这根本不是合并并且不会产生新的提交,或者它所谓的章鱼合并,它需要超过三个输入提交。 Octopus 合并有一定的限制,这意味着它们不适用于这种情况。真正的合并可能涉及进行递归合并,这也会使图片复杂化,但我将在这里忽略这种情况:复杂性与我们将要做的事情没有直接关系。 >


重做错误的合并

我们这里的情况是,我们开始于:

          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,我们可以在合并提交 git rev-parse 上使用 M,并带有时髦的 ^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 hash -- path 技巧使我们可以在某些情况下跳过 git add 步骤,但无论如何运行 git add 也无妨。)当我们全部完成后,我们运行 git merge --continuegit commit 以完成此合并:结果是新的合并提交 M2N,在我们的新分支 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 statusgit diff --cached 和/或 git commit,具体取决于我们是否确定一切顺利。

上面的结果是:

          I--J-----M2   <-- repair
         /    \   /
...--G--H      M-/--N   <-- our-branch (HEAD)
         \    /_/
          K--L   <-- their-branch

我们现在可以完全删除分支名称 repair:commit N 只是“神奇地固定”。

如果我们打算保持提交M2,我们可以使用git mergerepair合并到M中。我们可能想要运行 git merge --no-commit 以便我们获得完全控制:这将阻止 git merge 进行实际提交,以便我们可以检查即将进入新合并的快照。然后最后的 git merge --continuegit commitN 作为新的合并提交:

          I--J-----M2   <-- repair
         /    \   /  \
...--G--H      M-/----N   <-- our-branch (HEAD)
         \    /_/
          K--L   <-- their-branch

我们可以再次删除名称repair;它不再增加任何价值。

(我通常只是自己做一个简单的非合并修复提交,而不是另一个合并。使 N as 成为合并的合并基础是两个提交 {{1 }} 和 J,这意味着除非我们指定 L,否则 Git 将进行递归合并。递归合并往往很混乱,有时会出现奇怪的冲突。)

如果自错误合并以来有提交

发生在之后 bad-merge--s resolve 的提交只需要将它们的更改推进到我在上面绘制的作为最终提交 M 的内容中。你如何实现这一点并不是非常重要,尽管有些方法可能会让 Git 为你做更多的工作。这里要记住的是我之前说过的:最后,重要的是存储库中的提交。这包括图表——从提交到较早提交的向后连接——和快照。该图对 Git 本身很重要,因为它是 N 的工作方式以及 git log 如何找到合并基础。快照对很重要,因为它们是 Git 存储你关心的内容的方式。

,

好的,我设法自己解决了。如果幸运的人遇到类似情况,我会发布解决方案。

  1. develop 签出一个新分支,我们称之为 fix
  2. 将错误的分支合并到fix中,选择正确的部分并丢弃错误的部分
  3. fix 合并到错误的分支中,因为我想保持分支干净

看起来很简单,为什么我要花这么多时间才能想出解决方案......?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。