Разрешение конфликта
При предыдущем объединении обе задачи меняли разные файлы. Теперь оба изменения относятся к заголовку каталога. В основной линии автор хочет назвать страницу каталогом уроков, а в отдельной ветке — уточнить тематику веб-разработки. Git умеет сопоставлять текст, но не может сам выбрать редакционное намерение.
Разберём конфликт на одной Markdown-строке, прочитаем оба варианта и запишем осмысленное решение. Оглавление курса и архив содержат lesson-06/start и точный сценарий подготовки. Команды и конфликт пока не воспроизводились; листинги показывают ожидаемое состояние лаборатории.
Подготовка двух вариантов
Исходное состояние — результат слияния прошлого урока. На main есть поясняющий абзац и новый зелёный цвет. Назовём эту вершину M. Убедитесь, что статус пуст, и создайте новую feature:
git switch main
git switch -c feature/catalog-title
В content/courses.md замените только первую строку # Учебный каталог на # Курсы веб-разработки. Подготовьте и сохраните её, затем вернитесь на основную линию:
git add content/courses.md
git commit -m "Уточнить тематику заголовка"
git switch main
На main снова видна исходная первая строка, поскольку feature-коммит пока сюда не вошёл. Теперь замените её другим текстом: # Каталог уроков. Запишите отдельное изменение основной линии:
git add content/courses.md
git commit -m "Назвать страницу каталогом уроков"
git merge feature/catalog-title
Ожидается остановка слияния с конфликтом. Относительно M обе стороны заменили одну и ту же строку, предложив различный новый текст. Ни одна из правок не является продолжением другой. Автоматическое объединение не знает, следует ли предпочесть «каталог», «курсы» или «веб-разработку».
Полезно заметить, что остановка не отменяет уже созданные feature и main-коммиты. Они существуют и содержат исходные намерения. Новый merge-коммит пока не записан, поскольку итоговая версия этой строки ещё не подготовлена. Мы находимся внутри незавершённой операции, а не в обычном состоянии нового черновика.
Маркеры и три представления файла
Прочитайте git status --short и откройте Markdown. Для одного из обычных вариантов оформления конфликта начало файла будет выглядеть так:
<<<<<<< HEAD
# Каталог уроков
=======
# Курсы веб-разработки
>>>>>>> feature/catalog-title
Точное оформление зависит от настройки conflict style. Режимы с показом базы добавят также исходную строку. Смысл неизменен: блок отмечает конкурирующие тексты. Маркеры являются временной разметкой Git, а не частью Markdown-страницы. Если оставить их после сохранения, они могут попасть в итоговый документ как мусорный текст.
В данном слиянии HEAD соответствует выбранной main, а второй вариант — присоединяемой feature. Такой смысл относится именно к merge. Не переносите его автоматически на любую другую операцию, особенно rebase: там история переигрывается по другой схеме. Для понимания названий сначала определите, какая операция сейчас выполняется.
Если нужно перечитать сохранённые версии без маркеров, используйте состояния индекса во время конфликта:
git show :1:content/courses.md
git show :2:content/courses.md
git show :3:content/courses.md
В нашем простом конфликте первая стадия представляет базовый вариант, вторая — текущую сторону, третья — присоединяемую. После разрешения и добавления файла обычный индекс снова содержит выбранное итоговое представление. У конфликтов удаления или переименования состав стадий может отличаться; этот урок специально использует один существующий путь.
Решение по смыслу страницы
Разберём обе задачи словами. «Каталог уроков» сообщает назначение страницы. «Курсы веб-разработки» уточняет область материалов. Они не противоречат друг другу, поэтому результатом может стать новый текст, которого не было ни в одной стороне:
# Каталог курсов веб-разработки
Замените весь блок маркеров этой единственной строкой. Остальные абзацы и ссылки оставьте без изменения. Решение конфликта не обязано буквально выбирать верхнюю или нижнюю сторону. Нужно сохранить согласованный смысл и убедиться, что он подходит соседнему содержимому страницы.
В этом примере итог включает назначение и тематику. Если бы одна сторона обещала бесплатные материалы, а другая описывала платный продукт, механическое склеивание заголовков не решило бы противоречие. Тогда потребовалось бы уточнение требований. Git показывает место расхождения, но не заменяет предметное обсуждение.
Прочитайте весь файл после редактирования. Рядом с новым заголовком должны оставаться строка о группировке, пояснение начала обучения и две прежние ссылки. Не хватит удалить только строки со знаками: сохранение обоих заголовков подряд технически уберёт маркеры, но даст странице два H1 и невыбранное название.
Подготовка и завершение
Когда итоговый файл прочитан, сообщите Git, что выбранное состояние подготовлено:
git add content/courses.md
git status
git diff --cached
git commit -m "Согласовать назначение и тематику каталога"
git show --no-patch --format='%h %p %s' HEAD
Добавление не доказывает смысловую правильность решения: оно помещает ваш рабочий текст в индекс как результат для этого пути. Поэтому проверка содержимого перед add существенна. Ожидаемый статус сообщает о разрешённых конфликтах при ещё не завершённом merge; запись коммита завершает операцию.
Полученный merge-коммит имеет два родителя. История сохраняет и предложение основной линии, и предложение feature, а итоговый снимок — согласованный третий текст. Чистое дерево после записи позволяет продолжать работу без скрытой незавершённой операции.
Если хотите отказаться от разбора до коммита, используйте git merge --abort только при действительно начатом merge. В этой лаборатории дерево было чистым до операции, поэтому ожидается возврат к предшествующему состоянию. Для слияния поверх незаписанных правок возврат может быть сложнее; соответствующее ограничение описано в документации git-merge.
При обсуждении решения удобно отделить факт конфликта от качества предложений. Ни одна сторона не обязана быть ошибочной: каждый автор мог видеть только свою задачу. В нашем случае оба названия полезны, но описывают разные свойства страницы. Формулировка «каталог курсов веб-разработки» получается из требований к странице, а не из количества строк в одном варианте. Поэтому автоматическое предпочтение более длинного заголовка было бы случайным правилом.
Сохранённое решение должно оставаться понятным и вне интерфейса конфликта. Представьте, что вы открыли только итоговый Markdown без истории. Есть ли у него одно название, согласованы ли абзацы с этим названием, ведут ли ссылки к описанным курсам? Такой взгляд помогает обнаружить ошибки, которых Git не выделяет маркерами. Например, рядом могли остаться слова «здесь только JavaScript», хотя новый заголовок уже обещает несколько направлений. В нашем исходном тексте такой строки нет, поэтому дополнительных правок не требуется.
Если вы не уверены в намерении другого автора, сохранённые варианты позволяют сформулировать точный вопрос: какое назначение должно быть у этой страницы? Не нужно превращать весь конфликт в спор о том, чья ветка главнее. Общий предок и две правки показывают, где именно договорённость разошлась. Дальнейшее решение может быть третьим текстом, как в лаборатории, либо согласованным выбором одной стороны. Существенно, чтобы после записи не осталось неосознанной потери требования.
Теперь конфликт имеет осмысленное решение, а обе линии остались в истории. Далее рассмотрим перенос локальных коммитов: вместо соединения расходящихся линий переиграем маленькую собственную правку поверх обновлённой основной ветки.