Изучаем git. merge vs rebase для начинающих
Про git merge и git rebase написаны тысячи статей. Зачем же нужна еще одна?
Разбираясь в свое время с git rebase, я не нашел ни одной статьи, описывающей этот инструмент с точки зрения начинающих пользоваться гитом. Я видел множество схем ребейза, но так и не мог понять, для чего же все-таки нужен rebase, чем он так отличается от merge. Когда стоит использовать rebase и что будет плохого (и будет ли), если его не применять.
Я не люблю абстрактные схемы. Поэтому покажу простой пример из повседневной жизни двух коллег-программистов. Наглядно, с картинками и подробностями, мы увидим, как работает merge и rebase. Для давно работающих с гитом в статье не будет ничего нового. Тем же, кто только начинает знакомиться с rebase, надеюсь, статья будет полезной.
В проекте есть основная ветка master, в нее периодически коммитят и Вася, и Петя. Для работы над стилями подвала Вася создает новую ветку new-footer, делая свои коммиты в нее и время от времени подтягивая в эту ветку мастер. После завершения работы над new-footer Вася сольет ее в основную ветку.
В плане слияния веток git merge отличается от git rebase только тем, как выглядит история коммитов, насколько она красива и понятна. И сейчас мы посмотрим поэтапно, как происходит работа в случае merge и rebase и как выглядит история коммитов и веток.
Начинает проект Вася, он создает первый коммит и пушит его. Дальше начинается уже совместная работа с Петей. Пока наша ветка выглядит так (скриншоты взяты из phpStorm, инструмента git log)
Дальше Вася добавляет подвал прямым коммитом в мастер.
Вася хочет поделиться подвалом со своим коллегой Петей и пытается запушить мастер. Но Петя уже успел запушить со своей стороны в мастер коммит, касающийся меню. Как известно, git не даст запушить ветку до тех пор, пока Вася не подтянет свежий коммит от Пети. Что может быть проще? Вася делает
git pull origin master
и видит в истории это
Отлично! То, что нужно. Коммит от Пети следует сразу за initial, коммит от Васи самый свежий. Можно пушить ветку на сервер.
git push origin master
Но оставим пока так и работаем дальше. Вася делает еще один коммит в новом подвале
Напомню, в ветке new-footer всего лишь 2 коммита от Васи. Здесь же мы видим лишний merge и лишнюю связь, которая ни о чем не говорит. Легко представить тот бардак, который будет твориться при нескольких мерджах в долгоживущих ветках.
git rebase дает нам возможность сохранить историю коммитов чистой и понятной. Давайте все исправим и вернемся к тому моменту, когда мы собрались мерджить мастер в нашу ветку
Теперь делаем так
git rebase master и видим следующую картину
Намного лучше! Мы просто передвинули указатель HEAD так, как будто new-footer ответвилась от мастера только что. Вася делает второй коммит
Отличная картинка. Мы видим 2 коммита Васи и видим, что они сделаны в отдельной ветке. А если Вася подписывал бы начало своих коммитов названием ветки, а не своим именем, то история выглядела бы еще лучше 🙂
Я лично предпочитаю первый вариант, но здесь уже зависит от того, какие договоренности приняты в Вашем проекте.
Я до сих пор вспоминаю добрым словом своего тимлида с одной из прошлых работ, который настоятельно рекомендовал по максимуму работать с гитом в терминале.
Для тех, кто подумал, что я умный, советую статью Как я перестал бояться и полюбил git
Apply changes from one Git branch to another
In Git, there are several ways to integrate changes from one branch into another:
Merge branches
Suppose you have created a feature branch to work on a specific task, and want to integrate the results of your work into the main code base after you have completed and tested your feature:
Merging your branch into master is the most common way to do this.
It is very common that while you are working in your feature branch, your teammates continue to commit their work to master:
Git creates a new commit (M) that is referred to as a merge commit that results from combining the changes from your feature branch and master from the point where the two branches diverged.
Merge branches
Do one of the following:
If you do not need to specify options for the merge, select the branch that you want to merge into the current branch and choose Merge into Current from the submenu.
If you need to specify merge options, from the main menu choose VCS Git | Merge Changes to open the Merge dialog:
Select the branch that you want to merge into the current branch, click Modify options and choose from the following:
—no-ff : a merge commit will be created in all cases, even if the merge could be resolved as a fast-forward.
—ff-only : the merge will be resolved only if it is possible to fast-forward.
—squash : a single commit with all pulled changes will be created on top of the current branch.
-m : you will be able to edit the message for the merge commit.
—no-commit : a merge will be performed, but a merge commit will not be created so that you can inspect the result of the merge before committing.
If your working tree is clean (which means you have no uncommitted changes), and no conflicts occur between your feature branch and the target branch, Git will merge the two branches, and the merge commit will appear in the Log tab of the Git tool window Alt+9 :
If conflicts occur between your branch and the target branch, you will be prompted to resolve them (see Resolve conflicts). If there are unresolved conflicts left after a merge, the Merge Conflicts node will appear in the corresponding changelist in the Local Changes view with a link to resolve them.
You can cancel an unfinished merge operation by selecting the Abort action from the Git Branches popup.
Rebase branches (git-rebase)
When you rebase a branch onto another branch, you apply the commits from the first branch on top of the HEAD commit in the second branch.
Suppose you have created a feature branch to work on a specific task and make several commits to that branch:
While you develop in your branch, your teammates continue to commit their work to master:
When you perform the rebase operation you integrate changes you have done in your feature branch to the master branch by applying your commits on top of the current HEAD commit in master :
Rebase a branch on top of another branch
From the main menu select Git | Rebase :
From the list, select the target branch onto which you want to rebase the current branch:
IntelliJ IDEA will check out this branch before starting the rebase operation.
You can cancel an unfinished rebase operation or resume an interrupted rebase by selecting the Abort or Continue actions respectively from the top of the Git Branches popup.
If you do not need to specify options for the rebase, you can initiate a rebase without invoking the rebase dialog. In the Branches popup or in the Branches pane of the Git tool window select a branch and choose one of the following actions:
Pull into Current Using Rebase (for remote branches) to fetch changes from the selected branch and rebase the current branch on top of these changes.
Checkout and Rebase onto Current (for both remote and local branches) to check out the selected branch and rebase it on top of the branch that is currently checked out. If the remote branch doesn’t exist locally, IntelliJ IDEA will silently create a tracked local branch, checkout into it and rebase.
Rebase Current onto Selected (for both remote and local branches) to rebase the branch that is currently checked out on top of the selected.
For details on how to skip or squash commit during a rebase, refer to Edit project history by performing interactive rebase.
Watch this video to see how a merge or a rebase operation are reflected in the Log tab of the Git tool window Alt+9 :
Cherry-pick separate commits
Sometimes you only need to apply a single commit to a different branch instead of rebasing or merging an entire branch. This may be useful, for example, if you are working in a feature branch and want to integrate a hotfix from master that was committed after the two branches have diverged. Or you may want to backport a fix to a previous release branch. You can do so by using the Cherry-pick action.
The status of a cherry pick operation is displayed in the status bar. You can always abort an ongoing cherry-pick by selecting Abort Cherry-Pick in the Git Branches popup.
Apply a commit to another branch
In the Branches popup select the target branch that you want to integrate the changes to and choose Checkout from the popup menu to switch to that branch.
Open the Git tool window Alt+9 and switch to the Log tab.
Locate the commit containing the changes you want to cherry pick.
Select the required commit. Use the information in the Commit Details area to make sure these are the changes you want to transfer to another branch.
If the cherry-pick failed with conflicts, the selected changes will appear in Changes area that you can see in the Local Changes view. You can review these changes and commit them later if necessary.
Push the changes to the target branch.
Apply separate changes
Imagine you’ve made some changes to a file that you want to apply to a different branch, but these changes were committed together with other modified files. IntelliJ IDEA lets you apply separate changes instead of cherry-picking an entire commit.
In the Branches popup select the target branch that you want to integrate the changes to and choose Checkout from the popup menu to switch to that branch.
Open the Git tool window Alt+9 and switch to the Log tab.
Locate the commit that contains the changes that you want to apply.
In the Commit details pane on the right, select the files containing the changes you want to apply to the target branch and select Cherry-Pick Selected Changes from the context menu.
Commit the changes and then push them to the target branch.
Apply separate files
In addition to applying separate changes to a single file, you can copy an entire file’s contents to a different branch. This may be useful, for example, if the file you want to apply doesn’t exist in the target branch, or if changes to it were made within several commits.
Switch to the branch to which the changes will be applied.
The Changes tool window that opens shows a list of all files that are different in the selected branch compared with the branch that is currently checked out:
Files that exist in the selected branch and are missing in the current branch are marked with grey.
Files that exist in the current branch but are missing in the selected branch are marked with green.
Files that contain differences between the selected and the current branch are marked with blue.
You can click the Swap Branches link to change which branch is considered as a base against which you are comparing the other branch.
Commit and push the changes. IntelliJ IDEA will copy the entire contents of the file to the current branch.
Владеешь merge — освой и rebase
Независимо от используемых в проекте стратегий ветвления, приходится регулярно интегрировать изменения из одной ветки в другую. В git это можно сделать двумя основными способами: merge (слияние) и rebase (перебазирование).
В данной статье мы рассмотрим обе операции и отличия между ними, а также обозначим моменты, требующего особого внимания.
Сначала с помощью анимационных средств разберем каждую операцию по отдельности, а на заключительном этапе проведем параллельное сравнение. Если вам уже знакомы принципы работы этих действий, то сразу переходите к сравнительным характеристикам.
Согласно официальному р уководству Git rebase “повторно применяет коммиты поверх другой базовой ветки”, тогда как merge “объединяет две или более историй разработки”. Иначе говоря, основное отличие между ними в том, что слияние сохраняет историю в первозданном виде, а перебазирование ее перезаписывает. Прежде чем переходить к более подробному осмыслению принципов их внутренней работы, обратимся к примеру:
Слияние
Начнем с самого распространенного рабочего процесса интеграции изменений: слияния. Перед объединением изменений Ada с feature-2 Satoshi должен сначала обновить свой локальный указатель на master ветку, поскольку в данный момент она устарела. Как только master и o/master синхронизируются, Satoshi сможет включить все изменения в свою тематическую ветку.
Перебазирование
После повторной интеграции всех изменений Satoshi может продолжить работу над своей тематической веткой.
Понимая главное отличие между слиянием и перебазированием, проанализируем конечные результаты этих операций.
Сравнение слияния и перебазирования
Примечание: каждый раз перебазирование ветки будет сопровождаться созданием новых коммитов, даже если их содержимое останется одним и тем же. Таким образом, прежние коммиты будут в итоге полностью удалены из истории.
С большой силой приходит большая ответственность
Итак, теперь мы знаем, что перебазирование перезаписывает историю, а слияние ее сохраняет. Но что это значит в более широком смысле? Какие возможности и потенциальные недочеты таят в себе две эти операции?
Конфликты при интеграции изменений
Трудноразрешимые конфликты говорят о недостатке общения с коллегами ввиду очень длительной работы над одними и теми же файлами.
Опубликованные ветки
Кроме того, всякий раз при перебазировании уже опубликованной ветки, независимо от того, основывается ли на ней чья-либо работа, вам по прежнему необходимо принудительно опубликовать ее для внесения обновлений в удаленный сервер, тем самым полностью переписывая текущий указатель.
Потеря данных (как преимущество)
Главные правила перебазирования
Во избежание связанных с перебазированием проблем рекомендуется придерживаться следующих правил:
Усложненные случаи перебазирования
Существует множество усложненных операций перебазирования. К числу наиболее эффективных из них относится ранее упомянутый его интерактивный вариант, позволяющий определить способ повторного выполнения каждого коммита.
Данный режим применяется для разделения, сжатия, перегруппировки и даже полного удаления коммитов, и этим его возможности не ограничиваются.
Заключение
Такой подход равносилен высказыванию: “Хоть у меня и отличная машина, но лучше я ограничусь первой передачей, ведь скоростная езда смертельно опасна”. А почему бы не научиться переключать передачи и безопасно передвигаться на высоких скоростях?!
Мой собственный опыт показал, что знание принципов перебазирования углубляет понимание Git, а также развивает вас как разработчика, особенно если речь идет об управлении исходным кодом.
Как-то в самом начале моей карьеры разработчика я получил один из лучших советов от более опытного коллеги. Звучит он так: “Хватит стучать по клавишам в Source Tree, лучше научись использовать команды Git из терминала! Иначе ты не познаешь все возможности Git, и в перспективе не сможешь программировать автоматические конвейеры.”
С тех пор в качестве визуального средства для просмотра дерева истории я предпочитаю только GitK, а все команды набираю в терминале. И вам рекомендую!
Теперь, понимая разницу между слиянием и перебазированием, вы с большей уверенностью начнете применять обе операции.
Благодарю за внимание и желаю удачи в развитии навыков управления исходным кодом.
Введение в 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-merge
Вступительное слово
Основными командами пользовательского уровня для ветвления в Git являются git-branch, git-checkout, git-rebase, git-log и, конечно же, git-merge. Для себя я считаю git-merge зоной наибольшей ответственности, точкой огромной магической энергии и больших возможностей. Но это достаточно сложная команда, и даже достаточно длительный опыт работы с Git порой бывает недостаточным для освоение всех ее тонкостей и умения применить ее наиболее эффективно в какой-либо нестандартной ситуации.
Попробуем же разобраться в тонкостях git-merge и приручить эту великую магию.
Здесь я хочу рассмотреть только случай благополучного слияния, под которым я понимаю слияние без конфликтов. Обработка и разрешение конфликтов — отдельная интересная тема, достойная отдельной статьи. Я очень рекомендую так же ознакомиться со статьей Внутреннее устройство Git: хранение данных и merge, содержащей много важной информации, на которую я опираюсь.
Анатомия команды
Если верить мануалу, команда имеет следующий синтаксис:
По большому счету, в Git есть два вида слияния: перемотка (fast-forward merge) и «истинное» слияние (true merge). Рассмотрим несколько примеров обоих случаев.
«Истинное» слияние (true merge)
Мы отклоняемся от ветки master, чтобы внести несколько багов улучшений. История коммитов у нас получилась следующая:
Выполним на ветке master git merge feature :
Посмотрим историю коммитов в тестовом репозитории, который я создал специально для этого случая:
А теперь посмотрим информацию о коммите (M):
Мы видим двух родителей, объект-дерево, соответствующее данному состоянию файлов репозитория, а так же информацию о том, кто виновен в коммите.
Посмотрим, куда ссылается указатель master:
Действительно, он теперь передвинут на коммит (M).
Squash и no-commit
В случае применения такого слияния коммиты ветки feature не будут включены в нашу историю, но коммит Sq будет содержать все их изменения:
Позже, в случае выполнения «классического» git merge feature можно исправить это. Тогда история примет следующий вид:
Перемотка (fast-forward merge)
Рассмотрим другой случай истории коммитов:
Все как и в прошлый раз, но теперь в ветке master нет коммитов после ответвления. В этом случае происходит слияние fast-forward (перемотка). В этом случае отсутствует коммит слияния, указатель (ветка) master просто устанавливается на коммит Y, туда же указывает и ветка feature:
Стратегии слияния
Стратегия resolve
Здесь C — общий коммит двух веток, дерево файлов, соответствующее этому коммиту, принимается за общего предка. Анализируются изменения, произведенные в ветках master и feature со времен этого коммита, после чего для коммита (M) создается новая версия дерева файлов в соответствии с пунктами 4 и 5 нашего условного алгоритма.
Стратегия recursive
Для иллюстрации этой стратегии позаимствуем пример из статьи Merge recursive strategy из блога «The plasticscm blog»:
Итак, у нас есть две ветки: main и task001. И так вышло, что наши разработчики знают толк в извращениях: они слили коммит 15 из ветки main с коммитом 12 из ветки task001, а так же коммит 16 с коммитом 11. Когда нам понадобилось слить ветки, оказалось, что поиск реального предка — дело неблагодарное, но стратегия recursive с ее конструированием «виртуального» предка нам поможет. В результате мы получим следующую картину:
]
Выполнение рекурсивного слияния с этой опцией будет более продвинутым вариантом стратегии subtree, где алгоритм основывается на предположении, как деревья должны совместиться при слиянии. Вместо этого в этом случае указывается конкретный вариант.
Стратегия octopus
Эта стратегия используется для слияние более чем двух веток. Получившийся в итоге коммит будет иметь, соответственно, больше двух родителей.
Данная стратегия предполагает большую осторожность относительно потенциальных конфликтов. В связи с этим порой можно получить отказ в слиянии при применении стратегии octopus.
Стратегия ours
Не следует путать стратегию ours и опцию ours стратегии recursive.
Стратегия ours — более радикальное средство.
Стратегия subtree
Для иллюстрации данной стратегии возьмем пример из главы Слияние поддеревьев книги «Pro Git».
Добавим в наш проект новые удаленный репозиторий, rack:
Ясно, что ветки master и rack_branch имеют абсолютно разные рабочие каталоги. Добавим файлы из rack_branch в master с использованием squash, чтобы избежать засорения истории ненужными нам фактами:
Теперь файлы проекта rack у нас в рабочем каталоге.





























