Git 修复了错误的重命名映射



我有一个项目,branch-1
branch-2branch-1中,我复制了一个文件以再次使用它: 以前

root/
code.cpp

root/
src/
code.cpp
test/
code.cpp

意外地,当合并到 master 时(我无权更改),git 识别为rename {root/ => root/test/}code.cpp.
现在每次我更改代码时都会code.cppbranch-2并挑选branch-1,所有更改都将应用于test/code.cpp而不是src/code.cpp

我该如何纠正?

如果您需要在事后合并文件中的工作,请考虑git merge-file(见下文)。 但是"我该如何解决这个问题"的答案是:你没有。 Git 实际上首先没有文件重命名,所以没有什么可修复的!

现在,你可能会反对,嘿,Git 本身在这里说"重命名"。 确实如此。 但这并不是因为文件重命名了。 这是因为 Git 正在测试——当你要求它告诉你两个提交的时候——看看它是否可以假装文件被重命名了。 如果 Git 认为说"重命名此文件"会产生更短、更清晰的输出,Git 会这么说

您可以使用--no-renames选项将其关闭

git diff --no-renames <commit-hash-ID-1> <commit-hash-ID-2>

现在,当 Git 比较两个提交时,它永远不会说"重命名"。 相反,它会说"删除文件"和"添加文件"。

当 [I] 合并时

对于git merge,请使用-X no-renames而不是--no-renames。 (为什么这是一个与git diff--no-renames不同的选项拼写主要是历史的,以及Git通常糟糕的用户体验设计。

和樱桃采摘

挑选代码使用 Git的合并引擎,所以在这里,再次使用-X no-renames使 Git 停止调用"重命名"。

长:所有原始细节

我们将从git merge的概述开始,因为所有这些东西都使用 Git 的合并引擎。

Git 的自动组合工作方法

当您运行git merge other时,Git 执行以下操作:

  • 找到当前提交(通过HEAD);
  • 找到other指定的提交:这可以是分支名称或原始提交哈希ID,或者git rev-parse真正可以接受的任何内容;
  • 使用这两个提交,找到第三个提交,即合并库

为了用常规(和实际1)合并来说明这一点,让我们考虑以下图形片段:

o--...--L   <-- br1 (HEAD)
/
...--o--B

o--...--R   <-- br2

也就是说,您当前"在"分支br1,使用提交L(L这里代表一个丑陋的大哈希ID,我们将像git mergetool一样称LOCAL,或者像git restore一样称--ours)。 您要求 Git 通过git merge br2合并提交R。 Git 使用历史记录(从提交到早期提交的父链接)来查找第三个提交,我在这里称之为B。 这是合并基础。 它是"最佳公共(共享)提交",或者从技术上讲,是运行最低公共祖先算法的输出。阿拉伯数字

实际上,Git 现在运行:

git diff --find-renames <hash-of-B> <hash-of-L>

看看"我们"在"我们的"分支上发生了什么变化br1. 使用-X no-renames会使 Git 省略--find-renames部分。

然后 Git 运行第二个git diff命令:

git diff --find-renames <hash-of-B> <hash-of-R>

查看"他们"在"他们的"分支上发生了什么变化br2,因为相同的起点(提交B)。 同样,使用-X no-renames将禁用--find-renames步骤。

通过--find-renames步骤,Git 有时会将一些B并在L和/或R中删除的文件与一些不在B中但在L和/或R中新创建的文件配对。 Git 会将其称为重命名,并尝试保留此重命名更改。如果没有--find-renames步骤,Git 将不会进行此配对,这避免了 Git 找到错误对时出现的问题,但现在会给您留下"文件已删除"。

在所有情况下(无论是否--find-renames),Git 现在都有一系列可能性:

  • 该文件存在并且在所有三个提交(BLR)中具有相同的名称;
  • 文件在BL和/或BR过渡中重命名和/或删除;
  • 文件是在BL和/或BR过渡中的一个或两个中从头开始创建的。

第一种情况是最简单和最常见的一种,Git 通常可以很好地处理。 如果 Git 不知道如何组合Bto-LBto-R更改,则第二种和第三种情况可能会导致 Git 因合并冲突而停止。

如果确实存在文件的三个版本(包括重命名),我们现在有"同一文件"的"三个版本"(即使文件在L和/或R中具有不同的文件名)。 Git 现在尝试合并在BL和/或BR差异中所做的任何内容更改。 如果一方触摸了内容而另一方没有,Git 将从接触内容的一侧获取更改。 如果双方都没有触及内容,Git 将采用文件的三个版本中的任何一个。如果双方都接触了内容,Git 会对三个输入文件使用git merge-file来尝试合并更改。

git merge-file的结果可以是"成功合并"或"检测到合并冲突"。 如果检测到合并冲突,Git:

  • 将其合并的最佳近似写到工作树,并且
  • 将三个输入文件保留在其索引中,使用非零暂存号。

使用git ls-files --stage --unmerged,你可以在 Git 的索引中看到文件的三个版本;你可以使用git checkout-index或类似(包括git mergetool)来获取这三个版本。

但是,当文件被删除(或被检测为)时,至少有一个版本不在索引中。 您仍然可以从索引中恢复文件的合并基版本,这对于合并特别方便,因为识别提交B可能很棘手。 有关详细信息,请参阅此处的git checkout-index文档。

如果文件实际上没有被删除,而是被重命名(Git 只是错误地识别了正确的新文件)和/或有需要合并的工作,你现在必须绕过 Git 的正常自动化。


1当不需要真正的合并时,git merge命令有时会使用快捷方式。 我们不会在这里介绍这种情况,因为它不适用于任何问题情况。

2如果 LCA 算法发出多个提交哈希 ID,事情就会变得复杂;我在这里不涉及这种情况。

<小时 />

使用git merge-file

当 Git 对三个文件内容进行自动三向合并时,它将识别一个"侧"(例如,我们的/左侧/本地)接触而另一侧没有更改的部分,并将进行更改。 只有在"我们的"和"他们的"更改实际重叠或相邻的情况下,Git 才会声明合并冲突。

但是,当 Git 无法识别正确的输入文件时,它的自动合并是无用的。 对于这些情况,您只需提取三个输入。 然后,您可以将这三个文件提供给git merge-file命令:

git merge-file ours base theirs

或:

git merge-file foo.LOCAL foo.BASE foo.REMOTE

(正如git mergetool所说的那样)。 Git 将尽最大努力对ours(或foo.LOCAL)文件进行相同的三向合并,将basetheirs文件视为合并基础和"远程"。

git merge-file命令将使用相同类型的冲突标记;若要使其将正确的文本放入冲突标记中,请使用-L选项。

使合并提交结果

使用正确的名称获得正确的文件内容后,请使用git add(以及git rm,如果必要和适当)告诉 Git 你已成功合并更改。 然后使用git merge --continuegit commit完成合并。

如果git merge -X no-renames尝试提交合并是因为 Git 认为它已经成功完成了合并,即使它实际上并没有正确地这样做,请使用--no-commit选项来防止git merge进行提交。

樱桃采摘

如上所述,樱桃采摘实际上是使用合并引擎实现的。 运行:

git cherry-pick <commit-specifier>

运行与git merge相同的更改组合代码,但有一个更改:Git 使用指定提交的父级作为合并基础。

那是:

  • --ours提交是像往常一样HEAD提交;
  • --theirs提交是你像往常一样指定的提交;但是
  • 合并基是指定提交的(单个)父级。

其他一切都像常规合并一样工作,直到合并完成。 然后,你运行git cherry-pick --continue(或者再次,git commit)来让 Git 进行新的提交,而不是git merge --continue。 与git merge不同,新提交将是一个普通(单亲)提交。 与git merge一样,如果樱桃采摘认为它可以自己做樱桃采摘,但会出错,您可以使用--no-commit来防止这种情况发生。

这是@torek答案的补充

对于@torek答案,使用git merge-file需要3个文件,他没有提到如何获取这3个文件


git merge-file <target> <base> <changed>正如 git-merge-file 所记录的那样,git-merge-file会将更改从<base>更改为<changed><target>

对于这种情况:<target>将被src/code.cpp<changed>将被code.cpp(-X no-rename文件code.cpp将被标记为deleted by us),但我们没有<base>文件。

为了解决这个问题,我添加以下内容.gitconfig

[mergetool "manual"]
cmd = echo "$BASE" "$LOCAL" "$REMOTE" "$MERGED"

然后我跑git merge-tool --tool=manual code.cpp.当工具询问合并是否成功时,我按 Ctrl-C 打断它。现在我只剩下 4 个文件,"*_备份_*"*_基本_*"*_本地_*"*_远程_*"。<base>将是"*_BASE_*">

现在我可以正常运行git-merge-file

相关内容

  • 没有找到相关文章

最新更新