Мы тут немного переделали наш курс посвящённый web-разработке и добавили ещё целый месяц изучения JS. Ну и как обычно у нас — рассмотрим что-нибудь интересное, что разбирается у нас на курсе. В данном случае — git rebase.
Поехали.
Что на самом деле происходит во время git rebase, и почему вас должно это волновать.
Основы rebase-а
Таким вы могли бы представить себе rebase в git:
Вы могли бы подумать, что когда вы делаете rebase, вы «отсоедините» ветвь, которую хотите перебазировать, и «присоедините» ее к концу другой ветви. Это не очень далеко от истины, но стоит копнуть немного глубже. Вот что написано в документации про rebase:
“git-rebase: Forward-port local commits to the updated upstream head”— документация git
Не очень полезно, не так ли? Примерный перевод (и перевод перевода) может быть таким:
git-rebase: Переприменить (reapply) все коммиты из вашей ветки к концу другой ветки.
Самое важное слово здесь — «переприменить (reapply)», потому что rebase — это не просто ctrl-x/ctrl-v от одной ветки к другой. Rebase будет последовательно брать все коммиты из ветки, в которой вы находитесь, и повторно применять их к другой ветке. Это имеет два важных последствия:
- Переприменяя коммиты, git создает новые. Эти новые коммиты, даже если они вносят тот же набор изменений, будут рассматриваться git-ом как совершенно разные и независимые.
- Git rebase переприменяет коммиты, не уничтожая старые. Это означает, что даже после rebase-а старые коммиты все равно будут находиться в папке /objects в вашем каталоге .git. Если вы ещ` незнакомы с тем, как git рассматривает и сохраняет коммиты, вы можете узнать здесь кое-что интересное.
Таким образом, это может быть более точным представлением о том, что на самом деле происходит во время rebase-а:
Как вы можете видеть, ветвь feature имеет совершенно новые коммиты. Как уже было сказано, она имеет такой же набор изменений, но совершенно разные объекты с точки зрения git. И вы также можете видеть, что предыдущие коммиты не уничтожены. Они просто не доступны напрямую. Если вы помните, ветка является только указателем на коммиты. Поэтому, если ни ветви, ни теги не указывают на коммиты, становится почти невозможно их достать, но коммиты все еще существуют.
Теперь давайте поговорим о знаменитом золотом правиле.
Золотое правило rebase-а
«Не следует делать rebase общей ветки» ?—? Все и каждый о rebase
Вы, вероятно, уже сталкивались с этим правилом, возможно, сформулированном по-другому. Для тех, кому не довелось, это правило довольно простое. Никогда, НИКОГДА, НИКОГДА не делайте rebase общей ветки. Под общей веткой я подразумеваю ветку, которая существует в удаленном репозитории и которую другие люди из вашей команды могут запулить себе.
Слишком часто это правило преподносится как божественная истина, и я думаю, что следует в нем разобраться, если вы хотите улучшить свое понимание git.
Для этого давайте представим себе ситуацию, когда разработчик нарушает это правило, и посмотрим, что произойдет.
Скажем, Боб и Анна работают над одним и тем же проектом. Вот репозиторий Боба, Анны, и удаленный репозиторий на GitHub:
Все синхронизируются с удаленным репозиторием (GitHub)
Теперь Боб, невинно нарушает золотое правило rebase-а, в это же время Анна решает поработать над этой фичей и создает новый коммит:
Догадываетесь, что произойдет?
Боб пытается запушить, он получает отказ и видит такое сообщение:
Oh My Zsh с темой agnoster для тех, кому интересно.
Git не доволен, потому что он не знает, как объединить ветку feature Bob с веткой feature GitHub. Обычно, когда вы пушите свою ветку на удаленный репозиторий, git объединяет ветку, которую вы пытаетесь запушить с ветвью, находящейся в удаленном репозитории. Если быть точным, git пытается перемотать(fast-forward) вашу ветку, и мы поговорим об этом в будущем посте. Что вы должны запомнить, так это то, что удаленный репозиторий не может, простым способом, справиться с перебазированной веткой, которую Боб пытается запушить.
Одним из решений для Боба было бы сделать git push-force, который сообщает удаленному репозиторию:
“Don’t try to merge or do whatever work between what I push and what you already have. Erase your version of the feature branch, what I push is now the new feature branch”
And this is what we end up with:
“Не пытайся объединять или делать какую-либо другую работу между тем, что я пушу, и тем, что у тебя уже есть. Сотри свою версию ветки feature: то, что я пушу, теперь является новой веткой feature”
И это то, что мы получаем:
Если бы Анна знала, что произойдет, она не пошла бы на работу этим утром.
Теперь Анна хочет запушить ее изменения:
Это нормально, git просто сказал Анне, что у нее нет синхронизированной версии ветки feature, т.е. ее версия ветки и версия ветки GitHub разные. Естественно, Анна пулит. Точно так же, как git пытается объединить вашу локальную ветку с тем, что находится в удаленном репозитории при пуше, git пытается объединить то, что находится в удаленном репозитории, с тем, что находится в вашей локальной ветви, когда вы пулите.
Так выглядят коммиты в удаленной и локальной feature перед пулом:
A--B--C--D' origin/feature // GitHub
A--B--D--E feature // Anna
Когда вы пулите, git должен выполнить слияние, чтобы разрешить эту проблему. И вот что происходит:
Коммит M представляет собой мерж-коммит — коммит, в котором наконец-то воссоединились ветка Анны и GitHub-а. Анна, наконец, вздыхает с облегчением, ей удалось разрешить все конфликты слияния, и теперь она может запушить свою работу. Боб решает запулить, и теперь все синхронизированы.
Одного взгляда на этот беспорядок, должно быть достаточно, чтобы убедить вас в справедливости золотого правила. Вы должны иметь в виду, что вы находитесь перед беспорядком, созданным только одним человеком, в ветке, совместно используемой только двумя людьми. Представьте, что вы делаете это с командой из 10 человек. Одна из многочисленных причин, по которой люди используют git, состоит в том, что вы можете легко “вернуться назад во времени”, но чем более беспорядочна ваша история, тем сложнее это становится.
Вы также можете заметить дублирующиеся коммиты в репозитории — D и D’, которые имеют одинаковый набор изменений. Количество дублированных коммитов может быть таким же большим, как количество коммитов внутри вашей перебазированной ветки.
Если вы все еще не уверены, попробуйте представить Эмму, третьего разработчика. Она работала на feature еще до того, как Боб все испортил, и теперь хочет запушить изменения. Обратите внимание, что она пушит после нашего предыдущего мини-сценария.
черт возьми, Боб!
update: Как заметил один reddit-юзер, этот пост может заставить вас думать, что rebase можно использовать только для перебазирования ветки в верхнюю часть другой ветки. Это не так, вы можете перебазировать одну и ту же ветку, но это уже другая история.
Спасибо за внимание.
THE END
Как всегда ждём ваши вопросы, комментарии тут или на можно помучать преподавателей на открытом уроке.
Комментарии (10)
humbug
02.04.2018 18:36+1Угу. Про
git pull --rebase
не слышали, проgit rebase -mp
не слышали. Можно делать ребейз публичной ветки, но это исключительная ситуация, к которой надо соответствующе подготовиться.andy_shev
02.04.2018 23:09Нет, не надо делать
git rebase
для опубликованной (published) (не публичной (public), заметьте!) ветки.humbug
03.04.2018 04:20Меня данный пост не переубедил. Почему нет? По хорошему, с веткой должен работать только один человек. Если разработчиков больше одного, то необходимо согласовать ребейз со всеми и переписать историю. Это бывает, особенно при длительной работе над какой-нибудь фичей.
andy_shev
03.04.2018 11:18+1Я специально привёл терминологию. Вы путаете опубликованные ветки с публичными. Никто не запрещает делать
git rebase
в публичных ветках (тем более в приватных), а вот в опубликованных этого делать нельзя. Или ваши пользователи мазохисты до мозга костей, или вы что-то недопонимаете…
nick7ikin
03.04.2018 13:51Более обобщенно правило выглядит так: «не изменяйте опубликованную историю». А золотое правило git rebase — не использовать его )). Оставлю для подтверждения пару ссылок: Чем опасен rebase, или как получилось, что 2*3=5, Почему нужно перестать использовать Git rebase
nick7ikin
05.04.2018 15:28комрады, если ставите минус, пожалуйста, подкрепляйте своё мнение аргументами в камментах.
alex005
03.04.2018 13:51Я всегда делаю rabase, когда наступает путаница. Именно для этого он и нужен!
dravor
03.04.2018 13:51+2В тексте, расписывающим азбучные правила, так и не написано зачем вообще кому-то может понадобиться делать rebase.
snp
03.04.2018 17:54По-моему, текст можно было укоротить до одного позитивного кейса:
gut pull --rebase
; больше я не могу представить ни одного случая, зачем rebase может понадобиться.
А так-то да, git даёт кучу возможностей и инструментов для отстреливания себе ног.
Virviil
А что будет, если Анна выполнит git rebase origin/feature в локальную feature ветку?