可以使用Ours /他们解决单个文件上的Git冲突吗?

时间:2018-01-05 14:54:20

标签: git command-line merge

我已经在Stack Overflow和其他地方发现了很多关于使用OURS / THEIRS动态解决冲突的指令,如果您只是想用另一个文件覆盖一个文件(特别是二进制文件)。然而,在我发现的几乎每个例子中,它总是被集中应用于所有冲突,而我只想将它应用于单个冲突文件。

我找到的一个假设解决方案是使用git mergetool命令。然而,mergetool给了我一些问题,我选择"选择左"或者"选择正确的"没有任何反应。

无论合并工具如何,无论如何我都想知道是否有办法从命令行执行此操作。我确信有,如果有人让我知道这个命令,或者把我链接到我一定不能找到的SO问题,我会非常感激。

我也尝试过使用......

git checkout - 他们的路径/ TO / CONFLICTED / FILE

但是当我输入“git status'”时,它仍会显示该文件存在冲突。

非常感谢您的时间。

2 个答案:

答案 0 :(得分:2)

TL; DR

您需要git add最终解决方案,除非您使用其他方法来提取"我们的"或者"他们的"版本

每个合并工具都独立于Git(Git只运行它们并允许它们执行它们的操作)因此,对于此问题的特定子部分,您必须参考合并工具本身。

至于git checkout --oursgit checkout --theirs,这就是Git称​​ index 显示其完整复杂性的地方。请记住,索引,这是另一种神秘的,也称为临时区域,有时是缓存,基本上是你和Git建立的地方承诺你会做

当你跑步时:

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的提交)就是哈希标识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

合并过程 - 合并作为动词,因为它包括找到这三个提交,做差异,并组合变化。当两组更改影响相同的行时,会出现合并冲突。

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

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

Git将这些额外版本称为更高阶段。阶段编号是合并基础,并且没有--base选项可以访问它,但您可以使用git show :1:path来查看它。第二阶段是&#34;我们的&#34;版本:有--ours但您也可以运行git show :2:path来查看它。第3阶段是&#34;他们的&#34;版本,可通过git show :3:path获取。这三个阶段替换正常的阶段零条目,现在已经缺失。

实际上,当你运行git mergetool时,它的作用是找到索引中的三个版本,将它们提取到常规(非Git-ified)文件中,并在这三个文件上运行实际的合并工具。合并工具被假定为Do The Right Thing(无论结果是什么)将三个文件合并为一个合并文件,之后git mergetool可以对结果运行git add

从命令行开始 - 我是如何进行合并的 - 你可以编辑工作树文件及其冲突标记,并找出正确的结果。写出来,git add生成的文件,你很好,因为git add注意到该文件存在于三阶段版本中并删除这三个版本,而是写入阶段数字为零。

一旦阶段为零(并且不再是阶段1-3),该文件将被视为已解决。

现在,git checkout --ours -- path只是告诉Git:从索引中取出stage-2版本并将其放入工作树。 --theirs版本告诉Git取代阶段3版本。在这两种情况下,索引及其三个阶段版本都是独立的。此从索引中提取到工作树。 (这里的--以防万一 path 部分是一个名为--theirs的文件。如果文件名不像选项,你不需要--。这是一种习惯,一直使用--,但大多数人都没有。)

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

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

这个&#34;首先写入索引,然后从索引提取到工作树的副作用&#34;是如果文件处于冲突状态 - 阶段1-3活跃 - 它不再冲突!因此:

git checkout --ours -- file

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

git checkout HEAD -- file

解析文件(因为它从当前提交中提取,转到索引槽0,擦除1-3; 然后从槽0条目中提取它刚写道。)

答案 1 :(得分:0)

这是我对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)工具从文件的本地,基本和远程版本重新进行三种方式的合并,选择解决给定方向上的冲突。具体来说,有一些警告:它忽略了传递给merge命令的diff选项(例如算法和空白处理);从原始文件中干净地进行合并(因此,对文件的任何手动更改都将被丢弃,可能是好是坏);并具有不能与文件中的差异标记混淆的优点。

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

所有这些都通过运行git mergetool -t (get|merge|keep)(ours|theirs) [<filename>]来使用,如果未提供<filename>,则会处理所有冲突的文件。

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

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

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