Не так давно на одном из проектов нашей компании было принято решение наконец отказаться от использования Subversion для хранения и версионирования кода в пользу Git.
Основными целями перехода были следующие:
- Повышение прозрачности процесса разработки.
- Внедрение обязательной процедуры код ревью до выноса обновлений на тестовые среды.
- Внедрение непрерывной интеграции для сборки обновлений после код ревью и установки их на тестовые среды.
Обязательным условием для достижения поставленных целей было использование GitLab (этот сервер Git уже использовался у заказчика и там даже уже жил код, относящийся к фронтовой части решения) и Jira (также уже использовалась у заказчика).
В качестве целевой модели разработки было предложено использовать Git Flow, добавив в неё процедуру код ревью. Данная модель разработки де факто стала стандартом в разработке программного обеспечения с открытым исходным кодом и используется большинством гигантов индустрии. Именно поэтому её поддержка встроена во многие популярные средства работы с Git. На тему его использования написано большое количество материалов, приведу наиболее удачные из них для первоначального ознакомления: раз и два.
Сама по себе эта модель предлагает лишь общие принципы ведения кода, оставляя за рамками процессы, сопутствующие его написанию. Поэтому реализация всего остального, в том числе код ревью, зависит от конкретного сервера Git. В этом плане наиболее удобен GitHub: он изначально строился как платформа для совместной работы большого количества независимых разработчиков и позволяет ограничивать права на отправку коммитов (Push) в репозитории с возможностью создания запросов на отправку кода. Помимо этого, GitLab предлагает свой рабочий процесс для ведения кода под названием GitLab Flow, заточенный под использование GitLab CI. Поэтому в GitLab функционал по созданию запросов на отправку кода не реализован и для проведения ревью кода изменений предлагается использовать запросы на слияние веток. Для сборки и установки артефактов на проекте уже использовался Jenkins, позволяющий гибко создавать и настраивать задачи сборки и развёртывания, и на GitLab CI было решено не переходить, попутно отбросив идею использования GitLab Flow.
Также отмечу, что для проекта были настроены интеграции в Jira и Git. В Jira в плагине Git был добавлен для отслеживания репозиторий, созданный для хранения исходного кода, а в GitLab у данного репозитория была настроена интеграция с Jira в разделе «Интеграции» репозитория.
Для решения данной задачи был разработан рабочий процесс для работы с кодом, по своей структуре схожий с Git Flow, но позволяющий производить ревью кода при каждом выносе изменений в основные ветки процесса (develop, release-n и master) средствами GitLab. Далее будет описан получившийся процесс, а также смежные с ним этапы непрерывной интеграции и доставки ПО на стреды. В скобках приведены соответствующие команды для выполнения.
Репозиторий, созданный для хранения исходного кода, выкачивается в локальный репозиторий (git clone) и в нём инициализируется Git Flow (git flow init) — помимо ветки master (для создания тегов с целью хранения стабильных релизов) создаётся ветка develop (основная ветка разработки, в которую интегрируются ветки функций, релизов и исправлений), задаются маски для веток функций, релизов и исправлений, а также совершается переход в ветку develop.
Далее в рабочую копию переносится актуальная ветка исходного кода из Subversion, производится коммит кода (git add -A + git commit -m “Commit message”) в ветку develop локального репозитория и его загрузка в удалённый репозиторий (git push origin develop). После этого можно начинать разрабатывать новый функционал, используя Git для версионирования кода.
При разработке загружается актуальная версия ветки develop и из неё создаются ветки для разработки новых функций (git flow feature start MYFEATURE) в соответствии с кодами задач Jira, в рамках которых ведётся разработка.
Автоматически производится переход в созданную ветку (git checkout MYFEATURE), запланированный функционал разрабатывается и изменения коммитятся в локальную ветку MYFEATURE (git commit -m “Commit message”). Заметим, что для корректной интеграции Git и Jira в сообщениях коммитов следует указывать код задачи в Jira, к которой это исправление относится. Тогда данные коммиты будут отображаться в соответствующих им задачах, а также в разделе «Коммиты Git» проекта, с помощью которого однозначно можно установить, что вошло в тот или иной релиз.
Когда функционал выбранной задачи разработан и готов к выносу на среду тестирования, производится загрузка созданных коммитов в удалённую ветку с аналогичным названием (git push -u origin MYFEATURE) и на тимлида разработки или исполняющего его обязанности заводится запрос на слияние загруженной ветки с веткой develop.
Для запроса на слияние разработчик разрешает конфликты слияния (в случае их наличия) и тимлид разработки (или и.о.) производит code review, в ходе которого возможно создание дополнительных коммитов (git commit -m “Commit message”) с исправлениями замечаний, полученных в ходе ревью кода, в ветке с новым функционалом и их отправка в центральный репозиторий (git push -u origin MYFEATURE). После успешного завершения ревью тимлид разработки (или и.о.) подтверждает слияние веток. Здесь не лишним является установка флага удаления ветки после слияния – в противном случае количество веток может быстро разрастись до неприличных масштабов.
Чтобы обеспечить непрерывную интеграцию в репозитории GitLab, в разделе «Интеграции» настраивается Web Hook, который осуществляет вызов в Jenkins задачи для сборки и установки нового функционала на тестовую среду. Jenkins с помощью плагина для работы с Git выкачивает исходный код, получает из него название задачи и с помощью API Jira запрашивает список компонентов, которые были изменены и должны быть собраны, запускает процесс сборки, осуществляет прогон Unit тестов и при их удачном прохождении загружает созданные артефакты в Sonatype Nexus и устанавливает их на тестовую среду. Если же на одном из этапов произошёл сбой или Unit тесты завершаются неудачей, то с помощью плагина для Telegram команда разработки оповещается об исходе сборки. Если же установка прошла успешно, то команда QA оповещается о готовности задачи к тестированию.
Если появляются дефекты, то производится загрузка актуальной версии ветки develop и от коммита слияния ветки MYFEATURE с веткой develop создаётся ветка hotfix-MYFEATURE (git checkout [BASECOMMIT] -b hotfix-MYFEATURE).
При создании автоматически производится checkout в созданную ветку, вносятся исправления и изменения коммитятся в локальную ветку hotfix-MYFEATURE (git commit hotfix-MYFEATURE -m “Commit message”). Когда исправление закончено и готово к выносу на среду тестирования, производится их пуш в удалённую ветку с аналогичным названием (git push -u origin hotfix-MYFEATURE) и создаётся запрос на слияние с веткой develop.
Для запроса на слияние разработчик разрешает конфликты слияния (в случае наличия) и производится code review, в ходе которого возможно создание дополнительных коммитов с исправлениями полученных замечаний. После успешного завершения ревью производится слияние веток. Сразу после переноса исправления в ветку develop также срабатывает Web Hook для вызова задачи в Jenkins для сборки, прогона Unit тестов, загрузки созданных артефактов в Sonatype Nexus и установки исправления на тестовую среду. Для исправлений работает аналогичный механизм оповещений.
Если все дефекты исправлены, то производится загрузка актуальной версии ветки develop и от коммита слияния ветки hotfix-MYFEATURE с веткой develop создаётся ветка release-m.n (git flow release start RELEASENAME [BASECOMMIT]).
Создание релизной ветки также инициализирует запуск Web Hook для вызова задачи в Jenkins, которая выкачивает исходный код из Git, получает из него название релизной ветки и с помощью API Jira запрашивает список компонентов, которые были изменены в рамках задач релиза, выкачивает актуальные версии из Sonatype Nexus и устанавливает их на среду регрессионного тестирования. Вслед за установкой релиза на среду регрессионного тестирования запускаются скрипты подготовки среды к тестированию (перезапуск приложений, очистка БД и пр.) и производится прогон регрессионных автотестов для проверки работы основного функционала системы, по результатам которого формируется отчёт с помощью плагина Allure Reports для Jenkins. После установки команда QA оповещается в Telegram о результатах прогона автотестов и готовности релиза к ручному регрессионному тестированию.
Если в ходе регрессионного тестирования появляются дефекты, то производится загрузка актуальной версии ветки release-m.n и от последнего коммита создаётся ветка hotfix/BUGNAME по имени дефекта в Jira (git checkout -b hotfix/BUGNAME [BASECOMMIT]).
Автоматически производится checkout в созданную ветку, вносятся необходимые исправления и изменения коммитятся в локальную ветку hotfix/BUGNAME (git commit hotfix/BUGNAME -m “Commit message”). Когда исправление закончено и готово к выносу на среду регрессионного тестирования, производится их пуш в удалённую ветку с аналогичным названием (git push -u origin hotfix/BUGNAME) и создаётся запрос на слияние с веткой release-m.n.
Для запроса на слияние разработчик разрешает конфликты слияния (в случае наличия) и производится code review, в ходе которого возможно создание дополнительных коммитов с исправлениями замечаний, полученных в ходе ревью кода. Эти коммиты также производятся в локальную ветку hotfix/BUGNAME (git commit hotfix/BUGNAME -m “Commit message”) и производится их пуш в удалённую ветку с аналогичным названием (git push -u origin hotfix/BUGNAME). После успешного завершения ревью производится слияние веток. Слияние инициализирует запуск Web Hook для вызова задачи в Jenkins, аналогичной предыдущей, но отличающейся тем, что она выкачивает код из Git, получает из него название дефекта, с помощью API Jira запрашивает список компонентов, которые были изменены в рамках исправления, собирает эти компоненты, загружает в Sonatype Nexus и устанавливает их на среду регрессионного тестирования. Далее по аналогии производится подготовка среды к автотестированию, прогон регрессионных автотестов и нотификация о его результатах.
Когда все дефекты исправлены, производится установка релиза на продуктивную среду. Для этого производится слияние ветки release-m.n с ветками develop и master, а также создаётся релизный тег.
При его создании инициализирует запуск Web Hook для вызова задачи в Jenkins, которая выкачивает исходный код из Git, получает из него номер релиза и с помощью API Jira запрашивает список задач, которые вошли в релиз и компонентов, которые были изменены в рамках этих задач, после чего выкачивает актуальные версии артефактов из Sonatype Nexus и устанавливает их на продуктивную среду.
С хотфиксами для прода было решено использовать процесс, аналогичный релизному – в противном случае теряются стадии тестирования выносимых изменений.
При внедерении процесса также было проведено обучение для сотрудников, не имеющих практики работы с Git и GitLab, для которого была разработана соответствующая программа обучения. С её помощью вы сами сможете проводить обучение по использованию Source Tree и Intellij IDEA для работы с Git, а также GitLab для проведения ревью кода. В следующем посте приведу её, дополнив иллюстрациями.
Комментарии (8)
gecube
05.10.2018 22:23JenoOvchi я силюсь и пытаюсь понять: какое тут ноу-хау? Или зря? Или это просто описание Вашего подхода (и не более)?
JenoOvchi Автор
05.10.2018 23:50Приветствую!
По сути да — описание получившегося подхода.
Когда я брался за эту задачу, то стал искать готовые варианты модели работы с Git, включавшие бы в себя ревью кода, и мне они не попались. Поэтому, когда мы пришли к более-менее рабочему варианту, я систематизировал получившийся материал и решил поделиться.
З.Ы. Для контроля скинул текст паре человек на ревью — им показалось интересно :)
Ordinatus
Есть несколько важных нюансов:
Решать конфликты можно только локально из-за бага Gitlab, который делает еще и бэкмерж.
Вся политика слетает в первую неделю, если нет update гит хуков, которые проверяют всё — именование, исходную и целевую ветку слияния. Проверить именование можно только простой регулярочкой, типа такой:
^(refs\/heads\/)?(dev|master|private\/.+|feature\/.+|bugfix\/PX-[0-9]+|hotfix\/PX-[0-9]+|release\/[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?)
Чистить придется всё равно. Это можно сделать вручную через веб интерфейс, отсортировав бранчи по дате или простой коммандой в две строки:
Проверить всё ли ок по удаляемым веткам в for_delete_now.txt и не удалим ли мы лишнего и далее:
Такая схема очень быстро обрастает энтропией и ломается, если её не поддерживать.
К тому же гитлаб имеет очень странные баги, которые не исправляются годами и ломают некоторые моменты, например резолв конфликтов слияния, которые можно нормально сделать только локально, но сам МР только через веб, если нет руби. Пришлось переписывать МР на bash+curl через API что бы оно работало у разработчиков из CLI не затаскивая руби.
JenoOvchi Автор
Спасибо большое за ценные дополнения!
На счёт конфликтов — двояко: большая часть доступна для решения из GUI, но попадаются такие, которые можно решить только локально.
koropovskiy
бекмерж не является багом как таковым. Это нормальный способ решения конфликтов, если ветка отводилась от той же ветки в которую сливается.
Лучше конечно они бы делали промежуточную ветку, где решался бы конфликт, но уж что имеем.
При чистке полезно бывает проверить, что ветка уже слита. Например так:
Удалять можно не git командами, а через GitlabAPI, в моем случае это оказалось быстрее раза в 3.
Ordinatus
То ли у меня кривой инстанс (self-hosted не под моим управлением) то ли это баг, но API в этих эндпоинтах не стабилен.
Проверку на мерж не делаю, потому что если прошло 2 месяца с последнего коммита, то ветку так и так надо удалять.
hashtet
На всякий случай: [0-9] = \d
gecube
Ишью в трекере гитлабу соответствующие, надеюсь, созданы? Движение по ним есть ?