Улучшаем процесс ведения проекта в Git
Привет! Я давно заметил, что процесс добавления нового кода в проект в большинстве команд может быть не всегда стандартизирован. Из-за этого могут возникнуть сложности в коммуникации разработчиков как на уровне описания добавленного кода, так и понимания, какое влияние несет новый функционал на сам проект. Кроме того, команде аналитиков, разработчиков и заказчикам проекта важно иметь описание хронологии изменений проекта в читабельном виде.
Поэтому решил написать статью, в которой хотелось бы затронуть тему стандартизации процесса, используя конвенции и различные инструменты, позволяющие соблюсти правильное и понятное развитие кода проекта, что и применяется в нашей компании. Статья может быть полезна всем тем, кто ведет проекты в git.
Стандартная процедура добавления нового функционала в проект выглядит так:
Разработчик делает клонирование проекта из центрального хранилища на локальную машину.
Создает новую ветку и вносит новый commit на локальном репозитории.
Делает
push
ветки в центральное хранилище.Делает
merge request
данной ветки в main(or master).Происходит новый релиз проекта.
В процедуре возникает ряд вопросов:
как сделать автопроверку добавления некорректного описанного 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 (файл с описанием хронологических изменений в проекте) и публикует новый релиз проекта.
Попробую продемонстрировать пример семантического версионирования проекта.
Смотрим на последний commit, который был сделан в ветке feature/add-new-file:
На gitlab-сервере смотрим на имеющиеся ветки и теги и видим, что последний тег проекта равен v.1.1.0 :
Попробуем сделать слияние (merge-request) feature/add-new-file ветки c main-веткой. Мы видим, что изменения в данном commit-е касаются только добавления нового файла test.txt в проект:
Делаем merge-request с main-веткой:
После этого автоматически проходит заготовленный заранее нами сценарий pipeline(его мы разберем позже), который состоит только из одного шага: публикации (publishing) нового релиза проекта:
Видим, что pipeline пробежал:
Открываем главную страницу проекта и видим, что появился новый 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. Надеюсь, это будет полезно вам и позволит организовать удобное ведение изменений кода в проектах.