В очередной раз душа поэта не выдержала и написала велосипед. Просто не нашел готового.
Как выглядит хороший релиз?
- заходим в pipeline основной ветки
main
или ветки горячего релизаrelease/*
- запускаем там задачу
Release
- в Gitea/Gitlab/Github создается релиз (в их терминах) с описанием изменений и тег
- по факту создания тега начинается релизный пайплайн (сегодня не про него совсем)
Для этого нам нужно 2 вещи (остальное достаточно тривиально):
- вычислить следующую версию на базе предыдущей, а из сообщений комитов понять какую из 3х цифр увеличивать на 1
- сформировать release notes на базе сообщений комитов.
Предполагается использовать такой формат сообщений комитов (я это делаю в описании MR, а не отдельных комитов, за исключением горячих исправлений):
- my mega feature
- fix: small fix for other feature
Some description about feature.
Чем это отличается от https://www.conventionalcommits.org/en/v1.0.0/ ? Наличием нескольких строк заголовка. Хорошо, когда можно на каждый MR создать отдельное тестовое окружение для QA и других разработчиков. Тогда правило 1 MR - 1 изменение (фича или баг) хорошо. Когда же этого нет, то приходится комбинировать в одном MR несколько исправлений или новую фичу и несколько исправлений. Для этих случаев предлагаемый формат как раз подходит.
Как получить предыдущую (ака текущую) версию релиза (к ней будем прибавлять)?
git describe --tags --abbrev=0 --always > currentVersion.txt
Как получить текущую версию (ее можно использовать, если деплоить на тестовое окружение)?
git describe --tags --always > currentVersion.txt
Для парсинга же сообщений комитов пришлось написать Python-скрипт. Из хорошего: это только 1 файл, без зависимостей от библиотек. Так что легко добавить в любой Docker-образ. Для удобства сделал образ с ним stepin/git-parse-commits:1.0.0
:
git-parse-commits version
git-parse-commits --tag-prefix "v" --tag releaseVersion > nextVersion.txt
git-parse-commits --tag-prefix "v" releaseNotes > releaseNotes.md
Справка по утиле (больше подробностей в репозитарии):
git-parse-commits -h
usage: git-parse-commits [-h] [-j] [-t [TAG_PREFIX]] [-s [SCOPE]] [-i [INITIAL_REVISION]] [-l [LAST_REVISION]] [--tag]
{version,currentVersion,lastReleaseVersion,releaseVersion,releaseNotes} ...
Provides next release version and release notes from git commit messages.
positional arguments:
{version,currentVersion,lastReleaseVersion,releaseVersion,releaseNotes}
version Prints version of this tool
currentVersion Prints current version (useful for non-release builds)
lastReleaseVersion Prints version of last release
releaseVersion Prints version of next release from git commit messages
releaseNotes Prints release notes from git commit messages
options:
-h, --help show this help message and exit
-j, --json Output in json format
-t [TAG_PREFIX], --tag-prefix [TAG_PREFIX]
prefix for tags (optional)
-s [SCOPE], --scope [SCOPE]
scope to filter release note items
-i [INITIAL_REVISION], --initial-revision [INITIAL_REVISION]
start range from next revision
-l [LAST_REVISION], --last-revision [LAST_REVISION]
stop on this revision
--tag add tag prefix to version (only if tag prefix is defined)
Скрипт даже подходит для mono-репозитариев (изменения по компонентам):
git-parse-commits version
git describe --tags --abbrev=0 --match 'component1-*' > currentVersion.txt
git-parse-commits --tag --tag-prefix "component1-" --scope component1 releaseVersion > nextVersion.txt
git-parse-commits --tag-prefix "component1-" --scope component1 releaseNotes > releaseNotes.md
Соответственно, если изменений нет, то и releaseNotes.md пустой (и можно не релизить компонент).
Ну, и как это может выглядеть в pipeline на примере Gitlab:
create_changelog:
stage: "build"
image:
name: "stepin/git-parse-commits:latest"
entrypoint: [""]
variables:
GIT_DEPTH: "0"
script:
- git-parse-commits version
- CURRENT_VERSION="$(git-parse-commits currentVersion)"
- RELEASE_VERSION="$(git-parse-commits --tag-prefix 'v' releaseVersion)"
- echo "RELEASE_VERSION=$RELEASE_VERSION\nCURRENT_VERSION=$CURRENT_VERSION" > relNotes.env
- git-parse-commits --tag-prefix 'v' releaseNotes > releaseNotes.md
artifacts:
reports:
dotenv: relNotes.env
paths:
- releaseNotes.md
expire_in: 1 day
rules:
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_REF_NAME == "main" && $CI_PIPELINE_SOURCE != "schedule"
- if: $CI_COMMIT_REF_NAME == "release/*" && $CI_PIPELINE_SOURCE != "schedule"
release:
stage: "release"
image:
name: "registry.gitlab.com/gitlab-org/release-cli:latest"
entrypoint: [""]
script:
- echo "Release $RELEASE_VERSION"
release:
tag_name: "$RELEASE_VERSION"
tag_message: "Release $RELEASE_VERSION"
description: "releaseNotes.md"
assets:
links:
- name: "Container Image $CI_COMMIT_TAG"
url: "https://$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA"
needs:
- "create_changelog"
rules:
- if: $CI_COMMIT_REF_NAME == "main" && $CI_PIPELINE_SOURCE != "schedule"
when: manual
allow_failure: true
- if: $CI_COMMIT_REF_NAME == "release/*" && $CI_PIPELINE_SOURCE != "schedule"
when: manual
allow_failure: true
(CURRENT_VERSION может использоваться для нерелизной сборки проекта – будет уникальная версия)
Отмечу, что этот автоматизм не обязателен: можно в UI сделать релиз руками. Как и исправить его описание руками.
Понятно, что все это не обязательно и можно делать всегда руками. Только это приводит к потере времени и мелким ошибкам. Можно же и без них.