Бранч что это программирование
Перейти к содержимому

Бранч что это программирование

  • автор:

Git — создание ветвей (branch)

Продолжаю совместно с вами постепенно изучать магию Git\GitHub.

Слово магия здесь применено не случайно — не иначе, как магией возможности Git\GitHub не назовешь. По крайней мере, я впечатлен этими возможностями. Другое дело, что процесс изучения Git у меня лично идет как-то тяжеловато. Ну, это не страшно — главное, не останавливаться!

В этом разделе я попытаюсь осветить для себя (и возможно, для вас, уважаемый читатель) вопрос создания ветвей (branches) в Git, перемещение между ветвями (branches), слияние (merge) ветвей. Этот вопрос очень подробно и хорошо описан на странице официальной документации — “Git Branching — Basic Branching and Merging”. Здесь я попробую самостоятельно описать данный вопрос.

Инициализация Git-репозитория

Создаю тестовую директорию , в которой будут производиться эксперименты по созданию ветвей в Git. Внутри этой директории создаю два файла — индексный файл и файл таблиц стилей. А затем инициализирую Git-репозиторий, добавляю созданные файлы под версионный контроль Git:

Обратите на строку в выводе команды . Это не пустой набор служебной информации — здесь говориться о том, что в ветви нечего фиксировать и рабочая директория чистая.

Итак, мы уже кое-что узнали. А именно — при инициализации Git-репозитория была автоматически создана ветвь (branch) по имени . И на данный момент мы находимся в этой ветви.

Конечно, на самом деле это пока мало о чем говорит, так как не с чем сравнивать. Поэтому давайте я немного отредактирую оба файла и , проиндексирую\зафиксирую их.

В браузере примерный вид странички будет выглядеть таким образом:

Git - ветвь (branch) master

Git — создание новой ветви (branch)

В системе Git (как уже упоминалось ранее) имеется механизм ветвления (branches). Попробую на словах объяснить, что он из себя представляет.

Допустим, в моем текущем проекте, у которого имеются два файла, нужно внести кардинальные изменения. Причем, изменения такого рода, что они могут нарушить работу уже существующего проекта. Чтобы обезопасить себя (подстраховать), создаю точную копию существующего проекта и перехожу в нее для дальнейшей работы.

Давайте я на практике осуществлю вышесказанные действия. Для этого создаю новую ветвь с произвольным именем , которая на этом этапе будет точной копией ветви :

Строка услужливо информирует, что меня автоматически “перебросило” во вновь ветвь . Можно проверить себя, набрав в консоли:

Строка говорит сама за себя.

Отлично! Теперь давайте я внесу некоторые изменения в файлы и и мы вместе посмотрим на результат в окне браузера. Изменения будут касаться добавления блока-обертки, еще нескольких параграфов и другой легкой стилизации.

Не забуду также проиндексировать и зафиксировать внесенные изменения. Обратите внимание на вид команды — . Эта команда является сокращенным вариантом двух команд: и . Применяется, когда нужно “проскочить” этап индексирования и сразу зафиксировать изменения.

Смотрим, что у нас получилось в окне браузера — то, что и ожидалось:

Git - ветвь (branch) second

Git — переключение между ветвями (branches)

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

Git - ветвь (branch) master

Оп! Мы видим старую картину — Git “запечатлел” тот момент, когда мы совершили переход из ветви в ветвь . Другими словами, мы вернулись в последнее зафиксированное состояние ветви :

Если я снова вернусь в ветку и запущу команду просмотра логов Git, то коммитов окажется больше:

Мне кажется, уже сейчас должно быть понятно, что такое ветви (branches) в Git и для чего они предназначены. На самом деле это действительно очень просто.

Git — слияние ветвей (branches)

В предыдущем шаге я создал ветвь , в которую внес “рискованные” изменения, чтобы проверить, “оправдают” ли они себя. Изменениями я доволен и хотел бы добавить их в первоначальную ветку , чтобы потом продолжить развитие проекта уже с этого места.

Фактически, я хочу сделать слияние двух веток — и . Это сделать очень просто — для этого я перехожу в ветку . То есть, я должен находиться в той ветке, в которую я вношу изменения из другой ветки. А затем произвожу само слияние:

Команда слияния проста — я просто указываю имя той ветки (branch), которую хочу слить (merge) с текущей, в которой я нахожусь на данный момент.

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

Давайте снова “заглянем” в окно браузера — что он нам интересного покажет?

Git - результат слияния двух ветвей master и second

Показал он то, что и следовало показать — результат объединения двух ветвей и .

Git — графическое представление ветвей (branches)

Система Git имеет в своем составе возможность графического представления ветвления в репозитории. Причем, такое представление можно сделать даже в консоли, с помощью псевдографики.

Это можно сделать, набрав в консоли команду:

На Stack Overflow я нашел примеры красивых изображений консоли с псевдографическим выводом команды :

Красивая псевдографика команды git log

Красивая псевдографика команды git log

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

Помимо псевдографики, ветви в Git можно визуализировать с помощью настоящего графического приложения. Под Mac OS X и Linux имеется достаточно большое количество таких приложений. Например, под Mac OS X это GitX, под Linux — Gitk или Gitg:

Приложение Gitg под Linux

Git — удаление ветви (branch)

В разделе слияния ветвей в Git я научился процессу объединения двух ветвей в одну. Такой процесс в Git имеет название (слияние). Теперь ветвь имеет в себе все, что есть и в ветви . Поэтому ветвь можно удалить.

Выполняется это командой:

Посмотрим на вывод команды :

У меня осталась одна ветвь — .

Красивая функция trackBy

Пример красивой функции trackBy для Angular. Функция понравилась своей лаконичностью:<% highlight typescript %>public trackByNumber = (_. … Continue reading

Основы использования бранчинга для параллельной разработки

Как справедливо заметил Fred Brooks, серебряной пули, способной поразить зверя разработки программного обеспечения, не существует. Пока возникают новые требования, идеи и находятся новые баги, программы живут и изменяются. Путь, который проходит код от версии к версии, может быть крайне сложен и извилист. К его созданию причастно много людей: разработчики, тестировщики, бизнес-аналитики, заказчики и т.п. Несмотря на то, что существует много разных видов разработки – аутсорсинг, продуктовая разработка, open-source и т.п., проблемы, стоящие перед командой, остаются примерно одинаковыми. Программное обеспечение – вещь сложная, потребитель хочет получить его как можно быстрее (и дешевле). Качество при этом должно быть приемлемым. Перед командой разработки стоит серьезная задача – наладить эффективное взаимодействие. Одним из самых главных средств коллаборации внутри команды разработчиков является сам код, который они пишут.

В данный момент на рынке получают широкое распространение распределенные системы управления версиями – DVCS. Однако, львиную долю рынка удерживают традиционные и более простые в использовании централизованные системы, такие, как, например, SVN. Система управления версиями, а вернее, ее грамотное использование, играет ключевую роль в обеспечении эффективного взаимодействия. Вспомните, как давно вы читали книгу про свою VCS? Команде, в которой нет людей, способных выстроить грамотное взаимодействие через VCS, исходя из потребностей проекта, не позавидуешь.

Управление релизами

Давайте представим себе идеальное управление релизами. Релиз-менеджер может оценить состояние кода и выбрать реализованный функционал для включения в релиз. Этот функционал должен быть готов и протестирован. Также релиз-менеджер может включить исправления дефектов с прошлого релиза. Неготовый, нестабильный и непротестированный функционал в релиз попасть не должен. Если от QA-специалистов поступает информация о нестабильности того или иного функционала, релиз-менеджер должен иметь возможность убрать его из релиза. Часто возникает потребность в переносе исправлений дефектов на уже работающую у конечного пользователя версию, потому что он по каким-то причинам не может перейти на новую.

Если немного сменить точку зрения и посмотреть на процесс работы над кодом со стороны разработчика, то он должен сидеть в своей песочнице и не подвергаться влиянию дестабилизирующих коммитов со стороны коллег. В идеале, разработчики должны обмениваться только законченными и стабильными наборами изменений. Так ведь проще понять что было сделано, правда? Тем не менее, коммиты не должны диктовать разработчику стиль его работы, и он всегда должен иметь возможность вкомитить только частично выполненный функционал.
Описанные выше проблемы имеют несколько решений. Одно из них – правильный выбор и грамотное использование проектной системы управления версиями. Еще одно – понимание возможных стратегий бранчинга (ветвления) и цены, которую придется заплатить за всю эту роскошь.

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

Отступление про версионность кода

Как правило, системы управления версиями хранят историю изменений в виде линии (централизованные) или графа (распределенные). Ветка (бранч) – это просто линия разработки кода, которая имеет общую историю с другими ветками и существует параллельно с ними. Jeff Atwood в своем блоге сравнивает ветки с параллельными вселенными. В такой вселенной в какой-то момент история пошла по-другому относительно других. Это дает нам безграничные возможности, которые уравновешиваются безграничной сложностью наших вселенных.

Как правило, одна из наших историй является основной и носит гордое имя trunk или mainline. По аналогии с деревом, от нее отходят другие ветки. В эту ветку рано или поздно попадает готовый (или не совсем) функционал и исправления ошибок.

Branch per release

Рассмотрим первый из этих случаев, когда отдельная ветка создается под каждый релиз. Это делается для того, чтобы исправлять дефекты, найденные после выпуска релиза или во время его тестирования. Этот процесс обычно называется стабилизацией. При этом сами исправления (багфиксы) не остаются только в релизных ветках, а переносятся в mainline (если история релиза и mainline не слишком разошлись), делая ее стабильнее. Код в релизной ветке изолирован от дестабилизирующего влияния разработки нового функционала и при этом не блокирует ее. Сама по себе релизная ветка предоставляет легкую возможность осуществлять поддержку релизной версии. Когда прекращается поддержка релиза, его ветка замораживается. А пока идет проект, mainline продолжает свое развитие, являясь точкой, в которой накапливается новый функционал для следующих релизов.

Таким же образом можно осуществлять поддержку релизов для разных заказчиков, выделяя по ветке для каждого, если по каким-то причинам им нельзя поставить одну и ту же версию. Хочу отметить, что поддержка разных вариаций одной и той же версии — задача трудоемкая и ее следует избегать по мере возможности.

Branch per feature

Следующий случай — это выделение отдельной ветки для разработки нового функционала. Как правило, это одна логически законченная функциональная область, или просто feature. Новый функционал объединяется с основной веткой только после полного завершения, что позволяет избежать негативного влияния незавершенной работы на другие линии разработки. После того как новый функционал готов и объединен с основной веткой, другие ветки разработки должны быть интегрированы с mainline, чтобы не накапливался эффект отложенной интеграции. Использование веток для релизов и разработки позволяет нам не ждать, пока окончится тестирование и стабилизация релиза, а сразу приступить к разработке функционала для следующего.

Также можно создавать sub-branches для релизных и девелоперских веток, если нужны еще уровни изоляции. Во всех случаях создания новой ветки следует понимать цену ее поддержки, о чем будет упомянуто немного позже.

Интеграция между ветками

Основная ветка (mainline, trunk) является главным местом интеграции при помощи кода. Так или иначе, все изменения, сделанные разработчиками, попадают сюда. Тем не менее, она не должна превращаться в свалку нестабильного и незаконченного кода. Именно поэтому разработку новых фич рекомендуется проводить в отдельной ветке, интегрировать с основной, тестировать и только потом объединять изменения. Иными словами, mainline должна содержать достаточно законченный код, который может послужить основой для стабилизационной релизной ветки. Также, багфиксы из релизных веток, пройдя через mainline, попадают в ветки для разработки, таким образом, работа ведется над более стабильным кодом. Хорошим правилом является то, что мы не должны отдавать нестабильные изменения в другие ветки и что мы должны принимать стабильные изменения из других веток.

Рассмотрим ситуацию, отображенную на картинке:

  • В какой то момент в Mainline накопилось достаточное количество законченного функционала для выпуска Release 1.x. Для него была создана ветка, и после тестирования и стабилизации релиз ушел заказчикам.
  • Параллельно с этим стартовала разработка нового функционала:feature A и feature B, – каждая на своей ветке.
  • Баги, найденные закзачиками в Release 1.0, были исправлены на релизной ветке, и был выпущен Release 1.1. Багфиксы из него были объединены с Mainline, откуда попали в ветки для feature A и feature B. Таким образом, работа велась над более стабильным кодом.
  • Один из заказчиков по своим причинам не смог перейти на версию 1.1 и столкнулся с рядом специфичных для себя дефектов. Это было исправлено на специально сделанной для него ветке – Release 1.0.x.
  • Была закончена разработка feature A, и, после интеграции и тестирования, эти законченные изменения попали в Mainline. Ветка для feature B получает эти изменения сразу после их попадания в Mainline, чтобы работа велась над максимально актуальной версией кода.
  • Принимается решение о выпуске нового Release 2.x, включающего feature A, и для него создается ветка, на которой осуществляется сервис этого релиза, – 2.1, 2.2. Причем, багфиксы для релизной версии 2.2 не объединяются с Mainline, так как истории этих линий разработки кода уже слишком разошлись.
  • Feature branches не стоят на пути Continuous Integration
  • Семантические конфликты не являются специфичными исключительно для бранчинга
  • Feature toggling и Branch-by-abstraction имеют ряд своих недостатков по сравнению с Feature branches.

Интеграция через Mainline не является единственным способом интегрироваться – возможна интеграция напрямую между ветками. Martin Fowler называет такой способ Promiscuous Integration. Для такого метода интеграции очень важна коммуникация внутри проектной команды.

Стабильность веток

У такой модели есть градация стабильности, где самыми стабильными являются релизные ветки, менее стабильной является mainline, и самыми нестабильными являются ветки для разработки. Как правило, на диаграммах самые стабильные ветки отображаются выше всех, а нестабильные – ниже всех.

Накладные расходы, связанные с использованием бранчинга

С бранчингом связаны следующие издержки:

  • Механические – это те действия, которые нужно совершить, чтобы создать ветку, переключиться с ветки на ветку, объединить (merge) изменения и т.п. Как правило, такие действия трудоемки для централизованных систем и относительно просты для децентрализованных.
  • Интеллектуальные – это те усилия, которые приходится приложить, чтобы держать в голове все существующие ветки и их предназначение. Как правило, существуют инструменты, которые облегчают эту задачу. Сюда можно отнести кривую обучения для сотрудников, связанную с освоением системы управления версиями.
  • Цена за тестирование – использование параллельной разработки способно серьезно увеличить цену ручного тестирования. Отложенное тестирование позволяет сократить расходы, но при этом имеет ряд своих недостатков. Любое автоматическое тестирование значительно уменьшает цену тестирования при использовании бранчинга. В целом, этот пункт зависит от стратегии тестирования, принятой на проекте.

Типы зависимостей между ветками и способы их решения

Между ветками могут возникать следующие зависимости:

  • Архитектурные – если мы меняем архитектуру на одной ветке, другие ветки могут зависеть от этих изменений.
  • Функциональные – некоторая новая функциональность не может быть закончена или не имеет особой ценности, пока не будет закончен другой функционал, от которого она зависит.
  • Зависимости от исправления дефектов – в случае исправления дефекта на одной ветке, может существовать несколько веток, которые должны получить это изменение.

Существует несколько типовых решений для работы с таким зависимостями:

  • Саб-бранчинг – зависимая функциональность реализуется в отдельном саб-бранче и потом объединяется со всеми заинтересованными ветками.
  • Остановка – разработка на ветке замораживается, пока не будет готова нужная функциональность.
  • Архитектурная абстракция – путем абстракции в системе создаются границы, которые изолируют разные части функциональности. В этом случае проблема решается не только на уровне системы управления версиями, но и на уровне дизайна приложения.
  • Использование заглушек – в системе используются fakes/stubs, которые заменяются на реальный функционал по мере его готовности.
  • Релиз, патч, ре-релиз – система выпускается в не полностью готовом виде и патчами доводится до совершенства (эту практику в некоторых отраслях принято называть платным бета-тестированием).

Важно понимать, что грамотный модульный дизайн приложения может сильно уменьшить или свести на нет необходимость использования бранчинга и является мощным инструментом для решения проблем, связанных с параллельной разработкой.
Бранчинг позволяет нам одновременно вести два вида разработки: стабилизацию и реализацию нового функционала. Однако, это не единственный способ его применения. Например, отдельные ветки могут выделяться для разделения итераций или для изоляции разных команд.
Правильный выбор стратегии бранчинга зависит от потребностей проекта и возможностей/ограничений используемой системы управления версиями (которую, впрочем, никто не запрещает сменить). Ограничения реального мира, которые накладываются на процесс, часто невозможно решить без возможности осуществления паралелльной разработки. Тем не менее, неграмотное понимание и использование бранчинга часто приводит к анти-паттернам, которые и завершают этот материал.

Жизнь — это движение! А тестирование — это жизнь 🙂

— Бранч — это отдельная ветка в коде. Вот смотрите, мы сейчас работаем в trunk-е, основной ветке.

Когда мы только-только добавили наш код на сервер, у нас появилась «точка возврата» — сохраненная версия кода, к которой мы можем обратиться в любой момент.

Потом разработчик добавляет новый функционал и коммитит его — это версия 1 кода.

Потом он добавляет еще функционал — это версия кода 2.

При этом в самой VCS сохранены все версии, и мы всегда можем:

Посмотреть изменения в версии 1

Сравнить файлы из версии 1 и версии 2 — система наглядно покажет, где они совпадают, а где отличаются

Откатиться на прошлую версию, если версия 2 была ошибкой.

Потом разработчик снова добавляет код — это версия 3.

И так далее — сколько сделаем коммитов, столько версий кода в репозитории и будет лежать. А если мы хотим сделать бранч, то система копирует актуальный код и кладет отдельно. На нашем стволе появляется новая ветка (branch по англ. — ветка). А основной ствол обычно зовут trunk-ом.

Теперь, если я захочу закоммитить изменения, они по-прежнему пойдут в основную ветку. Бранч при этом трогать НЕ будут (изменения идут в ту ветку, в которой я сейчас нахожусь. В этом примере мы создали branch, но работать продолжаем с trunk, основной веткой)

Так что мы можем смело коммитить новый код в trunk. А для показа заказчику использовать branch, который будет оставаться стабильным даже тогда, когда в основной ветке всё падает из-за кучи ошибок.

С бранчами мы всегда будем иметь работающий код!

Вы можете спросит — «Зачем эти сложности? Мы ведь всегда может просто откатиться на нужную версию! Например, на версию 2. И никаких бранчей делать не надо!».

Это верно. Но тогда нужно будет всегда помнить, в какой точке «всё работает и тут есть все нужные функции». А если делать говорящие названия бранчей, обратиться к ним намного проще. К тому же иногда надо вносить изменения именно в тот код, который на продакшене (то есть у заказчика).

Допустим, мы выпустили сборку 3, идеально работающую. Спустя неделю Заказчик ее установил, а спустя месяц нашел баг. У нас за эти недели уже 30 новых коммитов есть и куча изменений.

Заказчику нужно исправление ошибки, но он не готов ставить новую версию — ведь ее надо тестировать, а цикл тестирования занимает неделю. А баг то нужно исправить здесь и сейчас! Получается, нам надо:

Обновиться на версию 3

Исправить баг локально (на своей машине, а не в репозитории)

Никуда это не коммитить = потерять эти исправления

Собрать сборку локально и отдать заказчику

Не забыть скопипастить эти исправления в актуальную версию кода 33 и закоммитить (сохранить)

Что-то не очень весело. А если нужно будет снова дорабатывать код? Искать разработчика, у которого на компьютере сохранены изменения? Или скопировать их и выложить на дропбокс? А зачем тогда использовать систему контроля версий?

Именно для этого мы и бранчуемся! Чтобы всегда иметь возможность не просто вернуться к какому-то коду, но и вносить в него изменения. Вот смотрите, когда Заказчик нашел баг, мы исправили его в бранче, а потом смерджили в транк.

Смерджили — так называют слияние веток. Это когда мы внесли изменения в branch и хотим продублировать их в основной ветке кода (trunk). Мы ведь объединяем разные версии кода, там наверняка есть конфликты, а разрешение конфликтов это merge, отсюда и название!

Если Заказчик захочет добавить новую кнопочку или как-то еще изменить свою версию кода — без проблем. Снова вносим изменения в нужный бранч + в основную ветку.

Веток может быть много. И обычно чем старше продукт, тем больше веток — релиз 1, релиз 2. релиз 52.

Есть программы, которые позволяют взглянуть на дерево изменений, отрисовывая все ветки, номера коммитов и их описание. Именно в таком стиле, как показано выше =) В реальности дерево будет выглядеть примерно вот так (картинка из интернета):

А иногда и ещё сложнее!

— А как посмотреть, в какой ветке ты находишься?

— О, для этого есть специальная команда. Например, в Mercurial это «hg sum»: она показывает информацию о том, где ты находишься. Вот пример ее вызова:

В данном примере «parent» — это номер коммита. Мы ведь можем вернуться на любой коммит в коде. Вдруг мы сейчас не на последнем, не на актуальном? Можно проверить. Тут мы находимся на версии 3. После двоеточия идет уникальный номер ревизии, ID кода.

Потом мы видим сообщение, с которым был сделан коммит. В данном случае разработчик написал «Try to fix bug with device».

И, наконец, параметр «branch»! Если там значение default — мы находимся в основной ветке. То есть мы сейчас в trunk-е. Если бы были не в нём, тут было бы название бранча. При создании бранча разработчик даёт ему имя. Оно и отображается в этом пункте.

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

PS — это выдержка из моей книги для начинающих тестировщиков, написана в помощь студентам моей школы для тестировщиков

Git. Урок 3.
Ветвление. Создание, переключение и удаление веток. Команды: branch, checkout, status, log, diff.

Smartiqa Git cover

1. Что такое ветка.
2. Зачем нужны ветки.
3. Создание новых веток.
4. Просмотр списка веток.
5. Переключение между ветками.
6. Просмотр состояния ветки. Команды: git status, git log, git diff.
6.1. Просмотр состояния файлов ветки. Команда git status.
6.2. Просмотр истории коммитов ветки. Команда git log.
6.3. Просмотр различий между коммитами. Команда git diff.
7. Удаление веток.

Дадим два определения ветки: на логическом и физическом уровнях.

1. Логический уровень.
С точки зрения логики, ветка – это последовательность коммитов. Чтобы проще было понять, что такое ветка, рассматривайте ее как некоторую временную шкалу. Коммиты в ней – снимки интересных моментов, идущие друг за другом в хронологической последовательности. Рисунок ниже поможет вам в интуитивном представлении.

Логическое представление веток. Ветки: main, develop, feature

На рисунке выше изображены три ветки: main , develop и feature . Каждая представляет из себя «поток» коммитов в хронологической последовательности. Важно заметить, что эти потоки не пересекаются, то есть работа в ветках идет параллельно.

2. Физический уровень
На физическом уровне, то есть с точки зрения внутренней реализации Git, ветка – это ссылка на последний коммит в этой ветке. Картинка ниже поможет вам понять, что к чему.

Внутреннее представление веток Git. Ветки: main, develop, feature

  1. HEAD – так называемый курсор Git. Главное назначение HEAD — определять, в каком состоянии находится рабочая копия (напомним, что рабочая копия – это все файлы репозитория, за исключением директории .git/ ). На какой коммит указывает HEAD – из того коммита и загружаются файлы в рабочую директорию.
  2. ORIG_HEAD – указатель, который появляется, когда мы вручную передвигаем HEAD на какой-нибудь НЕ последний коммит. ORIG_HEAD указывает на тот же коммит, на который указывал HEAD до передвижения назад. Нужен он, чтобы мы имели возможность вернуться на хронологически последний коммит без существенных затрат (в истории мы не будем видеть все коммиты старше нашего, а поэтому не сможем узнать хэш последнего).
  3. Пользовательские указатели. Пользователи сами могут создавать указатели. Например, вы можете создать указатель version-1.2.1 , который будет указывать на коммит, в котором хранится версия 1.2.1 вашего проекта. Это довольно удобно, поскольку вы можете переключаться на коммит с той или иной версией, не запоминая его хэш.

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

На самом деле, вначале, когда мы делаем свой первый коммит, Git автоматически создает основную ветку. Вы можете помнить, что ее имя по умолчанию «main» мы задавали в настройках Git в предыдущем уроке. Каждый раз, когда мы создаем новый коммит, Git автоматически перемещает указатель main на последний коммит. Тем не менее, в следующем уроке мы узнаем, как перемещать указатель ветки между коммитами самостоятельно.

Представление веток внутри Git

Пока мы разбирались, что такое ветка, у вас мог возникнуть вопрос: зачем нужны такие сложности, ведь можно просто делать коммиты и откатывать изменения, когда нужно.

Дело в том, что Git – универсальная система контроля версий: она подходит и большим командам крупных корпораций, и индивидуальным разработчикам для их личных проектов.

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

Итак, чаще всего ветки используются в следующих случаях.

  1. Ветки нужны, чтобы несколько программистов могли вести работу над одним и тем же проектом или даже файлом одновременно, при этом не мешая друг другу.
  2. Кроме того, ветки используются для тестирования экспериментальных функций: чтобы не повредить основному проекту, создается новая ветка специально для экспериментов. Если эксперимент удался, изменения с экспериментальной ветки переносятся на основную, если нет – новая ветка попросту удаляется, а проект остается нетронутым.
  3. Помимо прочего, ветки можно использовать для разных выходящих параллельно релизов одного проекта. Например, в репозитории Python может быть две ветки: python-2 и python-3 . До закрытия python-2 релизы этих версий языка выходили независимо друг от друга, поэтому могло иметь место такое разделение на ветки.

Способ 1. Команды git branch + git checkout

Команда git branch

На самом деле, git branch – очень мощная команда, которая умеет много всего. Сейчас мы рассматриваем ее как инструмент для создания веток. Ниже мы рассмотрим еще некоторые способы ее применения.

И как мы уже сказали, в качестве второго этапа после создания новой ветки, вам нужно переключиться на нее командой git checkout .

Способ 2. Команда git checkout -b

Команда git checkout

Как и git branch , git checkout – очень многофункциональная команда. Главное ее назначение – перемещать указатель HEAD . О том, как она работает и какие еще имеет применения, мы поговорим ниже.

  1. В директории .git\refs\heads создается новый файл, имя которого – имя ветки, которую вы хотите создать (при условии, что такого файла не существует). Например, если мы выполним команду git branch feature , то создастся файл .git\refs\heads\feature .
  2. В созданный файл записывается хэш текущего коммита. С него ветка и начнется. После этого файл .git\refs\heads\feature будет выглядеть так:

40 шестнадцатеричных цифр в файле выше и есть хэш последнего коммита, на который указывает новосозданная ветка.

Важно понимать, что как только вы создали новую ветку, она указывает на тот же коммит, что и основная ветка, и HEAD . Графически ситуация выглядит так:

Граф Git сразу после создания новой ветки. Ветки: main, feature

Граф Git после переключения на новую ветку и создания нескольких коммитов. Ветки: main, feature

Как мы видим, указатели feature и HEAD передвинулись на последний коммит 6670 .

С помощью команд git branch и git checkout вы можете создать неограниченное количество веток и переключаться между ними по мере необходимости. Обычно если ветка вам больше не нужна, ее сливают с основной и удаляют. Тема слияния веток заслуживает отдельного урока, поэтому про это мы поговорим в следующий раз, а удаление веток рассмотрим чуть ниже.

Команда git branch

# Выведем локальные и удаленные ветки
$ git branch -a
* develop
feature
main
remotes/origin/HEAD -> origin/main
remotes/origin/develop
remotes/origin/feature
remotes/origin/main

# Теперь выведем только локальные ветки
$ git branch
* develop
feature
main

# А теперь только удаленные ветки
$ git branch -r
origin/HEAD -> origin/main
origin/develop
origin/feature
origin/main

В примере выше можно увидеть, что перед веткой develop стоит звездочка. Такая запись означает, что сейчас указатель HEAD находится на ветке develop . Аналогично строка origin/HEAD -> origin/main означает, что указатель HEAD удаленного репозитория находится на удаленной ветке main .

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

Команда git checkout

Этот маленький фокус позволит вам сэкономить время, особенно, если вы часто переключаетесь между ветками.

  1. Программа проверяет, существует ли указанная ветка.
  2. Затем программа переключает указатель HEAD на новую ветку.
  3. Последним шагом программа меняет рабочую копию так, чтобы она соответствовала новой ветке.

Шаг 1. Проверка существования ветки

Нужен этот этап, чтобы случайно не переключиться на ветку, которой не существует. Чтобы понять, как происходит этот процесс, нужно сделать небольшое отступление и рассказать, как вообще Git хранит информацию о существующих ветках.

Итак, мы помним, что ветка – это тоже указатель, как и HEAD . Она указывает на последний коммит в «потоке» коммитов ветви графа Git. Графически это выглядит так:

Граф Git с двумя ветвями. Ветки: main, feature

Как видно из рисунка, у нас есть две ветки: main и feature . HEAD сейчас указывает на main, поэтому сейчас рабочая копия репозитория находится в состоянии коммита 5с5с .

Все, что нужно, чтобы задать ветку – просто записать, на какой коммит она указывает. Этого достаточно, поскольку по предкам этого коммита можно восстановить весь остальной граф. Поэтому внутри Git ветка – это просто файл в директории .git\refs\heads , названный так же, как и сама ветка. Например, в случае репозитория, граф которого изображен на картинке выше, мы получим:

Шаг 2. Переключение HEAD на нужную ветку

Теперь, когда Git убедился, что ветка с нужным именем существует, необходимо как-то переключить указатель HEAD на эту самую ветку. То есть сделать так, чтобы HEAD указывал на файл ветки. Делается это очень просто: нужно в файл .git\HEAD записать путь до нужной нам ветки: refs\heads\<имя ветки> . Тогда HEAD будет указывать на новую ветку.

В нашем случае, при переключении на ветку feature , файл .git/HEAD будет выглядеть так:

Граф Git с двумя ветвями на втором шаге переключения. Ветки: main, feature

Шаг 3. Изменение рабочей копии

  1. Любой файл, который есть в новом коммите ( 6670 ) на рисунке выше и которого нет в коммите, с которого мы переключились ( 5с5с ), будет добавлен в рабочую копию.
  2. Любой файл, который был в коммите, с которого мы переключились ( 5с5с ), и которого нет в новом коммите ( 6670 ), будет удален.
  3. Любой файл, которого нет ни в одном из этих двух коммитов, будет просто проигнорирован. То есть удален он НЕ будет.

У внимательного читателя мог возникнуть вопрос: почему файлы в рабочей копии подгружаются именно из последнего коммита ветки? Что если до переключения с данной ветки, в ней были какие-то изменения, еще не добавленные в коммит? Что с ними будет? Ответ довольно прост. Согласно трем шагам, указанным выше, Git просто проигнорирует все такие файлы. То есть они останутся в вашей рабочей копии в том же состоянии, в котором и были до этого. Как правило, такое поведение очень неудобно, поскольку позволяет легко запутаться в структуре собственного проекта. Представьте: у вас два параллельных релиза, а вы, переключаясь между ветками, случайно занесли файлы одного релиза в ветку другого.

  1. Либо добавить все изменения в коммит и благополучно сменить ветку
  2. Либо «отложить» изменения, не добавляя их в коммит, воспользовавшись командой git stash .

6.1. Просмотр состояния файлов ветки. Команда git status.

  1. git checkout <имя ветки> # переключаемся на нужную ветку
  2. git status # просматриваем статус файлов ветки

6.2. Просмотр истории коммитов ветки. Команда git log.

  1. либо чтобы узнать, кто внес те или иные изменения,
  2. либо чтобы вспомнить хэш коммита, к которому хочется откатиться,
  3. либо просто проследить за историей развития проекта.

Команда git log

-<число>, -n <число>
Вывести только последние несколько коммитов. Их число вы указываете в параметре к этому ключу.

—pretty=<значение>
Красивый вывод истории. Доступные значения: oneline , short , medium , full и др.

-p
Показывает изменения, сделанные в данном коммите.

—graph
Рисует ASCII-граф взаимосвязи коммитов.

—all
Выводит историю всех имеющихся коммитов на всех ветках.

# Выведем последние 7 коммитов, воспользовавшись «красивым» выводом в одну строку.
$ git log -7 —pretty=oneline
8bb113c62cffdd3cada27b4179410f110f6f1321 (HEAD -> develop, origin/develop) adding complex algebra project
603def20e5da2d512da2852011eb5be3fa156940 (origin/main, origin/HEAD) eng. hometask done
86f24ac203c9a1a609e4f303e5c896746b13a054 minor changes and files movements
0b1f66921a08d42122e0f69babd912a2cc01ec82 minor changes
8f0479b6676b7e38eb0dd409690845bd9a64f19d new hometasks were done
cb3395bb21a353ea4ee885c4d493690e3c6294cb adding code docs
0117d8a38ed557c95a7f2959c89c1f3107a1d614 discrete math hometask done

# Выведем последние 2 коммита с обычным оформлением.
$ git log -2
commit 8bb113c62cffdd3cada27b4179410f110f6f1321 (HEAD -> develop, origin/develop)
Author: smartiqa <info@smartiqa.ru>
Date: Thu Feb 11 02:04:24 2021 +0300

adding complex algebra project

commit 603def20e5da2d512da2852011eb5be3fa156940 (origin/main, origin/HEAD)
Author: smartiqa <info@smartiqa.ru>
Date: Fri Dec 4 02:00:41 2020 +0300

В описании команды git log выше сказано, что в зависимости от ситуации, она может вывести полную историю репозитория, или же только историю некоторых коммитов. Давайте разбираться, как работает Git при выводе истории.

Общее правило, которым руководствуется Git при выводе истории, такое: иди по предкам коммитов, пока они не закончатся. Иногда это бывает удобно, иногда – нет.

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

1. Переключиться на ветку, историю коммитов которой мы хотим просмотреть.
2. Выполнить команду git log <ключи> <имя родителя текущей ветки>..<имя текущей ветки> .
Например:

Граф Git произвольного репозитория. Ветки: main, feature

Если мы попытаемся просмотреть лог ветки main , то все будет хорошо: Git просто пойдет по предкам коммитов в обратном порядке и выведет: C5 , C3 , C2 , C1 . Но если мы попробуем просмотреть лог ветки feature , то получим: C6 , C4 , C3 , C2 , C1 , что может нас запутать: после вывода всех коммитов ветки feature , Git начал выводить коммиты ветки main до ответвления feature .

  1. Идем в обратном порядке. С6 младше feature ? Да. C6 Старше main ? Да, поскольку это другая ветка. Выводим C6 .
  2. C4 младше feature ? Да. C4 Старше main ? Да, выводим C4 .
  3. C3 младше feature ? Да. C3 Старше main ? Нет, поскольку, main указывает на C5 , который является наследником С3 . Таким образом С3 младше main . Останавливаемся.

В любом случае, если вам сложно визуально представить свой репозиторий, просто воспользуйтесь командой git log —all —graph . С ней вы увидите, как выглядит граф вашего репозитория и поймете, что нужно делать, чтобы получить историю той или иной ветки.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *