Что такое git rebase
git rebase для начинающих
Сам я в этой теме не очень разбираюсь и не люблю ее использовать, по причинам изложенным ниже, поэтому прошу комментировать, дополню по возможности.
Итак git работает с комитами. Каждый комит — набор изменений. У каждого комита есть уникальный hash. Когда происходит слияние веток посредством merge:
то все комиты сохраняются — сохраняются комментарии комита, его hash + как правило добавляется еще один искусственный комит. При этом комиты могут чередоваться друг с другом. Это не всегда удобно. Допустим ваш комит решили откатить — выискивать в общем списке где ваш комит, а где не ваш не очень приятно. И вообще — в общей истории хочется видеть действительно важные изменения, а не «ой, я забыл поставить ;». Для того, чтобы несколько комитов склеивать в один можно использовать rebase. Хотя в интерфейсе GitHub есть кнопочка squash & commit — это когда вы создаете pull request (PR) из одной ветки в другую (как правило из вашей рабочей ветки в основную) и после прохождения всех формальностей можно нажать squash & commit, обновить комментарий и ваши изменения появятся в основной ветке как один комит.
Из плюсов — можно весь код, все изменения посмотреть в IDE. Иногда бывает удобнее, чем через web UI, который на GitHub.
Осторожно rebase может менять hash комита и приводить к конфликтам слияний, особенно если над одной веткой трудятся несколько человек.
Хочу написать о двух случаях использования rebase:
При этом все комментарии, которые были в fixup комитах потеряются и будет использоваться комментарий от первого комита. Если вам были дороги комментарии, то стоит использовать squash вместо fixup.
Вдогонку напишу что у меня получилось. Дано — 2 ветки — master и b1. В процессе работы мы сделали комиты:
Комиты в master и b1 были сделаны независимо. Написал в один список, чтобы был понятен порядок в котором все делалось. Вот список комитов в каждом бранче:
# git checkout master && git log
8dcef6c «+1»
b505f18 «master after b1.1»
2fbbe67 «Initial commit»
# git checkout b1 && git log
2d7d4ea «b1.2»
85eac43 «b1.1»
2fbbe67 «Initial commit»
Делаем git merge master в b1
# git checkout b1 && git merge master && git log
5383781 «Merge branch ‘master’ into b1»
8dcef6c «+1»
2d7d4ea «b1.2»
b505f18 «master after b1.1»
85eac43 «b1.1»
2fbbe67 «Initial commit»
Добавился новый синтетический комит
Теперь делаем rebase
# git checkout b1 && git rebase master && git log
7f18e47 «b1.2»
6fb80cb «b1.1»
8dcef6c «+1»
b505f18 «master after b1.1»
2fbbe67 «Initial commit»
Обратите внимание — наши локальные комиты встали в конец списка, номера комитов изменились, синтетический комит исчез.
Ну и напоследок склеиваем наши комиты в один
pick 6fb80cb b1.1
pick 7f18e47 b1.2
Файл стало после нашего редактирования
pick 6fb80cb b1.1
fixup 7f18e47 b1.2
# git checkout b1 && git log
9062cd7 «b1.1»
8dcef6c «+1»
b505f18 «master after b1.1»
2fbbe67 «Initial commit»
Такие дела
Обзор Git Rebase
Удаление и перемещение коммитов в git опасны потерей данных или истории изменений. Git Rebase выполняет эти и другие операции. Владение этой командой повышает уверенность при работе с репозиторием.
Введение
Системы контроля версиями полезны, когда они могут рассказать историю всех изменений. Эта история может быть как реальный рассказ о жизни и развитии программной системы. Но может быть и кучкой коммитов, разобраться в которых нельзя и которые не несут пользы.
Вести аккуратную историю помогает команда git rebase. Основная ее цель “перетаскивать” коммиты из одного места в другое. Также с помощью нее можно избавляться от ненужных коммитов, склеивать одни и менять порядок других.
Умение пользоваться командой git rebase придает уверенности при работе с репозиторием.
Предостережение
Ребейзинг может “удалить” коммиты. Ребейзинг может испортить историю коммитов.
Однако в действительности удалить коммиты может только сборщик мусора. Это знание не сильно поможет, если потерять все ссылки на коммит, но если добавить лишнюю ветку или просто запомнить sha-1 хеш коммита, то всегда можно восстановить ребейзнутые коммиты.
О структуре коммитов
Ребейзинг переприменяет коммиты, то есть создает коммиты с таким же содержанием, но в другом месте. Новые коммиты получат новые хеши.
Указатели на коммиты
Некоторые названия настолько неудачно подобраны, что, услышав термин, человек сразу понимает о чем идет речь, понимает приблизительно правильно и у него нет причин разбираться глубже.
Проще говоря, ветка указывает на коммит. Поэтому если ответвить 5 веток от master, то все они будут указывать на один и тот же коммит. Однако новые коммиты будут свои в каждой из веток.
Создание коммита
Каждый коммит (кроме начального) имеет родительский коммит, который для него аналог “предыдущего”. Из одного коммита может выходить несколько дочерних коммитов. Это делается с помощью ветвления. А так как ветки могут соединяться в одну, то у коммита может быть и несколько родительских коммитов. Обычно их два, но есть возможность сделать merge трех и более коммитов одновременно.
Иногда коммит бывает создан не в том месте и не в то время. Не в том месте означает, что он был создан не в той ветке, а не в то время означает, что коммит был создан, но потом ветка разработки обогатилась новым кодом и хочется пересоздать коммит, начиная с более свежего кода.
Пересоздание коммитов
Что же делать? Подтягивать изменения. Можно слить ветку develop в ветку с задачей. Безопасно, но история будет подпорчена. Делается ребейзинг
Предполагается, что develop уже содержит новые коммиты, то есть, что уже были сделаны fetch/pull.
Как работает команда git rebase? В простом случае, это работает так:
Ниже приведены три дерева коммита
Ветка с задачей исходит из актуального коммита
Код разработки обновился, ветка с задачей исходит из старого коммита
Выполнен ребейзинг, ветка с задачей исходит из актуального коммита
Стоит обратить внимание на хеш коммита до ребейзинга (8f26890) и после (3b16148). Разумеется, по старому хешу можно найти старый коммит.
Создадим ветку, указывающую на старый коммит
Нетрудно видеть, что старый коммит остался в целости и сохранности, однако, если на него никто не указывает, то он не отображается в истории коммитов. Такие висячие коммиты удаляются сборщиком мусора в последствии (например, через месяц).
Потеря коммитов
Чтобы избавиться от риска потерять коммиты, перед ребейзингом следует создать дополнительную ветку
Простой Rebase
Формат команды такой
Но в Merge все наоборот!
Поэтому при ребейзинге придется дважды переключаться с ветки на ветку
Однако если использоваться слияние для подтягивания изменений, то сценарий похожий
Разница есть только со сценарием завершения задачи
Таким образом, для того, чтобы определить “что и куда”, надо задать вопрос “какую ветку надо изменить?”. Находиться следует в той ветке, которую надо изменить.
Интерактивный Rebase
Ребейзинг позволяет также переписывать историю своих коммитов.
Такая команда делает то же самое, что и раньше, но перед этим открывает текстовый редактор, в котором можно изменить поведение по умолчанию.
Rebase и коммиты удаленного репозитория
Конфликты
Конфликты случаются везде, где накладываются изменения из разных мест. Это происходит при слиянии (git merge), при отмене коммитов (git revert) и даже при переключении (git checkout, git stash). Во всех командах конфликты разрешаются по общим правилам. Поэтому в ребейзинге разрешение конфликтов такое же как и при слиянии.
При слиянии создается специальный мерж-коммит, в котором хранятся все изменения, сделанные в отдельной ветке. Поэтому все конфликты разрешаются в этом же коммите. В истории хранятся все коммиты из ветки с задачей и мерж-коммит. Последний можно рассматривать как завершающий штрих.
В помощниках вроде TortoiseGit процесс разрешения конфликтов почти ничем не отличается от слияния. В консоли используются следующие команды.
Детальный Rebase
Существует еще одна форма команды ребейзинга
FROM, TO, START – хеши коммитов или названия веток/тегов. Команда копирует коммиты из диапазона FROM..TO в START.
Стандартная команда, выполненная, находясь внутри ветки feature/A/super_code
Может быть заменена на
Примеры
Необходимо скопировать последний коммит из ветки с задачей в develop
(в git можно указать на родительский коммит с помощью значка шапочки)
Коммит сделан в ветке feature/A, а должен был быть в ветке bug/B.
Чистая история коммитов
Рефакторинг привыкли относить только к коду, но хорошая и наглядная история коммитов в репозитории может дать больше информации разработчику. Правило бойскаута из книги “Чистый код” Роберта Мартина «оставь место стоянки чище, чем оно было до твоего прихода» применимо и к истории коммитов. Поэтому использование Git Rebase также полезно в работе с репозиторием, как инструменты рефакторинга в IDE для работы с кодом.
Введение в Git Merge и Git Rebase: зачем и когда их использовать
Часто у разработчиков возникает выбор между Merge (слияние) и Rebase (перемещение). В Гугле вы увидите разное мнение, многие советуют не использовать Rebase, так как это может вызвать серьезные проблемы. В статье я объясню, что такое слияние и перемещение, почему вы должны (или не должны) использовать их и как это сделать.
Git Merge и Git Rebase преследуют одну и ту же цель. Они предназначены для интеграции изменений из одной ветки в другую. Хотя конечная цель одинаковая, принципы работы разные.
Некоторые считают, что вы всегда должны использовать Rebase, другие предпочитают Merge. В этом есть свои плюсы и минусы.
Git Merge
Слияние — обычная практика для разработчиков, использующих системы контроля версий. Независимо от того, созданы ли ветки для тестирования, исправления ошибок или по другим причинам, слияние фиксирует изменения в другом месте. Слияние принимает содержимое ветки источника и объединяет их с целевой веткой. В этом процессе изменяется только целевая ветка. История исходных веток остается неизменной.
Плюсы:
Слейте ветку master в ветку feature, используя команды checkout и merge.
Это создаст новый «Merge commit» в ветке feature, который содержит историю обеих веток.
Git Rebase
Rebase — еще один способ перенести изменения из одной ветки в другую. Rebase сжимает все изменения в один «патч». Затем он интегрирует патч в целевую ветку.
В отличие от слияния, перемещение перезаписывает историю, потому что она передает завершенную работу из одной ветки в другую. В процессе устраняется нежелательная история.
Переместите ветку feature на главной ветке, используя следующие команды.
Это перемещает всю ветку функции в главную ветку. История проекта изменяется, создаются новые коммиты для каждого коммита в основной ветке.
Интерактивное перемещение
Это позволяет изменять коммиты при их перемещении в новую ветку. Это лучше, чем автоматическое перемещение, поскольку обеспечивает полный контроль над историей коммитов. Как правило, используется для очистки истории до слияния ветки feature в master.
Это откроет редактор, перечислив все коммиты, которые будут перемещены.
Это точно определяет, как будет выглядеть ветка после выполнения перемещения. Упорядочивая объекты, вы можете сделать историю такой, как захотите. Вы можете использовать команды fixup, squash, edit, и так далее.
Какой из них использовать?
Так что же лучше? Что рекомендуют эксперты?
Трудно принять единственно правильное решение о том, что лучше использовать, поскольку все команды разные. Всё зависит от потребностей и традиций внутри команды.
Принимайте решения на основании компетенции команды в Git. Для вас важна простота или перезаписывание истории, а может быть что-то другое?
По мере роста команды становится сложно управлять или отслеживать изменения в разработке, применяя слияние. Чтобы иметь чистую и понятную историю коммитов, разумно использовать Rebase.
Руководство по Git. Часть №2: золотое правило и другие основы rebase
Посмотрим, что происходит, когда вы выполняете git rebase и почему нужно быть внимательным.
Это вторая и третья части гайда по Git из блога Pierre de Wulf в переводе команды Mail.ru Cloud Solutions. Первую часть можно почитать тут.
Суть rebase
Как именно происходит rebase:
Можно сказать, что rebase — это открепить ветку (branch), которую вы хотите переместить, и подключить ее к другой ветке. Такое определение соответствует действительности, но попробуем заглянуть чуть глубже. Если вы посмотрите документацию, вот что там написано относительно rebase: «Применить коммиты к другой ветке (Reapply commits on top of another base tip)».
Главное слово здесь — применить, потому что rebase — это не просто копипаст ветки в другую ветку. Rebase последовательно берет все коммиты из выбранной ветки и заново применяет их к новой ветке.
Такое поведение приводит к двум моментам:
Как видите, ветка feature содержит абсолютно новые коммиты. Как было сказано ранее, тот же самый набор изменений, но абсолютно новые объекты с точки зрения Git.
Это также означает, что старые коммиты не уничтожаются. Они становятся просто недоступными напрямую. Если вы помните, ветка — всего лишь ссылка на коммит. Таким образом, если ни ветка, ни тег не ссылаются на коммит, к нему невозможно получить доступ средствами Git, хотя на диске он продолжает присутствовать.
Теперь давайте обсудим «Золотое правило».
Золотое правило rebase
Золотое правило rebase звучит так — «НИКОГДА не выполняйте rebase расшаренной ветки!». Под расшаренной веткой понимается ветка, которая существует в сетевом репозитории и с которой могут работать другие люди, кроме вас.
Часто это правило применяют без должного понимания, поэтому разберем, почему оно появилось, тем более что это поможет лучше понять работу Git.
Давайте рассмотрим ситуацию, когда разработчик нарушает золотое правило, и что происходит в этом случае.
Предположим, Боб и Анна вместе работают над проектом. Ниже представлено, как выглядят репозитории Боба и Анны и исходный репозиторий на GitHub:
У всех пользователей репозитории синхронизируются с GitHub.
Теперь Боб, нарушая золотое правило, выполняет rebase, и в это же время Анна, работая в ветке feature, создает новый коммит:
Вы видите, что произойдет?
Боб пытается выполнить пуш коммита, ему приходит отказ примерно такого содержания:
Выполнение Git не было успешным, потому что Git не знает, как объединить feature ветку Боба с feature веткой GitHub.
Единственным решением, позволяющим Бобу выполнить push, станет использование ключа force, который говорит GitHub-репозиторию удалить у себя ветку feature и принять за эту ветку ту, которая пушится Бобом. После этого мы получим следующую ситуацию:
Теперь Анна хочет запушить свои изменения, и вот что будет:
Это нормально, Git сказал Анне, что у нее нет синхронизированной версии ветки feature, то есть ее версия ветки и версия ветки в GitHub — разные. Анна должна выполнить pull. Точно таким же образом, как Git сливает локальную ветку с веткой в репозитории, когда вы выполняете push, Git пытается слить ветку в репозитории с локальной веткой, когда вы выполняете pull.
Перед выполнением pull коммиты в локальной и GitHub-ветках выглядят так:
Когда вы выполняете pull, Git выполняет слияние для устранения разности репозиториев. И вот, к чему это приводит:
Коммит M — это коммит слияния (merge commit). Наконец, ветки feature Анны и GitHub полностью объединены. Анна вздохнула с облегчением, все конфликты устранены, она может выполнить push.
Боб выполняет pull, теперь все синхронизированы:
Глядя на получившийся беспорядок, вы должны были убедиться в важности золотого правила. Также учтите, что подобный беспорядок был создан всего одним разработчиком и на ветке, которая расшарена всего между двумя людьми. Представьте, что будет в команде из десяти человек.
Одним из многочисленных достоинств Git является то, что можно без проблем откатиться на любое время назад. Но чем больше допущено ошибок, подобных описанной, тем сложнее это сделать.
Также учтите, что появляются дубликаты коммитов в сетевом репозитории. В нашем случае — D и D’, содержащие одни и те же данные. По сути, количество дублированных коммитов может быть таким же большим, как и количество коммитов в вашей rebased ветке.
Если вы все еще не убеждены, давайте представим Эмму — третью разработчицу. Она работает в ветке feature перед тем, как Боб совершает свою ошибку, и в настоящий момент хочет выполнить push. Предположим, что к моменту ее push наш маленький предыдущий сценарий уже завершился. Вот что выйдет:
Этот текст мог заставить вас подумать, что rebase используют только для перемещения одной ветки на верхушку другой ветки. Это необязательно — вы можете выполнять rebase и на одной ветке.
Красота pull rebase
Как вы видели выше, проблем Анны можно было избежать, если бы она использовала pull rebase. Рассмотрим этот вопрос подробнее.
Допустим, Боб работает в ветке, отходящей от мастера, тогда его история может выглядеть вот так:
Боб решает, что настало время выполнить pull, что, как вы уже поняли, приведет к некоторым неясностям. Поскольку репозиторий Боба отходил от GitHub, Git спросит делать ли объединение, и результат будет таким:
Это решение подходит и работает нормально, однако, вам может быть полезно знать, что есть другие варианты решения проблемы. Одним из них является pull-rebase.
Когда вы делаете pull-rebase, Git пытается выяснить, какие коммиты есть только в вашей ветке, а какие — в сетевом репозитории. Затем Git объединяет коммиты из сетевого репозитория с самым свежим коммитом, присутствующим и в локальном, и в сетевом репозитории. После чего выполняет rebase ваших локальных коммитов в конец ветки.
Звучит сложно, поэтому проиллюстрируем:
Как вы помните, при rebase Git применяет коммиты один за одним, то есть в данном случаем применяет в конец ветки master коммит E, потом F. Получился rebase сам в себя. Выглядит неплохо, но возникает вопрос — зачем так делать?
По моему мнению, самая большая проблема с объединением веток в том, что загрязняется история коммитов. Поэтому pull-rebase — более элегантное решение. Я бы даже пошел дальше и сказал, что когда нужно скачать последние изменения в вашу ветку, вы всегда должны использовать pull-rebase. Но нужно помнить: поскольку rebase применяет все коммиты по очереди, то когда вы делаете rebase 20 коммитов, вам, возможно, придется решать один за другим 20 конфликтов.
Как правило, можно использовать следующий подход: одно большое изменение, сделанное давно — merge, два маленьких изменения, сделанных недавно — pull-rebase.
Сила rebase onto
Предположим, история ваших коммитов выглядит так:
Итак, вы хотите выполнить rebase ветки feature 2 в ветку master. Если вы выполните обычный rebase в ветку master, получите это:
Нелогично выглядит то, что коммит D существует в обоих ветках: в feature 1 и feature 2. Если вы переместите ветку feature 1 в конец ветки мастер, получится, что коммит D будет применен два раза.
Предположим, что вам нужно получить другой результат:
Для реализации подобного сценария как раз и предназначен git rebase onto.
Сначала прочтем документацию:
Нас интересует вот это:
С помощью этой опции указывается, в какой точке создавать новые коммиты.
Если эта опция не указана, то стартовой точкой станет upstream.
Для понимания приведу еще один рисунок:
То есть ветка master — это newbase, а ветка feature 1 — upstream.
git rebase
Перебазирование — это один из двух инструментов Git для внедрения изменений из одной ветки в другую. Такие же возможности предоставляет команда git merge (слияние). Операция слияния фиксирует изменения, всегда двигаясь вперед по истории проекта, в то время как перебазирование позволяет эффективно ее переписывать. Подробные сведения об операциях слияния и перебазирования см. в руководстве Сравнение слияния и перебазирования. Перебазирование может выполняться в двух режимах: ручном и интерактивном. Эти режимы будут подробно рассмотрены далее.
Что такое git rebase?
Перебазирование — это процесс перемещения последовательности коммитов к новому базовому коммиту или их объединение. Операцию перебазирования удобнее всего применить и отобразить в контексте создания функциональных веток. В общих чертах процесс можно представить следующим образом:
С точки зрения содержимого перебазирование — это замена одного коммита в основании ветки на другой, в результате чего создается впечатление, что ветка получила новое начало. В процессе этой операции Git создает новые коммиты и применяет их к указанному основанию, поэтому важно понимать, что в действительности ветка всегда состоит из совершенно новых коммитов.
Использование
Перебазирование выполняется прежде всего для обеспечения линейной истории проекта. Представим ситуацию: вы работаете над функциональной веткой feature, при этом код в главной ветке main уже изменился с начала вашей работы. Вам нужно отразить последние изменения ветки main в ветке feature, не засоряя при этом историю вашей ветки, чтобы создать впечатление, что ваша работа велась на основе последней версии ветки main. Впоследствии это позволит выполнить беспроблемное слияние ветки feature с веткой main. Почему не следует засорять историю? Ее аккуратность сыграет решающую роль при поиске в Git коммита, в котором появилась проблема. Можно привести более реалистичный пример.
Узнайте больше о командах git log и git bisect на соответствующих страницах.
Внедрить функцию в главную ветку main можно двумя способами: прямым слиянием или перебазированием с последующим слиянием. Первая операция выполняет трехстороннее слияние и создает коммит слияния, а вторая обеспечивает ускоренное слияние и абсолютно линейную историю. На приведенной ниже схеме показано, как перебазирование на ветку main обеспечивает ускоренное слияние.
Перебазирование часто используют для внедрения восходящих изменений в локальный репозиторий. При запросе таких изменений с помощью слияния у вас будет создаваться ненужный коммит слияния всякий раз, когда вы заходите просмотреть изменения в проекте. С другой стороны, перебазирование обеспечит такие условия, когда ваши изменения основываются на результатах работы коллег.
Не выполняйте перебазирование публичной истории
Ранее на странице Переписывание истории мы уже обозначили, что ни при каких обстоятельствах не следует выполнять перебазирование коммитов, отправленных в публичный репозиторий. Команда rebase заменит старые коммиты на новые, и другим разработчикам покажется, будто часть истории проекта просто исчезла.
Сравнение стандартного и интерактивного режимов команды git rebase
В стандартном режиме команда git rebase автоматически берет коммиты из текущей рабочей ветки и применяет их в конце переданной ветки.
Текущая ветка будет перенесена на основание в интерактивном режиме. Откроется редактор, где вы сможете вводить команды (описаны ниже) для каждого перебазируемого коммита. С помощью этих команд можно определить способ переноса отдельных коммитов на новое основание, а также переупорядочить список коммитов, чтобы изменить их будущий порядок. Когда команды будут указаны для каждого актуального коммита, Git начнет их применение. При перебазировании можно использовать следующие команды:
Дополнительные команды перебазирования
Как описано на странице Переписывание истории, с помощью перебазирования можно изменять предыдущие коммиты, группы коммитов, зафиксированные с помощью коммитов файлы и группы сообщений. Существуют и более сложные примеры использования, которые требуют добавления к команде git rebase различных опций.
Обзор
Интерактивное перебазирование позволяет полностью контролировать состояние истории проекта. Это дает разработчикам большую свободу, поскольку они могут зафиксировать засоренную историю, не отрываясь от написания кода, и очистить ее позже.
Большинство разработчиков используют интерактивное перебазирование, чтобы придать функциональной ветке аккуратность перед слиянием с основной базой кода. Они могут склеить незначительные коммиты, удалить устаревшие элементы и в целом навести порядок в ветке, прежде чем выполнить перенос в «официальную» историю проекта. Со стороны будет казаться, что для разработки функции потребовалось лишь несколько коммитов и тщательное планирование.
Оценить эффективность интерактивного перебазирования можно, взглянув на получившуюся историю ветки main. В глазах окружающих вы будете блестящим разработчиком, который внедрил новую функцию с первого раза и без лишних коммитов. Так интерактивное перебазирование помогает поддерживать порядок в истории проекта, а также сохраняет целесообразность каждого ее элемента.
Варианты конфигурации
Расширенные возможности перебазирования
Ветка featureB основана на featureA. При этом мы понимаем, что ветка featureB не зависит ни от одного изменения в ветке featureA, поэтому она могла бы отходить от главной ветки main.
Опасности перебазирования
Сама по себе команда git rebase не сопряжена с серьезной опасностью. Риск возникает, если вы используете интерактивное перебазирование для перезаписи истории и затем принудительно отправляете результаты в удаленную ветку, где работают другие пользователи. Этого делать не стоит, поскольку работа удаленных пользователей может быть перезаписана при осуществлении pull.
Восстановление при перебазировании восходящей ветки
Резюме
Готовы изучить Git?
Ознакомьтесь с этим интерактивным обучающим руководством.
- Что такое git push
- Что такое git reset