如何通过保留双方的所有添加来解决git冲突?

8
我是一名有用的助手,可以为您翻译文本。

我一直遇到以下问题,我想知道是否存在使用现有git命令的简单解决方案,或者是否需要编写自定义合并驱动程序

这里有一个示例来说明我的问题:假设我的存储库的master分支具有以下文件:

$ cat animal.hpp 
#include <string>

class Animal
{
public:
  virtual std::string say() const = 0;
};

我创建了一个名为mybranch的新分支,并进行了以下更改:
diff --git a/animal.hpp b/animal.hpp
index e27c4bf..3bb691a 100644
--- a/animal.hpp
+++ b/animal.hpp
@@ -5,3 +5,13 @@ class Animal
 public:
   virtual std::string say() const = 0;
 };
+
+class Dog : public Animal
+{
+public:
+  virtual std::string
+  say() const override
+  {
+    return "woof";
+  }
+};

与此同时,其他人向master分支添加了非常相似的更改:
diff --git a/animal.hpp b/animal.hpp
index e27c4bf..310d5a7 100644
--- a/animal.hpp
+++ b/animal.hpp
@@ -5,3 +5,13 @@ class Animal
 public:
   virtual std::string say() const = 0;
 };
+
+class Cat : public Animal
+{
+public:
+  virtual std::string
+  say() const override
+  {
+    return "meow";
+  }
+};

现在当我尝试合并mybranchmaster(或将mybranch变基到master),我遇到了冲突:
$ cat animal.hpp 
#include <string>

class Animal
{
public:
  virtual std::string say() const = 0;
};

<<<<<<< HEAD
class Dog : public Animal
=======
class Cat : public Animal
>>>>>>> master
{
public:
  virtual std::string
  say() const override
  {
<<<<<<< HEAD
    return "woof";
=======
    return "meow";
>>>>>>> master
  }
};

这个冲突的正确解决方案是:
$ cat animal.hpp 
#include <string>

class Animal
{
public:
  virtual std::string say() const = 0;
};

class Dog : public Animal
{
public:
  virtual std::string
  say() const override
  {
    return "woof";
  }
};

class Cat : public Animal
{
public:
  virtual std::string
  say() const override
  {
    return "meow";
  }
};

我通过手动编辑文件创建了上述决议,复制了两个类共同的代码并删除了冲突标记。但是我认为git应该能够自动创建此解决方案,因为它只需要添加在一个分支中添加的所有行,然后是在另一个分支中添加的所有行,而不会去重复共同的行。如何让git为我执行此操作?
(额外加分的解决方案可以允许我自定义顺序 - 首先是“Dog”还是首先是“Cat”。)
2个回答

8

有一种东西叫做联合合并

$ printf 'animal.hpp\tmerge=union\n' > .gitattributes
$ git merge --no-commit mybranch
Auto-merging animal.hpp
Automatic merge went well; stopped before committing as requested

在这种情况下,它实际上实现了所期望的结果。然而,在许多情况下,它可能会失败得非常严重,并且它永远不会失败(我的意思是,即使它产生了无意义的结果,它也从不认为自己已经失败),因此在.gitattributes中设置它并不明智(除非你有一个非常罕见的情况)。
相反,最好在事后使用git merge-file来调用它:
$ git merge --abort
$ rm .gitattributes
$ git merge mybranch
Auto-merging animal.hpp
CONFLICT (content): Merge conflict in animal.hpp
Automatic merge failed; fix conflicts and then commit the result.
$ git show :1:animal.hpp > animal.hpp.base
$ git show :2:animal.hpp > animal.hpp.ours
$ git show :3:animal.hpp > animal.hpp.theirs
$ mv animal.hpp.ours animal.hpp
$ git merge-file --union animal.hpp animal.hpp.base animal.hpp.theirs

确保结果是您想要的,然后进行一些清理并添加最终合并版本:

$ rm animal.hpp.base animal.hpp.theirs
$ git add animal.hpp 

现在,您已准备好提交结果。 编辑:这是我得到的结果(两种情况下均如此):

$ cat animal.hpp 
#include <string>

class Animal
{
public:
  virtual std::string say() const = 0;
};

class Cat : public Animal
{
public:
  virtual std::string
  say() const override
  {
    return "meow";
  }
};

class Dog : public Animal
{
public:
  virtual std::string
  say() const override
  {
    return "woof";
  }
};

我尝试了这两种解决方案,但它们都没有完全做到我想要的。它们都会生成相同合并的文件,其中"class Dog : public Animal"和"class Cat : public Animal"这两行代码挨在一起,而不是分开成两个不同的类定义。 - outofthecave
有点好奇。如果您愿意,我可以添加实际的合并文件:在我看来它是正确的。 - torek
如果您能添加合并后的文件,那就太好了。谢谢! - outofthecave
哇,这正是我想要的!但是在我的电脑上它并不起作用。也许我的git版本太旧了?我使用的是2.14.1版。 - outofthecave
这是相同的版本。我确实已经将merge.conflictstyle设置为diff3,但我怀疑它不会影响Git。最终问题在于联合合并基于逐行方式进行,并且您无法控制选定行的顺序,我本来以为它在这里也会出错,但它实际上起作用了,我很惊讶(中途改变答案!)。 - torek
这确实非常奇怪... 至于联合合并可能会有潜在的危险和不可控性,是的,我也读过。但如果它解决了我的问题,对我来说它是值得冒险的。 - outofthecave

5

在典型的工作流程中,Git 无法 处理这种类型的合并。这就是为什么它要求您进行干预。它非常努力地不想失去任何一方的更改,但实际上它并不知道您想如何解决问题或修复它。

您应该寻找一款合并工具来帮助您处理此问题。或者,您可以手动完成此操作并删除Git合并冲突行,并自己处理这两个文件的合并。


有点傻,git不能处理这种改变,因为逻辑相当简单。标记位置A,在位置A之前直接插入第一次提交的文本,然后在第一次提交的结尾和位置A之间(或者直接在位置A之前)插入第二次提交的文本。 - undefined
@WayneWerner:这并不是微不足道的问题,这也是为什么Git无法解决它的原因。当你给Git两个不同的版本时,它无法直观地知道你的意图,也不应该盲目地假设你想保留两个版本。例如,这可能是你和其他人共同完成的工作,Git不应该追踪谁对什么变更做出了什么贡献,以便推测出某些行为。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接