Git rebase — перебазирование коммитов и веток - Академия Selectel

Git rebase — перебазирование коммитов и веток

Знакомим с git rebase: рассказываем о преимуществах команды и показываем, в каких случаях ее стоит использовать и почему.

Введение

Rebase (перебазирование) — один из способов в git, позволяющий объединить изменения двух веток. У этого способа есть преимущество перед merge (слияние) — он позволяет переписать историю ветки, придав истории тот вид, который нам нужен.

Пример работы git rebase

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

Git rebase — что это

Из документации — это наложение коммитов поверх другого базового коммита. Под базовым понимается тот коммит, к которому применяются коммиты выбранной ветки.

git rebase [<upstream> [<branch>]]

Первый аргумент обязательный (upstream) — это базовый коммит, к которому применятся коммиты выбранной ветки. Второй аргумент можно не задавать, если HEAD указывает на ветку, которая будет нами перебазирована.

Как работает git rebase

Пример работы перебазирования — git rebase master

Чтобы понимать процесс работы перебазирования, обратимся к рисунку 1.

У нас есть две ветки — master и my_branch. Мы находимся на ветке my_branch (HEAD указывает на ветку my_branch). Выполняем команду: 

git rebase master

После этого git удалит и последовательно переместит коммиты C, D, F из ветки my_branch в ветку master — сначала C, затем D и F. Новые коммиты C’, D’, F’ полностью идентичны удаленным, меняется только хеш.

Сначала для ветки my_branch базовым коммитом был B, но после стал коммит E. Это и есть процесс под названием перебазирование.

Как использовать git rebase

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

Линейная история — реинтеграция тематической ветки после выполнения git rebase master

После того как мы использовали команду git rebase, можно перемотать ветку master командой git merge:

git checkout master
git merge my_branch

К команде слияния можно добавить флаг либо —ff (fast-forward merge), чтобы не создавать коммит слияния, или добавить —no-ff — для его создания. Создание коммита слияния помогает определить, когда ветки объединились, и какие коммиты тематической ветки были сделаны.

Слияние перебазированной ветки

Когда работа с тематической веткой закончена, удаляем ее:

git branch -d my_branch

Это приведет к законченному виду истории, когда мы внедрили изменения из тематической ветки в основную часть проекта. 

Линейная история мастер-ветки

Конфликты

Так как git rebase последовательно переприменяет коммиты, то могут возникнуть конфликты слияния (merge conflicts). Первая причина появления конфликта  — объединение коммитов, содержащих изменения в одних и тех же файлах. Вторая причина — несколько человек изменяют одинаковый файл на одной расшаренной ветке. Чтобы узнать, в каких файлах есть конфликтующие изменения, проверим статус.

git status

Нам будет предложено решить конфликтные коммиты, затем пометить их решенными:

git add/rm <conflicted_files> 

Дальше нужно продолжить перебазирование:

git rebase --continue

Или еще откатить изменения — вернуться в состояние до использования команды rebase.

git rebase --abort

Есть и третий вариант с перезапуском шага и перезагрузкой процесса перебазирования:

git rebase --skip

Но будьте аккуратны, skip пропустит (удалит) конфликтный коммит.

Git rebase interactive

Интерактивный режим rebase используется для перезаписи истории посредством изменения самих коммитов, а также информации в них. Переход в интерактивный режим перебазирования делается при помощи флага -i или —interactive. 

git rebase [-i | --interactive]

Выполнение этой команды создаст список коммитов в хронологическом порядке добавления, чтобы пользователь мог по своему желанию отредактировать их перед последующим перебазированием. Дальше мы рассмотрим подробнее, как это происходит.

Как пользоваться интерактивным режимом

Например, вот созданная ветка master с пятью коммитами:

2hqsibn selected new method in script.js (HEAD -> master)
	4kq5jn2 changes to the script.j
	qk01ru3 resolved conflict
fmjgyu6 added new files
z2zgn0c initial commit (origin/master)

Поставлено две задачи:

  1. Поменять местами коммиты qk01ru3 и 4kq5jn2;
  2. исправить ошибку в комментарии четвертого коммита (4kq5jn2).

Для этого мы включаем интерактивный режим rebase. В нем можно указать определенное количество коммитов для изменения. Чтобы это сделать, необходимо передать в аргумент коммит, предшествующий тому, который мы будем изменять (в данном случае это fmjgyu6), либо задать “HEAD~[x]”, где вместо [x] — нужное нам число коммитов.

git rebase -i HEAD~3 

Откроется текстовый редактор по умолчанию, где также будет приведено описание команд, используемых в интерактивном режиме rebase:

pick qk01ru3 resolved conflict 
	pick 4kq5jn2 changes to the sсript.j 
	pick 2hqsibn selected new method in script.js 

	# Rebase fmjgyu6..2hqsibn onto fmjgyu6
#
# Commands:

# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message

# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit

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

  1. Для того чтобы поменять коммиты местами, мы просто меняем местами их строчки.
  2. Чтобы исправить ошибку в комментарии, мы меняем команду “pick” в 4kq5jn2 на “reword” и в следующем окне переписываем комментарий на “changes to the script.js”.

В итоге имеем следующий порядок коммитов:

reword 4kq5jn2 changes to the sсript.js
	pick qk01ru3 resolved conflict
	pick 2hqsibn selected new method in script.js

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

Теперь наша ветка имеет вид:

vh9xwf3 selected new method in script.js (HEAD -> master)
	s70zmpx resolved conflict
b0jemdh changes to the script.js
fmjgyu6 added new files
z2zgn0c initial commit (origin/master)

Имейте в виду, что хеши коммитов, с которыми работал rebase, станут иными.

Какой режим выбрать: стандартный или интерактивный

Интерактивный rebase — продвинутая версия обычного rebase, которая дает возможность большего взаимодействия с коммитами. Если нужно разделить, объединить, удалить коммиты, изменить их описание и порядок, интерактивный режим справится на отлично. Если же такой потребности нет, быстрее будет использовать стандартный режим rebase, в котором от пользователя не требуются лишние действия до возникновения конфликтов.

Git rebase —onto

Onto относится к возможностям, раскрывающих rebase во всей красе.

git rebase --onto <newbase> [<upstream> [<branch>]]

Эта опция позволяет указать коммит, с которого будут перебазироваться коммиты (первый аргумент), иначе это называется новая база. Третий аргумент (branch) можно не указывать, если HEAD указывает на ветку, которая будет нами перебазирована.

Пример
У нас есть три ветки: master, feature-1, develop.

Ветки master, feature-1, develop до rebase

Нам нужно интегрировать изменения из ветки develop в master. Воспользуемся обычным rebase:

	git checkout develop
	git rebase master

Тогда получим вот такой результат с двумя одинаковыми коммитами D:

Ветки master, feature-1, develop после стандартного rebase

Это произошло потому, что перебазирование переприменило коммиты D, G, H, I, так как по отношению к ветке master коммиты ветки develop начинаются с коммита D как связывающего коммита. Теперь решим задачу с интеграцией изменений из develop немного по-другому. Чтобы избежать таких случаев как с коммитом D, воспользуемся новой командой:

git rebase --onto master feature-1 develop

Результат проиллюстрирован на рисунке ниже.

Ветки master, feature-1, develop после rebase с опцией —onto

Rebase удаленного репозитория

При работе с удаленным репозиторием в тематической ветке, например на GitHub, следует быть осторожным, используя rebase. Как вы уже знаете, rebase перезаписывает историю, в процессе изменяются хеши коммитов, а это может привести к конфликтам в работе с веткой у других членов команды. Поэтому, если вы работаете над тематической веткой не одни, стоит прибегнуть к нескольким правилам для предотвращения возможных проблем. 

  1. Синхронизировать изменения. Перед тем как вы будете заливать свой код на тот же GitHub, выполните git pull изменений, чтобы избежать конфликтных ситуаций.
  2. Не перебазировать давно созданные ветки. Количество шагов в rebase равно количеству коммитов на перебазируемой ветке, если не указаны иные опции. Поэтому с увеличением количества неперебазированных коммитов, растет и вероятность появления конфликта.
  3. Чтобы внедрить изменения в мастер, стоит создать свою локальную ветку и перебазировать ее поверх origin/master. Тогда останется лишь сделать перемотку или бесконфликтное слияние для владельца.
  4. Не проводить rebase уже отправленных коммитов в публичный репозиторий. Коллегам придется выполнить слияние, что приведет к путанице.

Pull rebase

Чтобы запушить свою ветку, когда git не знает, как объединить ветки, используется режим force:

git push origin <branch> --force

С этим режимом будут скопированы родительские коммиты feature на origin, указатель перемещается, как он установлен на локальном репозитории. Важно указать идентификатор ветки в <branch>, иначе запушатся все локальные ветки ориджина.

А чтобы извлечь изменения из удаленного репозитория, вместо обычного pull можно использовать режим rebase:

git pull --rebase origin <branch>

Локальные merge коммиты не образуются, а история будет выглядеть линейно.

Дополнительные опции перебазирования

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

ОпцииПояснение
-s <strategy>—strategy=<strategy>Использовать стратегию слияния вместо дефолтного “ort”, что изменит поведение rebase. Подробнее в документации.
-X <strategy-option>—strategy-option=<strategy-option>Эта опция для применения более одной стратегии в порядке, заданном пользователем.
-x <cmd>—exec <cmd>Выполнение одной или более shell-команд после каждого шага rebase в интерактивном режиме. Если выполнение команды неудачно, перебазирование остановится.
—no-keep-emptyНе оставлять пустые коммиты. То есть убирать те коммиты, которые ничего не меняют по отношению к родителю.
—allow-empty-messageПозволяет перебазировать пустые коммиты с пустым сообщением.
—autosquashВ интерактивном режиме берет коммиты, которые начинаются с fixup! или squash! и ставит соответствующую команду, чтобы объединить коммит с предыдущим.Чтобы это значение всегда работало по умолчанию, можно прописать следующее:“git config —global rebase.autosquash true”

Заключение

В этой инструкции мы рассмотрели, как сделать rebase ветки в git, узнали про возможность более продвинутой манипуляции с коммитами в режиме interactive и onto, а также тонкости, которые следует соблюдать при работе с rebase.