Истории успеха наших клиентов — лучшие проекты
Вход/ Регистрация

Git rebase: что делает и преимущества команды

2720
11 минут чтения
Средний рейтинг статьи: 2.6

В системе контроля версий Git есть два способа объединения одной ветки с другой, выраженные в виде разных команд:

  • git merge. Коммиты (изменения) из одной ветки переносятся в другую путем создания коммита слияния.

  • git rebase. Коммиты (изменения) из одной ветки переносятся из одной ветки в другую путем сохранения оригинального порядка изменений.

Говоря проще, при git merge коммиты одной из веток «схлопываются» в один, а при git rebase — остаются нетронутыми. При этом ветки объединяются.

Таким образом команда git rebase позволяет объединить коммиты обеих веток через образование общей истории изменений.

Image1

Разница между 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 для ваших проектов

Cloud MSK 15

477 ₽/мес

Процессор
1 x 3.3 ГГц
Память
1 ГБ
NVMe
15 ГБ
Канал
1 Гбит/с
Публичный IP
Cloud MSK 30

657 ₽/мес

Процессор
1 x 3.3 ГГц
Память
2 ГБ
NVMe
30 ГБ
Канал
1 Гбит/с
Публичный IP

Заключение

Объединение двух веток методом ребазирования, который реализуется командой git rebase, существенно отличается от классического слияния, выполняемого командой git merge.

  • git merge превращает коммиты одной ветки ветки в один коммит другой.

  • git rebase перемещает коммиты из одной ветки в конец другой, сохраняя оригинальный порядок.

При этом аналогичного поведения ребазирования можно добиться во время использования команды git pull, используя дополнительный флаг --rebase.

С одной стороны, команда git rebase позволяет добиться более чистой и наглядной истории коммитов в основной ветке, что увеличивает простоту поддержки репозитория. С другой стороны, команды git rebase снижает уровень детализации изменений внутри ветки, упрощая историю и удаляя некоторые ее записи.

По этой причине ребазирование — функция для более опытных пользователей, которые понимают механизм работы Git.

Чаще всего команда git rebase используется в связке с командой git merge, позволяя получить наиболее оптимальную структуру репозитория и веток внутри него.

2720
11 минут чтения
Средний рейтинг статьи: 2.6
Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
Пока нет комментариев