Как разрешить конфликт git, сохранив все дополнения с обеих сторон?

Я продолжаю сталкиваться со следующей проблемой, и я хотел бы знать, есть ли простое решение с помощью существующих команд 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";
+  }
+};

Теперь, когда я пытаюсь объединить mybranch с master (или перебазировать 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 первым.)


person outofthecave    schedule 12.09.2017    source источник


Ответы (2)


Существует существует вещь, называемая объединением:

$ 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";
  }
};
person torek    schedule 12.09.2017
comment
Я попробовал оба этих решения, но они не делают именно то, что я хочу. Оба они создают один и тот же объединенный файл, в котором строки class Dog : public Animal и class Cat : public Animal расположены рядом друг с другом вместо двух отдельных определений классов. - person outofthecave; 12.09.2017
comment
Любопытный. Я могу добавить фактически полученный объединенный файл, если хотите: он выглядит правильно. - person torek; 12.09.2017
comment
Было бы неплохо, если бы вы могли добавить объединенный файл. Спасибо! - person outofthecave; 12.09.2017
comment
Вау, это именно то, что я хочу! Но это не то, что он делает на моей машине. Может быть, мой мерзавец слишком стар? У меня версия 2.14.1. - person outofthecave; 12.09.2017
comment
Это же версия. У меня merge.conflictstyle установлено на diff3, но я сомневаюсь, что это влияет на Git здесь. В конечном счете, проблема в том, что слияние объединений работает на построчной основе, и вы не можете контролировать, какие строки заказа выбираются, и я ожидал, что здесь тоже произойдет осечка, но это действительно сработало, и я был удивлен ( изменил ответ в середине потока!). - person torek; 12.09.2017
comment
Это действительно очень странно... Что касается потенциально опасного и неконтролируемого объединения union, то да, я тоже это читал. Но если это решит мою проблему, это будет стоить риска для меня. - person outofthecave; 12.09.2017

Git не может справиться с таким слиянием в типичных рабочих процессах. Вот почему он просит вас вмешаться. Он очень, очень старается не потерять изменения ни с одной из сторон, но на самом деле он не знает, как вы хотите это разрешить или как вы хотите это исправить.

Вы должны изучить инструмент слияния, чтобы помочь вам в этом. В качестве альтернативы вы можете сделать это вручную и удалить конфликтные строки слияния Git и самостоятельно выполнить слияние этих двух файлов.

person Makoto    schedule 12.09.2017