company_banner

Git: советы новичкам – часть 3


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

    Глава 16. Откуда взялась ветка?


    Набираемся терпения и продолжаем рассматривать разные рабочие ситуации. Если мы сделаем несколько коммитов, а потом выполним команду fetch (скачаем свежие коммиты, но пока не применим их в рабочий каталог), то увидим немного сбивающую с толку картину:


    Что это ещё за ветка получилась? Мы ведь не создавали никакой ветки. Может её создал кто-то из сотрудников? Нет, никто её не создавал. Восстановим хронологию событий:

    • Сначала мы скачали свежие коммиты. Тогда последним был коммит «2».
    • Затем мы сделали коммиты «3» и «4» (но пока не пушили их).
    • В это время другие сотрудники запушили в удалённый репозиторий коммиты «5», «6» и «7». Тогда мы ничего не знали об этом.
    • Наконец, мы сделали fetch и увидели то, что на картинке.

    В Git каждый коммит хранит ссылку на предыдущий (это и позволяет нам соединять кружки на рисунках; каждый отрезок – это ссылка на предыдущий коммит). Когда мы сделали коммит «3», для нас последним коммитом был «2» поэтому они соединены. Но когда на origin кто-то запушил коммит «5», там последним был тоже коммит «2» –  ведь мы свои коммиты «3» и «4» ещё не запушили, и на origin их не было. А раз так, то для коммита «5» предыдущим тоже выступает коммит «2», именно эту связь Git и запомнил.

    Итого, разные люди независимо друг от друга поменяли результат коммита «2» – вот и возникла ветка. Кстати, эта ветка сейчас есть только в нашем локальном репозитории. В origin её пока нет, поскольку коммиты «3» и «4» мы до сих пор не запушили.

    Что дальше? Поскольку мы сделали fetch, а не pull, то скачанные коммиты ещё не применились к нашему рабочему каталогу. Давайте применим их – для этого выполним merge. Результат представлен на картинке:


    Произошедшее уже знакомо нам. Образовался автоматический merge-commit «8» – master и head теперь указывают на него. В рабочей копии появились изменения из коммитов «5», «6» и «7», которые объединились с нашими изменениями из коммитов «3» и «4». origin/master по-прежнему указывает на «7», поскольку последние наши операции проходили на локальном компьютере. А origin/master может сдвинуться только после общения нашего репозитория с origin.

    Наконец, делаем push, и вот теперь origin/master тоже указывает на «8», ведь:

    • Наш merge-commit «8» отправлен в origin.
    • Там он стал последним, а значит удалённый указатель master теперь показывает на него.
    • Нам скачалась информация об удалённом указателе master и мы её видим как origin/master.

    Вот он и показывает на «8». Логично.

    Не поддавайтесь малодушному желанию пропустить эти объяснения. В них нет ничего сложного, нужна лишь внимательность. Обязательно пройдитесь по шагам до тех пор, пока не поймете, почему все так работает.

    Глава 17. Почему push выдаёт ошибку?


    Вы обязательно столкнетесь с тем, что Git выдаёт ошибку при команде push. В чём проблема? Почему он не принимает наши коммиты? Push успешно завершится, только если для каждого отправляемого в origin коммита Git сможет найти предшественника. Пример:
     

    Здесь слева изображены коммиты в вашем локальном репозитории, а справа – коммиты в удалённом репозитории (origin).

    Хронология этих коммитов следующая:

    • Сначала в origin были коммиты «1» и «2».
    • Мы сделали pull (в локальном репозитории тоже оказались лишь эти два коммита).
    • Потом мы закоммитили «3» и «4» в локальный репозиторий (но не пушили).
    • Кто-то запушил коммит «5» в origin.

    И получилось то, что сейчас на картинке. Разобрались?

    Теперь наша попытка запушить «3» и «4» в origin завершится ошибкой. Git откажется пристыковать наши коммиты к последнему коммиту «5» в origin, поскольку в local предшественником для коммита «3» является коммит «2» – а вовсе не «5», как в origin! Для Git важно, чтобы предшественник был тот же.

    Проблема решается легко. Перед тем, как сделать push, мы сделаем pull (забираем коммит «5» себе). Тут вы можете спросить: «Секунду! А почему это забрать коммит «5» Git может, а послать коммиты «3» и «4» он не может? Вроде же ситуация симметричная в обе стороны». Правильный вопрос! А ответ на него простой. Если бы Git позволил отправить коммиты «3» и «4» в такой ситуации, то пришлось бы делать merge на стороне origin – а кто там будет разрешать конфликты? Некому. Поэтому Git заставляет вас сначала забрать свежие коммиты себе, сделать merge на своем компьютере (если будут конфликты, то разрешить их), а уже готовый результат он позволит вам отправить в origin командой push. При этом, никаких конфликтов в origin уже быть не может.

    Давайте посмотрим, как будет выглядеть локальная история, после того, как вы заберете коммит «5» командой pull.


    Здесь у «3» и «5» предок «2», как и на предыдущей картинке. А новый коммит «6» – это уже давно известный нам merge-commit.

    В таком состоянии локальные коммиты уже можно запушить. Пусть тут и появилось разветвление истории, но обе ветки при мерже объединились. А значит голова у ветки снова одна. То есть, ничего не мешает сделать push. После этого в origin коммиты будут выглядеть такой же точно «петелькой».

    Теперь, когда push выдаст вам ошибку, вы уже знаете почему и что с этим делать.

    Глава 18. Rebase


    В предыдущей главе мы сделали несколько локальных коммитов, а потом командой pull забрали коммиты других сотрудников из удалённого репозитория. У нас в локальном репозитории образовалась как бы «ветка», которая потом обратно объединилась с основной. После push это временное раздвоение ветки попало в origin, откуда его скачают сотрудники и увидят в своей истории. Часто такие «петли» считаются нежелательными. Поскольку вместо красивой линейной истории получается куча петель, которые затрудняют просмотр.

    Git предлагает альтернативу. Выше мы делали fetch+merge. Первая команда забирает свежие коммиты, вторая объединяет их с нашими незапушенными коммитами (если они есть) и создаёт merge-commit с результатом объединения.

    Так вот, оказывается можно вместо fetch+merge делать fetch+rebase. Что за rebase и чем он отличается от merge? Вспомним ещё раз, как проходил merge в предыдущем примере:


    Rebase действует по-другому – он отсоединяет вашу цепочку незапушенных коммитов от своего предка. Напомним, это были коммиты «3» и «4». Они отсоединяются от своего предка «2» и rebase ставит их «сверху» на только что скачанный коммит «5». То есть, «3» и «4» будут прицеплены сверху к «5» (а мерж-коммит «6» вообще не появится). Итог будет таким:


    Никакой петли больше нет, история линейная и красивая! Да здравствует rebase! Теперь мы знаем, что при скачивании коммитов из origin лучше объединять их со своими локальными коммитами при помощи rebase, а не merge.

    Хорошо, а если речь не о паре-тройке ваших коммитов, а о большой ветке с разработкой новой фичи. Когда настанет время влить эту фичу в главную ветку, как это лучше сделать – через rebase или merge? У обоих способов есть преимущества:

    • rebase позволит сохранить историю простой и линейной – он добавит цепочку ваших коммитов из ветки в конец основной ветки.
    • merge сделает петлю, но зато в истории более наглядно будет прослеживаться история разработки вашей фичи.

    Вопрос предпочтения rebase или merge в таких случаях обсудите с ведущим программистом вашего проекта.

    Глава 19. Эпилог


    Мы с вами разобрались в множестве команд Git для работы с репозиториями:

    • pull
    • commit
    • push
    • add
    • clone
    • checkout
    • stash
    • merge
    • rebase
    • abort
    • fetch

    Это не все команды, которые бывают нужны в работе – только самые частые. Будьте готовы, что потребуется освоить и другие. Работать с Git можно при помощи разных git-клиентов. Мы в основном используем эти три:

    • Консольный
    • SourceTree
    • TortoiseGit

    Выбор клиента – дело вкуса.

    Консольный – работает на всех платформах, но у него крайне аскетичный интерфейс. Если вы не привыкли работать в консоли, то скорее всего вам будет в нем некомфортно.

    SourceTree — графический клиент с довольно простым интерфейсом. Есть версии для наших основных платформ: Win и Mac. Однако, сотрудники часто жалуются на его медленную работу и глюки.

    TortoiseGit — еще один графический клиент. Есть версия для Win, для Mac`а нет. Интерфейс несколько непривычный, но многим нравится. Жалоб на глюки и тормоза существенно меньше, чем в случае с SourceTree.

    Интересно, что и SourceTree, и TortoiseGit не работают с репозиторием Git напрямую. Внутри себя они используют консольный Git. Когда вы нажимаете на красивые кнопки, вызываются консольные команды Git с разными хитрыми параметрами, а результат вызова снова показывают в красивом виде. Использование всеми клиентами консольного Git означает, что все они работают со стандартной файловой структурой Git-хранилища на вашем жёстком диске. А значит можно использовать смешанный стиль работы: одни операции выполнять в одном клиенте, а другие – в другом.

    Итак, вы узнали основные концепции, используемые системой контроля версий Git. А также, как работают основные команды. Наверняка при чтении статьи вам не хватало описания «какие кнопки нажимать». Однако, в каждом Git-клиенте это выглядит по-разному, поэтому нам пришлось отделить описание логики от описания интерфейса. Настало время выбрать один из клиентов и изучить его интерфейс пользователя.

    Успехов!

    Git: советы новичкам – часть 1
    Git: советы новичкам – часть 2
    Git: советы новичкам – часть 3
    Playrix 44,79
    Разработчик мобильных free-to-play игр
    Поделиться публикацией
    Похожие публикации
    Комментарии 24
    • 0
      Хочу предупредить новичков о подводных камнях связки fetch + merge. Об этом обычно почему-то не говорят — но очень многие инструменты различают первый коммит и второй среди предков коммита слияния. И историю считают по первому.

      В итоге, если делать fetch+merge — получится будто бы в ветке вы сделали коммиты 3 и 4, а потом коммитом 6 влили в нее чужой коммит 5. Если по такой истории будет, к примеру, автоматически строиться changelog — то коммит 5 попадет в него после пуша еще раз.

      Поэтому нужно переключаться на внешнюю ветку и сливать туда свои локальные коммиты.

      Кстати, в github/gitlab flow такой проблемы нет.
      • +4

        В качестве GUI-клиента под Windows (настоятельно) рекомендую GitExtensions.
        И не рекомендую TortoiseGit — его команды действуют совершенно неожиданно.

        • +1
          А я посоветую SourceTree для визуалов и не посоветую SmartGit, который сломал мой репозиторий на обычном fetch.
          • 0
            SourceTree, в свое время, неожиданно сделал мне коммит даже не задавая никаких вопросов, хотя я просто тыкался по дереву, в попытках вывести информацию об определенной ревизии, чем, в общем-то, убедил меня знакомство не продолжать. GitExtension таких вольностей себе не позволяет, пока явно в кнопку «коммит» не ткнешь — не закоммитишь.
            • 0

              Можно подробностей в чем заключалась поломка? Давно пользуюсь SmartGit он гораздо шустрее и адекватнее SourceTree.

              • 0
                Я согласен, что он шустрее в интерфейсе, именно поэтому решил его попробовать. Но при первом же знакомстве он просто сломал мой git svn репозиторий на fetch, поломав всю историю для веток. Дальше знакомство я не продолжал.
                • 0
                  А вы точно git svn правильно настроили?.. Если что, я знаю кучу способов поломать ему историю :-)
                  • 0
                    Предполагаю, что если я работаю с этим репозиторием в двух других клиентах уже полгода нормально, то вероятно проблема не в репозитории.
            • –1
              И не рекомендую TortoiseGit — его команды действуют совершенно неожиданно

              Можно конкретнее? Я так вообще люблю Mercurial и работаю с Git при помощи TortoiseHg с соответствующим плагином, выполняющим импорт в Mercurial и обратно, и проблем в общем то не встречал.
              • +4

                TortoiseSVN, TortoiseGit и TortoiseHg похожи только черепашкой на логотипе.

                • –1
                  То есть TortoiseHg работает с гитом лучше, чем TortoiseGit?
                  Заголовок спойлера
                  Забавны минусы за вопрос. Пофиг на карму уже, больше интересна логика слива.
                  • +2
                    Лично я про «лучше» ничего не утверждал (и не минусовал). По мне — каждому инструменту своя задача и TG я использую. А про эту «омонимику», когда все Tortoise существенно не похожи — не вы первый спотыкаетесь.
                    Спорить о клиентах git вообще неконструктивно. Они все — обёртка консоли, и если человек не понимает, что делает, то не понимает примерно одинаково, что в GUI, что в консоли.
                    • 0

                      (ответ также к начальному вопросу sumanai)


                      если человек не понимает, что делает, то не понимает примерно одинаково, что в GUI, что в консоли.

                      В моём случае (это было 5 лет назад) с TortoiseGit я совершенно не понимал, что делает та или иная кнопка, в то время как в консоли у меня проблем не возникало. Хуже того, не понимали другие пользователи, которые не умели в консоль. Никто не смог разобраться.


                      Обычный тогда сценарий: пользователь хочет закоммитить, жмет кнопку, рраз — и последние n коммитов испорчены и залиты на сервер. Пользователь жмёт "отменить", и на сервер льётся ещё бОльшая ерунда (частично изменённые файлы и т.п.) Затем я делаю git push -f, и бегу делать git reset --hard тем счастливчикам, кто успел получить дозу трэша с сервера.
                      Не все данные удавалось восстановить (у того, кто накосячил).


                      Может, сейчас TortoiseGit более юзабелен. Может, у нас всех были кривые руки (но уже тогда я умел в svn, hg, git и в консоли, и в GUI).
                      Но тогда первое впечатление было ужасно.

                  • 0
                    Так GUI да и вообще общие принципы вроде одинаковые. По крайней мере в Git/Hg
                  • –3
                    и меркуриал вообще и TortoiseHg в частности сделаны гораздо более по-человечески, чем git и TortoiseGit соответственно
                  • +1

                    Сколько не кручу гит, никогда одним клиентом не могу обойтись.

                    • 0
                      А чего в GitExtensions не хватает? И какой клиент к нему нужен в дополнение?
                      • +2

                        Как-то сложилось, что под виндой я использую до 4 клиентов. Просто для разных задач.


                        • Консоль.
                          "+" Максимальная гибкость, удобно работать в zsh.
                          "-" Неудобна для просмотра, а под виндой та консоль, то что идёт в поставке (mingw) работает слегка "инородно".
                        • SourceTree
                          "+" Хорошо для просмотра, неплохо для совсем простых действий, фоновый фетч, если нужно. HG из коробки
                          "-" Функциональность слабая, шаг вправо, шаг влево и "не шмогла". Спорное лицензирование, странное тяжёлое поведение, неспешность развития.
                        • Клиент в IDE.
                          "+" Рядом.
                          "-" Недостаточно функциональный (что в VS, что в IDEA), не очень удобно работать, когда репозиторий содержит несколько проектов (особенно разноязычных)
                        • TortoiseGit или (на работе одно, дома другое)
                          "+" функционал примерно посередине между консолью и SourceTree. Кажется это единственный клиент, который умеет понятно и удобно squash-ить
                          "-" Дурацкий интерфейс
                        • GitExtensions использую как замену SourceTree под Linux.

                        Правила, которые сами выработались:


                        • В IDE не делать пушей. Ни в какую ветку. Зато коммитить чуть реже, чем сохранять :)
                        • Push делать в клиенте-обозревателе (GE, ST). Это чтобы осмотреться и оценить перед отправкой.
                        • Консоль использовать для повторяющихся команд, или автоматизации, или как убер-ковырялку, или для сложных команд. Под zsh, впрочем, использую очень активно.
                        • TG только для Revision Graph (там он другой, для реп с ветвистой историей удобный) и для squash.
                        • Любой клиент только обёртка над консолью и всегда надо понимать что выполняет клиент (благо они все, кажется, пишут точную команду)

                        Ну собственно в GE не хватает revision graph (свернутого) и удобного squash. Плюс я не помню есть ли там удобный cherry pick, кажется нет.

                        • 0
                          В GitExtensions есть вкладка с консолью, ей довольно удобно пользоваться. Клиент в IDE за отдельный клиент считать бесполезно, поскольку он обычно не устанавливается отдельно; кстати к консоли это тоже относится.

                          Остался TortoiseGit, причем в плюсах у него — только squash… Скажите, а чем в GitExtensions этот squash не нравится?
                          • 0
                            Скажите, а чем в GitExtensions этот squash не нравится?

                            Комменты сам не собирает.


                            Вкладка консоли в GE есть, но в ней хоткеи смешаны от GE и bash, а когда запущено приложение, то еще и от приложения, часть приложений работает с глюками (tig в частности). Мне сильно проще отдельно запустить кривоватый git bash, который работает относительно предсказуемо.


                            На самом деле у каждого клиента еще куча своих "тараканов" и фич. У ST есть суперфича "обратить блок" (прямо в основном окне). Эта фича незаменима, если вам надо вручную пройти по >1K изменений и часть из них вернуть. Зато если надо работать с однобайтными кодировками — он пасует. TG может сделать reverse commit, cherry-pick, squash из окна просмотра истории (контекстное меню), но визуализация основного процесса (fetch-pull-add-commit-push) у него отвратительна (имхо) и неочевидна.


                            Тут спор "какой клиент лучше" по конструктивности примерно как "какие трусы лучше": кому какие удобны, тот те и использует.

                            • 0

                              А не подскажете какой нибудь клиент с аналогом чего то типа p4 reversion graph(чтобы "рельсы" лога были горизонтальны). Что Git extensions что smart git такого не умеют.

                    • +1
                      дублировать замечание не буду
                      З.Ы. Вот она — сила привычки в действии
                      • +1
                        Кажется это единственный клиент, который умеет понятно и удобно squash-ить

                        Никто не умеет так наглядно сквошить, как консольная утилита ключом "-i":
                        git rebase -i origin master
                        • 0

                          Синтетические примеры, конечно хороши. Но вот где-бы перенять боевой опыт матёрого GitGlow?

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

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