Теги и релизы: версии без паники

Теги и релизы: версии без паники

Коммиты - это хеши вроде 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.

Декомпозиция SemVer-версии v1.0.0: первая цифра MAJOR (красная) означает ломающие изменения, вторая MINOR (зелёная) - новые фичи без поломки совместимости, третья PATCH (amber) - багфиксы

Правила просты:

  • 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 теги хранят автора и дату - это важно для аудита. Lightweight теги подходят для временных меток или локальной работы. Для релизов - только annotated (`git tag -a`).

Работа с тегами

Создание и отправка

# Создать 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
Если тег опубликован и другие разработчики (или CI/CD) на него ссылаются - не удаляй. Создай новый тег. Удаление опубликованных тегов ломает кэши, зависимости и доверие команды.

Релизный процесс

Типичный 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

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.