Как объединить ветки в Git: git merge, squash и rebase с примерами

Чтобы объединить ветки в Git, перейдите в принимающую ветку и выполните git merge с именем второй.

Разбираем, когда Git перематывает указатель, а когда создаёт коммит слияния, чем помогают squash и rebase, как решить конфликт и отменить merge до и после push. Пять частых ошибок с текстом сообщений и решением.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 3202 статьи автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3856 экспертных мнений Подписаться на Телеграм-канал
Обложка: Как объединить ветки в Git: git merge, squash и rebase с примерами

Чтобы объединить ветки в Git, переключитесь на ветку, в которую нужно забрать изменения, и выполните git merge с именем второй ветки: git switch main, затем git merge feature/cart. Если в main за это время никто ничего не добавлял, Git просто передвинет указатель вперёд. Если добавлял, Git создаст коммит слияния с двумя родителями.

Ниже разберём, как выглядит каждый вариант в терминале, когда вместо merge лучше взять squash или rebase, как решить конфликт и как откатить слияние, если что-то пошло не так. Если ветки и коммиты для вас пока новые слова, начните с объяснения Git простыми словами, а сюда возвращайтесь с первым конфликтом. Те же команды пригодятся, если ветки за вас создаёт ИИ-агент: как работает Claude Code, мы разбирали отдельно.

CheckroiCheckroiПодборка курсов по Git362 курса • 32 школыСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Как объединить ветки в Git: пошаговая инструкция

Рой сливает две ветки кода в терминале

Возьмём типичную ситуацию: вы сделали корзину в ветке feature/cart и хотите перенести её в основную ветку main. Весь путь занимает пять команд.

  1. Сохраните работу. Закоммитьте или уберите в stash всё, что лежит в рабочей папке. Справка git merge не советует запускать слияние с заметными незакоммиченными правками: при конфликте из такого состояния трудно выбраться.
  2. Перейдите в принимающую ветку: git switch main. В старых версиях Git и во многих инструкциях то же самое делает git checkout main.
  3. Обновите её с сервера: git pull. Иначе вы сольёте фичу в устаревшую main, а при пуше получите отказ.
  4. Слейте ветку: git merge feature/cart.
  5. Отправьте результат: git push. Ветку фичи после этого можно удалить командой git branch -d feature/cart.

Так выглядит слияние, когда main с момента ответвления не менялась:

$ git switch main
Switched to branch 'main'
$ git merge feature/cart
Updating a790c37..c7973cd
Fast-forward
 cart.py | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 cart.py

Слово Fast-forward в выводе означает, что нового коммита Git не создавал. Чем это отличается от настоящего слияния, разберём в следующем разделе.

Направление слияния задаёт ветка, на которой вы стоите. Команда git merge X всегда забирает X в текущую ветку. Перед слиянием проверьте git branch: звёздочка стоит у текущей.

Fast-forward и коммит слияния: что сделает Git

У git merge два режима, и какой сработает, решает форма истории. По описанию флага --ff в той же справке, если вливаемая история продолжает текущую, Git по умолчанию только передвигает указатель ветки. Если ветки разошлись, он создаёт коммит слияния.

Перемотка вперёд

Fast-forward случается, когда в main после ответвления фичи не было новых коммитов. Ветке main достаточно «догнать» feature/cart, история остаётся прямой линией:

$ git log --oneline --graph
* c7973cd Добавить корзину
* a790c37 Первый коммит

Трёхстороннее слияние

Теперь другой случай. Пока вы делали каталог в feature/catalog, в main успели закоммитить описание доставки. Ветки разошлись, и перемотать указатель уже нельзя. Git берёт общего предка и две вершины, сводит изменения и записывает результат в новый коммит. В книге Pro Git на русском этот приём называют трёхсторонним слиянием.

$ git merge feature/catalog
Merge made by the 'ort' strategy.
 catalog.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 catalog.txt
$ git log --oneline --graph
*   86792f3 Merge branch 'feature/catalog'
|\
| * 8d095e7 Добавить каталог
* | e4948d1 Описать доставку
|/
* c7973cd Добавить корзину
* a790c37 Первый коммит

Перед записью коммита Git откроет редактор с сообщением Merge branch 'feature/catalog'. Можно оставить его как есть и закрыть редактор. Если открылся Vim, выход с сохранением набирается так: Esc, затем :wq и Enter.

Строка 'ort' в выводе называет алгоритм. В описании стратегий слияния сказано, что ort выбирается по умолчанию, когда вы сливаете одну ветку или делаете pull. Менять его в обычной работе не нужно.

Флаги —no-ff и —ff-only

Поведение по умолчанию можно переключить двумя флагами. Оба пригодятся, когда в команде договорились о форме истории. Такие правила обычно задаёт тимлид и записывает в регламент репозитория.

Команда Что делает Когда пригодится
git merge feature/b Перематывает, если можно, иначе создаёт коммит слияния Обычная работа
git merge --no-ff feature/b Создаёт коммит слияния всегда, даже когда хватило бы перемотки Нужно видеть в истории, какие коммиты пришли одной фичей
git merge --ff-only feature/b Только перемотка, иначе отказ с ненулевым кодом выхода История должна оставаться прямой линией

Вот как --ff-only отказывается сливать разошедшиеся ветки. Ничего не меняется, рабочая папка остаётся как была:

$ git merge --ff-only feature/a
fatal: Not possible to fast-forward, aborting.

А так --no-ff оставляет «петлю» даже там, где история могла бы быть прямой:

$ git merge --no-ff feature/b
Merge made by the 'ort' strategy.
 b.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
$ git log --oneline --graph -3
*   f4d2b82 Merge branch 'feature/b'
|\
| * 21d023e Фича B
|/
*   86792f3 Merge branch 'feature/catalog'
|\  
Канал основателя Checkroi Вани Буявца3 800 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Другие способы объединить ветки: squash и rebase

Схема веток Git из цветной пряжи на пробковой доске

Merge сохраняет все коммиты фичи как есть. Иногда это лишнее: в ветке десять коммитов вида «поправил опечатку», и в main они не нужны. Для таких случаев есть ещё два способа. Их же отрабатывают в онлайн-курсах по Git на учебных репозиториях с проверкой заданий.

Способ Команда Что будет в истории main
Слияние git merge feature Все коммиты фичи плюс коммит слияния (или перемотка)
Сжатие git merge --squash feature, затем git commit Один новый коммит со всеми изменениями фичи
Перебазирование git rebase main в ветке фичи, затем merge Коммиты фичи по одному, прямой линией, без коммита слияния
Перенос отдельных коммитов git cherry-pick <хеш> Копии выбранных коммитов

Squash: все коммиты ветки одним

Флаг --squash готовит в рабочей папке и индексе то же состояние, что дало бы обычное слияние, но коммит не создаёт. Его вы делаете сами, со своим сообщением. В ветке feature/search было три коммита, в main попадает один:

$ git merge --squash feature/search
Updating f4d2b82..bd75381
Fast-forward
Squash commit -- not updating HEAD
 search.py | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 search.py
$ git commit -m "Добавить поиск по товарам"
[main 6fe8c23] Добавить поиск по товарам
 1 file changed, 3 insertions(+)
 create mode 100644 search.py
$ git log --oneline -2
6fe8c23 Добавить поиск по товарам
f4d2b82 Merge branch 'feature/b'

Учтите побочный эффект. Git не запоминает, что ветка уже слита, поэтому git branch -d feature/search откажется её удалять. Удаляйте флагом -D, когда убедились, что всё на месте:

$ git branch -d feature/search
error: The branch 'feature/search' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/search'.

Rebase: перенести коммиты на вершину main

Rebase переписывает коммиты фичи так, будто вы начали работу от свежей main. После него слияние в main проходит перемоткой, а история читается сверху вниз без развилок:

Стоя в ветке feature/promo, выполните:

$ git rebase main
Successfully rebased and updated refs/heads/feature/promo.
$ git log --oneline --graph -3
* c745ae4 Промокоды
* 6fe8c23 Добавить поиск по товарам
*   f4d2b82 Merge branch 'feature/b'
|\  

Хеши коммитов после rebase новые: это уже другие коммиты с тем же содержимым. Отсюда главное правило из главы Pro Git о перебазировании: не перемещайте коммиты, которые уже отправлены в общий репозиторий. Если коллеги успели взять вашу ветку, после rebase их история разойдётся с вашей.

Что выбрать: merge или rebase

  • Merge подходит для общих веток и для слияния готовой фичи в main. Он ничего не переписывает, и его безопасно делать в любой момент.
  • Rebase подходит для своей локальной ветки, которую никто, кроме вас, не трогал. Им подтягивают свежую main перед пул-реквестом.
  • Squash подходит, когда в main нужен один понятный коммит вместо промежуточных.

Подтянуть свежую main в свою ветку можно и без rebase. Стоя в ветке фичи, выполните git merge main: изменения придут коммитом слияния, история останется нетронутой.

Все три способа стоит один раз отработать руками на учебном репозитории, такие задания есть и в курсах по GitLab, где слияние идёт через merge request. Глубже всех в эти правила погружается Git-специалист, который настраивает процессы для всей команды.

Как решить конфликт слияния

Конфликт возникает, когда обе ветки поменяли одну и ту же строку. В нашем примере в main цену подняли до 1 200, а в ветке feature/discount сделали скидку и поставили 900. Чем больше людей правит один файл, тем чаще так бывает, поэтому для веб-разработчика разбор конфликтов входит в базовые навыки. Git не знает, какое число правильное, и останавливается:

$ git merge feature/discount
Auto-merging price.txt
CONFLICT (content): Merge conflict in price.txt
Automatic merge failed; fix conflicts and then commit the result.

Конфликт ничего не ломает. Слияние просто стоит на паузе и ждёт вашего решения. Дальше действуйте по шагам.

  1. Найдите файлы с конфликтом командой git status. Они лежат в блоке Unmerged paths с пометкой both modified:
    $ 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:   price.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
  2. Откройте файл. Git разметил спорное место маркерами. Сверху версия текущей ветки (HEAD), под разделителем версия вливаемой:
    <<<<<<< HEAD
    Цена: 1200
    =======
    Цена: 900
    >>>>>>> feature/discount
  3. Оставьте правильный вариант и удалите все три маркера. Можно взять одну из версий целиком или собрать третью. Здесь логичная итоговая цена со скидкой 10 % от новой: Цена: 1080.
  4. Отметьте конфликт решённым: git add price.txt.
  5. Завершите слияние: git commit. Сообщение Git подставит сам.
    $ git add price.txt
    $ git commit --no-edit
    [main ea24dc6] Merge branch 'feature/discount'

В редакторе спорные блоки подсвечены цветом. Над каждым конфликтом VS Code показывает действия Accept Current Change, Accept Incoming Change и Accept Both Changes: оставить свою версию, вливаемую или обе подряд. Остальные способы, включая трёхсторонний редактор, описаны в статье VS Code о конфликтах слияния. Из терминала тот же выбор открывает git mergetool, если у вас настроена программа сравнения. Если вы пишете код в этом редакторе, пригодится и инструкция по запуску Python в VS Code.

Решили, что сливать сейчас рано? Прервите слияние командой git merge --abort. Git вернёт файлы к состоянию до merge, git status --short после этого пустой.

Как отменить merge

Способ отмены зависит от того, успели ли вы отправить слияние на сервер.

Ситуация Команда Что произойдёт
Слияние остановилось на конфликте git merge --abort Возврат к состоянию до merge
Коммит слияния есть, но не запушен git reset --hard ORIG_HEAD Ветка возвращается туда, где стояла до merge
Коммит слияния уже на сервере git revert -m 1 <хеш> Новый коммит, который отменяет изменения слияния

До пуша: reset на ORIG_HEAD

Перед слиянием Git записывает прежнюю вершину ветки в ссылку ORIG_HEAD, это сказано в разделе DESCRIPTION справки git merge. Вернуться к ней можно одной командой:

$ git reset --hard ORIG_HEAD
HEAD is now at 6e6a451 Подорожание

Флаг --hard стирает незакоммиченные правки в рабочей папке. Проверьте git status до запуска.

После пуша: revert с флагом -m

Если слияние уже забрали коллеги, историю не переписывают. Вместо этого создают коммит, который отменяет изменения. Для коммита слияния нужен флаг -m 1: он говорит Git, что основной линией считается первый родитель, то есть ветка, в которую сливали. Без флага Git откажется:

$ git revert HEAD~1 --no-edit
error: commit 4cd0348f54c26d338cda9c59b281d275f55897e6 is a merge but no -m option was given.
fatal: revert failed
$ git revert -m 1 HEAD --no-edit
[main 810ea87] Revert "Merge branch 'feature/discount'"
 Date: Fri Nov 6 10:25:00 2026 +0300
 1 file changed, 1 insertion(+), 1 deletion(-)

У отката слияния есть ловушка, о которой предупреждает справка git revert: после него следующие слияния принесут только те коммиты, которых не было в отменённом merge. На практике повторный git merge feature/discount после такого отката отвечает Already up to date., а в файле остаётся старая цена. Чтобы вернуть фичу, отмените сам revert: git revert <хеш revert-коммита>.

Частые ошибки и что с ними делать

Рой радуется решённому конфликту слияния

Ниже сообщения, которые чаще всего пугают на первых слияниях, и их причины.

Already up to date

Git считает, что всё из указанной ветки уже есть в текущей. Обычно вы стоите не на той ветке или сливаете в обратную сторону. Проверьте git branch и git log --oneline --graph --all. Вторая частая причина описана выше: ветку уже сливали, а потом откатили через revert.

not something we can merge

$ git merge feature/carts
merge: feature/carts - not something we can merge

Такой ветки Git не знает. Либо опечатка в имени, либо ветка есть только на сервере. Во втором случае сначала выполните git fetch, а потом сливайте origin/имя-ветки.

Your local changes would be overwritten by merge

$ git merge feature/readme
Updating 759910f..e197219
error: Your local changes to the following files would be overwritten by merge:
	README.md
Please commit your changes or stash them before you merge.
Aborting

В рабочей папке есть незакоммиченные правки в файле, который меняет слияние. Git ничего не тронул и остановился. Решение: git commit или git stash, затем снова git merge, а после него git stash pop, если прятали.

refusing to merge unrelated histories

У двух веток нет общего предка. Так бывает, когда вы создали репозиторий на GitHub с README, а локально сделали git init отдельно. По описанию флага --allow-unrelated-histories, Git по умолчанию отказывается сливать такие истории. Если вы уверены, что это один проект, добавьте флаг:

$ git merge other/main
fatal: refusing to merge unrelated histories
$ git merge other/main --allow-unrelated-histories --no-edit
Merge made by the 'ort' strategy.
 x | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 x

You have divergent branches при git pull

Pull делает две вещи: загружает коммиты с сервера и сливает их с вашими. Когда новые коммиты есть и у вас, и на сервере, Git просит выбрать способ объединения и останавливается:

hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

Выберите один из трёх вариантов из подсказки и сохраните его глобально, например git config --global pull.rebase false, если в команде принято сливать. Те же настройки pull.rebase и pull.ff перечислены в справке git pull.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 800 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Слияние веток на GitHub через пул-реквест

В командах слияние в main редко делают руками. Вы пушите ветку, открываете пул-реквест, коллеги смотрят код, и кнопка внизу страницы делает merge на сервере. Как создать копию чужого репозитория и предложить в него изменения, мы разбирали в статье про форк на GitHub.

У кнопки три режима, они соответствуют способам из этой статьи. Названия даём так, как они подписаны в справке GitHub о методах слияния:

  • Merge pull request: обычное слияние с флагом --no-ff, коммит слияния создаётся всегда.
  • Squash and merge: все коммиты пул-реквеста сжимаются в один.
  • Rebase and merge: коммиты добавляются в базовую ветку по одному, без коммита слияния.

Набор режимов настраивается в параметрах репозитория. Если кнопка предлагает только один, значит, остальные там выключили.

Где освоить Git системно

Слияние веток входит в набор операций, которые разработчик делает почти каждый день. Остальные, от cherry-pick до поиска сломавшего коммита через bisect, разбирают на курсах с практическими заданиями. Git входит в базу плана, как стать программистом с нуля, а для DevOps-инженера это ежедневный инструмент.

В каталоге Checkroi курсы по Git собраны с ценами и отзывами учеников. Отдельно есть подборка курсов по GitHub. Ниже пять программ из каталога.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Gitlab CI/CD с нуля
Перейти на сайт курса
MerionMerion16 250 ₽1354 ₽/мес.4 месяцаОбзор курса
Gitlab CI/CD
Перейти на сайт курса
Слёрм (Slurm)Слёрм25 000 ₽6250 ₽/мес.58 часовОбзор курса
Основы Git
Перейти на сайт курса
PurpleSchoolPurpleSchool3999 ₽2749 ₽/мес.2 месяцаОбзор курса
Обучение Git
Перейти на сайт курса
SkillboxSkillbox22 500 ₽ - 1 месяцОбзор курса
Основы Git
Перейти на сайт курса
HexletHexletБесплатно - 1 месяцОбзор курса

Больше программ — в полном каталоге курсов по Git

Часто задаваемые вопросы

Чем git merge отличается от git rebase?

Merge объединяет ветки коммитом слияния и ничего не переписывает, поэтому подходит для общих веток. Rebase переносит ваши коммиты на вершину другой ветки и создаёт их заново с новыми хешами: история становится прямой, но такие коммиты нельзя перемещать, если их уже забрали коллеги.

Как слить main в свою ветку?

Перейдите в свою ветку, обновите main командой git fetch и выполните git merge origin/main. Изменения придут коммитом слияния. Если ветку никто, кроме вас, не трогал, вместо merge можно сделать git rebase origin/main.

Как отменить git merge?

Если слияние остановилось на конфликте, выполните git merge --abort. Если коммит слияния создан, но не отправлен, поможет git reset --hard ORIG_HEAD. Если он уже на сервере, отменяйте новым коммитом: git revert -m 1 <хеш>.

Что такое fast-forward в Git?

Это слияние без нового коммита. Если в принимающей ветке после ответвления ничего не появилось, Git просто передвигает её указатель на последний коммит вливаемой ветки, и история остаётся прямой линией. Запретить перемотку можно флагом --no-ff.

Как объединить несколько коммитов в один при слиянии?

Выполните git merge --squash имя-ветки, а затем git commit с понятным сообщением. Все изменения ветки попадут в один коммит. На GitHub то же самое делает режим Squash and merge в пул-реквесте.

Почему git merge пишет Already up to date?

Git считает, что всё из указанной ветки уже есть в текущей. Чаще всего вы стоите не на той ветке или сливаете в обратную сторону. Ещё одна причина: ветку уже сливали, а потом откатили через git revert, и тогда нужно отменить сам revert.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!