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

15fea2c2172fdb5584151e4343c9bbde.png


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

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


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

99af3f3a99d4e89e992856b34d9ea180.png


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

  • Сначала мы скачали свежие коммиты. Тогда последним был коммит »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. Результат представлен на картинке:

073642adeeee1e5ece3c4a50b62fb873.png


Произошедшее уже знакомо нам. Образовался автоматический 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 сможет найти предшественника. Пример:
 

6dfc348c03baad37564e0eb755d266fa.png


Здесь слева изображены коммиты в вашем локальном репозитории, а справа — коммиты в удалённом репозитории (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.

4f4ed03b9437566f2e2fe7a8b84ce4f2.png


Здесь у »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 в предыдущем примере:

27b4bd264e28d79430ef2069a1b0862c.png


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

c61b4220e21ce9853980555716e27ce5.png


Никакой петли больше нет, история линейная и красивая! Да здравствует 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

© Habrahabr.ru