В системе контроля версий Git есть два способа объединения одной ветки с другой, выраженные в виде разных команд:
-
git merge. Коммиты (изменения) из одной ветки переносятся в другую путем создания коммита слияния.
-
git rebase. Коммиты (изменения) из одной ветки переносятся из одной ветки в другую путем сохранения оригинального порядка изменений.
Говоря проще, при git merge коммиты одной из веток «схлопываются» в один, а при git rebase — остаются нетронутыми. При этом ветки объединяются.
Таким образом команда git rebase позволяет объединить коммиты обеих веток через образование общей истории изменений.

Разница между git merge и git rebase
В этом руководстве будет рассмотрена команда git rebase, отвечающая за перебазирование коммитов (изменений) из одной ветки в другую.
Все показанные примеры использовали систему контроля версий Git версии 2.34.1, которая запускалась на облачном сервере Timeweb Cloud под управлением операционной системы Ubuntu 22.04.
В блоге Timeweb Cloud есть отдельные публикации, подробно рассказывающие об установке Git на популярные операционные системы:
Теория: git rebase
Понять устройство ребазирования в Git лучше всего на примере абстрактного репозитория, состоящего из нескольких веток. При этом операцию ребазирования необходимо рассматривать поэтапно.
Создание веток
Предположим мы создали репозиторий с единственной веткой master, в которую сделали только один коммит. Ветка master приобрела следующий вид:
-
master -
commit_1
После этого на основе master мы создали новую ветку hypothesis, внутри который решили протестировать некоторые фичи. Внутри новой ветки мы сделали несколько коммитов, улучшающих кодовую. Ветка приобрела такой вид:
-
hypothesis -
commit_4 -
commit_3 -
commit_2 -
commit_1
А уже потом мы добавили еще один коммит в ветку master, исправив некоторую уязвимость в срочном порядке. Таким образом, ветка master стала выглядеть так:
-
master -
commit_5 -
commit_1
Теперь наш репозиторий имеет структуру из двух веток:
-
master -
commit_5 -
commit_1
-
hypothesis -
commit_4 -
commit_3 -
commit_2 -
commit_1
Ветка master является основной, а hypothesis — второстепенной (производной). Поздние коммиты указываются выше ранних, подобно тому, как их выводит команда git log.
VDS и VPS
биллингом по всему миру: Россия, Азия и Европа.
Объединение веток
Предположим, мы хотим продолжить работу над улучшением фичи, под которую ранее выделили отдельную ветку hypothesis. Однако эта ветка не содержит жизненно важное исправление уязвимости, которое мы сделали в ветке master.
Поэтому нам хотелось бы «синхронизировать» состояние ветки hypothesis с веткой master так, чтобы коммит исправления оказался в ветке с фичей. То есть мы хотим получить примерно такую структуру репозитория:
-
master -
commit_5 -
commit_1
-
hypothesis -
commit_4 -
commit_3 -
commit_2 -
commit_5 -
commit_1
Как видно, ветка hypothesis будет точно повторять историю изменений ветки master, несмотря на то, что изначально она была создана до коммита commit_5. Иными словами, ветка hypothesis будет содержать историю обеих веток — и свою, и master.
Чтобы получить такой результат, необходимо выполнить ребазирование с помощью команды git rebase.
Впоследствии изменения, сделанные в hypothesis, можно будет объединить с веткой master с помощью классической команды git merge, создающей коммит слияния.
Тогда структура репозитория станет такой:
-
master -
commit_merge -
commit_5 -
commit_1
-
hypothesis -
commit_4 -
commit_3 -
commit_2 -
commit_5 -
commit_1
Более того, выполнение git merge после команды git rebase может снизить вероятность возникновения конфликтов.
Практика: git rebase
Разобравшись с теоретическим аспектом команды git rebase, можно перейти к ее тестированию в реальном репозитории некого импровизированного проекта. При этом структура репозитория будет повторять показанный ранее теоретический пример.
Создание репозитория
Для начала создадим отдельную директорию, в которой будет размещаться репозиторий:
mkdir rebase
После чего перейдем в нее:
cd rebase
Теперь можно инициализировать репозиторий:
git init
В консольном терминале должно вывестись стандартное информационное сообщение:
hint: Using 'master' as the name for the initial branch. This default branch name hint: is subject to change. To configure the initial branch name to use in all hint: of your new repositories, which will suppress this warning, call: ...
А в текущей директории появится скрытый каталог .git, который можно увидеть с помощью соответствующей команды:
ls -a
Флаг -a означает all и позволяет просматривать файловую систему в расширенном режиме. Ее содержимое будет таким:
. .. .git
Перед тем, как начать делать коммиты, необходимо указать базовые сведения о пользователе.
Сперва имя:
git config --global user.name "ИМЯ"
А потом и почту:
git config --global user.email "ИМЯ@ХОСТ.COM"
Наполнение ветки master
С помощью простых текстовых файлов мы будем имитировать добавление различных функций в проект. Каждая новая функция будет оформляться в отдельный коммит.
Создадим файл импровизированной функции:
nano function_1
И наполним его содержимом:
Функция 1
Теперь проиндексируем изменения, сделанные в репозитории:
git add .
На всякий случай можно проверить статус индексации:
git status
В консольном терминале должно появится сообщение со списком проиндексированных изменений:
On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: function_1
Теперь можно выполнить коммит:
git commit -m "commit_1"
После этого в консольном терминале выведется сообщение об успешном внесении изменений в ветку master:
[master (root-commit) 4eb7cc3] commit_1 1 file changed, 1 insertion(+) create mode 100644 function_1
Наполнение ветки hypothesis
Теперь необходимо создать новую ветку hypothesis:
git checkout -b hypothesis
Флаг -b необходим, чтобы сразу переключиться на созданную ветку.
В консольном терминале появится сообщение об успешном переключении на новую ветку:
Switched to a new branch 'hypothesis'
Теперь необходимо последовательно выполнить три коммита с тремя файлами по аналогии с веткой master:
-
commit_2с файломfunction_2и содержимымФункция 2 -
commit_3с файломfunction_3и содержимымФункция 3 -
commit_4с файломfunction_4и содержимымФункция 4
Если после этого просмотреть список всех коммитов:
git log --oneline
То в консольном терминале появится такая последовательность:
d3efb82 (HEAD -> hypothesis) commit_4 c9f57b7 commit_3 c977f16 commit_2 4eb7cc3 (master) commit_1
В этой команде флаг --oneline необходим для вывода информации о коммитах в сжатом формате одной строки.
Последнее, что нужно сделать, — добавить еще один коммит в основную ветку master. Переключимся на нее:
git checkout master
В консольном терминале должно появится соответствующее сообщение:
Switched to branch 'master'
После этого создадим еще один файл импровизированной функции:
nano function_5
Содержимое будет следующим:
Функция 5
Теперь можно проиндексировать изменения:
git add .
И выполнить очередной коммит:
git commit -m "commit_5"
Если проверить текущий список коммитов:
git log --oneline
То в ветке master их будет всего два:
3df7a00 (HEAD -> master) commit_5 4eb7cc3 commit_1
Объединение веток с git rebase
Для выполнения перебазирования сперва необходимо перейти в ветку hypothesis:
git checkout hypothesis
И выполнить ребазирование:
git rebase master
После этого в консольном терминале появится сообщение об успешном ребазировании:
Successfully rebased and updated refs/heads/hypothesis.
Теперь можно проверить список коммитов:
git log --oneline
В консольном терминале появится список, содержащий коммиты обоих веток в оригинальном порядке:
8ecfd58 (HEAD -> hypothesis) commit_4 f715aba commit_3 ee47470 commit_2 3df7a00 (master) commit_5 4eb7cc3 commit_1
Теперь ветка hypothesis содержит общую историю всего репозитория.
Разрешение конфликтов
Как и в случае с git merge, при использовании команды git rebase могут возникать конфликты, требующие ручного разрешения.
Давайте модифицируем наш репозиторий таким образом, чтобы искусственно создать конфликт ребазирования.
Создадим в ветке hypothesis еще один файл:
nano conflict
И запишем в него следующий текст:
Тут должен быть конфликт!
Проиндексируем изменения:
git add .
И выполним очередной коммит:
git commit -m "conflict_1"
Теперь переключимся на ветку master:
git checkout master
Создадим аналогичный файл:
nano conflict
И наполним его следующим содержимым:
Тут НЕ должен быть конфликт!
Аналогично, выполняем индексацию:
git add .
И делаем коммит:
git commit -m "conflict_2"
Заново откроем созданный файл:
nano conflict
И изменим его содержимое на следующий текст:
Тут точно НЕ должен быть конфликт!
Опять проиндексируется:
git add .
И снова сделаем коммит:
git commit -m "conflict_3"
Теперь можно обратно переключиться на ветку hypothesis:
git checkout hypothesis
А далее выполнить еще одно ребазирование:
git rebase master
В консольном терминале появится сообщение о конфликте:
Auto-merging conflict CONFLICT (add/add): Merge conflict in conflict error: could not apply 6003ed7... conflict_1 hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 6003ed7... conflict_1
Git предлагает отредактировать файл conflict, проиндексировать изменения с помощью команды git add, после чего продолжить ребазирование, указав флаг --continue.
Именно так мы и поступим:
nano conflict
Файл будет содержать две конфликтующие версии файла, обрамленные специальными символами:
<<<<<<< HEAD
Тут точно НЕ должен быть конфликт!
=======
Тут должен быть конфликт!
>>>>>>> 6003ed7 (conflict_1)
Наша задача убрать всё лишнее, наполнив файл итоговым вариантом произвольного текста:
Тут однозначно точно единогласно НЕ должен быть никакого конфликта!
Теперь индексируем изменения:
git add .
И продолжаем процесс ребазирования:
git rebase --continue
После этого в консольном терминале откроется текстовый редактор, предлагающий изменение оригинальное название того коммита, в котором возник конфликт:
conflict_1
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto bd7aefc
# Last commands done (4 commands done):
# pick 8ecfd58 commit_4
# pick 6003ed7 conflict_1
# No commands remaining.
# You are currently rebasing branch 'hypothesis' on 'bd7aefc'.
#
# Changes to be committed:
# modified: conflict
#
В консольном терминале появится сообщение об успешном завершении процесса ребазирования:
[detached HEAD 482db49] conflict_1 1 file changed, 1 insertion(+), 1 deletion(-) Successfully rebased and updated refs/heads/hypothesis.
Теперь если проверить список коммитов в ветке hypothesis:
git log --oneline
Можно увидеть оригинальную последовательность всех сделанных изменений:
482db49 (HEAD -> hypothesis) conflict_1 bd5d036 commit_4 407e245 commit_3 948b41c commit_2 bd7aefc (master) conflict_3 d98648d conflict_2 3df7a00 commit_5 4eb7cc3 commit_1
Обратите внимание, что коммиты conflict_2 и conflict_3, сделанные в ветке master, располагаются в истории изменений раньше, чем коммит conflict_1. Впрочем, это касается любых коммитов, сделанных в ветке master.
Ребазирование удаленного репозитория
Помимо работы с локальными ветками, ребазирование можно выполнить в момент подтягивания изменений из удаленного репозитория. Для этого к стандартной команде pull необходимо добавить флаг --rebase:
git pull --rebase remote branch
Здесь:
-
remote. Удаленный репозиторий. -
branch. Удаленная ветка.
По сути, такая конфигурация команды pull является эквивалентом git rebase за исключением того, что применяемые к текущей ветки изменения (коммиты) берутся из удаленного репозитория.
Преимущества git rebase
- Линейность
Команда git rebase позволяет сформировать достаточно линейную историю целевой ветки, представляющую собой последовательно сделанные коммиты.
Такая последовательность и отсутствие ветвления делает историю проще для восприятия и понимания.
- Снижение количества конфликтов
Предварительно выполненная команда git rebase может существенно снизить вероятность возникновения конфликтов при объединении веток с помощью git merge.
Конфликты проще разрешать в последовательно идущих коммитах, нежели в коммитах, сливающихся в один единый коммит слияния. Это особенно актуально при отправке веток в удаленные репозитории.
Недостатки git rebase
- Изменение истории
В отличие от слияния, ребазирование частично переписывает историю целевой ветки. При этом лишние элементы истории удаляются.
- Вероятность ошибок
Свойство существенно перестраивать историю коммитов может приводить к необратимым ошибкам внутри репозитория. А это значит, что некоторые данные могут быть безвозвратно утеряны.
Надежные VDS для ваших проектов
477 ₽/мес
657 ₽/мес
Заключение
Объединение двух веток методом ребазирования, который реализуется командой git rebase, существенно отличается от классического слияния, выполняемого командой git merge.
-
git mergeпревращает коммиты одной ветки ветки в один коммит другой.
-
git rebaseперемещает коммиты из одной ветки в конец другой, сохраняя оригинальный порядок.
При этом аналогичного поведения ребазирования можно добиться во время использования команды git pull, используя дополнительный флаг --rebase.
С одной стороны, команда git rebase позволяет добиться более чистой и наглядной истории коммитов в основной ветке, что увеличивает простоту поддержки репозитория. С другой стороны, команды git rebase снижает уровень детализации изменений внутри ветки, упрощая историю и удаляя некоторые ее записи.
По этой причине ребазирование — функция для более опытных пользователей, которые понимают механизм работы Git.
Чаще всего команда git rebase используется в связке с командой git merge, позволяя получить наиболее оптимальную структуру репозитория и веток внутри него.
