Git rebase — перебазирование коммитов и веток
Знакомим с git rebase: рассказываем о преимуществах команды и показываем, в каких случаях ее стоит использовать и почему.
Эта инструкция — часть курса «Введение в Git».
Смотреть весь курс
Введение
Rebase (перебазирование) — один из способов в git, позволяющий объединить изменения двух веток. У этого способа есть преимущество перед merge (слияние) — он позволяет переписать историю ветки, придав тот истории тот вид, который нам нужен.
В этой инструкции мы познакомим вас поближе с командой git rebase, расскажем о преимуществах и тонкостях работы с ней, покажем, в каких случаях ее стоит использовать и почему.
Git rebase — что это
Из документации — это наложение коммитов поверх другого базового коммита. Под базовым понимается тот коммит, к которому применяются коммиты выбранной ветки.
git rebase [ []]
Первый аргумент обязательный (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
Дальше нужно продолжить перебазирование:
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 = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop = 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 [ []]
Эта опция позволяет указать коммит, с которого будут перебазироваться коммиты (первый аргумент), иначе это называется новая база. Третий аргумент (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 --force
С этим режимом будут скопированы родительские коммиты feature на origin, указатель перемещается, как он установлен на локальном репозитории. Важно указать идентификатор ветки в , иначе запушатся все локальные ветки ориджина.
А чтобы извлечь изменения из удаленного репозитория, вместо обычного pull можно использовать режим rebase:
git pull --rebase origin
Локальные merge коммиты не образуются, а история будет выглядеть линейно.
Дополнительные опции перебазирования
Ниже приведена таблица некоторых опций, которые могут быть полезны для работы с rebase.
| Опции | Пояснение |
| -s —strategy= | Использовать стратегию слияния вместо дефолтного “ort”, что изменит поведение rebase. Подробнее в документации. |
| -X —strategy-option= | Эта опция для применения более одной стратегии в порядке, заданном пользователем. |
| -x —exec | Выполнение одной или более shell-команд после каждого шага rebase в интерактивном режиме. Если выполнение команды неудачно, перебазирование остановится. |
| —no-keep-empty | Не оставлять пустые коммиты. То есть убирать те коммиты, которые ничего не меняют по отношению к родителю. |
| —allow-empty-message | Позволяет перебазировать пустые коммиты с пустым сообщением. |
| —autosquash | В интерактивном режиме берет коммиты, которые начинаются с fixup! или squash! и ставит соответствующую команду, чтобы объединить коммит с предыдущим.Чтобы это значение всегда работало по умолчанию, можно прописать следующее:“git config —global rebase.autosquash true” |
Заключение
В этой инструкции мы рассмотрели, как сделать rebase ветки в git, узнали про возможность более продвинутой манипуляции с коммитами в режиме interactive и onto, а также тонкости, которые следует соблюдать при работе с rebase.
Работа с ветками в Git (git branch)
В чем плюсы использования rebase
Команда git rebase заслужила репутацию магической — какой-то невероятный фокус Git, которого должны избегать новички, и которая значительно облегчает совместную разработку в команде при аккупатном использовании. В этой статье (перевод документации [1]) будут сравниваться возможности git rebase с похожей по назначению командой git merge, и будут описаны все потенциальные возможности команды rebase для включения в обычный рабочий процесс Git.
[Обзор концепции merge и rebase]
Первое, что нужно понимать для git rebase — эта команда предназначена для той же цели, что и git merge. Обе эти команды разработаны для интекрации изменений из одной ветки в другую, только делают они это разными способами.
Рассмотрим ситуацию, что происходит, когда вы начали работать над новой функцией в выделенной для этого ветке (пусть это будет ветка feature, которая была получена от основной ветки проекта main), а другой участник команды разработки после этого обновил основную сетку (main) проекта новыми коммитами. Итак, сейчас в проекте существует 2 параллельно разрабатываемые ветки — main и feature. В результате дальнейшей работы получится разветвленная история изменений, с которой сталкиваются повсеместно пользователи, использующие Git как инструмент управления версиями и поддержки совместной разработки.
Теперь предположим, что новые коммиты в ветке main затрагивают также и код, над которым вы работали в своей отдельной ветке feature. Для внедрения новых коммитов из ветки main в ветку feature, у вас есть 2 опции: merge или rebase.
Merge. Это самая простая опция слить ветку main в ветку feature, используя команды наподобие следующих:
$ git checkout feature $ git merge main
Или можно выполнить эти команды одной строкой:
$ git merge feature main
Это создаст новый «коммит слияния» (merge commit) в ветке feature, который объединит друг с другом истории обоих веток, в результате получится структура веток наподобие следующей:
Слияние веток командой merge хорошо тем, что в ней отсутствуют деструктивные операции. Существующие ветки никаким образом не изменяются. Это позволяет избежать потенциальных проблем, связанных с rebase (что будет рассмотрено далее).
С другой стороны это также означает, что ветка feature получит дополнительную фиксацию слияния (merge commit; далее для простоты фиксацию будем называть коммитом — устоявшийся термин среди разработчиков) всякий раз, когда нужно слить друг с другом добавление в разработке новой функции и основной внешний код проекта. Если в основной ветке проекта (веика main) велась очень активная разработка, это может несколько загрязнить историю изменений в вашей ветке feature. Хотя проблему можно решить с помощью расширенных опций команды git log, это может затруднить для других разработчиков понимание истории проекта.
Rebase. Как альтернативу для слияния (merge) вы можете сделать перебазирование (rebase) ветки feature на ветку main с помощью следующих команд:
# Переключение на ветку feature: $ git checkout feature
# Текущая ветка feature будет перебазирована на самый свежий # коммит ветки main: # git rebase main
Последняя команда перенесет всю ветку feature на кончик ветки main, эффективно внедряя все новые изменения из коммитов ветки main. Однако вместо использования merge commit перебазирование перезапишет историю проекта таким образом, что создаются новые коммиты для каждого коммита в оригинальной ветке.
Основное достоинство rebase в том, что вы получаете намного более чистую историю проекта. Во-первых, это устраняет нежелательные фиксации слияния (merge commit), требуемые для команды git merge. Во вторых, как вы можете видеть на диаграмме выше, rebase также приводит к исключительно линейной истории проекта — вы можете продолжить разработку своей новой фичи с головы (HEAD) основной ветки main без каких-либо ответвлений. Это упрощает навигацию по проекту командами наподобие git log, git bisect и gitk.
Однако существует 2 компромисса, на которые приходится идти с этой чистой истории коммитов: безопасность и трассировка. Если вы не следуете Золотому Правилу для git rebase, то перезапись истории проекта может быть потенциальной катастрофой для совместного процесса разработки. И, что не так важно, rebase выбрасывает контекст, который предоставляет merge commit вы не сможете увидеть в истории изменения внешней ветки, когда встроите их в свою ветку feature.
Интерактивный rebase. Это режим перебазирования, который дает вам возможность изменить коммиты, когда они переносятся в новую ветку. Это более мощная функция, чем автоматический rebase (или стандартный rebase), поскольку дает полный контроль на историей коммитов ветки. Обычно это используется для подчистки запутанной истории перед слиянияем ветки feature в ветку main (более подробно про стандартный и интерактивный rebase см. [2]).
Чтобы начать интерактивную сессию перебазирования, передайте опцию -i в команду git rebase:
$ git checkout feature $ git rebase -i main
Это откроет текстовый редактор, где будут перечислены все коммиты, которые будут перемещаться в процессе перебазирования:
pick 33d5b7a Сообщение для коммита #1 pick 9480b3d Сообщение для коммита #2 pick 5c67e61 Сообщение для коммита #3
Этот листинг точно определяет, как будет выглядеть ветка после выполнения rebase. Путем изменения команды pick и/или переупорядочивания строк в этом списке вы можете добиться любого варианта вида истории, какой захотите. Например, если 2-й коммит зафиксирует небольшую проблему в 1-ом коммите, то вы можете решить сжать их в один коммит командой fixup:
pick 33d5b7a Сообщение для коммита #1 fixup 9480b3d Сообщение для коммита #2 pick 5c67e61 Сообщение для коммита #3
Когда вы сохраните и закроете этот файл, Git выполнит rebase в соответствии с вашими инструкциями, в результате чего проект будет выглядеть следующим образом:
Устранение малозначимых коммитов, как было сделано в этом примере, сделает вашу историю проекта более простой для понимания. Это как раз то, чего не может делать команда git merge.
[Золотое Правило rebase]
После того, как мы уяснили, чем является rebase, самое важное научиться, когда не нужно его делать. Золотое правило для git rebase состоит в том, что никогда не нужно его делать для публичных веток.
Например давайте подумаем, что произойдет, если вы сделаете rebase ветки main на вашу ветку feature:
Это перебазирование переместит все коммиты main в HEAD ветки feature. Проблема тут в том, что это произойдет только в вашем локальном репозитории. Все другие разработчики продолжат как ни в чем ни бывало работать с оригинальной веткой main. Поскольку rebase приведет к совершенно новым коммитам, то Git будет думать, что история вашей ветки main разошлась со всеми остальными.
Единственный способ синхронизации двух ответвлений main — слить их обратно друг в друга, что приведет к дополнительному merge commit, и два набора коммитов будут содержать одинаковые изменения (в оригинальной ветке main и вашей перебазированной ветке). Что и говорить, получается довольно запутанная ситуация.
Так что перед тем, как запустить git rebase, всегда задавайте себе вопрос: «Еще кто-нибудь работает с этой веткой?». Если ответ ДА, то уберите ваши руки подальше от клавиатуры, и задумайтесь над менее деструктивным способом применить свои изменения (например, команду git revert). В противном случае вы как угодно сможете перезаписать историю проекта.
Принудительный push. Если вы попытаетесь выгрузить (push) перебазированную ветку main обратно в сетевой репозиторий, то Git не даст вам это сделать, потому что получится конфликт с веткой main сетевого репозитория. Однко вы все еще можете сделать принудительную выгрузку передачей в push флага —force (или -f):
# Будьте очень осторожны с этой командой! $ git push -f
Эта команда перезапишет ветку main в сетевом репо на перебазированную версию из вашего репозитория, что приведет в шок остальных участников команды разработки (если они вдруг сделают git pull, то все результаты их работы потеряются). Поэтому перед выполнением принудительного push нужно хорошо понимать, что вы делаете.
Одним из случаев применения принудительного push может быть ситуация, когда вы выполнили локальную очистку после того, как локальная ветка feature была перенесена в сетевой репозиторий (например, с целью бэкапа). Это как если бы сказать: «Ой, я на самое деле не хочу делать push той оригинальной ветки feature. Вместо этого возьмите текущую версию». И снова важно отметить, чтобы никтр не отрабатывал фиксации из оригинальной версии ветки feature.
[Описание рабочего процесса]
Перебазирование может быть внедрено в рабочий процесс Git вашей команды настолько, насколько это удобно. В этой секции мы рассмотрим, насколько полезен может быть rebase на различный стадиях разработки новой фичи (feature).
Первый шаг для любого рабочего процесса, где может использоваться git rebase, состоит создание выделенной ветки для каждой новой разрабатываемой фичи. Это даст необходимую структуру веток проекта, позволяющую безопасно применять rebase:
Локальная очистка. Один из лучших способов внедрить rebase в ваш рабочий процесс — очистка локальных, находящихся в разработке фич проекта. Периодическими запусками интерактивного rebase вы можете гарантировать, что каждый коммит в вашей фиче сфокусирован на достижении главной цели и вносит значимые изменения. Это позволяет писать код, не беспокоясь о том, чтобы разбивать его на изолированные коммиты — вы сможете это починить постфактум.
При вызове git rebase у вас есть 2 опции для создания новой базы: переместить веку в голову родительской ветки (например на самый свежий коммит ветки main), либо на предыдущий коммпит вашей ветки feature. Первый вариант мы уже в примере выше из секции «Интерактивный rebase». Второй вариант хорош, когда нужно исправить только несколько последних коммитов. Например, следующая команда начнет интерактивный rebase только последних трех коммитов.
$ git checkout feature git rebase -i HEAD~3
Если указать HEAD~3 в качестве ссылки на новую базу, то фактически ветка не перемещается — вы просто интерактивно перезапишете 3 коммита, которые идут за ней. Обратите внимание, что при этом не будут внедрены внешние изменения в ветку feature.
Если вы хотите перезаписать таким способом всю feature, то может быть полезна команда git merge-base, чтобы найти оригинальную базу ветки feature. Следующая команда возвратит идентификатор фиксации (commit ID) оригинальной базы, который вы можете затем передать в команду git rebase:
$ git merge-base feature main
Такое использование интерактивного перебазирования — хороший способ внедрения git rebase в ваш рабочий процесс, поскольку это влияет только на локальные ветки. Единственное, что увидят другие разработчики — только ваш конечный продукт, что должно создать для всех чистую, простую для понимания историю веток проекта.
И снова, это работает только для приватных веток фичи. Если вы работаете совместно с другими разработчиками на одной же ветке фичи, то эта ветка публичная, и вам нельзя просто так перезаписать её историю.
Не существует альтернативы для git merge, когда очищаются локальные коммиты с интерактивным rebase.
Внедрение внешних изменений в feature. Выше в секции «Обзор концепции merge и rebase» мы видели, как в ветку feature могут быть внедрены изменения внешних изменений из ветки main с использованием git merge или git rebase. Слияние (merge) это безопасный вариант, который сохранит всю историю вашего репозитория, в то время как rebase создаст линейную историю путем перемещения вашей ветки feature в голову ветки main.
Это использование git rebase подобно локальной очистке (и может быть выполнено одновременно), но в процессе интегрируются внешние коммиты из main.
Следует иметь в виду, что вполне нормально делать rebase не ветку сетевого репозитория вместо main. Это может быть, когда происходит совместная работа над одной и той же фичей с другим разработчиком, и вам нужно внедрить его изменения в свой локальный репозиторий.
Например, если вы и другой разработчик (Джон) добавили свои коммиты в ветку feature, то ваш репозиторий после извлечения ветки feature Джона может выглядеть следующим образом:
Вы можете разобраться с этим разветвлением таким же способом, как интегрировали бы внешние изменения из main: либо слиянием (merge) вашей локальной ветки feature с веткой feature Джона, либо перебазированием (rebase) вашей локальной ветки feature в голову ветки feature Джона (john/feature).
Обратите внимание, что этот rebase не нарушает Золотое Правило Перебазирования, потому переносятся что только только коммиты вашей локальной ветки — все до этого осталось нетронутым. Это примерно как сказать: «добавь мои изменения к тому, что Джон уже сделал». В большинстве случаев это является более интуитивно понятным, чем синхронизация с сетевой веткой через коммит слияния.
По умолчанию команда git pull выполняет слияние (merge), однако можно заставить его интегрировать сетевую ветку с rebase передачей опции —rebase.
Просмотр изменений feature с запросом pull. Если вы используете запросы pull как часть процесса оценки вашего кода, то вам нужно избегать исопользования git rebase после создания запроса pull. Как только вы сделаете запрос pull, другие разработчики увидят ваши коммиты, т. е. ветка станет публичной. Перезапись истории сделает невозможным для Git и ваших участников команды разработки отследить и следовать коммитам, добавленным в ветку feature.
Любые измененние других разработчиков должны быть внедрены через git merge вместо git rebase.
По этой причине обычно хорошей идеей будет очистить ваш код с помощью интерактивного rebase перед выдачей запроса pull.
Интеграция одобренной функции (Approved Feature). После того, как ветка feature была одобрена вашей командой, у вас есть опция сделать rebase ветки feature в голову ветки main перед использованием git merge для интеграции feature в базу кода main.
Это аналогично ситуации, когда внешние внешние изменения внедряются в ветку feature, но поскольку вам не разрешено перезаписывать коммиты в ветке mainh, вы должны время от времени использовать git merge для интеграции feature. Однако путем выполнения rebase перед merge вы будете уверены, что merge будет быстро перенаправлено (fast-forwarded), в результате получится исключительно чистая история. Это также даст вам возможность скрещивать (squash) любые последующие коммиты, добавленные запросом pull.
Если в не чувствуете себя полностью в комфорте для использования git rebase, то всегда можете выполнять rebase во временную ветку. Таким способом, если вы вдруг случайно потеряете историю вашей ветки feature, то сможете извлечь нужный коммит из оригинальной ветки. Например:
$ git checkout feature $ git checkout -b temporary-branch $ git rebase -i main
# [Очистка истории] $ git checkout main $ git merge temporary-branch
Вместо заключения. Это все, что вам нужно знать, чтобы начать использовать rebase для ваших веток. Если вы предпочитаете иметь чистую, линейную историю, свободную от малозначимых коммитов слияния (merge commits), то должны обратиться к использованию git rebase вместо git merge, когда интегрируете изменения из одной ветки в другую.
С другой стороны, если вы хотите сохранить полную историю всех шагов по разработке в вашем проекте, и избежать риска risk перезаписи публичных коммитов, то можете придерживаться к стратегии git merge. Любой из этих вариантов отлично работает, просто крайней мере вы теперь хорошо знаете их достоинства и недостатки, и сможете теперь вполне оченить возможности git rebase.
[Ссылки]
1. Merging vs. Rebasing site:atlassian.com.
2. git rebase.
3. Краткий справочник по Git.
Владеешь merge — освой и rebase
Независимо от используемых в проекте стратегий ветвления, приходится регулярно интегрировать изменения из одной ветки в другую. В git это можно сделать двумя основными способами: merge (слияние) и rebase (перебазирование).
В данной статье мы рассмотрим обе операции и отличия между ними, а также обозначим моменты, требующего особого внимания.
Сначала с помощью анимационных средств разберем каждую операцию по отдельности, а на заключительном этапе проведем параллельное сравнение. Если вам уже знакомы принципы работы этих действий, то сразу переходите к сравнительным характеристикам.
Согласно официальному руководству Git rebase “повторно применяет коммиты поверх другой базовой ветки”, тогда как merge “объединяет две или более историй разработки”. Иначе говоря, основное отличие между ними в том, что слияние сохраняет историю в первозданном виде, а перебазирование ее перезаписывает. Прежде чем переходить к более подробному осмыслению принципов их внутренней работы, обратимся к примеру:
Как видно из примера, разработчики Ada и Satoshi изначально создали 2 тематические ветки ( feature-1 и feature-2 ), происходящие из одного и того же коммита ( C1 ) на ветке master . Затем Ada завершила работу с feature-1 , осуществив ее слияние с master (создав коммит слияния C4 ). Теперь у Satoshi есть два способа интегрировать изменения Ada в свою ветку feature-2 — слияние или перебазирование.
Слияние
Начнем с самого распространенного рабочего процесса интеграции изменений: слияния. Перед объединением изменений Ada с feature-2 Satoshi должен сначала обновить свой локальный указатель на master ветку, поскольку в данный момент она устарела. Как только master и o/master синхронизируются, Satoshi сможет включить все изменения в свою тематическую ветку.
Перебазирование
Имея в виду процесс слияния, рассмотрим тот же пример, но уже с точки зрения перебазирования. Так же как и в предыдущем случае, перед интеграцией изменений Satoshi должен убедиться, что его локальная и удаленная ветки master синхронизированы. Но затем вместо обычного слияния, сохраняющего историю в ее поэтапном виде, он может интегрировать все изменения с помощью операции перебазирования, таким образом перезаписывая историю.
Выполняя перебазирование feature-2 относительно master , Git вернется назад и повторно выполнит коммиты C5 и C6 один за другим прямо поверх C4 , создавая впечатление, что feature-2 изначально была ответвлением конечных изменений Ada.
Понимая главное отличие между слиянием и перебазированием, проанализируем конечные результаты этих операций.
Сравнение слияния и перебазирования
Как видим, после слияния две ветки объединились в новом коммите ( C7 ), придавая нелинейной истории ромбовидную форму и сохраняя ее в неизменном виде. В отличие от этой операции перебазирование привело не к созданию коммита слияния, а к возврату и повторному применению коммитов C5 и C6 поверх C4 , обеспечивая линейность истории. Более детальное изучение этих коммитов позволяет выявить изменения их хешей, подтверждающее факт перезаписи истории в результате перебазирования.
Примечание: каждый раз перебазирование ветки будет сопровождаться созданием новых коммитов, даже если их содержимое останется одним и тем же. Таким образом, прежние коммиты будут в итоге полностью удалены из истории.
С большой силой приходит большая ответственность
Итак, теперь мы знаем, что перебазирование перезаписывает историю, а слияние ее сохраняет. Но что это значит в более широком смысле? Какие возможности и потенциальные недочеты таят в себе две эти операции?
Конфликты при интеграции изменений
Допустим, попытка интегрировать изменения обернулась рядом неприятных конфликтов. Прибегнув к слиянию, вы бы решили их все за раз прямо в коммите C7 . А вот в случае с перебазированием вам бы пришлось решать одни и те же конфликты в каждом коммите ( C5 и C6 ) по мере их повторного применения.
Трудноразрешимые конфликты говорят о недостатке общения с коллегами ввиду очень длительной работы над одними и теми же файлами.
Опубликованные ветки
Еще одна потенциальная проблема связана с ситуацией, в которой ветка, подлежащая перебазированию, уже удаленно опубликована и положена в основу чьей-либо работы. Тогда такая перебазированная ветка может запутать и усложнить процесс для всех участников, поскольку Git укажет, что она одновременно и отстает, и опережает. В этом случае проблема решается путем извлечения изменений из удаленного репозитория с последующим внедрением в текущий, для чего служит флаг —rebase ( git pull —rebase ).
Кроме того, всякий раз при перебазировании уже опубликованной ветки, независимо от того, основывается ли на ней чья-либо работа, вам по прежнему необходимо принудительно опубликовать ее для внесения обновлений в удаленный сервер, тем самым полностью переписывая текущий указатель.
Потеря данных (как преимущество)
Поскольку слияние сохраняет историю, а перебазирование ее переписывает, то последняя операция может привести к потере данных. При повторном выполнении новых коммитов старые удаляются (после сборки мусора). Именно эта особенность повышает эффективность команды rebase , позволяющей очистить историю разработки, прежде чем сделать ее общедоступной. А с помощью интерактивного перебазирования можно удалять ненужные коммиты, сжимать изменения или просто обновлять сообщения коммитов.
Главные правила перебазирования
Во избежание связанных с перебазированием проблем рекомендуется придерживаться следующих правил:
- Не перебазируйте ветку, опубликованную удаленно…
- …если только вы не уверены, что кроме вас с ней никто не работает (и что ее принудительная публикация не вызовет проблем).
- Создайте резервную ветку, исходящую из конечной точки ветки, подлежащей перебазированию. Это позволит легко сравнить результат (по завершении) и при необходимости вернуться к состоянию, предшествующему перебазированию.
Усложненные случаи перебазирования
Существует множество усложненных операций перебазирования. К числу наиболее эффективных из них относится ранее упомянутый его интерактивный вариант, позволяющий определить способ повторного выполнения каждого коммита.
Данный режим применяется для разделения, сжатия, перегруппировки и даже полного удаления коммитов, и этим его возможности не ограничиваются.
Заключение
Многие разработчики предпочитают вместо rebase выполнять merge , поскольку они уверены, что так не потеряют результаты своей работы. В некотором смысле такой веский аргумент оправдывает отказ от неудобных в работе инструментов. А вот нежелание постичь все преимущества эффективных возможностей, будучи о них осведомленным, оправдать нельзя!
Такой подход равносилен высказыванию: “Хоть у меня и отличная машина, но лучше я ограничусь первой передачей, ведь скоростная езда смертельно опасна”. А почему бы не научиться переключать передачи и безопасно передвигаться на высоких скоростях?!
Мой собственный опыт показал, что знание принципов перебазирования углубляет понимание Git, а также развивает вас как разработчика, особенно если речь идет об управлении исходным кодом.
Как-то в самом начале моей карьеры разработчика я получил один из лучших советов от более опытного коллеги. Звучит он так: “Хватит стучать по клавишам в Source Tree, лучше научись использовать команды Git из терминала! Иначе ты не познаешь все возможности Git, и в перспективе не сможешь программировать автоматические конвейеры.”
С тех пор в качестве визуального средства для просмотра дерева истории я предпочитаю только GitK, а все команды набираю в терминале. И вам рекомендую!
Теперь, понимая разницу между слиянием и перебазированием, вы с большей уверенностью начнете применять обе операции.
Благодарю за внимание и желаю удачи в развитии навыков управления исходным кодом.
- Продвинутый функционал Git: хитрые приемы и команды
- Поддержание документации в актуальном состоянии с помощью Bit и GitHub
- Как на самом деле работает Git
Merging vs. rebasing
Командой git rebase , словно опасным заклинанием, часто пугают новичков. В действительности она может значительно облегчить жизнь команде разработчиков, если использовать ее правильно. В этой статье проводится сравнение команды git rebase со схожей командой git merge и описываются все возможные ситуации, в которых уместно включить перебазирование в стандартный рабочий процесс Git.
Conceptual overview
В первую очередь нужно понимать, что команда git rebase помогает решить ту же проблему, что и команда git merge . Обе команды предназначены для включения изменений из одной ветки в другую, но делают это по-разному.
Представьте, что вы начали работать над новой функциональной возможностью в отдельной ветке, после чего другой участник команды добавляет новые коммиты в главную ветку main . Возникает история форков, знакомая каждому, кто использовал Git для совместной работы.
А теперь предположим, что новые коммиты в главной ветке main затрагивают функцию, над которой вы работаете. Для включения новых коммитов в свою функциональную ветку feature можно использовать два варианта: слияние или перебазирование.

Связанные материалы
Перемещение полного репозитория Git
СМ. РЕШЕНИЕ
Изучите Git с помощью Bitbucket Cloud
The merge option
Проще всего слияние ветки main в функциональную ветку выполняется с помощью следующей команды:
git checkout feature
git merge main
При желании этот код можно записать в одну строку:
git merge feature main
Эта операция создает в ветке feature новый «коммит слияния», связывающий истории обеих веток. Структура веток будет выглядеть так:
Слияние (merge) — это отличная неразрушающая операция. Существующие ветки никак не изменяются. Эта операция позволяет избегать потенциальных проблем, связанных с выполнением команды rebase (и описанных ниже).
С другой стороны, это означает, что каждый раз, когда вам будет необходимо включить вышестоящие изменения, в функциональную ветку feature будет попадать внешний коммит слияния. Если работа в главной ветке main ведется активно, история вашей функциональной ветки быстро засорится. Хотя эту проблему можно устранить, используя продвинутые варианты команды git log , другим разработчикам будет тяжело разобраться в истории проекта.
The rebase option
Вместо слияния можно выполнить перебазирование функциональной ветки feature на главную ветку main с помощью следующих команд:
git checkout feature
git rebase main
В результате вся функциональная ветка feature окажется поверх главной ветки main , включая в себя все новые коммиты в ветке main . Если вместо команды merge при коммитах используется rebase, эта команда перезаписывает историю проекта, создавая новые коммиты для каждого коммита в исходной ветке.
Главное преимущество rebase — более чистая история проекта. Во-первых, эта команда устраняет ненужные коммиты слияния, необходимые для git merge . Во-вторых, как показано на рисунке выше, команда rebase создает идеальную линейную историю проекта — вы сможете отследить функционал до самого начала проекта без каких-либо форков. Это упрощает навигацию в проекте с помощью таких команд, как git log , git bisect и gitk .
Однако такая безупречная история коммитов требует определенных жертв: жертвовать приходится безопасностью и отслеживаемостью. Если не следовать Золотому правилу Rebase, перезапись истории проекта может обернуться катастрофическими последствиями для совместных рабочих процессов. Кроме того, при выполнении rebase теряется контекст, доступный в коммите со слиянием: вы не сможете увидеть, когда вышестоящие изменения были включены в функционал.
Interactive rebasing
Интерактивная операция rebase позволяет изменять коммиты при их перемещении в новую ветку. Этот вариант предоставляет еще больше возможностей, чем автоматическое выполнение rebase, поскольку дает полный контроль над историей коммитов ветки. Обычно его используют для очистки запутанной истории, перед тем как сливать функциональную ветку в главную ветку main .
Чтобы запустить интерактивное перебазирование, передайте параметр i команде git rebase :
git checkout feature
git rebase -i main
Откроется текстовый редактор. В нем будут перечислены все коммиты, подготовленные к перемещению:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Этот список точно отражает, как будет выглядеть ветка после перебазирования. Изменяя команду pick и (или) порядок коммитов, вы можете придать истории ветки нужный вид. Так, если второй коммит содержит исправление небольшой проблемы в первом, их можно объединить с помощью команды fixup :
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Когда вы сохраните и закроете файл, Git выполнит перебазирование в соответствии с вашими указаниями. История проекта затем примет следующий вид:
Удаление незначительных коммитов помогает быстрее разобраться в истории функциональной ветки. Команда git merge просто не в состоянии этого сделать.
The golden rule of rebasing
Разобравшись с возможностями rebase, необходимо в первую очередь понять, когда эту команду не нужно использовать. Золотое правило для команды git rebase — никогда не использовать ее в публичных ветках.
К примеру, представьте, что произойдет, если вы выполните rebase главной ветки main на свою функциональную ветку feature .
Перебазирование перемещает все коммиты ветки main в конец ветки feature . Проблема в том, что это происходит только в вашем репозитории, в то время как другие разработчики продолжают работать с исходной веткой main . Поскольку в результате перебазирования создаются абсолютно новые коммиты, Git будет считать, что история вашей главной ветки main разошлась с остальными.
Единственный способ синхронизировать две главные ветки main — выполнить их обратное слияние. Это приведет к дополнительному коммиту слияния и двум наборам коммитов, которые содержат одни и те же изменения (исходные изменения и изменения из вашей ветки после rebase). Нужно ли говорить, что ситуация получится крайне запутанная?
Поэтому перед выполнением команды git rebase следует убедиться, что текущую ветку не просматривает кто-то другой. Если в ней действительно ведется работа, прекратите любые действия и подумайте, как можно внести изменения неразрушающим способом (например, с помощью команды git revert ). В остальных случаях вы можете свободно перезаписывать историю при необходимости.
Force-pushing
Git заблокирует попытку поместить перебазированную ветку main обратно в удаленный репозиторий, поскольку она вступит в конфликт с удаленной веткой main . Но эту операцию можно выполнить принудительно, добавив флаг —force :
# Be very careful with this command! git push --force
При этом удаленная ветка main станет соответствовать ветке в вашем репозитории после rebase. В итоге путаться начнут и ваши коллеги. Поэтому будьте внимательны и используйте эту команду только в том случае, если полностью понимаете, чего хотите добиться.
Одна из немногих ситуаций, требующих форсированного помещения кода, — это локальная очистка после помещения частной функциональной ветки в удаленный репозиторий (например, для создания резервной копии). Это равноценно заявлению: «Ой, я ведь не хотел отправлять исходную версию этой функциональной ветки. Лучше возьмите текущую версию». Здесь также важно, чтобы никто после коммитов не начал работу из исходной версии функциональной ветки.
Workflow walkthrough
Перебазирование можно использовать при работе с Git ровно в том объеме, который подходит команде. В этом разделе вы узнаете о преимуществах перебазирования на разных этапах разработки функций продукта.
В любом рабочем процессе с применением команды git rebase сначала нужно создать отдельную ветку для каждой функции. Полученная структура веток необходима для безопасного перебазирования:
Local cleanup
Один из самых эффективных сценариев перебазирования в рабочем процессе — это очистка локальных функциональных веток, в которых еще ведется работа. Если периодически проводить интерактивное перебазирование, ни один коммит в вашей ветке не потеряет смысла. Вы сможете быть уверены, что ваш код не распадется на изолированные коммиты. Если это случится, ситуацию всегда можно будет исправить.
При использовании команды git rebase есть два варианта для нового положения ветки: вышестоящая ветка для функциональной ветки (например, ветка main ) или более ранний коммит в функциональной ветке. Первый вариант описывался в примере в разделе Интерактивное перебазирование. Второй вариант удобен, когда нужно исправить лишь несколько недавних коммитов. Например, следующая команда запускает интерактивную операцию rebase только для трех последних коммитов.
git checkout feature git rebase -i HEAD~3
Указав HEAD~3 в качестве нового положения, вы не перемещаете ветку как таковую, а лишь интерактивно переписываете 3 последующих коммита. Следует отметить, что при этой операции вышестоящие изменения не включаются в функциональную ветку.
Если с помощью этого способа вы хотите переписать всю функциональную ветку, найти начальное положение функциональной ветки поможет команда git merge-base . Следующая команда возвращает ID коммита начального положения, который затем можно передать в команду git rebase :
git merge-base feature main
Такое использование интерактивной операции rebase отлично поможет включить git rebase в рабочий процесс, поскольку затронет только локальные ветки. Другие разработчики увидят только законченную версию с простой и отслеживаемой историей функциональной ветки.
Повторим, что это работает только для частных функциональных веток. Если вы работаете в одной функциональной ветке с другими разработчиками, она является публичной и переписывать ее историю запрещено.
Варианта очистки локальных коммитов с интерактивным использованием rebase с помощью команды git merge не существует.
Incorporating upstream changes into a feature
В разделе Обзор основных моментов рассматривалось включение вышестоящих изменений из главной ветки main в функциональную ветку с помощью команды git merge или git rebase . Операцию слияния можно выполнять безопасно, поскольку при этом сохраняется вся история репозитория. При выполнении перебазирования создается линейная структура: функциональная ветка перемещается в конец ветки main .
В этом случае команда git rebase используется так же, как и локальная очистка (которую можно осуществить одновременно), но при ее выполнении включаются вышестоящие изменения из главной ветки main .
Помните, что можно еще выполнять rebase в удаленную ветку, отличную от ветки main . Например, при совместной работе над функциональной возможностью с другим разработчиком, когда требуется включить его изменения в ваш репозиторий.
Например, если вы вместе с разработчиком Джоном добавляли коммиты в ветку feature , то после получения удаленной ветки feature из репозитория Джона ваш репозиторий может выглядеть следующим образом:
С таким ветвлением можно работать так же, как и при включении вышестоящих изменений из ветки main : либо выполнить слияние в локальную ветку feature для ветки john/feature , либо выполнить перебазирование локальной ветки feature на конец ветки john/feature .
Обратите внимание: такое перебазирование не нарушает «золотое правило», поскольку перемещаются только коммиты вашей ветки feature , тогда как предшествующие элементы остаются нетронутыми. В сущности, эта операция позволяет лишь добавить ваши изменения к наработкам Джона. Чаще всего это проще, чем выполнять синхронизацию с удаленной веткой посредством коммита слияния.
По умолчанию команда git pull выполняет слияние. Однако если передать ей параметр —rebase , будет выполнено перебазирование для удаленной ветки.
Reviewing a feature with a pull request
Если в процессе проверки кода используются пул-реквесты, не используйте команду git rebase после создания пул-реквеста. Сразу после создания пул-реквеста другие разработчики смогут видеть ваши коммиты, то есть ваша ветка станет публичной. В случае перезаписи ее истории Git и ваши коллеги не смогут отслеживать последующие коммиты в функциональную ветку.
Все изменения, сделанные другими разработчиками, нужно добавлять командой git merge , а не git rebase .
Поэтому хорошей идеей является очистка кода с помощью интерактивной операции rebase перед созданием пул-реквеста.
Integrating an approved feature
После одобрения функциональной ветки коллегой вы можете перебазировать ее на конец ветки main , а затем использовать команду git merge для включения функциональной ветки в основную базу кода.
Это похоже на включение вышестоящих изменений в функциональную ветку, но поскольку переписывать коммиты в главной ветке main запрещено, вам придется использовать для включения функциональной ветки команду git merge . Выполняя перебазирование перед слиянием, вы обеспечиваете ускоренное слияние и идеальную линейную историю. Такой подход также позволяет склеивать любые последующие коммиты, добавленные до закрытия запроса pull.
Если вы еще не привыкли работать с командой git rebase , всегда можно выполнить rebase во временную ветку. В таком случае, если вы случайно запутаете историю функциональной ветки, всегда можно будет переключиться в исходную ветку и попробовать снова. Например:
git checkout feature
git checkout -b temporary-branch
git rebase -i main
# [Clean up the history]
git checkout main
git merge temporary-branch
Резюме
Теперь у вас достаточно информации, чтобы начать использовать rebase для своих веток. Если вы предпочитаете иметь чистую линейную историю без ненужных коммитов слияния, используйте команду git rebase вместо git merge при включении изменений из другой ветки.
Но если вам нужно сохранить полную историю проекта и избежать перезаписи публичных коммитов, воспользуйтесь командой git merge . Теперь вы можете не только применить слияние, но и оценить преимущества команды git rebase — оба варианта эффективны в работе.