TL;DR
请参阅the git merge-file command,它确实可以让您做自己想做的事。
长
git checkout 的 -m 或 --merge 标志具有多种不同的含义。
当用于:
git checkout -m <commit-specifier>
它或多或少都有你想要的意思;问题是它适用于所有路径。
当用于:
git checkout -m [--] <paths>
它有不同的含义:这意味着对于<paths> 中的每个命名路径,Git 应该在具有(或具有)多个更高阶段索引条目的文件的工作树副本中重新创建合并冲突.
这里有一个更根本的问题。其中一部分只是棘手的措辞——例如,我们都说“工作树中的变化”——但另一部分在于如何思考 Git 做了什么:
...当前分支上的大部分(如果不是全部)更改都会被清除
这表明您正在考虑每个文件的工作树副本中的内容随着 的更改,而实际情况并非如此。 Git 不会在任何地方存储更改,1 文件的工作树副本主要供您根据需要使用:Git 主要使用快照,文件存储在我喜欢称之为冻结的位置-dried 格式,在与提交相关联的 blob 对象中,在索引中。
是当前分支和当前提交的概念,但分支只是一个名称(存储在HEAD) ,而提交是一个提交对象,由其哈希 ID(存储在分支名称中)、永久(大部分)和不可变(完全)标识。提交间接包含每个源文件的完整快照。索引在 Git 中也是至关重要的东西,它也存储快照,但与提交不同的是,索引中的内容是可变的。
同时,每个提交都存储了一组 parent 提交的哈希 ID——通常恰好是一个这样的提交。当你让 Git 向你展示一些提交时,Git 实际上会从父 和 提交本身中提取所有文件,2 然后 比较(所有中的文件)两次提交并向您展示了不同。因此,当您查看提交时,它似乎发生了变化。
Git 对索引执行相同的技巧:它比较当前提交与索引,向您显示差异并调用那些 为提交暂存的更改。然后,它将索引(如果您现在运行git commit,基本上是您提议将成为下一次提交的快照)与工作树进行比较。无论索引和工作树之间有什么不同,Git 都会显示这些差异,称那些 更改未暂存为提交。但在所有三组文件中——提交的文件、索引中的文件和工作树中的文件——实际上并不是更改,而是快照。 p>
git checkout 通常会做的——有很多例外,因为git checkout 实际上是多个不同的命令都挤在一个面向用户的动词中——是从提交快照中提取文件,将这些文件写入索引 (以便索引和提交匹配)然后将索引副本写入工作树(以便索引和工作树匹配)。但在执行任何操作之前,它首先检查以确保您不会丢失任何未保存的工作,方法是将当前提交与索引进行比较,并将索引与工作树进行比较:如果这两者不匹配,则有git checkout 的东西会崩溃。
不过,一旦您使用git checkout -- <paths> 模式,您实际上就会切换到完全不同的后端操作。此操作不是从提交开始,而是从索引开始。这些文件是在过去的某个时间从提交 复制到 索引的,因此该索引有一些文件集。自上次正常结帐或硬重置或其他任何事情以来,该集合可能已更新:每个git add 表示将文件从工作树复制到索引中,如果工作树文件没有t 匹配索引副本,好吧,现在它匹配索引中的文件集已更改。该索引甚至可能有非零阶段条目,它们表示来自不完整git merge 的持续合并冲突。在这种情况下,索引实际上存储的不是一个,而是 三个 一些文件的冻干副本,从三个输入到早期的 git merge 操作。3 但是,一个无论如何,this 类型的git checkout 根本不会返回提交:它只是从索引中获取文件并写入它们,或者-m 重新合并它们,并且破坏工作树中的任何内容。它这样做没有首先询问是否可以。4
(编辑:还有git checkout --patch,但这实际上调用了第三种模式。补丁操作,它比较一个文件的两个版本,并让你选择这个差异的一部分应用到一个在这两个版本中,实际上是由一个在两个版本之间运行git diff 的Perl 程序处理的。这实现了git checkout --patch、git add --patch、git stash --patch 和git reset --patch。)
无论如何,最重要的是git checkout -m -- path 不会做你想做的事。你可以得到你想要的,但不能使用git checkout。相反,您需要做的是提取您想要传递给git merge 的三个输入文件——将这三个文件放在任何地方;它们甚至不需要在存储库本身的工作树中——然后在它们上运行the git merge-file command。
1好吧,除非您存储git diff 的输出,或者,作为一种特殊情况,存储来自git rerere 的合并冲突的每个部分,但所有这些都是低于正常能见度。
2由于内部冻干文件格式,Git 实际上不必费心提取 相同 文件,只需提取至少有一点不同的文件。
3从技术上讲,每个文件最多三个条目。例如,在修改/删除冲突等情况下,某个文件只有两个条目。此外,当您完成解决合并冲突和git add 文件时,更高阶段的条目会消失。但是,在您提交之前,那些较高阶段的条目存储在一个秘密的、不可见的“REUC”类型的索引条目中,特别是这样您就可以使用git checkout -m 来恢复冲突。无法查看或保存此不可见条目,这是当前索引格式的几个缺陷之一。
4从用户友好性设计的角度来看,这尤其糟糕,因为git checkout 的other 形式非常小心,不会丢失工作。