<div><img src="https://top-fwz1.mail.ru/counter?id=3548135;js=na" style="position:absolute;left:-9999px;" alt="Top.Mail.Ru" /></div>
Бесплатный перенос IT-инфраструктуры в облако

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

Миша Курушин
Миша Курушин
Технический писатель
28 декабря 2024 г.
38
11 минут чтения
Средний рейтинг статьи: 5

В системе контроля версий 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

Объединение веток

Предположим, мы хотим продолжить работу над улучшением фичи, под которую ранее выделили отдельную ветку 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 для ваших проектов

Заключение

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

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

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

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

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

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

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

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