Золотое правило git rebase

https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372
  • Перевод
Всем привет!

Мы тут немного переделали наш курс посвящённый 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 будет последовательно брать все коммиты из ветки, в которой вы находитесь, и повторно применять их к другой ветке. Это имеет два важных последствия:

  1. Переприменяя коммиты, git создает новые. Эти новые коммиты, даже если они вносят тот же набор изменений, будут рассматриваться git-ом как совершенно разные и независимые.
  2. 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

Как всегда ждём ваши вопросы, комментарии тут или на можно помучать преподавателей на открытом уроке.
Отус
151,00
Профессиональные онлайн-курсы для разработчиков
Поделиться публикацией

Комментарии 18

    0

    А что будет, если Анна выполнит git rebase origin/feature в локальную feature ветку?

      +4

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

        0
        Нет, не надо делать git rebase для опубликованной (published) (не публичной (public), заметьте!) ветки.
          0

          Меня данный пост не переубедил. Почему нет? По хорошему, с веткой должен работать только один человек. Если разработчиков больше одного, то необходимо согласовать ребейз со всеми и переписать историю. Это бывает, особенно при длительной работе над какой-нибудь фичей.

            +1
            Я специально привёл терминологию. Вы путаете опубликованные ветки с публичными. Никто не запрещает делать git rebase в публичных ветках (тем более в приватных), а вот в опубликованных этого делать нельзя. Или ваши пользователи мазохисты до мозга костей, или вы что-то недопонимаете…
        –2
        Более обобщенно правило выглядит так: «не изменяйте опубликованную историю». А золотое правило git rebase — не использовать его )). Оставлю для подтверждения пару ссылок: Чем опасен rebase, или как получилось, что 2*3=5, Почему нужно перестать использовать Git rebase
          0

          комрады, если ставите минус, пожалуйста, подкрепляйте своё мнение аргументами в камментах.

            0
            В тех постах, на которые вы ссылаетесь, тоже есть комментарии…
            0
            Ну вы же чушь пишите про git rebase ... Это совершенно мощный инструмент разработчика. Как один из его неявных вариантов git pull --rebase ... Надо, как и везде, понимать что к чему и как использовать инструменты. Например, разработка ядра не могла бы вестись человеческим образом (да и любого проекта, где оперируют сериями патчей) без git rebase ...
            0
            Я всегда делаю rabase, когда наступает путаница. Именно для этого он и нужен!
              +2
              В тексте, расписывающим азбучные правила, так и не написано зачем вообще кому-то может понадобиться делать rebase.
                –1

                По-моему, текст можно было укоротить до одного позитивного кейса: gut pull --rebase; больше я не могу представить ни одного случая, зачем rebase может понадобиться.


                А так-то да, git даёт кучу возможностей и инструментов для отстреливания себе ног.

                  –1
                  почитайте про rebase -i
                +1
                Никогда, НИКОГДА, НИКОГДА не делайте rebase общей ветки.

                Да можно так делать, просто надо договариваться.


                Скажем, Алиса склонировала себе ветку Боба для код-ревью или для проверки работы, а потом Боб сделал rebase и изменил 3 последних коммита. Боб сообщает ей об этом. Если Алиса сделает git pull то локально у нее будет merge commit. Ей надо сделать git reset --hard HEAD~3 && git pull, тогда новые коммиты применятся через fast forward.


                В общем-то, можно даже не договариваться. Алиса получает merge commit, [говорит "Ай-яй-яй, Боб"], делает git reset --hard HEAD~1 для отмены мерж коммита, находит последний общий коммит в историях и откатывается на него, потом делает git pull.


                Если Алиса тоже делала коммиты в эту ветку, то принцип такой же, просто надо после pull применить эти коммиты.

                  0

                  Да не надо ничего искать, достаточно git checkout origin/bob -B bob...

                    0
                    Хм, действительно) Помню, что зачем-то так делал. Может какая-то другая ситуация была, а может правда про checkout не подумал.
                  +1
                  Как человек, немного знакомый с гитом, хочу добавить несколько замечаний:
                  Это имеет два важных последствия:

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

                  Git rebase переприменяет коммиты, не уничтожая старые.
                  Все команды гита, изменяющие состояние репозитория, обычно не уничтожают неиспользуемые объекты. Из-за этого в гите довольно сложно необратимо повредить репозиторий) Для очистки объектов существуют специальные команды типа git gc.

                  Я думаю, что куда больше проблем, чем при нарушении «золотого правила», возникает вот здесь:
                  Естественно, Анна пулит.
                  Если вы хоть немного знакомы с гитом, никогда не делайте git pull. git pull — это команда, придуманная, чтобы немного облегчить работу с гитом новичкам. Правильная последовательность действий такая:
                  • git fetch — чтобы забрать все изменения из удаленного репозитория.
                  • изучение текущего состояния удаленной ветки (сравнение коммитов со своими, просмотр изменений и т.п.).
                  • В зависимости от того, что там оказалось, вы можете в своей локальной копии ветки захотеть сделать либо fast forward либо rebase либо merge либо reset либо пойти к коллегам разбираться, почему в ветке фигня. А возможно, что придется переписать часть своих изменений.
                    0
                    … или git remote update -p. Тем не менее git pull --rebase ... очень полезен.

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

                  Самое читаемое