Найти тему
ZDG

Git на практике #4. Решение конфликтов.

Оглавление

Предыдущая часть:

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

Откуда берутся конфликты?

Предположим, в вашем проекте есть файлы A, B, C.

В процессе работы вы разветвили проект на две ветки. Теперь каждая ветка содержит копии файлов A, B, C.

В первой ветке вы отредактировали файлы A и B, а во второй B и C.

Затем вы решили слить ветки вместе, чтобы получить все изменения в одном общем результате.

Что при этом произойдёт:

  • Файл A был изменён только в первой ветке, и эти изменения попадут в общий результат.
  • Файл C был изменён только во второй ветке, и эти изменения попадут в общий результат.

А вот файл B был изменён и там, и там (предполагается, что это разные изменения, а не одно и то же).

Какие изменения файла B должны попасть в результат – cделанные в первой ветке или во второй ветке?

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

Как получается, что файл при этом сохраняет правильную структуру?

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

Если же файл бинарный, вроде графической картинки или видео, то вставка изменений даже из одного источника в принципе невозможна, поэтому файлы заменяются целиком. Но об этом немного позже.

На данный момент нас интересует ситуация, когда изменения в файле B разные, и сделаны в одном и том же месте.

Тогда и возникает настоящий конфликт – если изменения в разных местах могут мирно сосуществовать, то изменения в одном месте должны решить, кто из них главнее, и выживет только одно.

Отображение конфликтов

Самое интересное (и поначалу не очень понятное) здесь то, как Git отображает конфликты. Он сохраняет в одном в файле обе версии изменений, чтобы мы сами могли выбрать, какие оставить.

Как это происходит, мы сейчас посмотрим.

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

Сейчас я нахожусь в ветке main:

-2

Вот файлы проекта:

-3

Не обращайте внимания на папки. Я буду вносить изменения в три файла: readme1.txt, readme2.txt и readme3.txt.

Сейчас в этих файлах находятся такие данные: readme1.txt содержит "1", readme3.txt содержит "3", а readme2.txt содержит:

-4

Теперь я отращиваю новую ветку от main, назову её conflict:

-5

Я нахожусь в ветке conflict и начинаю менять файлы. Поменяю содержимое файла readme3.txt на

-6

и содержимое файла readme2.txt на

-7

Теперь я добавлю в коммит изменённые файлы и сделаю коммит, чтобы все изменения остались в этой ветке:

-8

Теперь переключусь обратно в ветку main:

-9

Файлы здесь всё ещё неизменённые, и я отредактирую их следующим образом: поменяю содержимое файла readme1.txt на

-10

а содержимое файла readme2.txt на

-11

и также сделаю коммит в этой ветке:

-12

Наконец, подходим к сути.

Я сливаю ветку conflict обратно с веткой main. Значит, в main окажутся изменения, сделанные и в main, и в conflict:

-13

И как видим, Git нашёл конфликт в одном файле: readme2.txt. Давайте посмотрим на содержимое всех трёх файлов. Файл readme1.txt:

-14

В нём находятся изменения из ветки main, так как в ветке conflict он не менялся.

Файл readme3.txt:

-15

В нём находятся изменения, сделанные в ветке conflict, так как в ветке main он не менялся.

Наконец, файл readme2.txt:

-16

И тут мы видим, что Git вроде как испортил наш файл – вставил туда какой-то мусор. Но на самом деле это всего лишь изменения из двух веток. Давайте посмотрим внимательнее.

"Первая строка изменена в ветке main" и "Десятая строка изменена в ветке conflict" – как видим, изменения из разных веток мирно сосуществуют, так как не пересекаются друг с другом.

Пересекаются же изменённые строки с четвёртой по седьмую. Как разобраться в этом нагромождении?

Изменения, сделанные в ветке main, находятся между "<<<< HEAD" и "====":

-17

Почему там написано HEAD, а не main? Потому что мы сейчас находимся в ветке main, а HEAD, как вы помните, текущая голова ветки.

Изменения, сделанные в ветке conflict, находятся между "====" и ">>>> conflict":

-18

А всё вместе выглядит как два последовательных блока, разделённых "====":

-19

Как решить конфликт

Всё, что нужно сделать, это удалить ненужный блок и оставить нужный. При этом также нужно удалить строчки "<<<<", "====" и ">>>>".

Например, я хочу оставить изменения, сделанные в ветке conflict. Тогда я удаляю эту часть:

-20

и оставшуюся строчку ">>>>>> conflict". Получается такой файл:

-21

Но блоки изменений необязательно удалять или сохранять целиком. Я могу оставить часть изменений из main и часть из conflict, если это меня устраивает:

-22

Также, внутри одного файла может быть несколько конфликтов, и каждый конфликт будет содержать по два аналогичных блока. И вы можете в одном конфликте выбрать блок из одной ветки, а в другом конфликте – блок из другой ветки. В общем, тут всё на ваше усмотрение.

Git здесь не занимается никакой магией, а просто показывает вам разные версии изменений для сравнения и принятия решения.

Окончательное решение

После того, как вы отредактировали конфликтный файл, он всё ещё находится в неприсоединённом состоянии both modified (модифицирован двумя сторонами):

-23

Слияние веток также не было доведено до конца – из-за конфликта не был сделан финальный коммит со всеми изменениями. Всё, что нужно сделать – добавить текущие файлы в коммит и сделать сам коммит, чтобы закрепить изменения:

-24

Готово.

Дальше мы посмотрим, как автоматически принимать изменения с какой-либо стороны.