Независимо от используемых в проекте стратегий ветвления, приходится регулярно интегрировать изменения из одной ветки в другую. В git это можно сделать двумя основными способами: merge (слияние) и rebase (перебазирование).

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

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

Согласно официальному руководству Git rebase “повторно применяет коммиты поверх другой базовой ветки”, тогда как merge “объединяет две или более историй разработки”. Иначе говоря,основное отличие между ними в том, что слияние сохраняет историю в первозданном виде, а перебазирование ее перезаписывает. Прежде чем переходить к более подробному осмыслению принципов их внутренней работы, обратимся к примеру: 

Обзор истории коммитов с позиции Satoshi, в которой удаленный репозиторий “origin” для удобства обозначен как “о”. Обратите внимание, что в настоящий момент локальная ветка “master “ (C1) отстает от своего удаленного аналога “o/master” (C4).

Как видно из примера, разработчики 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 сможет включить все изменения в свою тематическую ветку.

Процесс слияния: 

После всех изменений в feature-2 Satoshi может продолжить разработку ветки и на заключительном этапе объединить ее с master

Ниже представлен окончательный результат слияния. Как видно, история разработки сохраняет все свои этапы  —  добавляется только коммит слияния C7.

Перебазирование 

Имея в виду процесс слияния, рассмотрим тот же пример, но уже с точки зрения перебазирования. Так же как и в предыдущем случае, перед интеграцией изменений Satoshi должен убедиться, что его локальная и удаленная ветки master синхронизированы. Но затем вместо обычного слияния, сохраняющего историю в ее поэтапном виде, он может интегрировать все изменения с помощью операции перебазирования, таким образом перезаписывая историю.

Выполняя перебазирование feature-2 относительно master, Git вернется назад и повторно выполнит коммиты C5 и C6 один за другим прямо поверх C4, создавая впечатление, что feature-2 изначально была ответвлением конечных изменений Ada. 

Процесс перебазирования: 

После повторной интеграции всех изменений Satoshi может продолжить работу над своей тематической веткой. 

Ниже представлен конечный результат перебазирования. Обратите внимание на повторное выполнение коммитов C5 и C6 поверх C4, повлекшее за собой перезаписывание истории разработки и полное удаление старых коммитов! 

Как видно, хеши C5 и C6 изменились. Обусловлено это тем, что фактически они являются вновь созданными коммитами (хотя их содержание все еще может быть идентичным)

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

Сравнение слияния и перебазирования 

Начальное состояние и параллельное сравнение конечных состояний слияния и перебазирования

Как видим, после слияния две ветки объединились в новом коммите (C7), придавая нелинейной истории ромбовидную форму и сохраняя ее в неизменном виде. В отличие от этой операции перебазирование привело не к созданию коммита слияния, а к возврату и повторному применению коммитов C5 и C6 поверх C4, обеспечивая линейность истории. Более детальное изучение этих коммитов позволяет выявить изменения их хешей, подтверждающее факт перезаписи истории в результате перебазирования. 

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

С большой силой приходит большая ответственность 

Итак, теперь мы знаем, что перебазирование перезаписывает историю, а слияние ее сохраняет. Но что это значит в более широком смысле? Какие возможности и потенциальные недочеты таят в себе две эти операции? 

Конфликты при интеграции изменений 

Допустим, попытка интегрировать изменения обернулась рядом неприятных конфликтов. Прибегнув к слиянию, вы бы решили их все за раз прямо в коммите C7. А вот в случае с перебазированием вам бы пришлось решать одни и те же конфликты в каждом коммите (C5 и C6) по мере их повторного применения. 

Трудноразрешимые конфликты говорят о недостатке общения с коллегами ввиду очень длительной работы над одними и теми же файлами. 

Опубликованные ветки 

Еще одна потенциальная проблема связана с ситуацией, в которой ветка, подлежащая перебазированию, уже удаленно опубликована и положена в основу чьей-либо работы. Тогда такая перебазированная ветка может запутать и усложнить процесс для всех участников, поскольку Git укажет, что она одновременно и отстает, и опережает. В этом случае проблема решается путем извлечения изменений из удаленного репозитория с последующим внедрением в текущий, для чего служит флаг --rebase (git pull --rebase).

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

Потеря данных (как преимущество) 

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

Главные правила перебазирования 

Во избежание связанных с перебазированием проблем рекомендуется придерживаться следующих правил: 

  • Не перебазируйте ветку, опубликованную удаленно…
  • …если только вы не уверены, что кроме вас с ней никто не работает (и что ее принудительная публикация не вызовет проблем). 
  • Создайте резервную ветку, исходящую из конечной точки ветки, подлежащей перебазированию. Это позволит легко сравнить результат (по завершении) и при необходимости вернуться к состоянию, предшествующему перебазированию. 

Усложненные случаи перебазирования 

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

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

Заключение

Многие разработчики предпочитают вместо rebase выполнять merge, поскольку они уверены, что так не потеряют результаты своей работы. В некотором смысле такой веский аргумент оправдывает отказ от неудобных в работе инструментов. А вот нежелание постичь все преимущества эффективных возможностей, будучи о них осведомленным, оправдать нельзя! 

Такой подход равносилен высказыванию: “Хоть у меня и отличная машина, но лучше я ограничусь первой передачей, ведь скоростная езда смертельно опасна”. А почему бы не научиться переключать передачи и безопасно передвигаться на высоких скоростях?!

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

Как-то в самом начале моей карьеры разработчика я получил один из лучших советов от более опытного коллеги. Звучит он так: “Хватит стучать по клавишам в Source Tree, лучше научись использовать команды Git из терминала! Иначе ты не познаешь все возможности Git, и в перспективе не сможешь программировать автоматические конвейеры.”

С тех пор в качестве визуального средства для просмотра дерева истории я предпочитаю только GitK, а все команды набираю в терминале. И вам рекомендую! 

Теперь, понимая разницу между слиянием и перебазированием, вы с большей уверенностью начнете применять обе операции. 

Благодарю за внимание и желаю удачи в развитии навыков управления исходным кодом. 

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Alexis Määttä Vinkler: Differences Between Git Merge and Rebase — and Why You Should Care

Предыдущая статьяПочему Dockerfile больше не нужен для создания контейнера в Go
Следующая статьяВыбор лучшего фреймворка для создания мобильного приложения