Привет! Я давно заметил, что процесс добавления нового кода в проект в большинстве команд может быть не всегда стандартизирован. Из-за этого могут возникнуть сложности в коммуникации разработчиков как на уровне описания добавленного кода, так и понимания, какое влияние несет новый функционал на сам проект. Кроме того, команде аналитиков, разработчиков и заказчикам проекта важно иметь описание хронологии изменений проекта в читабельном виде.

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

Стандартная процедура добавления нового функционала в проект выглядит так:

  1. Разработчик делает клонирование проекта из центрального хранилища на локальную машину.

  2. Создает новую ветку и вносит новый commit на локальном репозитории.

  3. Делает push ветки в центральное хранилище.

  4. Делает merge request данной ветки в main(or master).

  5. Происходит новый релиз проекта.

Source: https://www.atlassian.com/cs/git/tutorials/using-branches/git-merge
Source: https://www.atlassian.com/cs/git/tutorials/using-branches/git-merge

В процедуре возникает ряд вопросов:

  • как сделать автопроверку добавления некорректного описанного commit-а(commit-message) локально, чтобы впоследствии он не был добавлен в удаленное централизованное хранилище кода;

  • каким образом описывать commit, чтобы иметь понимание его влияния на проект;

  • как фиксировать автоматически новый релиз проекта, а также иметь описание хронологии изменений кода в истории.

Ответить на эти вопросы могут конвенции и инструменты, о которых я расскажу ниже.

Pre-commit hooks

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

В случае, если вы пытаетесь произвести commit с сообщением(commit-message), не соответствующим стандарту (только если вы не внесли дополнительные изменения в файл конфигурации), инструмент сommitlint его блокирует. Теперь commit не может быть внесен на локальный репозиторий, либо на удаленный GitLab instance-сервер, что позволяет исключить человеческий фактор некорректного заполнения commit-сообщений, структурное и полное написание которых зачастую игнорируются. 

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

Запускается инструмент commitlint как husky pre-commit hook, то есть локально на сервере, с которого автор хочет произвести отправку сommit-а на удаленный gitlab-сервер. 

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

<type>[optional scope]: <description>
[optional body]
[optional footer(s)]

Так выглядит пример commit-сообщения в соответствии с конвенцией описания:

feat(model): Add new OCR model which shows better performance on validation set

Ниже представлена таблица с описанием того, какие типы (название <type> в конвенции выше) использовать в случае добавления нового commit-а:

Type

Description

feat

A new feature

fix

A bug fix

docs

Documentation only changes

style

Changes that do not affect the meaning of the code (white-space, formatting etc)

refactor

A code change that neither fixes a bug nor adds a feature

perf

A code change that improves performance

test

Adding missing tests or correcting existing tests

build

Changes that affect the build system or external dependencies

ci

Changes to our CI configuration files and scripts

chore

Other changes that don't modify src or test files

revert

Reverts a previous commit

Pre-commit hooks в действии

Теперь покажу на примере, каким образом срабатывают pre-commit hooks. Для этого создадим проект под названием semantic-versioning, в котором помимо ветки main будет создана дочерняя ветка с названием feature/add-new-file.

  • Для примера добавил в эту ветку новый файл с названием test.txt:

  • Попытаемся сделать commit-message, не проходящий по конвенции наименования типов, описанных ранее в таблице:


    Видно, что наш commit не проходит из-за несоответствия конвенции названия типа внутри commit-message. Вместо указанного типа manual_test внутри commit-сообщения необходимо указать один из следующих: [feat, fix, refactor, config, ci, perf, test, docs, chore].

  • Теперь исправим название типа внутри сообщения на конвенциональные
    (manual_test -> feat)):

    Видим, что добавление нового commit-а прошло без ошибок.

  • Сделаем git-push в соответствующую названию remote ветку на gitlab server:

Semantic versioning

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

Semantic versioning — это способ автоматического версионирования проекта в зависимости от описания commit-message. Для того, чтобы проделать такое версионирование задействуется менеджер автоверсионирования проекта semantic-release, который использует знания о типе совершённых commit-ов исходя из commit-message для определения степени влияния внесенных изменений в проект. 

Semantic-version проекта — это tag (snapshot, фиксированное состояние) проекта, который характеризуется тремя числами, разделенными через точку:

При внесении новых commit-ов в проект возможно изменение его semantic-version.

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

  • MAJOR-число увеличивается, когда в проект вносятся глобальные изменения (breaking change);

  • MINOR-число увеличивается, когда происходит добавление новой фичи или функционала в проект;

  • PATCH-число увеличивается, когда происходят изменения в проекте вроде исправления ошибок (bug fixes).

Инструмент semantic-release помогает автоматически определять следующий tag проекта (то есть его semantic-version), генерирует обновленный changelog (файл с описанием хронологических изменений в проекте) и публикует новый релиз проекта.

Попробую продемонстрировать пример семантического версионирования проекта.

  1. Смотрим на последний commit, который был сделан в ветке  feature/add-new-file:

  1. На gitlab-сервере смотрим на имеющиеся ветки и теги и видим, что последний тег проекта равен v.1.1.0 :

  1. Попробуем сделать слияние (merge-request) feature/add-new-file ветки  c main-веткой. Мы видим, что изменения в данном commit-е касаются только добавления нового файла test.txt в проект:

  1. Делаем merge-request с main-веткой:

  1. После этого автоматически проходит заготовленный заранее нами сценарий pipeline(его мы разберем позже), который состоит только из одного шага: публикации (publishing) нового релиза проекта:

  1. Видим, что pipeline пробежал:

  1. Открываем главную страницу проекта и видим, что появился новый tag с номером v1.2.0:

Можно заметить, что в main-ветке будет храниться состояние проекта, соответствующее новому tag с версией v1.2.0:

Также посмотрим на сгенерированный CHANGELOG.md файл, который хранит хронологически упорядоченный список изменений,  внесенных в проект:

В итоге мы видим, что после выполнения всех шагов появилась новая версия проекта с тегом v1.2.0 и описанием того, какие изменения были сделаны в проект в контексте данного тега(внутри CHANGELOG.md-файла). Так как в нашем случае commit-message содержал описание типа добавления новой фичи (“feat: …”), что считается по конвенции минорным изменением проекта (MINOR changes), тег проекта автоматически увеличился с v1.1.0 -> v1.2.0.

Реализация semantic versioning

Чтобы запустить  семантическое версионирование проекта используем node.js-плагины, которые указываем в файле package.json для скачивания:

"devDependencies": {
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/commit-analyzer": "^8.0.1",
"@semantic-release/git": "^9.0.0",
"@semantic-release/gitlab": "^6.0.4",
"@semantic-release/release-notes-generator": "^9.0.1",
"semantic-release": "^17.0.7"}

Для того, чтобы установить необходимые пакеты в пайплайне, который состоит только из одного stage с наименованием “publishing”, нужно запустить команду npm install в корне проекта, где должен лежать файл package.json.

Следующий шаг —  запуск команды npx semantic-release, которая и выполняет автоверсионирование проекта. Сценарий pipeline-а, который указан ниже должен быть описан в файле .gitlab-ci.yml:

stages:
  - publishing
publishing:
 stage: publishing
 script:
    - npm install
    - npx semantic-release -b $CI_COMMIT_REF_NAME
  only:
    - main

Обратите внимание, что генерация и публикация тега новой версии проекта происходит только на main-ветке.

Для конфигурации релиза проекта нужно добавить файл releaserc.json в корне проекта, в котором можно кастомизировать настройки релиза.

Ради интереса можно взглянуть на логи stage-а публикации (publishing) внутри нашего pipeline после слияния веток. Там видно, что commit определился как минорный тип изменений для проекта:

Происходит вычисление нового тега в связи с данным commit-ом:

и, соответственно, его создание локально на gitlab-runner-е и последующая публикация на gitlab-сервере:

Заключение

В статье я рассмотрел best practices организации ведения кода в проекте: конвенции описания commit-сообщений, инструмент автопроверки нестандартных описаний коммитов commitlint и способ автоверсионирования проекта semantic versioning. Надеюсь, это будет полезно вам и позволит организовать удобное ведение изменений кода в проектах.

Комментарии (2)


  1. SabMakc
    06.05.2022 13:15

    С одной стороны - отличный подход, который может упростить работу с проектом.

    Но история коммитов - это далеко не changelog, т.е. не история изменения проекта. Так что назвать это лучшими практиками лично я не могу. Это далеко не универсальный подход.

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


  1. noodles
    07.05.2022 17:01
    +1

    Кроме того, команде аналитиков, разработчиков и заказчикам проекта важно иметь описание хронологии изменений проекта в читабельном виде.

    Может будет лучше релиз-ноутсы + граммотный техрайтер в штате, вместо бездушной простыни коммит-логов от приходящих\уходящих разработчкиов?