Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve a git conflict by keeping all additions from both sides?

I keep running into the following problem and I'd like to know whether there is an easy solution to it using existing git commands or whether I'll need to write a custom merge driver.

Here's an example to demonstrate my problem: Say the master branch of my repo has the following file:

$ cat animal.hpp 
#include <string>

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

I create a new branch called mybranch and make the following change:

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";
+  }
+};

In the meantime, someone else adds the following, very similar change to 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";
+  }
+};

Now when I try to merge mybranch with master (or rebase mybranch onto master), I get a conflict:

$ 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
  }
};

The correct resolution to this conflict is:

$ 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";
  }
};

I've created the above resolution by manually editing the file, duplicating the code that is common to the two classes, and removing the conflict markers. But I think git should be able to create this resolution automatically, given that it only needs to add all the lines that were added in one branch, followed by all the lines that were added in the other branch, without deduplicating common lines. How can I make git do this for me?

(Bonus points for solutions that allow me to customize the order - Dog first or Cat first.)

like image 219
outofthecave Avatar asked Sep 12 '17 17:09

outofthecave


People also ask

How do I accept both changes in Git conflict?

The only way to "accept both" would be to just re-stage (marking as resolved) the conflicted files without resolving them ( >>> and <<< would be there still), but your result couldnt be compiled or executed. And this is terrible practice, even if you make commits later to resolve.


2 Answers

There is a thing called a union merge:

$ 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

In this particular case, it actually achieves the desired results. However, in many cases it can misfire rather badly, and it never fails (I mean it never considers itself to have failed, even if it produced a nonsense result), so it's not really wise to set it up in .gitattributes after all (unless you have a really rare case).

Instead, it's better to invoke it after the fact, using 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

Make sure the result is what you wanted, then clean up a bit and add the final merged version:

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

and you are now ready to commit the result. Edit: Here's the result I got (in both cases):

$ 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";
  }
};
like image 194
torek Avatar answered Oct 13 '22 03:10

torek


Git can't handle that kind of merge for you in typical workflows. That's why it's asking you to intervene. It's trying very, very hard not to lose changes from either side, but it doesn't really know how you want to resolve it or how you want to fix it.

You should look into a merge tool to help aid you with this. Alternatively, you can do this manually and remove the Git merge conflict lines, and handle the merge of these two files yourself.

like image 30
Makoto Avatar answered Oct 13 '22 02:10

Makoto