Урок курса
Обе стороны изменили одну строку
Git и GitHub с нуля: бесплатный курс с практикойВ прошлом уроке merge завершался автоматически: Git либо сдвигал указатель (fast-forward), либо сам создавал merge-коммит, когда ветки разошлись, но трогали разные части файлов. Если обе ветки по-разному изменили одну строку, Git не может выбрать итоговую версию и останавливает автоматическое слияние.
Природа конфликта слияния и состояние MERGING
Git умеет сливать изменения автоматически, если они не перекрываются. Конфликт возникает ровно тогда, когда одна и та же строка (или соседние строки) изменена по-разному в обеих ветках — начиная с того коммита, где ветки разошлись. Git не угадывает, какая версия правильная, и останавливает merge.
В этот момент репозиторий переходит в состояние MERGING: новый коммит не создаётся, рабочая папка содержит файл с маркерами, а Git ждёт вашего решения.
git status в этом состоянии показывает характерную картину:
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
Здесь важны три детали.
Первая: both modified: README.md — это не обычный статус «файл изменён». Пометка означает, что обе ветки зафиксировали разные версии этого файла после общего предка, и Git не смог выбрать одну из них автоматически.
Вторая: Git прямо подсказывает два выхода — исправить конфликт и выполнить git commit, либо отменить всё через git merge --abort и вернуться к состоянию до команды merge.
Третья: ни одна из двух версий строки не теряется. Git встраивает обе в файл — именно для того, чтобы вы могли их прочитать и решить, что оставить.
Подготовка конфликта: расходящиеся ветки с одной изменённой строкой
Чтобы получить конфликт самостоятельно, нужно создать общего предка и две ветки, каждая из которых по-своему изменила одну строку. Все команды ниже выполняйте по одной.
Создайте новую папку conflict-demo через файловый менеджер. Перейдите в неё в терминале:
cd conflict-demo
git init -b main
Задайте автора (Git потребует эти данные перед первым коммитом):
git config user.name "Student"
git config user.email "student@example.com"
Откройте текстовый редактор, создайте файл README.md с единственной строкой и сохраните:
Статус: в разработке
Зафиксируйте общего предка:
git add README.md
git commit -m "Начало"
Этот коммит — точка расхождения. Обе ветки будут отсчитываться от него.
Создайте ветку feature и переключитесь на неё:
git switch -c feature
Откройте README.md в редакторе, замените строку на Статус: готово и сохраните. Зафиксируйте:
git add README.md
git commit -m "feature: обновить статус"
Вернитесь на main:
git switch main
Откройте README.md — редактор покажет исходную строку Статус: в разработке, потому что main ещё не знает о коммите feature. Замените строку на Статус: в тестировании и сохраните. Зафиксируйте:
git add README.md
git commit -m "main: обновить статус"
Теперь граф выглядит так: от общего предка («Начало») отходят два независимых коммита. main считает строку Статус: в тестировании, feature — Статус: готово. git status показывает чистую рабочую папку на main, и можно запускать merge.
Анатомия конфликтных маркеров и выполнение merge
Находясь на main с чистой рабочей папкой, выполните:
git merge feature
Git попытается объединить ветки и обнаружит, что одна строка изменена с обеих сторон. Вывод будет таким:
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
Auto-merging README.md означает, что Git начал объединение. CONFLICT (content) — что автоматика споткнулась именно о содержимое файла. Слияние остановлено.
Откройте README.md в редакторе. Вместо одной строки вы увидите блок с тремя маркерами:
<<<<<<< HEAD Статус: в тестировании ======= Статус: готово >>>>>>> feature
<<<<<<< HEAD открывает блок и означает: всё, что ниже до разделителя, — это версия текущей ветки (HEAD, то есть main).
======= — разделитель. Выше него — версия main, ниже — версия feature.
>>>>>>> feature закрывает блок. Имя после стрелок — это ветка, которую вы сливали.
В нашем примере между <<<<<<< HEAD и ======= находится строка Статус: в тестировании (то, что зафиксировал main), а между ======= и >>>>>>> feature — строка Статус: готово (то, что зафиксировал feature).
Если файл содержит строки вне этого блока — они уже объединены автоматически и маркеров не получили. Проверяйте весь файл целиком, а не только блок с маркерами: Git мог объединить другие части файла без вашего участия, и эти части тоже войдут в итоговый коммит.
Разрешение конфликта: редактирование, add, commit и проверка
Разрешить конфликт — значит вручную привести файл к тому виду, который должен попасть в историю. Никаких специальных команд для этого нет: открываете файл в редакторе и редактируете текст.
Задача — убрать все три маркера (<<<<<<<, =======, >>>>>>>) и оставить на их месте итоговый текст. Итоговый текст зависит от условия задачи. Если нужно объединить смысл обеих правок, строка может выглядеть, например, так:
Статус: в тестировании (готово к релизу)
Если нужна только одна из версий — оставьте только её. Главное: ни одного символа <<<<<<<, ======= или >>>>>>> в файле остаться не должно.
После сохранения файла выполните:
git add README.md
Эта команда переводит файл из состояния «unmerged» в «staged» — Git понимает, что конфликт разрешён. git status теперь покажет:
On branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: README.md
Создайте merge-коммит:
git commit -m "Merge feature: объединить статус"
Проверьте результат:
git status
Вывод должен содержать nothing to commit, working tree clean — репозиторий вышел из состояния MERGING.
git log --graph --oneline --all
Вы увидите merge-коммит с двумя входящими линиями — от main и от feature. Это граф со слиянием, где обе ветки сошлись в одной точке.
Ловушка: маркер в истории. Частая ошибка — отредактировать файл, случайно оставить одну строку с маркером, выполнить git add и git commit. Git не проверяет содержимое файла на наличие маркеров — он только смотрит, переведён ли файл из «unmerged» в «staged». В итоге маркер попадает в коммит как обычный текст. Файл с точки зрения Git «разрешён», но внутри него мусор.
Перед git add всегда открывайте файл и проверяйте визуально: нет ли строк, начинающихся с <<<<<<<, ======= или >>>>>>>. Можно также выполнить git diff до add — незакрытый маркер будет виден в выводе.
Задача 1
Чистая main и feature изменили одну исходную строку. После merge есть unmerged README с маркерами. Редактором собрали правильный итог, удалили маркеры, сохранили файл, но add ещё не выполнили. Завершилось ли слияние?
Задача 2
Самостоятельная практика:
Откройте папку prepared/merge_conflict из архива git_practice.zip. Начальное состояние: HEAD на main; ветки main и feature разошлись, обе изменили одну строку в README.md; рабочая папка чистая.
Задача:
- Выполните
git status— убедитесь, что рабочая папка чистая. - Выполните
git log --graph --oneline --all— посмотрите на граф расходящихся веток. - Выполните
git merge feature— получите сообщение о конфликте. - Выполните
git status— найдитеboth modified: README.md. - Откройте
README.md, найдите все три конфликтных маркера, прочитайте обе версии строки. Отредактируйте файл: удалите маркеры, оставьте итоговый текст, содержащий смысл обеих правок. Сохраните файл. - Выполните
git add README.md. - Выполните
git commit -m "Merge feature: объединить правки статуса". - Выполните
git status— убедитесь, что вывод содержит «nothing to commit, working tree clean». - Выполните
git log --graph --oneline --all— убедитесь, что появился merge-коммит с двумя входящими линиями.
Доступный API: git status, git log --graph --oneline --all, git merge, git add, git commit -m.
Платформа проверяет только ответ ниже; действия в своём репозитории и письменные ответы вы проверяете самостоятельно.
Контрольный случай:
После add и успешного merge-коммита status чистый. В закоммиченном README всё ещё видна строка <<<<<<< HEAD. Доказывает ли успешный commit, что текст разрешён правильно?
Задача 3
Самостоятельная практика:
Подготовьте конфликт самостоятельно с нуля. Файлы создавайте и редактируйте обычным текстовым редактором; каждую команду Git выполняйте отдельно.
- Создайте новую папку my_conflict через файловый менеджер, перейдите в неё в терминале (
cd my_conflict) и отдельно выполнитеgit init -b main, как в lesson_03. Начальная ветка должна явно называться main. - Задайте обе локальные настройки автора отдельными командами:
git config user.name "Student", затемgit config user.email "student@example.com". - Создайте README.md с единственной строкой
Статус: в разработкеи сохраните файл. - Выполните
git add README.md, затем отдельной командойgit commit -m "Начало". - Выполните
git switch -c feature. - В README.md замените строку на
Статус: готовои сохраните файл. Выполнитеgit add README.md, затем отдельноgit commit -m "feature: обновить статус". - Выполните
git switch main. - В README.md замените ту же строку на
Статус: в тестированиии сохраните файл. Выполнитеgit add README.md, затем отдельноgit commit -m "main: обновить статус". - Выполните
git merge feature— получите конфликт. - Откройте README.md, прочитайте обе версии и маркеры. Составьте итоговую строку
Статус: готово, в тестировании, удалите все маркеры, сохраните файл. - Выполните
git add README.md, затем отдельной командойgit commit -m "Merge feature: объединить статус". - Выполните
git status— убедитесь: «nothing to commit, working tree clean». - Выполните
git log --graph --oneline --all— убедитесь в наличии merge-коммита с двумя родителями.
Доступный API: cd, git init -b main, git config user.name, git config user.email, git add, git commit -m, git switch, git switch -c, git merge, git status, git log --graph --oneline --all.
Письменное объяснение:
Коллега говорит: «Я получил конфликт при merge, открыл файл, оставил версию из ветки main (убрал строки с версией feature), но не трогал сами маркеры <<<<<<<, =======, >>>>>>> — они же просто комментарии, Git их проигнорирует. Потом сделал git add и git commit. Всё прошло без ошибок». Что на самом деле произошло, и в чём проблема?
Платформа проверяет только ответ ниже; действия в своём репозитории и письменные ответы вы проверяете самостоятельно.
Контрольный случай:
В конфликтном файле вручную исправили выделенный блок. Вне блока Git автоматически включил новую строку коллеги. Как проверить результат перед add?
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
