Изучаем git. merge vs rebase для начинающих

август 21 , 2016

Про git merge и git rebase написаны тысячи статей. Зачем же нужна еще одна?
Разбираясь в свое время с git rebase, я не нашел ни одной статьи, описывающей этот инструмент с точки зрения начинающих пользоваться гитом. Я видел множество схем ребейза, но так и не мог понять, для чего же все-таки нужен rebase, чем он так отличается от merge. Когда стоит использовать rebase и что будет плохого (и будет ли), если его не применять.
Я не люблю абстрактные схемы. Поэтому покажу простой пример из повседневной жизни двух коллег-программистов. Наглядно, с картинками и подробностями, мы увидим, как работает merge и rebase. Для давно работающих с гитом в статье не будет ничего нового. Тем же, кто только начинает знакомиться с rebase, надеюсь, статья будет полезной.


Представим ситуацию: двое коллег, назовем их Вася и Петя, работают над одним проектом. Пусть это будет простой сайт. Файл index.html с разметкой из меню, содержимого и подвала. И стили для страницы style.css. Вася создает первый коммит и дальше работает над подвалом сайта. Петя занимается верхним меню и содержимым главной страницы - контентом.

В проекте есть основная ветка master, в нее периодически коммитят и Вася, и Петя. Для работы над стилями подвала Вася создает новую ветку new-footer, делая свои коммиты в нее и время от времени подтягивая в эту ветку мастер. После завершения работы над new-footer Вася сольет ее в основную ветку.

В плане слияния веток git merge отличается от git rebase только тем, как выглядит история коммитов, насколько она красива и понятна. И сейчас мы посмотрим поэтапно, как происходит работа в случае merge и rebase и как выглядит история коммитов и веток.

Начинает проект Вася, он создает первый коммит и пушит его. Дальше начинается уже совместная работа с Петей. Пока наша ветка выглядит так (скриншоты взяты из phpStorm, инструмента git log) git merge vs rebase. Первый коммит

Дальше Вася добавляет подвал прямым коммитом в мастер. git merge vs rebase. Второй коммит

Вася хочет поделиться подвалом со своим коллегой Петей и пытается запушить мастер. Но Петя уже успел запушить со своей стороны в мастер коммит, касающийся меню. Как известно, git не даст запушить ветку до тех пор, пока Вася не подтянет свежий коммит от Пети. Что может быть проще? Вася делает
git pull origin master
и видит в истории это git merge vs rebase. История после простого git pull

Что это за хрень? У нас фактически всего лишь 3 коммита: initial, один от Васи и один от Пети. Но в истории мы видим уже 4. Дополнительный коммит 'merge branch master ...' не несет никакой смысловой нагрузки. Плюс непонятное ответвление, тоже никому не нужное. Мы всего лишь подтянули коммит Пети в той же ветке, мы не сливали одну ветку в другую (с нашей точки зрения). А представьте, какую кучу хлама будет содержать ветка после десятка таких git pull. Как это исправить?

Вернемся к тому моменту, когда мы собрались делать git pull. И сделаем его чуть иначе
git pull --rebase origin master.
Теперь история коммитов выглядит так git merge vs rebase. История после git pull rebase

Отлично! То, что нужно. Коммит от Пети следует сразу за initial, коммит от Васи самый свежий. Можно пушить ветку на сервер.
git push origin master

Просто удивительно, сколько разработчиков пренебрегают, забывают или просто не знают об этой возможности. Если все в команде договорятся делать git pull --rebase, то основные долгоживущие ветки будут выглядеть гораздо чище. Но это мы отвлеклись, продолжим.

После этого Вася приступает к работе над оформлением подвала и благоразумно заводит новую ветку
git checkout -b new-footer
Дальше Вася делает в новой ветке первый коммит git merge vs rebase. Первый коммит в новой ветке

Но за это время шустрый Петя уже успел закоммитить в мастер правки по тексту на главной странице. Вася ответственный разработчик и он помнит, что время от времени нужно подтягивать в свою ветку свежий мастер, дабы убедиться, что его работа не ломает работу Пети (и наоборот). Вася переключается на мастер, стягивает этот свежий коммит (конечно, через git pull --rebase) и переходит обратно в свою ветку, чтобы эти изменения влить в new-footer.
git checkout master
git pull --rebase origin master
git checkout new-footer
git merge vs rebase. Новая ветка до вливания

Задача Васи - влить ветку мастер в свою ветку new-footer. Вася делает
git merge master
и видит это git merge vs rebase. Новая ветка после merge

Что-то не очень. Подтянули один коммит и опять получили лишний 'merge branch'. Ладно, некоторые программисты не видят в этом ничего плохого и утверждают, что хотят знать, когда в текущую ветку вливался мастер. И когда мастер заливался во второй раз. И в третий. Ну вы поняли. Будет очень здорово, если в комментариях мне пояснят - зачем?

Но оставим пока так и работаем дальше. Вася делает еще один коммит в новом подвале git merge vs rebase. Новая ветка после merge и второго коммита

Затем он тестирует свою ветку, убеждается, что все хорошо, и сливает ее в мастер.
git checkout master
git merge --no-ff new-footer

Заметьте, я делаю git merge с опцией --no-ff, чтобы в мастере были видны коммиты, сделанные в другой ветке. Чаще всего встречается именно такая практика, именно ее я и придерживаюсь. В итоге наша история выглядит так git merge vs rebase. Мастер после вливания новой ветки

Напомню, в ветке new-footer всего лишь 2 коммита от Васи. Здесь же мы видим лишний merge и лишнюю связь, которая ни о чем не говорит. Легко представить тот бардак, который будет твориться при нескольких мерджах в долгоживущих ветках.

git rebase дает нам возможность сохранить историю коммитов чистой и понятной. Давайте все исправим и вернемся к тому моменту, когда мы собрались мерджить мастер в нашу ветку git merge vs rebase. Новая ветка до вливания

Теперь делаем так
git rebase master и видим следующую картину git merge vs rebase. Новая ветка после ребейза мастера

Намного лучше! Мы просто передвинули указатель HEAD так, как будто new-footer ответвилась от мастера только что. Вася делает второй коммит git merge vs rebase. Новая ветка после ребейза мастера

И наконец Вася сливает ветку new-footer в мастер и удаляет ее.
git checkout master
git merge --no-ff new-footer
git branch -d new-footer git merge vs rebase. Мастер после мерджа новой ветки --no-ff

Отличная картинка. Мы видим 2 коммита Васи и видим, что они сделаны в отдельной ветке. А если Вася подписывал бы начало своих коммитов названием ветки, а не своим именем, то история выглядела бы еще лучше :-)

Обратим внимание на опцию --no-ff. git merge по умолчанию пытается просто передвинуть указатель, не делая мерджа. Если мы сделаем просто git merge new-footer, то итоговая история будет такой git merge vs rebase. Мастер после мерджа новой ветки --ff

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

P.S. Есть негласное правило. Без опаски пользуйтесь git rebase смело до тех пор, пока Вы работаете в своей ветке один. Как только к ветке присоединяется ваш коллега, подтягивайте изменения из основной ветки через merge. Это связано с тем, что rebase перезаписывает историю коммитов, и пушить на сервер вам придется с опцией --force. Но это уже другая история.

P.P.S. К сожалению, кроме незнания/неумения, есть еще одна банальная причина, по которой программисты не пользуются rebase. Многие из нас работают с гитом исключительно средствами любимой IDE. Так вот часто случается, что git pull в IDE по умолчанию не ставит опцию --rebase. Нужно для этого тыкать отдельный чекбокс. И git merge находится ближе в меню, чем git rebase. И все.

Я до сих пор вспоминаю добрым словом своего тимлида с одной из прошлых работ, который настоятельно рекомендовал по максимуму работать с гитом в терминале.

Привыкая работать в командной строке, Вы гораздо больше узнаете о возможностях используемого инструмента. Я не призываю к фанатизму и не предлагаю отказываться от графического интерфейса. Есть операции, которые удобнее делать в IDE, например, просмотр истории или разрешение конфликтов при слиянии. Но уверяю, что 90-95% повседневной работы с гитом быстрее и удобнее выполнять в терминале. И тогда сами собой пропадут вопросы "что такое git pull --rebase и как его делать в phpStorm"

Для тех, кто подумал, что я умный, советую статью Как я перестал бояться и полюбил git

И небольшой опрос напоследок

Все статьи о git

Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!