Не так давно на одном из проектов нашей компании было принято решение наконец отказаться от использования 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)


  1. Ordinatus
    04.10.2018 12:36

    Есть несколько важных нюансов:

    Для запроса на слияние разработчик разрешает конфликты слияния (в случае наличия)

    Решать конфликты можно только локально из-за бага 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 branch in $(git branch -r | grep -Ev \'origin/release\' | grep -Ev \'origin/master\'); do if [[ "$(git log $branch --after "60 day" | wc -l)" -eq 0 ]]; then echo $branch | sed -e \'s:origin/::g\' >> ../for_delete_now.txt; fi; done

    Проверить всё ли ок по удаляемым веткам в for_delete_now.txt и не удалим ли мы лишнего и далее:
    cat ../for_delete_now.txt | while read branch; do git push origin --delete $branch; done; git remote prune origin


    Такая схема очень быстро обрастает энтропией и ломается, если её не поддерживать.
    К тому же гитлаб имеет очень странные баги, которые не исправляются годами и ломают некоторые моменты, например резолв конфликтов слияния, которые можно нормально сделать только локально, но сам МР только через веб, если нет руби. Пришлось переписывать МР на bash+curl через API что бы оно работало у разработчиков из CLI не затаскивая руби.


    1. JenoOvchi Автор
      04.10.2018 13:40

      Спасибо большое за ценные дополнения!
      На счёт конфликтов — двояко: большая часть доступна для решения из GUI, но попадаются такие, которые можно решить только локально.


    1. koropovskiy
      04.10.2018 14:33

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

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

      git branch --list --remotes origin/* --merged=origin/master


      Удалять можно не git командами, а через GitlabAPI, в моем случае это оказалось быстрее раза в 3.


      1. Ordinatus
        04.10.2018 15:07

        То ли у меня кривой инстанс (self-hosted не под моим управлением) то ли это баг, но API в этих эндпоинтах не стабилен.
        Проверку на мерж не делаю, потому что если прошло 2 месяца с последнего коммита, то ветку так и так надо удалять.


    1. hashtet
      05.10.2018 13:38

      На всякий случай: [0-9] = \d


    1. gecube
      05.10.2018 22:21

      Ишью в трекере гитлабу соответствующие, надеюсь, созданы? Движение по ним есть ?


  1. gecube
    05.10.2018 22:23

    JenoOvchi я силюсь и пытаюсь понять: какое тут ноу-хау? Или зря? Или это просто описание Вашего подхода (и не более)?


    1. JenoOvchi Автор
      05.10.2018 23:50

      Приветствую!
      По сути да — описание получившегося подхода.
      Когда я брался за эту задачу, то стал искать готовые варианты модели работы с Git, включавшие бы в себя ревью кода, и мне они не попались. Поэтому, когда мы пришли к более-менее рабочему варианту, я систематизировал получившийся материал и решил поделиться.
      З.Ы. Для контроля скинул текст паре человек на ревью — им показалось интересно :)