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


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

Вступление

Как справедливо заметил 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, которые заменяются на реальный функционал по мере его готовности.
  • Релиз, патч, ре-релиз – система выпускается в не полностью готовом виде и патчами доводится до совершенства (эту практику в некоторых отраслях принято называть платным бета-тестированием).

Заключение

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

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

Анти-паттерны бранчинга:
  • Merge Paranoia – разработчики боятся объединять код, поэтому накапливается негативный эффект отложенной интеграции.
  • Merge Mania – разработчики больше времени тратят на объединение изменений, чем на разработку.
  • Big Bang Merge – ветки не обмениваются законченными изменениями, поэтому происходит одно гигантское объединение в конце.
  • Never-Ending Merge – объединение никогда не останавливается, так как всегда есть что объединять.
  • Wrong Way Merge – объединение более поздней ветки разработки с более ранней версией.
  • Branch Mania – создание большого количества веток без нужной на то причины.
  • Cascading Branches – создание веток без объединения их с mainline в конце разработки.
  • Mysterious Branches – создание ветки без причины.
  • Теmporary Branches – создание ветки с изменяющейся причиной ее существования: ветка становится временным рабочим пространством.
  • Volatile Branches – старт ветки в нестабильном состоянии или перенос нестабильных изменений в другие ветки.
  • Development Freeze – остановка всей разработки для создания веток, объединения или создания релизов.
  • Berlin Wall – использование веток для разделения людей в команде, вместо разделения областей, над которыми они работают.
Теги:

habr.com

Ветки (Branch) и слияния (Merge) в TFS

Большинство современных средств для совместного владения кодом поддерживают технологии веток и слияний. Сегодня я немного расскажу об этой штуке в TFS и покажу, как мы ее используем. Поехали... Механизм создания веток позволяет некоторую папку в хранилище кода скопировать в другую папку хранилища как есть. Т.е. на момент создания ветки (branch) мы получим две папки абсолютно идентичные друг-другу и отличающиеся только названием. Для создания ветки, достаточно на любой папке открыть контекстное меню  и выбрать: Ветки всегда создаются на сервере, т.е. после выбора этого пункта и ввода имени папки в которую необходимо делать ветку, на сервере TFS будет произведено копирование всех файлов с папки источника в папку приемник. Кстати, TFS запоминает все такие ветки и даже может их визуализировать. Первая картинка к этой статье, как раз показывает ветки одного из наших проектов. Правда визуализация немного хромает, если преобразовать ветку в папку. Но об этом в другой раз. Зачем могут быть нужны две папки с абсолютно одинаковым содержимым? На самом деле, все просто. Вы можете сколько угодно изголяться над одной из веток и потом просто ее удалить. Во второй ветке у вас останется исходный код. Согласитесь неплохой способ проводить эксперименты. Еще одна замечательная возможность предоставляемая TFS заключается в слиянии (merge) веток. Если две папки связаны между собой в результате Branche, то выбрав одну из них, мы все изменения можем слить в другую (на картинке выше пункт merge):

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

Теперь о том, как мы эти ветки используем у себя.

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

Если еще не запутал, то во-вторых, ветки у нас применяются для разделения проекта. Разработка нового функционала ведется в основной папке проекта. Ближе к концу спринта, когда основной функционал реализован, все изменения перемещаются в заранее подготовленную ветку Prerelease. В этой ветке идет стабилизация версии перед релизом. После того, как QA дает отмашку на релиз, все изменения перемещаются в ветку Release и разворачиваются в эксплуатацию. Благодаря этим трем веткам всегда есть возможность спокойно проводить стабилизацию перед релизом, не опасаясь, того, что кто-то начнет писать новый функционал и сломает тот код, который в рамках стабилизации уже был подвергнут регрессу. Также, наличие ветки с кодом идентичным развернутому в Operation позволяет править ошибки обнаруженные в процессе эксплуатации именно в том коде, который эксплуатируется. И даже если в ветке разработки поправили модель данных или сломали вообще все, есть возможность внести минимальные изменения в стабильный код и развернуть его после проверки в эксплуатацию. Выглядит это примерно так:

Ну и объединяя две предыдущие картинки, схема веток Core и проектов будет выглядеть так:

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

losev-al.blogspot.com

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

Вступление

Как справедливо заметил 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, которые заменяются на реальный функционал по мере его готовности.
  • Релиз, патч, ре-релиз – система выпускается в не полностью готовом виде и патчами доводится до совершенства (эту практику в некоторых отраслях принято называть платным бета-тестированием).

Заключение

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

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

Анти-паттерны бранчинга:

  • Merge Paranoia – разработчики боятся объединять код, поэтому накапливается негативный эффект отложенной интеграции.
  • Merge Mania – разработчики больше времени тратят на объединение изменений, чем на разработку.
  • Big Bang Merge – ветки не обмениваются законченными изменениями, поэтому происходит одно гигантское объединение в конце.
  • Never-Ending Merge – объединение никогда не останавливается, так как всегда есть что объединять.
  • Wrong Way Merge – объединение более поздней ветки разработки с более ранней версией.
  • Branch Mania – создание большого количества веток без нужной на то причины.
  • Cascading Branches – создание веток без объединения их с mainline в конце разработки.
  • Mysterious Branches – создание ветки без причины.
  • Теmporary Branches – создание ветки с изменяющейся причиной ее существования: ветка становится временным рабочим пространством.
  • Volatile Branches – старт ветки в нестабильном состоянии или перенос нестабильных изменений в другие ветки.
  • Development Freeze – остановка всей разработки для создания веток, объединения или создания релизов.
  • Berlin Wall – использование веток для разделения людей в команде, вместо разделения областей, над которыми они работают.

Автор: Cortlendt

Источник

www.pvsm.ru

FeatureBranch

С распространением распределенных систем управления версиями (DVCS), таких как Git и Mercurial, я все чаще вижу дискуссии на тему правильного использования ветвления(брэнч) и слияния(мердж), и о том, как это укладывается в идею непрерывной интеграции (CI). В данном вопросе есть определенная неясность, особенно когда речь заходит о feature branching (ветвь на функциональность) и ее соответствие идеям CI.

Простой (изолированный) Feature Branch
Основная идея feature branch заключается в создании нового брэнча, когда вы начинаете работать над какой-то функциональностью. В DVCS вы делаете это в своем собственном репозитории, но те же принципы работают и в централизованных VCS. Я проиллюстрирую свои мысли следующим рядом диаграмм. В них основная линия разработки (trunk) отмечена синим, и двое разработчиков, отмеченные зеленым и фиолетовым (Reverend Green и Professor Plum).

Я использую обозначенные цветные прямоугольники как обозначения локальным коммитам в брэнчи. Стрелки между брэнчами, обозначают слияния, оранжевые прямоугольники выделяют сами слияния. В этом примере, есть обновления в основной линии, скажем пара починенных багов. Когда это происходит, наши разработчики сливают их в свои локальные ветви. Для того, чтобы получить ощущение времени, давайте предположим, что речь идет о нескольких днях работы, когда каждый разработчик коммитит свои изменения примерно раз в день. Чтобы убедиться что код работает, они могут запускать билды и тесты на свои ветки. В рамках данной статьи предположим, что вместе с каждым коммитом и мерджем бегут автоматические билды и тесты на брэнч, в который он был сделан. Основное преимущество feature branching заключается в том, что каждый разработчик может работать над своей задачей и быть изолированным от того что происходит вокруг. Они могут сливать изменения из основной линии в своем собственном темпе и быть уверенными, что это не помешает разрабатываемой функциональности. Более того, это дает возможность команде выбрать, что из новых разработок внести в релиз, а что оставить на потом. Если Reverend Green опаздывает, мы можем предоставить версию только с изменениями Professor Plum. Или же мы можем наоборот, отложить дополнения профессора, быть может потому что мы не уверены, что они работают так, как мы хотим. В данном случае мы просто попросим профессора не сливать свои изменения в основную линию, до тех пор пока мы не будем готовы выпустить его функциональность. Такой подход дает нам возможность проявлять избирательность, команда решает какую функциональность сливать перед каждым релизом. Несмотря на всю привлекательность этого образа, в данном подходе могут таиться определенные проблемы.

Хотя разработчики могут работать над своей функциональностью в изоляции, в какой то момент результат их трудов должен быть интегрирован. В нашем примере Professor Plum с легкостью обновляет основную линию своими изменениями, слияния нету, ведь он уже получил все изменения в основной линии в свою ветвь (и прошел билд). Однако не все так просто для Reverend Green, он должен слить все свои изменения (G1-6) с изменениями Professor Plum (P1-5). (В этом примере многие пользователи DVCS могут почувствовать что я пропускаю многие детали, в таком простом, даже упрощенном объяснении feature branching. Я объясню более сложную схему позже.) Я сделал этот прямоугольник мерджа огромным, потому что это опасный мердж. Он может пройти без проблем, вероятно, что разработчики работали над разными частями кода без интеракций, и тогда слияние пройдет гладко. Но они так же могли работать над частями которые взаимодействуют, и в тогда их ожидает кромешный ад.

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

Проблема которая беспокоит нас сильнее, это семантические конфликты. Самым простым примером может быть тот случай, в котором Professor Plum изменяет имя метода, который Reverend Green вызывает в своем коде. Инструменты для рефакторинга помогут вам переименовать метод без проблем, но только в вашем коде. Поэтому, если G1-6 содержат новый код, который вызывает foo, Professor Plum не узнает об этом, поскольку это изменение не находится в его брэнче. Осознание того, где собака зарыта к нам прийдет только в большом мердже. Переименование функции является самым явным примером семантического конфликта. На практике они могут быть намного более скрытны. Тесты — ключ разгадки к ним, но чем больше кода надо слить, тем больше шансов на конфликты и тем сложнее их починить. Риск конфликтов в целом и семантических в частности делает большие слияния страшными. Последствием страха перед большими мерджами является нежелание реафакторинга. Содержать код в чистоте требует постоянных усилий и чтобы в этом преуспеть каждый должен прибирать мусор когда его видит. Однако, такой рефакторинг в feature branch проблематичен, постольку поскольку он делает Большой Страшный Мердж еще больше и страшнее. В результате разработчики боятся рефакторинга как огня и код обрастает уродами. В приведенной выше проблеме я вижу основную причину, по которой feature branching является плохой идеей. В тот момент когда команда боится рефакторинга для поддержания здорового кода — они в продолжительном пике без шансов на элегантный выход.
Непрерывная интеграция
Именно эти проблемы и должна решать непрерывная интеграция. С CI моя диаграмма будет выглядеть так.

Здесь намного больше мерджей, но слияние это одно из тех вещей которые лучше делать понемногу часто чем редко и тоннами. В резулттате если Professor Plum изменяет часть кода на котором зависит Reverend Green, наш зеленый коллега выяснит это намного раньше, в мерждах P1-2. На данный момент ему нужно изменить G1-2 для работы с этими изменениями, вместо G1-6 (как это было в прошлом примере). CI эффективен для нейтрализации проблем больших мерджей, но кроме этого это еще и критически важный коммуникационный механизм. В данном сценарии потенциальный конфликт проявится, когда Professor Plum сольет G1 и поймет что Reverend Green использует библиотеки профессора. Тогда Professor Plum может найти Reverend Green и вместе они могут обсудить взаимодействие их функциональности. Быть может функциональность Professor Pum требует некоторые изменения, которые не уживаются с функциональностью Reverend Green. Вдвоем они могут принять намного лучшие решения по дизайну, которые не помешают их работе. С изолированными брэнчами наши разработчики не узнают о проблеме до последнего момента, когда часто уже поздно чтобы решить конфликт безболезненно. Коммуникация одна из ключевых факторов в разработке программного обеспечения и одно из главных свойств CI — это содействие ей.

Важно упомянуть что в большинстве случаев feature branching имеет другой подход к CI. Один из принципов CI в том, что все коммитят в основную линию каждый день, так что если feature branch живет больше чем один день — это превращает его в что то очень далекое от CI. Я слышал людей, говорящих что они используют CI потому что их билды бегут на CI сервере, на каждой ветви и на каждый коммит. Это непрерывная сборка, и это хорошо, но тут нету интеграции, поэтому это и не CI.

«Беспорядочная» интеграция
Ранее я сказал в скобках, что есть и другие способы feature branching. Скажем Professor Plum и Reverend Green в начале итерации вместе заваривают ароматный зеленый чай и обсуждают свои задачи. Они обнаруживают что среди задач есть взаимодействующие части и решают интегрироваться между друг другом вот так:

С таким подходом они сливаются с основной линией в конце, как и в первом примере, но они так же часто делают мерджи между собой, чтобы избежать Большого Страшного Мерджа. Идея в том что основное преимущество feature branching это изоляция. Когда вы изолируете изолируете ваши брэнчи, есть риск мерзкого конфликта, нарастающего вне вашего ведения. Тогда изоляция это иллюзия, которая болезненно разобьется раньше или позже.

Все же, это более трудоемкая интеграция является формой CI или речь идет совсем о другом звере? Я думаю, что они разные, опять же, ключевое свойство CI в том, что каждый интегрируется с основной линией каждый день. Интеграция среди feature branches, которую я с вашего позволения назову «беспорядочной интеграцией» (promiscuous integration, PI), не включает и даже не нуждается в основной линии. Я считаю что эта разница очень важна.

Я вижу CI в основном как средство для рождения release candidate на каждом коммите. Задача CI системы и процесс деплоймента опровергнуть готовность к продакшену текущего release candidate. Эта модель нуждается в какой то основной линии разработки которая представляет текущее состояние полной картины.

--Dave Farley

Беспорядочная интеграция vs непрерывная интеграция
И все же, если PI отличается от CI, то при каких случая PI лучше чем CI? С CI вы теряете возможность использовать систему управления версиями для выборочного подхода к изменениям. Каждый разработчик влияет на основную линию, поэтому вся функциональность растет в ней же. С CI, основная линия должна всегда быть здоровой, и в теории (а часто и на практике) вы можете делать релиз после каждого коммита. Имея полузаконченную функциональность, или функциональность которую вы предпочитаете не выпускать, вы не повредите функциональности всей системы, но потребует кое какой маскировки, чтобы спрятать это от пользовательского интерфейса, как например не включение нового пункта в меню. В таких случаях PI может предоставить что то посередине.Это позволяет Reverend Green выбрать когда принять изменения Professor Plum. Если Professor Plum делает какие то изменения в API ядра системы в P2, Reverend Green может импортировать P1-2 но оставить остальные, до тех пор пока Professor Plum не закончит свою работу и не сольет в основную ветвь. Однако в общем я не считаю что выборка функциональности для релиза с помощью VCS это хорошая идея. Feature branching это модулярная архитектура для нищих, вместо того чтобы строить систему с возможностью легкой замены функциональности при райнтайме/деплойменте, люди привязывают себя к source control для этого механизма через ручной мердж.

— Dan Bodart

Я предпочитаю проектировать программное обеспечение так, чтобы можно было включить и выключить функциональность при помощи изменения конфигурации. Для этого есть две полезные техники FeatureToggles и BranchByAbstraction. Они требуют от вас больше размышлений на тему того, что и как разделить на модули и как контролировать эти варианты, но мы пришли к выводу что результат намного более аккуратен, чем тот что выходит, если надеяться на VCS.

Что больше всего меня беспокоит в PI это его подверженность способностям коммуникации внутри команды. С CI основная линия служит коммуникационной точкой. Даже если Professor Plum и Reverend Green никогда не разговаривали, они найдут возникающий конфликт в день его формирования. С PI они будут должны заметить то, что работают над взаимодействующим кодом. Постоянно обновляющаяся основная линия способствует уверенности каждому в том, что он интегрируется со всеми, не надо выяснять кто чем занимается, соответственно и меньше шансов на изменения, которые остаются скрытыми до поздней интеграции. PI возник из опен-сорса и, предположительно, менее интенсивный темп опен сорс проекта может быть фактором для него. В работе на полную ставку вы работаете немало часов в день над проектом. Это позволяет работать над функциональностью с приоритетами. С опен сорсом люди часто жертвуют час тут и пару дней там. Функциональность может занять одному разработчику не мало времени для выполнения, в то время когда другие, с большим количеством свободного времени, смогут довести свои изменения до приемлемого качества раньше. В такой ситуации выборочный подход может быть более важен. Важно осознавать, что инструменты, которыми вы, пользуетесь не зависят от стратегии которую вы выбираете. Несмотря на то, что многие ассоциируют DVCS с feature branching, они могут быть использованы и с CI. Все, что вам нужно сделать, это пометить одну из веток как основную линию. Если все делают pull и push в эту ветку каждый день, тогда у вас есть самая что ни на есть основная линия. На самом деле, в хорошо дисциплинированной команде я предпочту использовать DVCS для CI проекта, чем централизованную VCS. С менее дисциплинированной командой я буду беспокоиться что использование DVCS подтолкнет людей к ветвям-долгожителям, в тот момент когда централизованная VCS и усложнение брэнчинга подтолкнет их к частым коммитам в основную линию.

P. S. От переводчика на изучение вопросов к подходам использования VCS меня сподвигла эта статья, благодаря которой я начал искать более подробные описания «правильного» использования бранчинга и натолкнулся на выше переведенный текст. Хотя я не претендую на качество перевода, мне просто хочется попасть в ленту к разработчикам и дать им повод задуматься от противоположного принятому в опен сорсе подходу (форкинг). Не бейте больно палками, но критикуйте конструктивно, я это делаю в первый раз :-).

Теги:
  • continuous integration
  • ci
  • vcs
  • branching
  • merge conflict

habr.com

Git: обучение основам

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

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

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

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

Тогда мы создаем новый бранч, а затем, когда все наши желаемые изменения находятся в рабочей директории, мы коммитим их в новый бранч. Так что коммит сейчас создан для нового бранча, а не для основного. Если кто-то сделает чекаут основного бранча, он не увидит наших изменений. Затем мы снова можем переключиться на основной бранч, и мы можем переключиться на бранч revise_navigation, и каждый раз Git будет подстраивать файлы и папки из рабочей директории, чтобы они соответствовали нужному бранчу. Допустим, мы снова переключаемся на основной бранч и создаем в нем несколько коммитов, наш бранч revise_navigation находится в своем старом состоянии.

Мы можем переключаться между основным бранчем и бранчем revise_navigation, и в каждом будет свой набор изменений. Когда мы закончим, мы можем сказать - все, можно забирать эти изменения из revise_navigation. Допустим, нужна неделя, чтобы кто-то одобрил изменения для revise_navigation, ну вот они одобрены, в это время было сделано несколько коммитов, но все нормально, мы собираемся сделать слияние, и будет создан новый коммит, а этот коммит сделает слияние изменений для бранча the revise_navigation. Таким образом, теперь в основном бранче содержатся изменения, которые были сделаны в revise_navigation.

Мы можем вернуться в revise_navigation, что-то еще поменять и снова сделать слияние. Кстати, нам не всегда нужно делать слияние с основным бранчем, у нас может быть много бранчей, и мы можем обмениваться изменениями между этими бранчами. Прежде чем мы начнем создавать бранчи, я хочу снова вам кое-что показать, но на этот раз я хочу поговорить о том, где находится HEAD в конкретный момент, потому что я считаю, что вам это необходимо понимать. У нас есть основной бранч, и мы знаем, что HEAD всегда указывает на последний коммит в основной бранче, на верхушку текущего бранча. Но как только мы создаем новый бранч, все меняется.

Мы создаем новый бранч, в этот момент HEAD до сих пор указывает на коммит 534de, основной бранч и revise_navigation на данном этапе абсолютно одинаковы. не было сделано никаких дополнительных коммитов, так что HEAD указывает на тот же коммит. Только тогда, когда мы создадим новые коммиты в бранче revise_navigation, HEAD переместится на новый коммит, коммит, которого нет в основном бранче. И мы можем перемещаться туда и обратно. Если мы переключимся обратно на основной бранч, тогда HEAD будет указывать на верхушку текущего бранча, а это основной бранч.

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

Page 2

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

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

Когда мы использовали его в первый раз, мы просто прописывали git diff. И тогда эта команда возвращала изменения, сделанные между рабочей директорией и буфером. То есть, это все изменения, которые могут быть вставлены в буфер, изменения, которые мы недавно сделали, но еще не вставили в буфер. Как только мы отправим их в буфер, они не будут больше показаны в git diff, Помните, нам нужно было добавить к этой команде другую опцию, это staged или cached, и это синонимы. Эти опции показывают нам различие между буфером и репозиторием или HEAD, тем, на что указывает HEAD.

В общем, diff - очень гибкое средство. Оно позволяет нам передать другие вещи, так что мы можем сравнивать не только рабочую директорию, буфер и репозиторий. Если мы передадим SHA, который обращается к коммиту, нам будет показана разница между рабочей директорией и директорией в тот момент времени, когда был сделан коммит. Например, пишем git log oneline, и вот у нас есть список, и я собираюсь использовать вот этот SHA - cdae0ed. Если мы напишем git diff и просто передадим этот SHA, который относится к этому коммиту. Мне возвращаются все различия между директорией в тот момент времени и моей текущей рабочей директорией.

Давайте еще кое-что рассмотрим. Давайте зайдем сюда, вернемся немного назад, здесь много изменений. Очищаем экран, git diff, и вот на этом 1506576 нажимаем Return. Видите, у меня много изменений. Мне показано все, что было изменено, включая переименование файлов и тому подобное. Нажимаю Q, чтобы выйти. Это все хорошо, потому что сейчас мы можем сравнить то, где мы находимся сейчас, с тем, что было раньше, просто посмотрев назад - что же изменилось за это время? И мы можем быть более конкретными, указав конкретный файл.

Например, мы хотели бы использовать тот же коммит и выяснить, какие есть различия только в файле tours.html, эта команда посмотрела бы и сказал нам. Это сравнение между предыдущим этапом и рабочей директорией в ее нынешнем состоянии. Очень удобный инструмент. Теперь, мы не обязательно должны использовать рабочую директорию, на самом деле мы можем вообще сравнить два коммита. Мы можем передать два различных tree-ishe, используя range. Мы уже видели range, когда работали с файлом логов, таким образом, это диапазон между двумя произвольными коммитами. Давайте снова напишем git log oneline, и мы увидим список этих коммитов.

Давайте сравним эти два коммита, вот эти, которые начинаются с c. Что же изменилось между ними? Пишем git diff, а затем первый, за которым следуют две точки, а затем второй, вот и все. Слепок в этой временной точке против слепка в другой временной точке, покажи мне, что изменилось между ними, и Git отдаст мне сводку всего того, что было изменено. Нам интересно только то, что изменилось в файле tours, Git его просматривает и говорит, что на самом деле ничего не поменялось между этими двумя точками во времени.

Еще раз, вы можете передать сюда любой tree-ish, например, если вы напишете git log oneline и захотите сравнить это с HEAD, git diff ..HEAD, сравнение будет сделано. Если мы хотим написать git diff ..HEAD parent-parent, мы можем сделать это, все те трюки, которые касаются обращения к коммитам, мы можем здесь использовать. Вы можете заглянуть в документацию Помощи, и здесь есть много модификаторов и опций, которые можно использовать с diff. Однако я покажу вам всего лишь пару из них, которые я считаю наиболее полезными.

Давайте снова пропишем git log oneline, а затем начнем фактически с начала, git diff, а затем мы перейдем отсюда к HEAD. Первые опции, которые мне кажутся полезными, это stat и summary, и вы можете использовать их по отдельности или вместе, они покажут вам сводку того, что было изменено, список файлов со сводкой того, как много всего здесь было изменено, то, что было добавлено, то, что было удалено. А другие две опции, которые мне кажутся наиболее полезными, это опции b и w.

Давайте посмотрим. На самом деле, вы не увидите здесь никакой разницы, потому что ее и нет, но опция -b - это то же самое, что и --ignore-space-change (игнорирование изменений с пробелами). Итак, она работает с пробелом. То есть она игнорирует, изменил или нет кто-то один пробел на два, четыре пробела на пять, она это игнорирует, это опция b. Второй является опция w, которая игнорирует все пробелы. Она игнорирует любое изменение, которое можно сделать с пробелом (--ignore-all-space), она просто говорит, забудьте об этом.

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

Это мощные средства, и они помогут вам получить больше выгоды от Git.

Page 3

В последнем ролике мы рассмотрели, как получить выгоду от логов коммитов, так что когда мы рассматриваем логи коммитов, мы видим список коммитов. А что если мы хотим рассмотреть конкретный коммит? Мы хотим увидеть, что изменилось. О чем был этот коммит? Я хочу его изучить и понять, что было закоммичено. Это можно сделать при помощи git show, итак, git show, а затем SHA. Давайте это рассмотрим. Пишем git log oneline и получаем список наших коммитов, а теперь давайте покажем один из них, git show, рассмотрим вот этот, где солнечные очки перемещаются выше по списку предложенных принадлежностей для путешествия.

Я просто вставляю сюда SHA и нажимаю Return. Мне показан полный SHA коммита, мне показаны автор и дата, сообщение коммита и суть коммита (то есть разницу между версиями). Помните, мы видели разницу раньше, когда пытались увидеть, что изменилось, прежде чем сделать коммит. Мы можем сравнить два коммита, чтобы увидеть, что изменилось. Вот, Git показывает нам, что было закоммичено, разницу между тем, что было до и что стало после. Вот, солнечные очки появились в этом месте, а в этом месте удалены. Вот в чем состоялось изменение, и вот здесь мы видим плюс и минус, а также изменения показаны другим цветом.

Я могу нажать пробел, чтобы убедиться, что после этого ничего нет, а затем Q, чтобы выйти отсюда. Вот и все, вот на что похожа команда show. Давайте снова посмотрим на наши логи, рассмотрим другой, допустим, тот, где был добавлен файл. Вот на что он похож, git show, а затем SHA, и вот как он выглядит. Здесь добавлен файл, разница здесь - /dev/null, то есть компьютер говорит, что он не существовал до этого, а теперь он здесь. Если сравнить ни с чем, все добавлено, все эти элементы добавлены, все новое.

То же самое происходит, если мы что-то удаляем, все, конечно, будет удалено, и все будет показано с минусами. Конечно, вы можете передать сюда форматы, обычно формат появляется наверху, например, format=, и мы можем написать oneline, а затем передать SHA, я просто напишу HEAD, и вот как выглядит наш HEAD. Давайте взглянем на предыдущий HEAD, а вот тот, что был до этого, и давайте вернемся на два HEAD назад, или HEAD~3, который показывает еще более ранний коммит, так что мы можем возвращаться и смотреть на предыдущие коммиты.

Есть некоторые другие опции, которые вы можете передать. Вы можете посмотреть документацию, чтобы найти их. Теперь, то что мы передаем, это tree-ish объект, как и ранее, но git show на самом деле обрабатывает их немного по-другому. Если мы посмотрим в документации git help show, вы увидите, что эта команда принимает blob, tree, tag и commit, и наиболее часто мы работаем здесь с commit (коммитами). Если мы передадим tree, она покажет имена, находящиеся внутри tree, это эквивалент git ls-tree с опцией name-only, это то же самое.

Для чистых blob, а это файлы, будет показан контекст. Давайте я вам это быстренько покажу. Пишем git ls-tree master, вот наш список. Здесь видны наши активы, я просто беру первую часть и говорю git show это tree. Это tree, на котором мы хотим сфокусироваться, это объект tree-ish, который мы передаем, и Git дает мне список того, что находится тут внутри. Опять же, это то же самое, что и git ls-tree с опцией name-only. Давайте сделаем то же самое с файлом.

Здесь есть файл index, опять же, мы передаем объект tree-ish, git show, а затем мы передаем этот SHA, и вот тут появляется контекст. Я хотел бы отметить, что это не то же самое, что написать git show index.html. Это не tree-ish. Мы не можем это передать. Это не ссылка, которую Git воспринимает как tree-ish объект, поэтому он говорит нам, что он не знает, что это вообще такое. Мы должны передать SHA или какую-то другую ссылку на файл, чтобы он мог понять tree-ish.

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

Page 4

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

Первой будет git log oneline. Я считаю ее одной из наиболее полезных опций, потому что она дает нам однострочный список того, что находится в файлах с логами, вместо большого списка, который нужно прокручивать, эта опция сжимает список. Она дает нам часть SHA, который мы можем использовать, чтобы обратиться к коммитам, если нужно с ними поработать. Мы также можем использовать число коммитов, чтобы установить лимит на то, сколько коммитов будет показано, например, -3 покажет три коммита, -5 покажет пять коммитов. Мы также можем отфильтровать коммиты по времени.

Например, git log, а затем мы можем использовать опцию since, а затем в кавычках мы можем вставить различное время. Например, 2012-06-20, и Git понимает, что это 2012 год 20ое июня. Нажимаем Return и получаем только те записи, которые были сделаны с тех пор. Мы также можем использовать after вместо since, это синонимы. Также мы можем использовать until, и будут показаны коммиты, сделанные до определенной даты, это синоним опции before. То есть before и until оба работают одним и тем же способом, и мы, естественно, можем комбинировать эти опции. Также мы можем использовать другие форматы.

Например, 2 weeks ago (2 недели назад), просто как строку, и Git нас поймет. И, допустим, 3 days ago (3 дня назад), тогда будет перечислено все в промежутке от двух недель до трех дней назад, нажимаем в конце Return. Вы можете даже просто написать 3.days и 2.weeks, и Git сможет это также понять. Есть много форматов, чтобы сделать одно и то же. Вы свободны в том, как указать временной период.

Мы также можем сделать поиск по автору (author), автор, это человек, который сделал коммит, допустим, мы ищем кого-то с именем Кевин (Kevin), И если бы было несколько Кевинов, были бы показаны все они. А если я напишу Kevin Skoglund (Кевин Скогланд), тогда, конечно, будет показано мое имя, и вот показан Kevin Skoglund. На данном этапе только мы сами делали коммиты, но если бы мы работали с другими людьми, то неплохо фильтровать коммиты и видеть, какие из них сделал, например, Джон. Допустим, я хочу видеть только коммиты Джона, а именно то, что он сделал за последнюю неделю. Мы также можем использовать GREP для сообщений коммитов. GREP - это глобальный поиск регулярных выражений, поэтому мы можем написать git log grep =, а затем в кавычках любое регулярное выражение, которое мы хотим найти. Я ищу temp, и мне будут возвращены две вещи, в которых есть сообщения о файле temp, мы видим оба этих элемента.

Это действительно хорошая возможность, если вы хотите найти коммит по конкретной теме. Вот вы видите, что хорошие сообщения для коммитов очень и очень полезны, потому что они позволяют нам находить элементы в сообщениях, которые касаются конкретных разделов в коде, над которым мы работаем. Мы также можем указать конкретный промежуток (range), давайте напишем git log oneline, и вот мы здесь, и допустим, мы хотим видеть все коммиты, начиная от этого коммита, пишем git log, а затем вставляем этот SHA точка точка, таким образом мы определяем промежуток и говорим начать с этого. Мне будут показаны только эти коммиты, я продолжу и снова пропишу все в одну строку, и нам действительно понятно, что происходит.

Мне показаны коммиты, начиная от этого и заканчивая этим, вот так мы используем диапазон (range). Мы снова будем использовать эту опцию позже. Мы также можем запросить информацию о том, что случилось с конкретным файлом. Например, я могу сказать - скажи мне все, что случилось с тех пор, когда был сделан initial commit, git log от initial commit и вперед, и я могу вставить коммит или просто оставить его пустым. Если в конце диапазона (range) ничего не стоит, мне будет сказано, что случилось с файлом index.html. Я хочу посмотреть логи, которые касаются этого файла, давайте попробуем это сделать.

Есть один подходящий коммит, вот он, и это то, что было изменено в данном файле. Итак, если вы работаете с конкретным файлом и хотите просмотреть историю, чтобы увидеть, что происходило с файлом раньше, логи позволят вам сделать это. Мы можем узнать больше информации о коммитах при помощи git log -p, это опция patch (патч), и она показывает нам разницу в том, что было сделано в каждом из них. Вот здесь сложения, а здесь вычитания. Так что мы действительно можем увидеть разницу. Это действительно удобно, особенно если вы хотите узнать, что произошло с каждым из этих файлов, потому что мы можем вернуться и сразу после log вставить опцию -p, и она не только говорит о том, какие коммиты были применены, она также показывает мне изменения. Я могу получить и то, и другое, каждое отдельное перечисленное изменение, которое случилось с файлом index.html.

Нечто подобное мы можем сделать при помощи git log --stat и --summary, и вы можете их использовать раздельно или вместе. Они покажут вам статистику того, что было изменено в каждом файле, и вот вы видите, в файл игнорирования было кое-что добавлено. Вот здесь в resources что-то было добавлено, а что-то было удалено, а вот здесь эта команда дает нам небольшое резюме о том, что было сделано. Это неплохая возможность, если вам не особо нужны детали того, что было сделано, вы просто хотите узнать о числе изменений и о том, где они были сделаны.

Далее идет log --format, мы уже видели одну из самых полезных опция и это oneline, которая предоставляет логи в одну строку, git log format фактически эквивалентен oneline. Но все же здесь есть небольшая разница, потому что эта опция возвращает полный SHA, а не частичный SHA, так что вы можете сравнить обе эти опции. Вот и все, и вот вы видите разницу между ними. Мы также можем указать другие формы форматов, и поэтому хорошо знать о том, что представляют собой форматы, потому что в дополнение к oneline, мы можем использовать short, или мы можем использовать medium, это по умолчанию, или мы можем использовать full, что дает нам немного больше информации, например, автора коммита, а есть и fuller, которая дает нам еще больше информации.

Здесь есть имейл, который сгенерирован удобным способом для написания имейла, здесь также есть raw. И она показывает нам сырую информацию, которая сохранена в Git. И последняя опция, которая мне нравится и которую я считаю полезной, это git log --graph. Она показывает нам граф каждого из наших коммитов. Прямо сейчас наш граф довольно прост и линеен, но когда у нас будут бранчи, и когда мы начнем ветвление и объединение, тогда эта опция покажет нам, что эти бранчи действительно объединены, мы к этому вернемся немного позже.

Хорошая комбинация этих опций - это git log --oneline --graph --all --decorate, если все это объединить, вы увидите симпатичный компактный список. В нем будут все бранчи, которые имеются в наличие, и эта комбинация даже нам скажет, на что указывает HEAD, а это верхушка основного бранча. Так что это действительная хорошая комбинация опций, которую стоит запомнить. Пожалуйста, поэкспериментируйте, войдите в файлы с логами и найдите форматы, которые полезны для вас и для вашей работы.

smarly.com

Как правильно работать с SVN

Цель написания данного документа — рассмотреть несколько возможный стратегий применения svn при создании репозитория web-проекта. Репозиторий должен удовлетворять основному требованию — в стабильную версию проекта не должны попадать дестабилизирующие изменения. Вариант, когда репозиторий испольуется просто как хранилище файлов, не рассматриваем из-за его явной простоты. Более того, он не соответствует основному требованию, предъявляемому к репозиторию. Если у вас есть замечания или предложения по этому документу Вы можете написать их в коментариях.

Стартегия 1

Описание

Данная стратегия является упрощенной и может быть применена в небольших коллективах разработчиков. Однако, предпочтительнее использовать стратегию 2, по скольку она в полной мере удовлетворяет требованию, что в стабильную версию проекта не должны попадать дестабилизирующие изменения. SVN позволит легко перейти от использования одной стратегии к другой, необходимо будет лишь утвердить ряд новых требований, предъявляемых к внесению изменений в репозиторий.

Структура репозитория
/ /trunk /tags/ /0.0.1 /0.0.2 ... /branches/ /0.0.1 /0.0.2 ...

Директория /trunk — основная ветка разработки проекта. В нее вносятся все изменения и исправления ошибок. Директория /tags содержит релизы проекта. Именно из поддиректорий директории /tags исходный код выкладывается на рабочие сервера.

Директория /branches необходима для упрощения внесения больших изменений в код проекта. В ней хранятся ветви разработки. Если человек разрабатывает большую фичу, то он должен создать себе бранч и время от времени синхронизировать его с trunk. По окончании разработки фичи этот бранч сливается с транком и удаляется.

Итак, /trunk — основная ветвь разработки системы. Релизы создаются на основе содержимого директории /trunk. Создавать релизы на основе бранчей в данном случае не рекомендуется.

Соглашение о релизах и ветвях разработки

Номер релиза состоит из трех цифр, разделенных точкой. Для удобства описания обозначим его как X.Y.Z.

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

Цифра Y отвечает за добавление новых фич и исправление ошибок, то есть номер Y увеличиваем при добавлении новых возможностей, заметных на уровне конечного пользователя. Поясню на счет исправления ошибок — на первый взгляд может показаться, что за исправление ошибок должен отвечать номер Z, но это не так. Например, в релизе 1.2.3 исправили несколько ошибок, залили их в /trunk. А через некоторое время выпустили новую версию 1.3.0 с фичами и этими исправлениями ошибок, т.е. багфиксы «автоматом» слились в Y при создании нового релиза из /trunk.

Z — исправление ошибок, внесение незначительных исправлений (например, небольшие изменения дизайна).

Примеры

Пример 1 Текущий релиз: 1.2.4. Задание: Необходимо исправить ряд небольших ошибок, замеченных с момента релиза.

Действия: Вносим изменения в /trunk. Создаем стабилизирующий релиз /tags/1.2.5/. Выкладываем.

Пример 2 Текущий релиз: 1.2.4 Задание: Необходимо разработать дополнительную фичу, выполнение задания займет от нескольких дней до нескольких недель.

Действия: Создаем бранч /branches/1.3.0/. Работаем в бранче. Несколько раз в неделю синхронизируемся с /trunk для поддержания актуальности бранча и упрощения последующего слияния бранча с транком. По окончании разработки бранч синхронизируется с транк. Тестируется. Если все в порядке, то изменения заливаются в транк. Бранч удаляется. На основе транка делается релиз /tags/1.3.0/.

Пример 3 Текущий релиз: 1.2.4 Задание: Необходимо разработать несколько (пусть две) дополнительных фич, выполнение задания займет от нескольких дней до нескольких недель.

Действия: Создаем бранчи /branches/1.3.0 и /branches/1.3.1. Работаем независимо в разных бранчах. Несколько раз в неделю бранчи синхронизируются с trunk для поддержания актуальности бранчей и упрощения последующего слияния бранчей с транком. По окончании разработки бранчи по очереди синхронизируется с транком. Результат тестируется. Если все в порядке, то изменения заливаются в /trunk. Бранчи удаляются. На основе транка делается релиз /tags/1.3.0/.

Стартегия 2

Описание

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

Структура репозитория
/ /trunk /tags/ /0.1.0 /0.1.1 ... /branches/ /0.1-stable /0.2.0 /0.2.1 ...

Директория /trunk — основная ветка разработки проекта. В нее вносятся все изменения и исправления ошибок. Директория /tags содержит релизы проекта. Именно из поддиректорий директории /tags исходный код выкладывается на рабочие сервера.

Директория /branches необходима для упрощения внесения больших изменений в код проекта, а также для внесения исправлений в текущий релиз проекта.

Соглашения о релизах

Номер релиза состоит из трех цифр, разделенных точкой. Для удобства описания обозначим его как X.Y.Z.

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

Цифра Y отвечает за добавление новых фич и исправление ошибок, то есть номер Y увеличиваем при добавлении новых возможностей, заметных на уровне конечного пользователя. Поясню на счет исправления ошибок — на первый взгляд может показаться, что за исправление ошибок должен отвечать номер Z, но это не так. Например, в релизе 1.2.3 (на самом деле изменения вносятся в бранч /branches/1.2-stable) исправили несколько ошибок, залили их в /trunk. А через некоторое время выпустили новую версию 1.3.0 с фичами и этими исправлениями ошибок, т.е. багфиксы «автоматом» слились в Y при создании нового релиза из /trunk.

Z — исправление ошибок, внесение незначительных исправлений (например, небольшие изменения дизайна).

Соглашения о ветвях разработки

Ветви разработки могут быть двух типов в зависимости от назначения ветви. Первый вариант — ветви для существенных изменений (номера вида 0.1.0, 0.2.0 и т.д.). Если человек разрабатывает большую фичу, то он должен создать себе бранч и время от времени синхронизировать его с /trunk. По окончании разработки фичи этот бранч сливается с /trunk и удаляется. Далее будем называть такие ветви временными.

Если ветвь временная, то ее название будет состоять из трех цифр — X.Y.Z. Номер Y должен быть на единицу больше номера текущего релиза. То есть если текущий релиз имеет версию 1.2.4, то создавать ветвь необходимо с номером 1.3.0.

Второй вариант — ветви, соответствующие релизам, выкладываемым на рабочие сервера (номера вида 0.1-stable, 0.2-stable). Эти ветви необходимы для исправления ошибок в текущем релизе. На основе этих ветвей создаются стабилизационные релизы. Это ветви релиза. Номера ветвей релиза имеют другую структуру — X.Y-stable.

Примеры

Пример 1 Исходные данные: исходники в /trunk. Задание: Необходимо создать первый релиз и выложить код на рабочие сервера. Действия: Создаем бранч для поддержки (см. пример 2) релиза (svn copy /trunk /branches/1.0-stable)

Создаем первый релиз (svn copy /branches/1.0-stable /tags/1.0.0)

Пример 2 Исходные данные: В релизе 1.0.0 из примера 1 обнаружен ошибка. Задание: Необходимо исправить ошибку. Действия: Исправляем ошибку в /branches/1.0-stable Портируем изменения в /trunk Создаем стабилизационный релиз /tags/1.0.1 (svn copy /branches/1.0-stable /tags/1.0.1)

Переключаем сервер на новый релиз командой svn switch.

Пример 3 Исходные данные: текущий релиз 1.0.1. Задание: разработать новую фичу для проекта, вермя выполнения около недели. Действия: Создаем бранч /branches/1.2.0 Все изменения относительно новой фичи вносятся в эту ветвь. Ветвь систематически синхронизируется с /trunk для поддержания актуальности ветви. По окончании работ над фичей ветвь сливается с /trunk. /trunk тестируется. Если все в порядке, то бранч /branches/1.2.0 удаляется. На основе /trunk создается новые релиз /tags/1.2.0 На основе /tags/1.2.0 создается бранч релиза /branches/1.2-stable

/tags/1.2.0 выкладывается на рабочие сервера

Замечания

/trunk — основная ветвь разработки системы. Релизы создаются на основе ветвей /branches/x.x-stable.

Следует отметить, что при выходе релиза 1.2.0 отпадает необходимость в поддержке бранча /branches/0.1-stable. Эту ветвь можно удалить.

Выводы

Вторая стратегия более надежна, но ее использование влечет за собой необходимость всегда деражть под рукой две рабочии копии проекта — бранч релиза и транк. Также потребуются некоторые усилия по поддержанию этих версий синхронизированными (в плане исправления ошибок). Первая стратегия более простая, не требует особых навыков управления репозиторием, и, пожалуй, больше подходит на начальном этапе использования svn или для небольших команд разработки.

world-blog.ru


Смотрите также

Календарь

ПНВТСРЧТПТСБВС
     12
3456789
10111213141516
17181920212223
24252627282930
31      

Мы в Соцсетях

 

vklog square facebook 512 twitter icon Livejournal icon
square linkedin 512 20150213095025Одноклассники Blogger.svg rfgoogle