Теги и релизы: версии без паники
Теги и релизы: версии без паники
Коммиты - это хеши вроде a1b2c3d. Никто не скажет «давай откатимся на a1b2c3d» в разговоре. Теги дают коммитам человеческие имена: v1.0.0, v1.2.3-rc1, v2.0.0-beta. Когда что-то ломается на проде, ты говоришь «откатись на v1.1.2» - и все понимают, о чём речь.
Теги - это не просто метки. Это основа релизного процесса. CI/CD запускает деплой по тегу, Go modules разрешают зависимости по тегам, changelog генерируется из тегов. Без тегов - хаос.
Семантическое версионирование (SemVer)
SemVer - это соглашение о формате версий: MAJOR.MINOR.PATCH.
Правила просты:
- PATCH (1.0.0 → 1.0.1): исправил баг, обновил зависимости, поправил опечатку в логе. Пользователь API ничего не заметит.
- MINOR (1.0.1 → 1.1.0): добавил новый endpoint, новый параметр с дефолтным значением, новую функцию в SDK. Старый код продолжает работать.
- MAJOR (1.1.0 → 2.0.0): удалил endpoint, переименовал поле в JSON-ответе, сменил формат авторизации. Старый код сломается.
v0.1.0 → v0.2.0 # до v1.0.0 всё может ломаться, это нормально
v1.0.0 → v1.0.1 # багфикс
v1.0.1 → v1.1.0 # новая фича
v1.1.0 → v2.0.0 # breaking change
Pre-release версии
Между стабильными релизами бывают промежуточные:
v2.0.0-alpha.1 # ранняя версия, может всё сломать
v2.0.0-beta.1 # feature-complete, но не протестирована на проде
v2.0.0-rc.1 # release candidate, почти готово
v2.0.0-rc.2 # ещё один кандидат (нашли баг в rc.1)
v2.0.0 # стабильный релиз
Pre-release версии сортируются до основной: v2.0.0-alpha.1 < v2.0.0-beta.1 < v2.0.0-rc.1 < v2.0.0.
Lightweight vs Annotated теги
Git поддерживает два типа тегов.
Lightweight - просто указатель на коммит, без дополнительных данных:
git tag v0.1.0
Annotated - полноценный объект Git с автором, датой и сообщением:
git tag -a v0.1.0 -m "Initial release: basic auth and health endpoint"
Разница видна при просмотре:
# Lightweight - показывает только коммит
git show v0.1.0
# commit a1b2c3d...
# Annotated - показывает информацию о теге + коммит
git show v0.1.0
# tag v0.1.0
# Tagger: Dmitriy Popov <dev@example.com>
# Date: Mon May 1 12:00:00 2026 +0300
# Initial release: basic auth and health endpoint
# commit a1b2c3d...
Работа с тегами
Создание и отправка
# Создать annotated тег на текущем коммите
git tag -a v1.0.0 -m "Release v1.0.0: user auth, progress tracking"
# Отправить тег в remote
git push origin v1.0.0
# Отправить все локальные теги разом
git push origin --tags
Тегирование старого коммита
Забыл поставить тег на позапрошлый релиз? Не проблема:
# Найти нужный коммит
git log --oneline
# a1b2c3d Fix build
# d4e5f6g Add login endpoint ← вот этот
# h7i8j9k Initial commit
# Поставить тег на конкретный коммит
git tag -a v0.1.0 d4e5f6g -m "Release v0.1.0: login endpoint"
git push origin v0.1.0
Список и фильтрация
# Все теги
git tag
# Теги, начинающиеся с v1
git tag -l "v1.*"
# Теги с датами и сообщениями
git tag -l -n1
# Последний тег в текущей ветке
git describe --tags --abbrev=0
Удаление тегов
# Удалить локально
git tag -d v0.0.1
# Удалить из remote
git push origin --delete v0.0.1
Релизный процесс
Типичный flow для команды:
1. develop → накапливаются фичи через MR
2. Решили: пора релиз
3. Создаём ветку release/v1.2.0 от develop
4. Финальные правки, тестирование
5. Мержим release/v1.2.0 в main
6. Ставим тег v1.2.0 на main
7. Мержим main обратно в develop (чтобы подтянуть правки)
# На main после мержа release-ветки
git checkout main
git pull origin main
git tag -a v1.2.0 -m "Release v1.2.0: rate limiting, OAuth improvements"
git push origin v1.2.0
GitLab Releases
GitLab умеет создавать Release из тега - страницу с описанием, changelog и прикреплёнными файлами (артефактами):
# Через GitLab UI:
# Repository → Tags → кнопка "Create release" рядом с тегом
# Через API:
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data '{"name":"v1.2.0","tag_name":"v1.2.0","description":"## Changes\n- Rate limiting\n- OAuth"}' \
"https://gitlab.example.com/api/v4/projects/1/releases"
Release в GitLab - это удобная точка входа для тех, кто хочет посмотреть «что нового в последней версии».
CI/CD по тегу
Pipeline можно настроить так, чтобы деплой запускался только при создании тега:
# .gitlab-ci.yml
deploy:
stage: deploy
script:
- echo "Deploying $CI_COMMIT_TAG"
- ./deploy/deploy.sh
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
Это значит: пуш в main ничего не деплоит, а git push origin v1.2.0 запускает полноценный деплой. Контроль и предсказуемость. Как устроен такой pipeline изнутри - в уроке про GitLab CI и публикацию образов, а откат на предыдущий тег на боевом сервере - в уроке про деплой на VPS.
Go modules и теги
В Go тег определяет версию модуля. Это не опция - это требование. Go modules используют теги для разрешения зависимостей.
# Go требует префикс v
git tag -a v1.2.0 -m "Release v1.2.0" # правильно
git tag -a 1.2.0 -m "Release 1.2.0" # Go не поймёт
Для major version 2+ Go требует суффикс в пути модуля:
// go.mod для v2+
module github.com/user/project/v2
В PHP/Composer тег тоже определяет версию пакета - Packagist подбирает релизы по git-тегам. Префикс v опционален, но рекомендуется. В коде это выглядит так - константа версии в основном классе библиотеки, которая совпадает с тегом:
<?php
declare(strict_types=1);
namespace User\Project;
final class Project
{
public const string VERSION = '2.0.0';
}
# Тег для v2
git tag -a v2.0.0 -m "Release v2.0.0"
Если ты пишешь библиотеку (на Go или PHP), теги - это контракт с пользователями. Нет тега - нет стабильной версии.
Защита тегов в GitLab
В GitLab можно защитить теги от случайного создания или удаления:
Settings → Repository → Protected tags
Tag pattern: v*
Allowed to create: Maintainers
Это гарантирует, что теги v* может создавать только maintainer. Разработчик не сможет случайно запушить v99.0.0.
Changelog из тегов
Если команда придерживается Conventional Commits (feat:, fix:, refactor:), changelog можно генерировать автоматически:
# Список коммитов между двумя тегами
git log v1.1.0..v1.2.0 --oneline
# Только feat и fix
git log v1.1.0..v1.2.0 --oneline --grep="feat:" --grep="fix:" --all-match=false
Существуют инструменты для автоматической генерации: git-cliff, conventional-changelog, встроенный GitLab Changelog. Но даже ручной git log между тегами - уже полезно. Остальные способы искать по истории - фильтры по автору, дате и содержимому diff - в следующих уроках.
Мини-задание
- Создай annotated тег
v0.1.0на текущем коммите с осмысленным сообщением - Запуши тег в remote и убедись, что он виден в GitLab
- Посмотри список тегов с фильтром
git tag -l "v0.*" - Поставь тег
v0.0.1на старый коммит (не текущий) - Посмотри разницу между коммитами двух тегов:
git log v0.0.1..v0.1.0 --oneline