Понимание git конфликтов - PullRequest
       36

Понимание git конфликтов

3 голосов
/ 17 февраля 2020

Я думаю, что, возможно, что-то неправильно понимаю о том, как git обрабатывает вещи, и, следовательно, я сталкиваюсь с довольно раздражающими конфликтами.

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

Время идет, ветка A продолжает развиваться, пока оно объединено с мастером. На данный момент, я думаю, что теперь, когда он был объединен с мастером, я могу просто объединить мастер с B и получить все изменения от A, а также от всех остальных.

Проблема в том, что сейчас что я пытаюсь это сделать, я получаю несколько конфликтов «Оба добавлены», «Оба изменены» и тому подобное - но в файлах я не изменился. Я полностью понимаю конфликты в файлах, которые я изменил - я их вызвал, я прекрасно это знаю.

Думая, что мое объяснение запуталось, я отправился в Google Slides и создал этот «удивительный» рисунок, чтобы проиллюстрировать мой сценарий. (в котором стрелки представляют слияния, как в «объединенном мастере в A», если только они не указывают на первый коммит в ветви - в этом случае они означают «разветвленные отсюда»; то есть довольно стандартная запись git, кроме направления стрелки - не уверен здесь): er


Относительно файлов, которые изменили другие люди, я не понимаю, почему они перечислены как конфликты. То есть существует файл, который был изменен к тому времени, когда я создал ветвь A, и тот же файл снова изменился, когда я попытался объединить мастер с B. Конечно, ветвь B все еще имеет супер старую версию этого файла, но, тем не менее, git не должен признать, что они не были изменены в моих коммитах, и просто перезаписать их, или что-то еще? Что мне здесь не хватает?

РЕДАКТИРОВАТЬ: просто чтобы уточнить, я единственный вкладчик обеих ветвей A и B.

Ответы [ 2 ]

4 голосов
/ 17 февраля 2020

Ваш график очень хорош, но он вводит в заблуждение.

Давайте начнем с вопроса: на какой ветке синие коммиты?

Это вопрос с подвохом. Они не на ветви, они на нескольких ветвях , во множественном числе. Если вы скажете , что они на ветке A, ну, это правда, но они также на master, и большинство из них также на ветке B.

На вашем чертеже нет идентификаторов для каждого коммита. Из-за этого им трудно говорить: я мог бы сказать «второй серый коммит слева» или «самый правый синий коммит», но это немного громоздко, поэтому позвольте мне перерисовать график как текст, используя по одной заглавной букве в каждом совершить. Я также не буду использовать стрелки здесь, поскольку их слишком сложно сделать в тексте.

A--B--C--D--E--F--G--H--I--J--K--L   <-- master
    \        \            /
     M--N--O--P--Q---R---S   <-- branch-A
         \        \
          T--U--V--W--X--Y--Å--Ø--Z   <-- branch-B

То есть tip commit из master - это commit L. Коммит tip из branch-A является коммитом S. Коммит tip из branch-B является коммитом Z.

В Git ветвь name , например master, всегда указывает на один совершить. Вы получаете на выбор , который коммит; коммит, выбранный таким способом, является tip commit ветви. Любой более ранний коммит, который доступен с , этот совет также находится в ветке. Итак, начиная с L и работая в обратном направлении - влево на этих чертежах - следуя стрелкам от коммита до коммита, 1 мы go от L до K, затем от K до J. Но J - это коммит слияния: у него два родителя, а не один. Таким образом, от J мы go до S и I. От этих двух мы go до H и R, и до G и Q и F и P и E. Достигнуть E можно двумя способами, но мы все равно посещаем его только один раз. Отсюда мы можем go на O и D, N и C, M и B и A. Таким образом, этот список коммитов - это набор коммитов, который находится на master.

(Обратите внимание, что мы не можем go с Q до W, хотя мы можем go в другую сторону, от W до Q: все стрелки направлены в одну сторону и направлены назад.)

Набор коммитов на branch-A начинается с S и переходит обратно на R, затем Q, затем P, затем E и O и D и N и так далее. Все эти коммиты также находятся на master.

Набор коммитов на branch-B начинается с Z, перемещается обратно через Ø и Å и Y и X и W, затем поднимает P и V, а затем E и O и U и так далее. Таким образом, в branch-B имеется четыре коммита, которых нет ни в одной другой ветви.


1 Технически, все внутренние стрелки Git указывают назад . Итак, вы нарисовали свои стрелки назад, нарисовав их вперед. Situation Эта ситуация возникает из-за того, что коммиты полностью доступны только для чтения: родитель не может заранее знать, какие у него есть sh идентификаторы, которые могут быть у его возможных потомков, но дочерний коммит знает заранее / во время создания, какой родитель имеет sh удостоверения личности, которые есть у ребенка. Таким образом, стрелки должны указывать назад.

(Чтобы двигаться вперед, вы сообщаете Git, где вы хотите оказаться, а затем он движется назад оттуда, чтобы убедиться, что это может закончиться там с какой-то другой отправной точки.)


Как работает git merge, очень сокращенно

Когда вы запускаете:

git checkout master
git merge branch-B

Git должен найти базу слияния коммит коммитов L и Z. Чтобы сделать это, он работает в обратном порядке, как и большинство операций Git, из этих коммитов с ветвями, чтобы найти лучший общий коммит: коммит, который доступен из обеих ветвей ветвей, и, следовательно, в обеих ветвях. , но "ближе всего к подсказкам" как бы. В этом случае, хотя это, возможно, не сразу очевидно, это коммит Q. Начните с L, go обратно через K до J и затем до S, затем R и затем Q. Между тем, начните с Z, go обратно до W и до Q. Коммиты P, E, O и т. Д. Также находятся в обеих ветвях, но коммит Q "лучше", потому что это последний такой коммит: он является потомком всех этих коммитов.

Git теперь будет запускать две команды git diff внутри. Это сравнит базу слияния - commit Q - с двумя подсказками ветвления:

git diff --find-renames <hash-of-Q> <hash-of-L>   # what we changed
git diff --find-renames <hash-of-Q> <hash-of-Z>   # what they changed

В этих двух списках различий, если какой-то файл кажется вновь созданным в обоих Советы по ветке, вы получите конфликт добавления / добавления для этого файла. Файл не был в Q, но это в L и Z.

Для файлов, которые находятся во всех трех коммитах, Git попытается объединить любые изменения, показанные в двух наборах различий. Там, где эти изменения не перекрываются (и не «касаются краев»), Git может объединить их. Там, где они перекрываются, но эти перекрытия абсолютно одинаковы - охватывают одинаковые исходные строки в Q и вносят одинаковые изменения - Git можно объединить, просто взяв одну копию изменения. Все остальные совпадения приводят к конфликтам слияния.

На данный момент ваша задача - разрешать любые конфликты любым способом, каким вам нравится. Например, в конфликтах добавления / добавления всего файла, если два файла совпадают, просто выберите любой из них. Если нет, объедините два файла как-нибудь. Когда вы закончите, запишите окончательное содержимое каждого конфликтующего файла в индекс, используя git add. Это помечает конфликт индекса как «разрешенный», и теперь вы можете запустить git merge --continue или git commit для завершения слияния. 2


2 git merge --continue проверяет, чтобы убедиться, что вы завершаете слияние. Если нет, то это ошибки. Если да, то он просто запускает git commit, так что в любом случае нет никакой разницы, если только вы на самом деле не завершаете конфликтующее слияние.

1 голос
/ 17 февраля 2020

не должен git признать, что они не были изменены в моих коммитах, и просто перезаписать их?

Нет.

Это не имеет значения что вы не изменили файл. В голове git нет ничего о , кто создал коммиты или , кто получает приоритет. Ни master, ни b не являются "лучше" в некотором роде.

Дело только в том, что файл, известный как master, и файл, известный как b, отличаются таким образом, что не могут быть автоматически согласованы.

Git может автоматически согласовать некоторые различия, очевидно; например, если файл отличается в одной ветви, потому что строка 1 была изменена, и отличается в другой ветви, потому что строка 1000 была изменена. Но иногда он просто вскидывает руки и позволяет вам разобраться. Это не из-за , который изменил файл, это из-за характера различий.

Что касается , какие различия, я думаю, это поможет прочитать { ссылка } - это лучшее эссе, которое я когда-либо читал о том, что такое слияние и как оно работает. Все дело в LCA и различиях от него до концов веток. На вашей диаграмме, LCA - это второй коммит слева, полностью назад, в точке, где A был впервые разветвлен. Все различия между этим коммитом и концом master и все различия между этим коммитом и концом b должны быть учтены для объединения. Ну, git говорит вам, что для этого файла он не может этого сделать. И если вы подумаете о том, как файл изменился оттуда до конца master и оттуда до конца b, вы поймете почему.

Не имеет значения кто изменил файлы или как вы рисуете диаграмму. Неважно, что есть путь, который описывает (в вашем уме) историю произошедшего. Все, что имеет значение, - это три состояния вещей: состояние вещей во время самого раннего совершения, положение вещей в конце master и положение вещей в конце b.

...