如何在冲突时将文件拆分为我们自己和他们自己

时间:2018-11-11 06:53:31

标签: git merge-conflict-resolution

我有一种方法来获取2个文件副本,其中第一个具有远程内容,第二个具有本地内容

3 个答案:

答案 0 :(得分:3)

我认为没有git选项可以将文件的两个版本都保留为两个副本。

但是您可以使用以下命令轻松实现此目标:

git merge the_branch
git checkout --theirs -- path/to/file ; mv path/to/file path/to/file.theirs
git checkout --ours -- path/to/file ; mv path/to/file path/to/file.ours
git checkout -m -- path/to/file

最后,您有三个文件:

  • file.theirs及其版本;
  • file.ours,以及您的版本;
  • file,带有版本和冲突标记。

答案 1 :(得分:1)

TL; DR

考虑使用git mergetool(尽管我自己从来没有这样做)或git show(有时我这样做)。还可以考虑将merge.conflictStyle设置为diff3,这会使Git将基本版本以及左侧和右侧版本写入冲突的工作树副本中。我发现启用此功能后,我几乎不需要看到这两个输入。 (但只有几乎永远不会。)

长(ish)

实际上,该文件在所有三个版本中都已经存在 ,但它们都在索引中,而不在工作树中。因此,诀窍是使它们脱离索引(它们是特殊的仅Git格式),进入您的工作树,您可以在其中查看和使用它们。

让我们简要而详细地了解发生冲突时git merge的工作方式。为了发生冲突,我们必须做这样的事情:

$ git checkout ours
Switched to branch 'ours'
$ git merge theirs

Git将查看提交图,发现ourstheirs有所不同,但是有一个常见的 merge base commit: 1

          o--...--L   <-- ours (HEAD)
         /
...--o--B
         \
          o--...--R   <-- theirs

提交 L 左侧 local --ours提交。提交 R 右侧远程--theirs提交。提交 B 这是合并基础。然后,Git实际上执行了两个git diff命令,一个命令找出自合并基础以来我们 发生了什么变化:

git diff --find-renames <hash-of-B> <hash-of-L>   # what we changed
git diff --find-renames <hash-of-B> <hash-of-R>   # what they changed

然后合并尝试使用与提交 B 相关的内容作为合并更改的基础,从而组合这两组更改。但是,我们更改了一些文件,他们更改了相同的文件,并且我们的更改与它们的更改发生了冲突,从而导致合并冲突。

这时,Git所做的是将文件的所有三个副本放入索引中,索引的临时插槽编号为非零

  • 第1阶段包含合并基础:文件 B:P ,其中 B 是基础提交,而 P 是路径的路径名文件(无论如何都可以在该提交中找到-我们可能已经重命名了该文件!)。
  • 第2阶段包含文件的版本:文件 L:P
  • 第3阶段包含文件的版本:文件 R:P

工作树包含Git尝试使用冲突标记合并两组更改的尝试。请注意,此版本与三个输入版本中的任何一个都不相同!合并的某些部分可能已经解决。发生冲突的更改的默认样式为merge,该样式仅显示左侧( B -vs- L )和右侧( B -vs- R 更改,而不会向您显示 B 本身的部分。在许多情况下就足够了,但是当更改纯粹是删除操作时,了解 B 中的哪些行将被删除通常非常有帮助,这不是您可以推断的。将冲突样式设置为diff3会使Git还在两个更改部分之间记录 B 代码部分。


1 可能有多个合并基础提交。在这种情况下,Git默认通过合并合并基础来构造新的合并基础提交。这个过程有点混乱,但是幸运的是,它很少见,即使发生了,也很少会使情况变得更糟。

提取三个版本

  • 您可以使用git checkout-index提取三个版本中的任何一个,尽管使用该命令有些麻烦:

    git checkout-index --all -- path/to/file
    

    这会将所有三个文件写入带有临时名称的文件,然后您必须重命名这些文件。 (此主题有多种变体,但都有些令人讨厌。)

  • 您可以使用git mergetool,它会自动提取所有三个版本,然后对所有三个版本调用您选择的合并工具命令。

  • 或者,您可以手动提取一个或两个文件。 DogEata's answer中的方法可以使用,但是如果您不担心行尾问题,这种方法会更短:

    git show :1:path/to/file > path/to/file.base
    git show :2:path/to/file > path/to/file.ours
    git show :3:path/to/file > path/to/file.theirs
    

    这使用the git show commandgitrevisions语法来访问索引副本,将它们显示为标准输出,并将输出重定向到新文件。

请注意,git add写入插槽零

Git知道合并正在进行中,并且还没有以几种方式完成,但是最重要的是在索引的某些路径的插槽1、2和/或3中有这些文件。找到该路径的正确内容并将其写入工作树中的该路径后,运行:

git add path/to/file

将文件复制回索引,进行常规格式的工作树复制,然后压缩为索引中包含的特殊的仅Git格式。

如果文件通常位于零位置的索引中,那只会用固定的工作树版本覆盖旧的索引副本。如果索引中的文件有多个副本,且插槽编号较高,则git add still 会写入插槽0,但这一次,它会删除编号较高的条目。现在文件已解决。

如果您使用git mergetool,则git mergetool命令可以自动为您运行git add,从而隐藏了额外的步骤。有些人觉得这特别方便。

答案 2 :(得分:0)

the official documentation中挑选樱桃的想法以及此处的其他评论,我想出了一个可能对您有所帮助的小功能。

此脚本也涵盖了基准冲突的情况,由于某种原因,使用git checkout --ours的解决方案无法解决这些冲突。

在我的特定用例中,我有兴趣在每次运行命令时将所有文件分开,并根据需要进行调整。

鱼的功能:

function git-conflict-split
    for file in (git ls-files -u | cut -c 51- | sort -u)
        rm $file
        git checkout-index --stage 3 -- $file
        mv $file $file.THEIRS
        git checkout-index --stage 2 -- $file
        mv $file $file.OURS
        git checkout-index --stage 1 -- $file
        echo "meld $file.OURS $file $file.THEIRS"
    end
end