【问题标题】:git checkout --merge/--ours/--theirs seem to be doing the same (wrong?) thing?git checkout --merge/--ours/--theirs 似乎在做同样的事情(错误?)?
【发布时间】:2019-09-07 20:38:23
【问题描述】:

我正在尝试从另一个分支合并(如果重要的话,它是一个孤立的分支)。但是,当我执行以下操作时:

git merge <branch-name>

它似乎正确合并。但是,如果我这样做:

git checkout --merge <branch-name> -- <file-names>

当前分支上的大部分(如果不是全部)更改都会被清除。不管我用--merge--ours还是--theirs,结果都是一样的。

我原以为checkout 在使用--merge 标志时会做与merge 相同的事情,除了指定的文件。

发生了什么事?有什么我不明白的地方吗?

【问题讨论】:

    标签: git


    【解决方案1】:

    TL;DR

    请参阅the git merge-file command,它确实可以让您做自己想做的事。

    git checkout-m--merge 标志具有多种不同的含义。

    当用于:

    git checkout -m <commit-specifier>
    

    它或多或少都有你想要的意思;问题是它适用于所有路径。

    当用于:

    git checkout -m [--] <paths>
    

    它有不同的含义:这意味着对于&lt;paths&gt; 中的每个命名路径,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 -- &lt;paths&gt; 模式,您实际上就会切换到完全不同的后端操作。此操作不是从提交开始,而是从索引开始。这些文件是在过去的某个时间从提交 复制到 索引的,因此该索引有一些文件集。自上次正常结帐或硬重置或其他任何事情以来,该集合可能已更新:每个git add 表示将文件从工作树复制到索引中,如果工作树文件没有t 匹配索引副本,好吧,现在它匹配索引中的文件集已更改。该索引甚至可能有非零阶段条目,它们表示来自不完整git merge 的持续合并冲突。在这种情况下,索引实际上存储的不是一个,而是 三个 一些文件的冻干副本,从三个输入到早期的 git merge 操作。3 但是,一个无论如何,this 类型的git checkout 根本不会返回提交:它只是从索引中获取文件并写入它们,或者-m 重新合并它们,并且破坏工作树中的任何内容。它这样做没有首先询问是否可以。4

    (编辑:还有git checkout --patch,但这实际上调用了第三种模式。补丁操作,它比较一个文件的两个版本,并让你选择这个差异的一部分应用到一个在这两个版本中,实际上是由一个在两个版本之间运行git diff 的Perl 程序处理的。这实现了git checkout --patchgit add --patchgit stash --patchgit 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 checkoutother 形式非常小心,不会丢失工作。

    【讨论】:

    • 呃! git 很神奇,但也很丑陋和不直观。因为这个blog entry,我才开始使用checkout 命令。这也在另一个 SOA (stackoverflow.com/a/55403131/1366368) 中被引用。当我第一次看到博客文章时,我发现它不起作用。然而,我当时认为--merge--ours--theirs 可能已经解决了这个问题,但遗憾的是它没有。 :'(
    • 我不确定为什么 git 让这件事变得如此困难。我认为这将是一个相当常见的操作,即使它并非一直有效,因为文件本身可能不一致。
    • 我想另一种选择是使用git checkout --patch &lt;branch&gt; -- &lt;files&gt;,但我宁愿让 git 做繁重的工作,只有在绝对必要时才需要我的帮助。
    • 我本来想提git checkout --patch,但忘了。这实际上是一个第三个独立的后端,它实现了(至少)签出、添加和重置的补丁模式,实际上是一个Perl程序。请注意,--patch 只查看一对文件,而不是来自一个基础的文件加上两个分支提示。
    • 小记:“REUC”扩展在git.git中Documentation/technical/index-format.txt"Resolve undo"部分提到
    猜你喜欢
    • 2017-12-05
    • 2023-03-20
    • 2021-10-28
    • 2011-03-14
    • 1970-01-01
    • 2014-09-04
    • 2017-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多