Урок курса

Обе стороны изменили одну строку

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; рабочая папка чистая.

Задача:

  1. Выполните git status — убедитесь, что рабочая папка чистая.
  2. Выполните git log --graph --oneline --all — посмотрите на граф расходящихся веток.
  3. Выполните git merge feature — получите сообщение о конфликте.
  4. Выполните git status — найдите both modified: README.md.
  5. Откройте README.md, найдите все три конфликтных маркера, прочитайте обе версии строки. Отредактируйте файл: удалите маркеры, оставьте итоговый текст, содержащий смысл обеих правок. Сохраните файл.
  6. Выполните git add README.md.
  7. Выполните git commit -m "Merge feature: объединить правки статуса".
  8. Выполните git status — убедитесь, что вывод содержит «nothing to commit, working tree clean».
  9. Выполните 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 выполняйте отдельно.

  1. Создайте новую папку my_conflict через файловый менеджер, перейдите в неё в терминале (cd my_conflict) и отдельно выполните git init -b main, как в lesson_03. Начальная ветка должна явно называться main.
  2. Задайте обе локальные настройки автора отдельными командами: git config user.name "Student", затем git config user.email "student@example.com".
  3. Создайте README.md с единственной строкой Статус: в разработке и сохраните файл.
  4. Выполните git add README.md, затем отдельной командой git commit -m "Начало".
  5. Выполните git switch -c feature.
  6. В README.md замените строку на Статус: готово и сохраните файл. Выполните git add README.md, затем отдельно git commit -m "feature: обновить статус".
  7. Выполните git switch main.
  8. В README.md замените ту же строку на Статус: в тестировании и сохраните файл. Выполните git add README.md, затем отдельно git commit -m "main: обновить статус".
  9. Выполните git merge feature — получите конфликт.
  10. Откройте README.md, прочитайте обе версии и маркеры. Составьте итоговую строку Статус: готово, в тестировании, удалите все маркеры, сохраните файл.
  11. Выполните git add README.md, затем отдельной командой git commit -m "Merge feature: объединить статус".
  12. Выполните git status — убедитесь: «nothing to commit, working tree clean».
  13. Выполните 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?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку