Git rebase — перебазирование коммитов и веток
Знакомим с git rebase: рассказываем о преимуществах команды и показываем, в каких случаях ее стоит использовать и почему.
Введение
Rebase (перебазирование) — один из способов в git, позволяющий объединить изменения двух веток. У этого способа есть преимущество перед merge (слияние) — он позволяет переписать историю ветки, придав истории тот вид, который нам нужен.
В этой инструкции мы познакомим вас поближе с командой git rebase, расскажем о преимуществах и тонкостях работы с ней, покажем, в каких случаях ее стоит использовать и почему.
Git rebase — что это
Из документации — это наложение коммитов поверх другого базового коммита. Под базовым понимается тот коммит, к которому применяются коммиты выбранной ветки.
git rebase [<upstream> [<branch>]]
Первый аргумент обязательный (upstream) — это базовый коммит, к которому применятся коммиты выбранной ветки. Второй аргумент можно не задавать, если HEAD указывает на ветку, которая будет нами перебазирована.
Как работает git rebase
Чтобы понимать процесс работы перебазирования, обратимся к рисунку 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)
Поставлено две задачи:
- Поменять местами коммиты qk01ru3 и 4kq5jn2;
- исправить ошибку в комментарии четвертого коммита (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
Обратите еще раз внимание, что коммиты в интерактивном режиме расположены в другом порядке, от более старого к новому. Приступим к решению задач.
- Для того чтобы поменять коммиты местами, мы просто меняем местами их строчки.
- Чтобы исправить ошибку в комментарии, мы меняем команду “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.
Нам нужно интегрировать изменения из ветки develop в master. Воспользуемся обычным rebase:
git checkout develop
git rebase master
Тогда получим вот такой результат с двумя одинаковыми коммитами D:
Это произошло потому, что перебазирование переприменило коммиты D, G, H, I, так как по отношению к ветке master коммиты ветки develop начинаются с коммита D как связывающего коммита. Теперь решим задачу с интеграцией изменений из develop немного по-другому. Чтобы избежать таких случаев как с коммитом D, воспользуемся новой командой:
git rebase --onto master feature-1 develop
Результат проиллюстрирован на рисунке ниже.
Rebase удаленного репозитория
При работе с удаленным репозиторием в тематической ветке, например на GitHub, следует быть осторожным, используя rebase. Как вы уже знаете, rebase перезаписывает историю, в процессе изменяются хеши коммитов, а это может привести к конфликтам в работе с веткой у других членов команды. Поэтому, если вы работаете над тематической веткой не одни, стоит прибегнуть к нескольким правилам для предотвращения возможных проблем.
- Синхронизировать изменения. Перед тем как вы будете заливать свой код на тот же GitHub, выполните git pull изменений, чтобы избежать конфликтных ситуаций.
- Не перебазировать давно созданные ветки. Количество шагов в rebase равно количеству коммитов на перебазируемой ветке, если не указаны иные опции. Поэтому с увеличением количества неперебазированных коммитов, растет и вероятность появления конфликта.
- Чтобы внедрить изменения в мастер, стоит создать свою локальную ветку и перебазировать ее поверх origin/master. Тогда останется лишь сделать перемотку или бесконфликтное слияние для владельца.
- Не проводить 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.