git merge берёт коммиты из другой ветки и привешивает их к
текущей. Базовая операция совместной работы.
Два сценария
Fast-forward
Если у текущей ветки нет новых коммитов, которых нет в сливаемой, Git просто «перематывает» указатель текущей ветки на вершину сливаемой. Новый коммит не создаётся, история остаётся линейной.
до: main: A → B
feat: A → B → C → D
после: main: A → B → C → D (main догнал feat)
feat: A → B → C → D
Происходит при git merge feat из main, если на main не было
новых коммитов после ветвления.
Three-way merge
Если у обеих веток есть свои коммиты после развилки, Git создаёт merge-коммит - особый коммит с двумя родителями.
до: main: A → B → E
feat: A → B → C → D
после: main: A → B → E → M (M - merge-коммит)
\ /
C → D
feat: A → B → C → D
Содержимое merge-коммита - слияние tree'ев E и D с учётом общего предка B. Если строки не пересекаются - Git справится автоматически. Если пересекаются - конфликт.
Команды
git switch main
git merge feature # слить feature в main
git merge --no-ff feature # создать merge-коммит даже если ff возможен
git merge --ff-only feature # отказаться, если ff невозможен
git merge --squash feature # сжать все коммиты feature в один stage
git merge --abort # отменить начатый merge
Флаг --no-ff - частая стратегия команд, которые хотят сохранять
топологию: «вот тут была feature-ветка». Без --no-ff фичевые
коммиты сольются в основную линию и факт ветвления визуально
пропадёт.
Конфликты
При конфликте Git помечает файл специальными маркерами:
<<<<<<< HEAD
print("из main")=======
print("из feature")>>>>>>> feature
Что делать:
- Открыть файл в редакторе или mergetool.
- Удалить маркеры, оставить нужную версию (или их комбинацию).
git add <файл>- пометить как resolved.git commit- завершить merge (Git предложит готовое сообщение).
Полезное:
git status # покажет, какие файлы в конфликте
git diff # увидеть три версии в конфликтных hunk'ах
git checkout --ours file # принять версию текущей ветки целиком
git checkout --theirs file # принять версию сливаемой ветки целиком
Подводные камни
--squashсоздаёт коммит без указания родителя из feature. Это значит, что после squash-merge сливаемая ветка считается нссмерженной (нельзя удалить черезbranch -d). Это часто путает; нужно использоватьbranch -D.- Если в процессе merge передумал -
git merge --abortвернёт всё в исходное состояние.
- Если в процессе merge передумал -
git mergeс unstaged-изменениями в рабочей копии может отказаться, если эти изменения пересекаются с merge'ем. Решение - закоммитить или stash перед merge.