Задание
Конфликт в pull request — это ситуация, когда ветка PR и main изменили одни и те же строки, и GitHub не может слить их сам. Решают его так же, как конфликт слияния из модуля про ветки: объединяют изменения вручную и делают новый коммит. В практикуме вы пройдёте командный цикл вдвоём — вы и «коллега», роль которого играет второй клон portfolio-colleague, — закроете main от прямых правок и доведёте оба PR до слияния.
Каждый приём по отдельности знаком: ветка и pull request из первого урока модуля, ревью, issues, форки. Теперь соберём их вместе и добавим то, что в реальной команде случается постоянно: две правки одной строки.
portfolio (вы) portfolio-colleague (коллега)
feature/footer-note feature/colleague-footer
| |
+--- PR #5 ---> main <--- PR #6 ----+
обе ветки стартуют от одного и того же main
и обе меняют одну и ту же строку подвала
На схеме: две ветки отходят от одного main и вливаются в него отдельными PR. Первый PR уйдёт без проблем, а второй упрётся в конфликт.
Что нужно сделать, по шагам:
- Включить для
mainправило: изменения только через pull request. - Создать две ветки: одну в
portfolio, вторую вportfolio-colleague. Обе меняют подпись в подвале, которую вы дописали, когда публиковали сайт. - Слить первый PR, увидеть конфликт во втором и решить его.
- Слить второй PR и обновить оба клона.
Шаг 1. Правило для main
Чтобы никто (и вы тоже) не пушил в main напрямую, включают правило. На GitHub оно называется ruleset (набор правил для веток). По документации GitHub, у бесплатного аккаунта публичные репозитории получают полный набор возможностей, поэтому для вашего публичного portfolio оно доступно. Если у вас приватный репозиторий на бесплатном тарифе, расширенных настроек может не быть.
- Откройте репозиторий, вкладку Settings.
- В левой панели в разделе Code, planning, and automation выберите Rulesets (если пункт раскрывается, внутри нажмите Rulesets ещё раз).
- Нажмите New ruleset, затем New branch ruleset.
- В поле Ruleset name введите название, например «Защита main».
- Статус Enforcement status по умолчанию Disabled (правило не действует). Нажмите на него и выберите Active.
- В разделе Target branches нажмите Add a target и выберите вариант, который включает ветку по умолчанию (в интерфейсе он называется Include default branch).
- В разделе Branch protections отметьте Require a pull request before merging. Число обязательных одобрений оставьте равным 0: вы здесь единственный участник, а свой PR одобрить нельзя. Пункты Restrict deletions и Block force pushes отмечены по умолчанию — оставьте их: они запрещают удалить
mainи перезаписать её принудительной отправкой. - Список Bypass list оставьте пустым и нажмите Create.
Теперь прямой git push в main будет отклонён, а изменения попадают туда только слиянием PR. Для личного проекта это тренировка, для командного — обязательная страховка от случайных правок в рабочей ветке.
Rulesets и классические правила
Старый способ защиты веток называется branch protection rules. GitHub поддерживает оба, но развивает rulesets: они гибче, и их можно временно отключать, не удаляя. Если в чужом проекте вы увидите «branch protection», это тот же замысел.
Шаг 2. Две ветки с одной строкой
Обе ветки должны стартовать от одного и того же состояния main. Поэтому сначала обновите оба клона, и только потом создавайте ветки. Начните с вашего portfolio:
$ cd ~/projects/portfolio
$ git switch main
$ git pull
$ git switch -c feature/footer-note
Switched to a new branch 'feature/footer-note'
В index.html сократите подпись в подвале. Было:
<footer><p>© 2026 Алекс Иванов. Сайт опубликован на GitHub Pages.</p></footer>
Стало:
<footer><p>© 2026 Алекс Иванов. Сайт на GitHub Pages.</p></footer>
$ git add index.html
$ git commit -m "Сократить подпись в подвале"
$ git push -u origin feature/footer-note
...
* [new branch] feature/footer-note -> feature/footer-note
branch 'feature/footer-note' set up to track 'origin/feature/footer-note'.
Теперь переходите в клон «коллеги». Пока вы ничего не сливали, поэтому git pull там ничего нового не принесёт: main тот же.
$ cd ~/projects/portfolio-colleague
$ git switch main
$ git pull
$ git switch -c feature/colleague-footer
Switched to a new branch 'feature/colleague-footer'
«Коллега» меняет ту же самую строку подвала, только по-своему: оставляет подпись полной и дописывает слова про Git:
<footer><p>© 2026 Алекс Иванов. Сайт опубликован на GitHub Pages, сделано с помощью Git.</p></footer>
$ git add index.html
$ git commit -m "Добавить в подвал слова про Git"
$ git push -u origin feature/colleague-footer
...
* [new branch] feature/colleague-footer -> feature/colleague-footer
branch 'feature/colleague-footer' set up to track 'origin/feature/colleague-footer'
На GitHub откройте для каждой ветки pull request кнопкой Compare & pull request. Первый (feature/footer-note) получит номер #5, второй (feature/colleague-footer) — #6; номера у вас могут отличаться. В описании второго для порядка укажите, что это правка «коллеги».
Шаг 3. Первый PR и конфликт во втором
Слейте PR #5: Merge pull request, Confirm merge, Delete branch. Теперь откройте PR #6. Блок слияния внизу вкладки Conversation изменился: GitHub пишет This branch has conflicts that must be resolved («в ветке есть конфликты, которые нужно решить»), а кнопка Merge pull request недоступна. Рядом появилась кнопка Resolve conflicts.
Причина понятна: main уже получил короткую подпись «Сайт на GitHub Pages.», а ветка «коллеги» меняет ту же строку по-своему. Какую оставить, решает человек.
Решить можно двумя путями.
- На GitHub. Кнопка Resolve conflicts открывает редактор: вы видите файл с маркерами
<<<<<<<,=======и>>>>>>>, удаляете маркеры и ненужные строки, нажимаете Mark as resolved, затем Commit merge. Редактор справляется только с простыми конфликтами; если кнопка недоступна, остаётся командная строка. - У себя на компьютере. Надёжнее и в реальной работе нужно почти всегда. Так мы и сделаем.
Идея та же, что при слиянии веток: влить свежий main в ветку PR, решить конфликт и отправить результат. Решает его тот, кто владеет веткой, то есть «коллега»:
$ cd ~/projects/portfolio-colleague
$ git switch main
$ git pull
From https://github.com/alex-ivanov/portfolio
917622a..139f2b6 main -> origin/main
Updating 917622a..139f2b6
Fast-forward
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git switch feature/colleague-footer
Switched to branch 'feature/colleague-footer'
$ git merge main
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Разбор по строкам
git pullнаmain— клон «коллеги» узнал о слиянии PR #5 и перемоталmainна коммит139f2b6.git switch feature/colleague-footer— возвращаемся в ветку PR #6. Решать конфликт нужно в ней, а не вmain.git merge main— влить свежийmainв ветку (rebase пока не нужен: он в следующем модуле).CONFLICT (content)— одну и ту же строку вindex.htmlизменили обе стороны; Git остановился и ждёт решения.
Откройте index.html в VS Code. Подвал выглядит так:
<<<<<<< HEAD
<footer><p>© 2026 Алекс Иванов. Сайт опубликован на GitHub Pages, сделано с помощью Git.</p></footer>
=======
<footer><p>© 2026 Алекс Иванов. Сайт на GitHub Pages.</p></footer>
>>>>>>> main
Между <<<<<<< HEAD и ======= — версия вашей ветки («коллеги»), между ======= и >>>>>>> main — версия из main. Объедините смысл — короткую подпись из main и слова про Git от «коллеги» — и оставьте одну строку без маркеров:
<footer><p>© 2026 Алекс Иванов. Сайт на GitHub Pages, сделано с помощью Git.</p></footer>
Дальше всё как в конфликте из модуля про ветки: подготовить файл, закоммитить слияние и отправить ветку.
$ git add index.html
$ git commit
[feature/colleague-footer 66e19ee] Merge branch 'main' into feature/colleague-footer
$ git push
To https://github.com/alex-ivanov/portfolio.git
c1bd89b..66e19ee feature/colleague-footer -> feature/colleague-footer
Разбор по строкам
git add index.html— сообщить Git, что конфликт в файле решён.git commitбез-m— откроется редактор с готовым сообщениемMerge branch 'main' into feature/colleague-footer; сохраните и закройте окно, менять сообщение не нужно.66e19ee— коммит слияния, который объединил две ветки истории.git push— отправляет ветку;-uне нужен, связь сoriginуже есть. push проходит как обычно: конфликт остался в слиянии, а на GitHub уходит уже решённый результат.
Вернитесь на страницу PR #6: предупреждение исчезло, конфликт решён, а в списке коммитов появился коммит слияния.
Шаг 4. Слияние и порядок в клонах
Слейте PR #6 теми же кнопками: Merge pull request, Confirm merge, Delete branch. Затем приведите в порядок оба клона. В каждом выполните:
$ git switch main
$ git pull
$ git branch -d feature/footer-note
$ git fetch --prune
В portfolio-colleague имя удаляемой ветки — feature/colleague-footer. Проверим результат:
$ git log --oneline -7
75011d6 Merge pull request #6 from alex-ivanov/feature/colleague-footer
66e19ee Merge branch 'main' into feature/colleague-footer
139f2b6 Merge pull request #5 from alex-ivanov/feature/footer-note
c1bd89b Добавить в подвал слова про Git
08f0777 Сократить подпись в подвале
917622a Merge pull request #4 from alex-ivanov/feature/github-link
28c6c68 Добавить ссылку на GitHub в контакты
Обратите внимание на историю. Две маленькие правки превратились в семь строк, три из которых — коммиты слияния. Через пару недель командной работы история будет вся из таких строк, и найти среди них настоящие изменения непросто. Как привести историю в порядок — задача следующего модуля.
Шпаргалка: день в команде
Утро: git switch main
git pull
Новая задача: git switch -c feature/что-делаем
Работа: правки, git add, git commit -m "..."
Main ушёл: git switch main, git pull,
git switch feature/..., git merge main
Отправка: git push -u origin feature/что-делаем
Pull request: Compare & pull request, описание, Closes #N
Ревью: новые коммиты в ту же ветку (после Commit suggestion
сначала git pull)
После merge: git switch main, git pull,
git branch -d feature/..., git fetch --prune
Проверьте себя
- В настройках репозитория есть активный ruleset для ветки по умолчанию с правилом Require a pull request before merging.
- Оба PR (#5 и #6) в состоянии Merged, обе ветки удалены на GitHub и локально (
git branchпоказывает толькоmain). - Во втором клоне в
index.htmlодна строка подвала без маркеров<<<<<<<: с подписью «Сайт на GitHub Pages, сделано с помощью Git». - В
git log --onelineвидны два коммита слиянияMerge pull requestи один коммитMerge branch 'main' into feature/colleague-footer. - Вы можете объяснить, почему конфликт решают в ветке PR, а не в
main.
Частые ошибки
Закоммитили прямо в main, и push отклонён. Когда правило включено, GitHub отвечает отказом. Текст может немного отличаться, но смысл такой:
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote: - Changes must be made through a pull request.
To https://github.com/alex-ivanov/portfolio.git
! [remote rejected] main -> main (push declined due to repository rule violations)
Коммит не пропал, он у вас в локальном main. Перенесите его в ветку, а main верните как на GitHub:
$ git switch -c feature/readme-update
$ git switch main
$ git reset --hard origin/main
HEAD is now at 75011d6 Merge pull request #6 from alex-ivanov/feature/colleague-footer
reset --hard стирает несохранённое
Команда git reset --hard возвращает файлы к состоянию origin/main и безвозвратно удаляет все правки, не попавшие в коммит. Здесь это безопасно, потому что коммит сохранён в новой ветке feature/readme-update. Перед сбросом выполните git log feature/readme-update и убедитесь, что коммит там. Незакоммиченные правки сначала сохраните.
В файле остались маркеры конфликта. Git не проверяет, что вы убрали <<<<<<<: после git add файл считается решённым. На сайте потом видны эти строки. Перед git add найдите в файле <<<<<<<, ======= и >>>>>>> (в VS Code — поиск по файлу).
Конфликт решали в main. Свежий main вливают в ветку PR, а не наоборот: так правки остаются на проверке в PR, а в main попадает уже готовый результат.
Ветка стартовала от старого main. Вы забыли git pull перед git switch -c, и конфликты появляются там, где их не должно быть. Начинайте день с git switch main и git pull.
Принудительная отправка. Увидев отказ, не добавляйте --force — так можно стереть чужую работу. Правильное лечение — git pull, решить конфликт, git push.
Что важно запомнить
- Правило (ruleset) для
mainзапрещает прямой push: изменения идут только через pull request. - Две ветки, меняющие одни строки, дают конфликт в PR после слияния первой из них.
- Конфликт решают в ветке PR:
git merge main, правка маркеров,git add,git commit,git push. - На GitHub есть кнопка Resolve conflicts, но она справляется только с простыми случаями.
- После слияния в каждом клоне:
git switch main,git pull,git branch -d,git fetch --prune.
Вы прошли весь цикл командной работы. Но посмотрите на git log: он засорён коммитами слияния, а в реальных проектах к ним добавляются ещё и «wip», «ещё раз», «опечатка». Как перенести ветку на свежий main без лишних слияний и как склеить мусорные коммиты в аккуратные — в следующем модуле.