- Владеешь merge — освой и rebase
- Слияние
- Перебазирование
- Сравнение слияния и перебазирования
- С большой силой приходит большая ответственность
- Конфликты при интеграции изменений
- Опубликованные ветки
- Потеря данных (как преимущество)
- Главные правила перебазирования
- Усложненные случаи перебазирования
- Заключение
- Команда Git Merge
- Пример использования
- Конфликт слияния
- Продвинутые сценарии git merge
- Параметр —no-commit
- Параметр —squash
- Параметр —no-ff
- Git merge
- Порядок действий
- Подготовка к слиянию
- Проверка выбора принимающей ветки
- Получение последних коммитов из удаленного репозитория
- Выполнение слияния
- Ускоренное слияние
- Трехстороннее слияние
- Разрешение конфликтов
- Представление конфликтов
- Резюме
Владеешь merge — освой и rebase
May 1 · 6 min read
Независимо от используемых в проекте стратегий ветвления, приходится регулярно интегрировать изменения из одной ветки в другую. В 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 сможет включить все изменения в свою тематическую ветку.
После всех изменений в feature-2 Satoshi может продолжить разработку ветки и на заключительном этапе объединить ее с master .
Ниже представлен окончательный результат слияния. Как видно, история разработки сохраняет все свои этапы — добавляется только коммит слияния C7 .
Перебазирование
Имея в виду процесс слияния, рассмотрим тот же пример, но уже с точки зрения перебазирования. Так же как и в предыдущем случае, перед интеграцией изменений Satoshi должен убедиться, что его локальная и удаленная ветки master синхронизированы. Но затем вместо обычного слияния, сохраняющего историю в ее поэтапном виде, он может интегрировать все изменения с помощью операции перебазирования, таким образом перезаписывая историю .
Выполняя перебазирование feature-2 относительно master , Git вернется назад и повторно выполнит коммиты C5 и C6 один за другим прямо поверх C4 , создавая впечатление, что feature-2 изначально была ответвлением конечных изменений Ada.
После повторной интеграции всех изменений Satoshi может продолжить работу над своей тематической веткой.
Ниже представлен конечный результат перебазирования. Обратите внимание на повторное выполнение коммитов C5 и C6 поверх C4 , повлекшее за собой перезаписывание истории разработки и полное удаление старых коммитов!
Понимая главное отличие между слиянием и перебазированием, проанализируем конечные результаты этих операций.
Сравнение слияния и перебазирования
Как видим, после слияния две ветки объединились в новом коммите ( C7 ), придавая нелинейной истории ромбовидную форму и сохраняя ее в неизменном виде. В отличие от этой операции перебазирование привело не к созданию коммита слияния, а к возврату и повторному применению коммитов C5 и C6 поверх C4 , обеспечивая линейность истории. Более детальное изучение этих коммитов позволяет выявить изменения их хешей, подтверждающее факт перезаписи истории в результате перебазирования.
Примечание: каждый раз перебазирование ветки будет сопровождаться созданием новых коммитов, даже если их содержимое останется одним и тем же. Таким образом, прежние коммиты будут в итоге полностью удалены из истории.
С большой силой приходит большая ответственность
Итак, теперь мы знаем, что перебазирование перезаписывает историю, а слияние ее сохраняет. Но что это значит в более широком смысле? Какие возможности и потенциальные недочеты таят в себе две эти операции?
Конфликты при интеграции изменений
Допустим, попытка интегрировать изменения обернулась рядом неприятных конфликтов. Прибегнув к слиянию, вы бы решили их все за раз прямо в коммите C7 . А вот в случае с перебазированием вам бы пришлось решать одни и те же конфликты в каждом коммите ( C5 и C6 ) по мере их повторного применения.
Трудноразрешимые конфликты говорят о недостатке общения с коллегами ввиду очень длительной работы над одними и теми же файлами.
Опубликованные ветки
Еще одна потенциальная проблема связана с ситуацией, в которой ветка, подлежащая перебазированию, уже удаленно опубликована и положена в основу чьей-либо работы. Тогда такая перебазированная ветка может запутать и усложнить процесс для всех участников, поскольку Git укажет, что она одновременно и отстает, и опережает. В этом случае проблема решается путем извлечения изменений из удаленного репозитория с последующим внедрением в текущий, для чего служит флаг —rebase ( git pull —rebase ).
Кроме того, всякий раз при перебазировании уже опубликованной ветки, независимо от того, основывается ли на ней чья-либо работа, вам по прежнему необходимо принудительно опубликовать ее для внесения обновлений в удаленный сервер, тем самым полностью переписывая текущий указатель.
Потеря данных (как преимущество)
Поскольку слияние сохраняет историю, а перебазирование ее переписывает, то последняя операция может привести к потере данных. При повторном выполнении новых коммитов старые удаляются (после сборки мусора). Именно эта особенность повышает эффективность команды rebase , позволяющей очистить историю разработки, прежде чем сделать ее общедоступной. А с помощью интерактивного перебазирования можно удалять ненужные коммиты, сжимать изменения или просто обновлять сообщения коммитов.
Главные правила перебазирования
Во избежание связанных с перебазированием проблем рекомендуется придерживаться следующих правил:
- Не перебазируйте ветку, опубликованную удаленно…
- …если только вы не уверены, что кроме вас с ней никто не работает (и что ее принудительная публикация не вызовет проблем).
- Создайте резервную ветку, исходящую из конечной точки ветки, подлежащей перебазированию. Это позволит легко сравнить результат (по завершении) и при необходимости вернуться к состоянию, предшествующему перебазированию.
Усложненные случаи перебазирования
Существует множество усложненных операций перебазирования. К числу наиболее эффективных из них относится ранее упомянутый его интерактивный вариант, позволяющий определить способ повторного выполнения каждого коммита.
Данный режим применяется для разделения, сжатия, перегруппировки и даже полного удаления коммитов, и этим его возможности не ограничиваются.
Заключение
Многие разработчики предпочитают вместо rebase выполнять merge , поскольку они уверены, что так не потеряют результаты своей работы. В некотором смысле такой веский аргумент оправдывает отказ от неудобных в работе инструментов. А вот нежелание постичь все преимущества эффективных возможностей, будучи о них осведомленным, оправдать нельзя!
Такой подход равносилен высказыванию: “Хоть у меня и отличная машина, но лучше я ограничусь первой передачей, ведь скоростная езда смертельно опасна”. А почему бы не научиться переключать передачи и безопасно передвигаться на высоких скоростях?!
Мой собственный опыт показал, что знание принципов перебазирования углубляет понимание Git, а также развивает вас как разработчика, особенно если речь идет об управлении исходным кодом.
Как-то в самом начале моей карьеры разработчика я получил один из лучших советов от более опытного коллеги. Звучит он так: “Хватит стучать по клавишам в Source Tree, лучше научись использовать команды Git из терминала! Иначе ты не познаешь все возможности Git, и в перспективе не сможешь программировать автоматические конвейеры.”
С тех пор в качестве визуального средства для просмотра дерева истории я предпочитаю только GitK, а все команды набираю в терминале. И вам рекомендую!
Теперь, понимая разницу между слиянием и перебазированием, вы с большей уверенностью начнете применять обе операции.
Благодарю за внимание и желаю удачи в развитии навыков управления исходным кодом.
Источник
Команда Git Merge
В какой-то момент история репозитория ветвится. Например, в проекте требуется пофиксить баг — обычно это выполняют в отдельной ветке, а потом сливают в основную ветку master. Команда git merge как раз и служит для слияния веток.
Пример использования
Допустим, есть основная ветка master. И есть ветка hotfix, в которой мы фиксим баг. Мы его пофиксили и хотим слить изменения в ветку master. Для этого надо перейти на ветку master — ту ветку, в которую сливают изменения:
И залить изменения из ветки hotfix в текущую ветку master:
Это типичный пример использования команды.
Но не всегда слияние происходит гладко. Пока мы делали hotfix, кто-то мог отредактировать тот же файл в том же месте, что и мы. Тогда слияние не удается, возникает конфликт.
Конфликт слияния
Допустим, файл index.html отредактировал кто-то еще в том же месте. Тогда при попытке выполнить команду merge мы получим сообщение:
Что же делать? Если открыть index.html, мы увидим, что Git его пометил примерно так:
Надо удалить разметку в файле и оставить только один вариант спорной строки. После этого нужно добавить конфликтный файл в индекс:
И сделать снимок:
На этом все, конфликт будет разрешен.
Если же разрешать конфликты в данный момент некогда, можно отменить текущее слияние merge (чтобы выполнить его в следующий раз):
Продвинутые сценарии git merge
Параметр —no-commit
Бывают ситуации, когда история снимков (ряд команд commit) в ветке была сделана не очень осмысленно, то есть вы делали снимки состояний, которые не стоит сохранять в истории на всеобщем обозрении. Здесь то и пригодится параметр —no-commit. Он используется нечасто — только в том случае, если хочется уничтожить историю снимков ветки, так чтоб никто их никто никогда не увидел и к ним нельзя было вернуться.
Но сначала вернемся к вышеприведенным командам и рассмотрим, как работает merge без параметра:
Первая команда (checkout) переведет нас на ветку master. Это значит, что наша папка на диске физически заполнятся версиями файлов из ветки master
А команда merge:
- перенесет в master все изменения ветки hotfix. То есть содержимое текущей папки изменится с учетом залитых изменений
- и сделает commit (если нет нет конфликтов). То есть сделает снимок полученного состояния и занесет его в историю. При этом история коммитов ветки hotfix будет сохранена и ее можно будет посмотреть.
Теперь рассмотрим, как работает команда merge с параметром.
Первый шаг такой же — переходим на ветку master, в которую надо залить изменения:
Но в этот раз допустим, что снимки в ветке hotfix были ненужными и мы хотим, чтоб их не было в истории. В этом случае надо добавить параметр —no-commit:
Эта команда обновит текущие папки — перенесет в них все изменения ветки hotfix, но финального снимка сделано не будет. Мы сможем еще кое-что поменять в файлах и потом выполнить команду commit самостоятельно. При этом в историю попадет только финальный снимок. Будет видно, что ветка hotfix слита с веткой master, и все. А снимки ветки hotfix видны не будут.
Параметр —squash
Эта команда такая же, как предыдущая, разница будет только при просмотре истории. Перейдем на ветку master, в которую мы хотим залить изменения:
После выполнения команды:
изменения появятся в текущих папках ветки master, аналогично тому, как это было после выполнения предыдущей команды с аргументом —no-commit. При этом финального снимка (commit) так же не произойдет.
Но затем после самостоятельного выполнения команды commit мы увидим в истории этот снимок как самый обычный снимок в ветке master. То, что ветка hotfix сливалась в ветку master, отображено в истории не будет.
Параметр —no-ff
Параметр —no-ff запрещает перемотку.
ff означает fast forward, то есть перемотка.
Во-первых объясню, что такое перемотка. Она случается при слиянии, если в той ветке, от которой мы ответвились, не было снимков после нашего ответвления. Называется это перемоткой, потому что технически никакого нового снимка в этом случае делать не надо, выполняя слияние двух предков. Наш снимок считается последним, и на него просто перематывается текущий указатель. Смотрите пример.
На картинке мы ответвились от С и сделали снимки X и Y. После снимка C в ветке master никто ничего не делал, и при слиянии указатель просто переместится на снимок Y:
Еще раз обратите внимание, что в ветке master после ответвления от С снимков не появилось, именно поэтому перемотка и возможна.
С помощью аргумента —no-ff можно запретить перемотку, а именно: сделать при слиянии новый отдельный снимок, хоть в нем и нет технической необходимости. Сделаем это:
На картинке это выглядит так:
Будет создан дополнительный снимок M и указатель переместится на него (хотя мог бы просто переместиться на Y, если б выполняли слияние без параметра —no-ff).
Когда именно стоит использовать параметр —no-ff сказать сложно — отчасти это вопрос вкуса. Некоторые любят делать отдельный снимок при любом важном слиянии.
Источник
Git merge
Слияние используется в Git, чтобы собрать воедино разветвленную историю. Команда git merge выполняет слияние отдельных направлений разработки, созданных с помощью команды git branch , в единую ветку.
Обратите внимание: все приведенные ниже команды выполняют слияние в текущую ветку, в то время как целевая ветка остается без изменений. Поэтому команда git merge часто используется в сочетании с командами git checkout (для выбора текущей ветки) и git branch -d (для удаления устаревшей целевой ветки).
Порядок действий
Команда git merge объединяет несколько последовательностей коммитов в общую историю. Чаще всего команду git merge используют для объединения двух веток. Этот вариант слияния рассматривается в следующих примерах. В таких случаях команда git merge принимает два указателя на коммиты (обычно последние в ветке) и находит общий для них родительский коммит. Затем Git создает коммит слияния, в котором объединяются изменения из обеих последовательностей, выбранных к слиянию.
Представим, что у нас есть новая функциональная ветка, которая отходит от главной ветки main . И мы хотим объединить эту функциональную ветку с main .
При вызове этой команды произойдет слияние указанной функциональной ветки с текущей — в данном случае с веткой main . Git автоматически определяет алгоритм слияния (подробнее см. ниже).
Коммиты слияния отличаются от других наличием двух родительских элементов. Создавая коммит слияния, Git попытается автоматически объединить две истории. Однако если Git обнаружит, что вы изменили одну и ту же часть данных в обеих историях, сделать это автоматически не удастся. Это называется конфликтом управления версиями, и для его разрешения Git потребуются действия пользователя.
Подготовка к слиянию
Перед слиянием следует предпринять несколько подготовительных действий, чтобы операция прошла без проблем.
Проверка выбора принимающей ветки
Выполните команду git status . Это позволит убедиться, что HEAD указывает на ветку, принимающую результаты слияния. При необходимости выполните команду git checkout , чтобы переключиться на принимающую ветку. Для примера выполним команду git checkout main .
Получение последних коммитов из удаленного репозитория
Убедитесь, что в принимающей ветке и ветке для слияния содержатся последние изменения из удаленного репозитория. Выполните команду git fetch , чтобы получить из него последние коммиты. Затем убедитесь, что в ветке main также содержатся последние изменения. Для этого выполните команду git pull .
Выполнение слияния
После указанных выше действий по подготовке можете приступать к слиянию. Для этого выполните команду git merge , где — название ветки, которая будет объединена с принимающей.
Ускоренное слияние
Ускоренное слияние происходит, когда последний коммит текущей ветки является прямым продолжением целевой ветки. В этом случае для объединения истории Git не выполняет полноценное слияние, а просто переносит указатель текущей ветки в конец целевой ветки. Объединение историй проходит успешно, поскольку все коммиты целевой ветки теперь доступны из текущей ветки. Так, ускоренное слияние одной из функциональных веток с веткой main будет выглядеть следующим образом:
Однако выполнить ускоренное слияние не получится, если ветки после разделения развивались независимо друг от друга. Если до целевой ветки нет прямого пути, Git будет вынужден объединить их методом трехстороннего слияния. Такое слияние выполняется с помощью специального коммита, который служит для объединения двух историй. Метод называется трехсторонним, поскольку Git использует три коммита для создания коммита слияния (последние коммиты двух веток и общий родительский элемент).
Хотя можно применять любую из этих стратегий слияния, многие разработчики предпочитают использовать метод ускоренного слияния (с помощью перебазирования) для небольших функций или исправлений багов, а трехстороннее слияние — для интеграции функций с более продолжительным временем разработки. В последнем случае местом соединения двух веток служит коммит слияния.
В первом примере демонстрируется ускоренное слияние. С помощью кода создается новая ветка, после чего в нее добавляется два коммита. Затем она включается в основную ветку посредством ускоренного слияния.
Это распространенная модель работы с ветками, отведенными под решение краткосрочных задач. Чаще они нужны, чтобы создать изолированную среду для разработчика, нежели для продолжительной работы над объемными функциями.
Обратите внимание, что теперь Git сможет без проблем выполнить команду git branch -d , поскольку ветка new-feature теперь доступна из главной ветки.
Если при ускоренном слиянии вам понадобится коммит слияния для учета изменений, вы можете выполнить команду git merge с параметром —no-ff .
Эта команда выполнит объединение указанной ветки с текущей с обязательным созданием коммита слияния (даже если слияние будет ускоренным). Это полезно для учета всех слияний в репозитории.
Трехстороннее слияние
Следующий пример похож на предыдущий, но в нем слияние должно быть трехсторонним, поскольку работа над веткой main ведется одновременно с работой над функцией. Так часто происходит в случае объемных функций или когда над одним проектом одновременно работают несколько разработчиков.
Обратите внимание, что Git не может выполнить ускоренное слияние, потому что невозможно перенести указатель main на ветку new-feature без использования предыдущих коммитов.
В большинстве случаев ветка new-feature отводится под более объемные функции с продолжительным временем разработки, за которое в ветке main появляются новые коммиты. Если бы реальный размер вашей функциональной ветки был так же мал, как в приведенном выше примере, было бы проще перебазировать ее на ветку main и выполнить ускоренное слияние. В этом случае не потребовалось бы засорять историю проектов лишними коммитами слияния.
Разрешение конфликтов
При попытке объединить ветки, в которых изменена одна и та же часть того же файла, Git не сможет сделать выбор между версиями. В таком случае операция останавливается прямо перед созданием коммита слияния, чтобы пользователь вручную разрешил конфликты.
Преимущество слияния в Git заключается в том, что разрешение конфликтов при слиянии проходит по привычной схеме «редактирование — индексирование — коммит». При обнаружении конфликта выполните команду git status , чтобы увидеть, какие файлы необходимо исправить. Так, если в обеих ветках изменена одна и та же часть файла hello.py , вы увидите следующее:
Представление конфликтов
Когда Git обнаруживает конфликт в ходе слияния, к затронутым файлам добавляются визуальные индикаторы по обе стороны проблемного содержимого: >>>>>>. Чтобы обнаружить конфликты, попробуйте поискать в проекте эти индикаторы.
Обычно содержимое перед отметкой ======= относится к принимающей ветке, а все, что указано после нее, — к ветке, для которой выполняется слияние.
После обнаружения конфликтующих участков кода вы можете исправить их по своему усмотрению. Когда вы будете готовы завершить слияние, выполните команду git add для конфликтующего файла или файлов — так вы сообщите Git, что конфликт разрешен. Затем выполните обычную команду git commit , чтобы создать коммит слияния. Поскольку процесс ничем не отличается от фиксирования обычного снимка состояния, рядовому разработчику не составит труда разрешить конфликты при слиянии.
Обратите внимание, что конфликты возможны только в процессе трехстороннего слияния и не могут возникать при ускоренном слиянии.
Резюме
В этом документе содержатся общие сведения о команде git merge . Слияние — необходимый инструмент для работы в Git. Мы познакомились с принципами его работы, а также обсудили различия между ускоренным и полноценным трехсторонним слиянием. Ниже перечислены основные моменты.
- При слиянии в Git последовательности коммитов объединяются в общую историю.
- В Git существуют два основных способа объединения изменений: ускоренное и трехстороннее слияние.
- Если в обеих последовательностях коммитов нет конфликтующих изменений, Git объединит их автоматически.
В документе также упоминаются другие команды Git: git branch , git pull и git fetch . Подробные сведения о них см. на соответствующих страницах.
Готовы попробовать ветвление?
Ознакомьтесь с этим интерактивным обучающим руководством.
Источник