Предыдущая часть:
Перед тем как обращаться к более экзотическим (и скажем прямо, не сильно-то и нужным для личного пользования) возможностям Git, рассмотрим одну из насущно необходимых.
Откуда берутся конфликты?
Предположим, в вашем проекте есть файлы A, B, C.
В процессе работы вы разветвили проект на две ветки. Теперь каждая ветка содержит копии файлов A, B, C.
В первой ветке вы отредактировали файлы A и B, а во второй B и C.
Затем вы решили слить ветки вместе, чтобы получить все изменения в одном общем результате.
Что при этом произойдёт:
- Файл A был изменён только в первой ветке, и эти изменения попадут в общий результат.
- Файл C был изменён только во второй ветке, и эти изменения попадут в общий результат.
А вот файл B был изменён и там, и там (предполагается, что это разные изменения, а не одно и то же).
Какие изменения файла B должны попасть в результат – cделанные в первой ветке или во второй ветке?
Git в этом случае поступает мудро. Допустим, если в первой ветке мы изменили начало файла, а в другой конец файла, и эти изменения друг с другом не пересекаются, то в финальный результат попадут изменения из обеих веток.
Как получается, что файл при этом сохраняет правильную структуру?
Здесь надо оговориться, что Git может так работать только с текстовыми файлами, состоящими из строк. Он видит, какие строки и в каких местах были изменены, и поэтому может вставить изменения в нужные места файла и ничего не сломать.
Если же файл бинарный, вроде графической картинки или видео, то вставка изменений даже из одного источника в принципе невозможна, поэтому файлы заменяются целиком. Но об этом немного позже.
На данный момент нас интересует ситуация, когда изменения в файле B разные, и сделаны в одном и том же месте.
Тогда и возникает настоящий конфликт – если изменения в разных местах могут мирно сосуществовать, то изменения в одном месте должны решить, кто из них главнее, и выживет только одно.
Отображение конфликтов
Самое интересное (и поначалу не очень понятное) здесь то, как Git отображает конфликты. Он сохраняет в одном в файле обе версии изменений, чтобы мы сами могли выбрать, какие оставить.
Как это происходит, мы сейчас посмотрим.
Я вернусь к своему проекту notes. В связи с тем, что в комментариях высказывали претензии, напомню, что проект notes нужен чисто для примера. Вам не нужно его повторять, там нет ничего особенного, вы можете практиковаться на любых своих файлах.
Сейчас я нахожусь в ветке main:
Вот файлы проекта:
Не обращайте внимания на папки. Я буду вносить изменения в три файла: readme1.txt, readme2.txt и readme3.txt.
Сейчас в этих файлах находятся такие данные: readme1.txt содержит "1", readme3.txt содержит "3", а readme2.txt содержит:
Теперь я отращиваю новую ветку от main, назову её conflict:
Я нахожусь в ветке conflict и начинаю менять файлы. Поменяю содержимое файла readme3.txt на
и содержимое файла readme2.txt на
Теперь я добавлю в коммит изменённые файлы и сделаю коммит, чтобы все изменения остались в этой ветке:
Теперь переключусь обратно в ветку main:
Файлы здесь всё ещё неизменённые, и я отредактирую их следующим образом: поменяю содержимое файла readme1.txt на
а содержимое файла readme2.txt на
и также сделаю коммит в этой ветке:
Наконец, подходим к сути.
Я сливаю ветку conflict обратно с веткой main. Значит, в main окажутся изменения, сделанные и в main, и в conflict:
И как видим, Git нашёл конфликт в одном файле: readme2.txt. Давайте посмотрим на содержимое всех трёх файлов. Файл readme1.txt:
В нём находятся изменения из ветки main, так как в ветке conflict он не менялся.
Файл readme3.txt:
В нём находятся изменения, сделанные в ветке conflict, так как в ветке main он не менялся.
Наконец, файл readme2.txt:
И тут мы видим, что Git вроде как испортил наш файл – вставил туда какой-то мусор. Но на самом деле это всего лишь изменения из двух веток. Давайте посмотрим внимательнее.
"Первая строка изменена в ветке main" и "Десятая строка изменена в ветке conflict" – как видим, изменения из разных веток мирно сосуществуют, так как не пересекаются друг с другом.
Пересекаются же изменённые строки с четвёртой по седьмую. Как разобраться в этом нагромождении?
Изменения, сделанные в ветке main, находятся между "<<<< HEAD" и "====":
Почему там написано HEAD, а не main? Потому что мы сейчас находимся в ветке main, а HEAD, как вы помните, текущая голова ветки.
Изменения, сделанные в ветке conflict, находятся между "====" и ">>>> conflict":
А всё вместе выглядит как два последовательных блока, разделённых "====":
Как решить конфликт
Всё, что нужно сделать, это удалить ненужный блок и оставить нужный. При этом также нужно удалить строчки "<<<<", "====" и ">>>>".
Например, я хочу оставить изменения, сделанные в ветке conflict. Тогда я удаляю эту часть:
и оставшуюся строчку ">>>>>> conflict". Получается такой файл:
Но блоки изменений необязательно удалять или сохранять целиком. Я могу оставить часть изменений из main и часть из conflict, если это меня устраивает:
Также, внутри одного файла может быть несколько конфликтов, и каждый конфликт будет содержать по два аналогичных блока. И вы можете в одном конфликте выбрать блок из одной ветки, а в другом конфликте – блок из другой ветки. В общем, тут всё на ваше усмотрение.
Git здесь не занимается никакой магией, а просто показывает вам разные версии изменений для сравнения и принятия решения.
Окончательное решение
После того, как вы отредактировали конфликтный файл, он всё ещё находится в неприсоединённом состоянии both modified (модифицирован двумя сторонами):
Слияние веток также не было доведено до конца – из-за конфликта не был сделан финальный коммит со всеми изменениями. Всё, что нужно сделать – добавить текущие файлы в коммит и сделать сам коммит, чтобы закрепить изменения:
Готово.
Дальше мы посмотрим, как автоматически принимать изменения с какой-либо стороны.