Git фиксация потеряна после слияния

Мы имеем 3 ветки (A, B, C), как показано ниже:

---\--A1--\------Am------Merge1---An---Merge2---
    \      \              /             /
     \      \--C1---C2---/             /
      \                               /
       \--B1--------------Bn---------/

Проблема появляется в Merge2. Некоторая фиксация на ветке C ( не все, но некоторые, пусть C2) теряется на ветке A после Merge2, которая присутствует между Merge1 и Merge2.

При выполнении Merge2 существует только один конфликт файлов, который не связан с потерянной фиксацией (C2). И мы разрешаем конфиг и успешно завершаем слияние.

Кажется, что C2 реверсируется Merge2 на ветке A без какого-либо журнала.

Что случилось? Что может быть причиной этой ситуации?

Ответ 1

Вы имеете в виду не то, что сам коммит потерян, а то, что изменения, внесенные (в некоторый файл) в этот коммит, были отменены (un -m ade).

Стоит отметить, что коммиты не "вносят изменения" в файлы. Вместо этого каждый коммит хранит полный набор файлов, целых и неповрежденных. Когда вы просите Git показать вам коммит (скажем, его ID - c2c2c2...):

$ git show c2c2c2

что делает Git:

  1. извлечь коммит c2c2c2...
  2. извлечь родительский коммит c2c2c2...
  3. создать список различий от родителя к ребенку

Вот как Git удается показать вам, что изменилось: он сравнивает "то, что у вас было перед этим коммитом" с "тем, что вы имели на тот коммит". (Git может сделать это довольно быстро, оптимизированно, потому что каждый файл сводится к уникальному хешу "отпечаток", и Git может сначала просто сравнить отпечатки пальцев (хэши). Если хеши совпадают, файлы одинаковы. Извлечение реальных файловых данных может быть затруднительно только в том случае, если хэши различаются.)

Этот процесс - сохранение целых файлов вместо накопления изменений - называется "хранением снимков". (Другие системы управления версиями имеют тенденцию накапливать изменения, которые называются "хранением дельт". Они делают это, потому что сохранение изменений в файлах, очевидно, занимает гораздо меньше места, чем сохранение файлов. Git ловко обходит проблему и заводит, используя в любом случае меньше места на диске, чем в старых системах контроля версий на основе дельты.)

Почему "Git хранит снимки, а не дельты" так важно здесь

Фиксация слияния является особенной, одним конкретным и очевидным способом. Посмотрите на собственную диаграмму и рассмотрите Merge1. Какой коммит приходит прямо перед ним?

Ответ здесь заключается в том, что и Am и C2 приходят "прямо перед" Merge1. То есть commit Merge1 имеет два родительских коммита.

Если вы попросите Git показать, что вы совершаете Merge1, с каким родителем он должен сравниваться?

Здесь вещи становятся особенно странными. Две команды git log -p и git show кажутся очень похожими. На самом деле они очень похожи. Единственным очевидным отличием является то, что git log -p показывает более одного коммита, в то время как git show показывает только один коммит, который вы указали ему показать. Вы можете запустить git log -n 1 -p <commit> чтобы показать только один коммит, и теперь кажется, что они точно такие же.

Они не!

Когда вы используете git show для фиксации слияния, Git пытается решить проблему "что зафиксировать для сравнения", сравнивая одновременно обоих родителей. Результирующий diff называется комбинированным diff.

Однако, когда вы используете git log -p слияния, Git просто подбрасывает метафорические руки, говорит: "Я не могу показать патчи против двух родителей", сдается и переходит к следующему коммиту. Другими словами, git log -p даже не пытается использовать diff для слияния.

Но подождите, там больше

Теперь, в этом случае у вас может возникнуть искушение посмотреть, сможете ли вы выяснить, что случилось с вашим файлом из коммита c2c2c2... с помощью git show для двух слияний, в частности, для Merge2, где изменения были отменены. Но git show по умолчанию создает комбинированный diff, а комбинированный diff намеренно пропускает большую часть diff. В частности, в комбинированном diff перечислены только файлы, которые были изменены от всех родителей.

Допустим, файл, в котором были отменены ваши изменения из C2 это файл f2. И, согласно графику, два родителя Merge2 - это An (у которого f2 так, как вы хотите) и Bn (которого нет).

На самом деле здесь произошло то, что во время слияния, которое создало Merge2, вы как-то сказали Git использовать версию f2 из коммита Bn. То есть файл f2 в Merge2 точно такой же, как файл f2 в Bn, и отличается от f2 в коммите An.

Если вы используете git show для просмотра Merge2, комбинированный diff пропустит f2, потому что он такой же, как f2 в Bn.

То же самое верно, только еще хуже, с git log -p: он полностью пропускает слияние, потому что слишком сложно показывать различия.

Даже без -p, когда вы запрашиваете "измененные файлы", git log завершает свою работу, полностью пропуская слияние. Вот почему вы не можете увидеть это в выводе журнала.

(Кроме того, причина, по которой git log master -- f2 никогда не показывает сам коммит C2, заключается в том, что добавление имени файла в параметры git log приводит к "упрощению истории". В том, что я считаю несколько ошибочным поведением, Git завершает работу. упрощая слишком много истории, так что он никогда не показывает коммит C2. Добавление --full-history до того, как -- f2 восстанавливает C2 в показанном наборе коммитов. Однако слияние по-прежнему отсутствует, поскольку git log пропускает его.)

Как увидеть изменения

Есть решение. И git show и git log имеют дополнительный флаг -m, который "разделяет" слияния. То есть вместо того, чтобы рассматривать Merge2 как коммит слияния, они разбивают слияние на два "виртуальных коммита". Один из них будет " Merge2 vs An ", и вы увидите все различия между этими двумя коммитами, а другой будет " Merge2 vs Bn ", и вы увидите все различия между этими двумя коммитами. Это покажет, что файл f2 был переустановлен на тот же, что и в Bn, потеряв версию из C2 которая появляется в An но не в Bn.

(Включите --full-history а также -m чтобы убедиться, что коммит C2 обнаруживается.)

Как это произошло в первую очередь

Эта часть не совсем понятна. Вы сказали, что произошел конфликт слияния, что означает, что git merge остановился и получил ручную помощь от человека. В какой-то момент во время этой помощи человек, вероятно, обновил индекс версией файла f2 из Bn (или, по крайней мере, версией f2, в которой не было внесено изменение обратно в C2).

Это может произойти во время слияний, и это немного коварно, потому что Git показывает слияния с этими сжатыми (комбинированными) различиями, или в случае git log -p, по git log -p, по умолчанию. Это то, что нужно остерегаться, особенно если слияние требует ручного разрешения конфликта. В большинстве случаев этот тип ошибки слияния можно найти с помощью автоматических тестов. (Конечно, не все может быть проверено.)

Ответ 2

У меня тоже была эта проблема и вот как я ее исправил:

  1. Использовал git log чтобы показать историю коммитов.

  2. Использовал git show <filename> чтобы показать изменения, внесенные в упрямый файл.

  3. Не сработало Искал StackOverflow, пробовал несколько раз.

  4. Закрыл и снова открыл файл. Вуаля.

Недавно открывшийся файл в моем текстовом редакторе Atom отображал ожидаемые изменения.