【问题标题】:Possible to resolve Git conflict on single file using Ours / Theirs?可以使用我们的/他们的解决单个文件上的 Git 冲突吗?
【发布时间】:2018-06-15 09:48:25
【问题描述】:

我在 Stack Overflow 和其他地方找到了许多使用 OURS/THEIRS 动态解决冲突的说明,以防您只想用另一个文件(尤其是二进制文件)覆盖一个文件。但是,在我发现的几乎每个示例中,它总是被大量应用于所有冲突,而我只想将它应用于单个冲突文件。

我发现的一个假设解决方案是使用 git mergetool 命令。但是,mergetool 给了我一些问题,我选择“选择左”或“选择右”,但没有任何反应。

不过,不管 mergetool 是什么,我都想知道是否有办法从命令行执行此操作。我确定有,如果有人让我知道命令,或者以其他方式将我链接到我一定找不到的 SO 问题,我将不胜感激。

我也尝试过使用...

git checkout --theirs PATH/TO/CONFLICTED/FILE

但是当我输入'git status'时,它仍然显示文件冲突。

非常感谢您的宝贵时间。

【问题讨论】:

    标签: git command-line merge


    【解决方案1】:

    TL;DR

    您将需要git add 最终分辨率,除非您使用不同的方法来提取“我们的”或“他们的”版本。

    每个合并工具都独立于 Git(Git 只是运行它们并让它们做自己的事情),因此对于这个问题的特定子部分,您必须咨询合并工具本身。

    至于git checkout --oursgit checkout --theirs,这就是Git 所称的索引 显示其全部复杂性的地方。请记住,索引,否则有点神秘,也称为 暂存区,有时也称为 缓存,本质上是您和 Git 构建 next 的地方提交你会做的

    当你运行时:

    git merge <commit-or-branch-specifier>
    

    Git 发现 三个 提交:

    • 一个是您的当前提交,它是您在任何给定时间一直使用的那个,所以这并不特别,除了您可以通过名称HEAD 来引用它或单个字符 @(例如,git rev-parse HEADgit rev-parse @ 以获取其哈希 ID)。
    • 一个是您刚刚命名的提交。如果您运行了git merge otherbranch,您可以运行git rev-parse otherbranch 来查看它的提交哈希ID 现在是什么。 (分支名称具有 moving: 的属性,即现在由分支名称标识的提交不一定是昨天或明天由该名称标识的提交。分支名称的这种运动是如何分支增长。)当然,如果您运行 git merge a123456,另一个提交,即 --theirs 的提交,是哈希 ID a123456
    • 最后一次提交是 merge base,Git 会自动为您找到。它通过使用来自您的提交和另一个提交的父链接来找到此提交,通过两个分支向后工作,直到找到两个分支首先重新组合在一起的适当点。

    找到三个提交后,Git 运行,生效:

    git diff --find-renames <merge-base> <ours>    # see what we changed
    git diff --find-renames <merge-base> <theirs>  # see what they changed
    

    合并过程——合并 就像一个动词一样——包括查找这三个提交、进行 diff 以及合并更改。当两组更改影响相同的行时,您会遇到合并冲突。

    在没有合并冲突的文件中,Git 将结果放入您的工作树(作为普通文件)和索引(作为文件的特殊 Git 形式,准备提交)。因此,对于不冲突的文件,您通常无需执行任何其他操作。

    但是,当发生合并冲突时,Git 会做两件不寻常的事情:首先,它将合并冲突的版本写入工作树,以便您可以将其作为纯文件进行编辑。其次,它写入索引,而不是文件的一个版本,而是所有三个:合并基础版本、“我们的”版本和“他们的”版本.

    Git 将这些额外的版本称为更高的阶段。阶段号是合并基数,没有--base 选项可以访问它,但您可以使用git show :1:<em>path</em> 来查看它。第二阶段是“我们的”版本:有--ours,但您也可以运行git show :2:<em>path</em> 来查看它。第 3 阶段是“他们的”版本,可通过git show :3:<em>path</em> 获得。这三个阶段替换正常的阶段零条目,现在缺少。

    实际上,当您运行git mergetool 时,它所做的就是在索引中找到三个版本,将它们提取到常规(非 Git 化)文件中,然后对这三个文件运行实际的合并工具。假设合并工具做正确的事(无论结果如何)将三个文件合并为一个合并文件,之后git mergetool 可以在结果上运行git add

    不过,从命令行——这就是我的合并方式——你可以编辑工作树文件及其冲突标记,并找出正确的结果是什么。写出来,git add 结果文件,你很好,因为git add 注意到文件以三阶段版本形式存在并删除这三个版本,而是写入阶段编号为零。

    一旦有阶段 0(不再是阶段 1-3),文件就被认为已解决。

    现在,git checkout --ours -- <em>path</em> 只是告诉 Git:从索引中取出 stage-2 版本并将其放入工作树中。 带有--theirs 的版本告诉 Git 上台-3 版本。在这两种情况下,索引及其三个分阶段的版本都被单独留下。这only 从索引中提取到工作树。 (这里的-- 是为了以防 path 部分是一个名为 --theirs 的文件。如果文件名与选项不同,则不需要--。一直使用-- 是一种好习惯,但大多数人不这样做。)

    由于索引仍然具有所有三个暂存版本,因此该文件尚未解析。运行 git add 获取工作树文件并将其放入插槽 0,清除 1 到 3 条目,现在文件已解析。

    奇怪的是,运行 git checkout HEAD -- <em>path</em>git checkout <em>otherbranch</em> -- <em>path</em> 会导致文件被解析。这是 Git 让实现决定接口的工件:在内部,当您使用 git checkout <em>name</em> -- <em>path</em> 时,Git 必须首先在给定的 name 中找到文件的 Git 形式(提交哈希或HEADotherbranch 之类的名称)。然后它必须 复制 Git 表单 into 索引......并且这种复制会清除插槽 1-3 条目,写入正常的插槽零条目。最后,Git 然后将(Git-form)文件从索引条目 0 提取到工作树。

    这种“先写入索引,然后从索引提取到工作树”的副作用是,如果文件处于冲突状态(阶段 1-3 处于活动状态),则不再发生冲突!因此:

    git checkout --ours -- file
    

    解析文件(因为它从索引槽 2 中提取),但是:

    git checkout HEAD -- file
    

    是否解析文件(因为它从当前提交中提取,进入索引插槽 0,清除 1-3;然后从插槽 0 条目中提取它刚刚写的)。

    【讨论】:

    • 解释太多了,不过谢谢 :)
    【解决方案2】:

    这是我对this question 的回答的(略微)修改版本。

    每当您需要合并单个文件的帮助时,我发现最好编写一个自定义 mergetool 来满足您的需求。为此,我建议将以下行添加到您的 .gitconfig 文件(或等效文件)中:

    [merge]
      conflictstyle = diff3
    [mergetool.getours]
      cmd = git-checkout --ours ${MERGED}
      trustExitCode = true
    [mergetool.mergeours]
      cmd = git-merge-file --ours ${LOCAL} ${BASE} ${REMOTE} -p > ${MERGED}
      trustExitCode = true
    [mergetool.keepours]
      cmd = sed -I '' -e '/^<<<<<<</d' -e '/^|||||||/,/^>>>>>>>/d' ${MERGED}
      trustExitCode = true
    [mergetool.gettheirs]
      cmd = git-checkout --theirs ${MERGED}
      trustExitCode = true
    [mergetool.mergetheirs]
      cmd = git-merge-file --theirs ${LOCAL} ${BASE} ${REMOTE} -p > ${MERGED}
      trustExitCode = true
    [mergetool.keeptheirs]
      cmd = sed -I '' -e '/^<<<<<<</,/^=======/d' -e '/^>>>>>>>/d' ${MERGED}
      trustExitCode = true
    

    get(ours|theirs) 工具仅保留文件的相应版本并丢弃来自其他版本的所有更改(因此不会发生合并)。这是您要求的关于二进制文件的方法。

    merge(ours|theirs) 工具从文件的本地、基本和远程版本重新执行三路合并,选择解决给定方向的冲突。这有一些警告,特别是:它忽略了传递给合并命令的差异选项(例如算法和空白处理);是否从原始文件干净地合并(因此对文件的任何手动更改都将被丢弃,这可能是好是坏);并且具有不会被文件中应该存在的差异标记混淆的优点。

    keep(ours|theirs) 工具只需编辑差异标记和封闭部分,通过正则表达式检测它们。这样做的好处是它保留了合并命令中的差异选项,并允许您手动解决一些冲突,然后自动解决其余的冲突。它的缺点是如果文件中有其他冲突标记,它可能会混淆。

    这些都是通过运行git mergetool -t (get|merge|keep)(ours|theirs) [&lt;filename&gt;] 使用的,如果没有提供&lt;filename&gt;,它会处理所有冲突的文件。

    一般来说,假设您知道没有差异标记来混淆正则表达式(并且您没有处理无法合并的二进制文件),该命令的keep* 变体是最强大的。如果您将 mergetool.keepBackup 选项保留为未设置或 true,则在合并后您可以将 *.orig 文件与合并结果进行比较,以检查它是否有意义。例如,我在mergetool 之后运行以下命令,只是为了在提交前检查更改:

    for f in `find . -name '*.orig'`; do vimdiff $f ${f%.orig}; done
    

    注意:如果merge.conflictstyle 不是diff3,那么sed 规则中的/^|||||||/ 模式需要改为/^=======/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-11
      • 2018-10-05
      • 2022-01-08
      • 1970-01-01
      相关资源
      最近更新 更多