Теги в Git: как работать с git tag и версионировать релизы
git tag ставит постоянную метку на конкретный коммит, обычно для обозначения релиза, например v1.2.0. Аннотированный тег создаётся командой git tag -a v1.2.0 -m "сообщение", а на сервер теги отправляются отдельно: git push origin v1.2.0 или git push --tags.
git tag — это именованная метка на конкретном коммите. В отличие от ветки, тег не двигается при новых коммитах: v1.2.0 через год будет указывать на тот же снимок кода. Поэтому теги используют для релизов, а CI/CD-системы часто запускают сборку и публикацию именно по появлению тега.
Лёгкие и аннотированные теги
В Git два вида тегов, и разница важна.
| Лёгкий (lightweight) | Аннотированный (annotated) | |
|---|---|---|
| Создание | git tag v1.0.0 | git tag -a v1.0.0 -m "..." |
| Что это | просто указатель на коммит | отдельный объект в базе Git |
| Автор, дата, сообщение | нет | есть |
| Подпись GPG/SSH | нет | возможна (-s) |
Учитывается git describe по умолчанию | нет | да |
| Когда использовать | личные временные метки | релизы и всё, что публикуется |
Для релизов используйте аннотированные теги: в них записано, кто и когда выпустил версию, и их можно подписать.
Как создать тег
На текущем коммите
git tag -a v1.2.0 -m "Релиз 1.2.0: экспорт отчётов, исправлен таймаут API"
Без -m Git откроет редактор для сообщения — удобно, если хотите вписать список изменений.
На старом коммите
Забыли поставить тег при релизе — не беда, тег можно повесить на любой коммит:
git log --oneline -5
# 7be41d0 feat: экспорт отчётов
# 1c02e8f fix: таймаут запросов
git tag -a v1.1.0 1c02e8f -m "Релиз 1.1.0"
Подписанный тег
git tag -s v1.2.0 -m "Релиз 1.2.0"
git tag -v v1.2.0 # проверить подпись
Нужен настроенный ключ GPG (или SSH при gpg.format=ssh). GitHub и GitLab показывают такие теги с отметкой о проверке.
Как посмотреть теги
git tag # все теги по алфавиту
git tag -l "v1.*" # по шаблону
git tag -n # с первой строкой сообщения
git tag --sort=-v:refname # по версии, новые сверху
git show v1.2.0 # данные тега и коммит
git describe --tags # ближайший тег: v1.2.0-3-g7be41d0
Обратите внимание на сортировку: по умолчанию v1.10.0 окажется перед v1.9.0, потому что строки сравниваются посимвольно. Ключ --sort=v:refname сортирует как версии.
Вывод git describe читается так: три коммита после тега v1.2.0, текущий коммит 7be41d0 (буква g означает git). Это удобный источник номера версии для сборок между релизами.
Как отправить тег на сервер
Обычный git push теги не отправляет — это частая причина вопроса «почему тега нет на GitHub».
git push origin v1.2.0 # один тег
git push --tags # все локальные теги
git push --follow-tags # коммиты + аннотированные теги, указывающие на них
--follow-tags безопаснее --tags: он не отправит случайные лёгкие теги, которые вы ставили для себя. Можно включить по умолчанию: git config --global push.followTags true.
Получить теги с сервера: git fetch --tags. Обычный git fetch тоже подтягивает теги, указывающие на загружаемые коммиты.
Как удалить или перенести тег
Удаление
git tag -d v1.2.0 # локально
git push origin --delete v1.2.0 # на сервере
# старый эквивалент: git push origin :refs/tags/v1.2.0
Перенос тега на другой коммит
git tag -a -f v1.2.0 <новый-хеш> -m "Релиз 1.2.0"
git push origin -f v1.2.0
Технически это возможно, но в публичных проектах переносить опубликованный тег не стоит. У тех, кто уже скачал тег, он не обновится сам: Git не перезаписывает существующие локальные теги при fetch без --force. В итоге у разных людей «v1.2.0» будет разным кодом. Правильнее выпустить v1.2.1.
Как узнать, какие коммиты вошли в релиз
Теги удобно использовать как границы диапазонов во всех командах чтения истории:
git log v1.1.0..v1.2.0 --oneline # коммиты между релизами
git diff v1.1.0 v1.2.0 --stat # какие файлы изменились
git tag --contains 1c02e8f # в каких релизах есть этот коммит
git merge-base --is-ancestor 1c02e8f v1.2.0 && echo "есть в релизе"
Последние две команды отвечают на частый вопрос поддержки: «исправление уже доехало до пользователей или нет?» Если исправление попало только в main, а релиз собран раньше, git tag --contains покажет пустой список, и станет понятно, что нужен новый тег.
Ещё один приём — скачать исходники конкретной версии без истории: git archive --format=zip -o app-1.2.0.zip v1.2.0. Так формируют архивы для поставки заказчику или для сборки в изолированной среде.
Как переключиться на версию по тегу
git switch --detach v1.1.0
# HEAD is now at 1c02e8f fix: таймаут запросов
Вы окажетесь в состоянии detached HEAD: смотреть и собирать можно, но новые коммиты не будут принадлежать ни одной ветке. Если нужно исправить старую версию, создайте ветку от тега:
git switch -c hotfix/1.1.x v1.1.0
# правки, коммит
git tag -a v1.1.1 -m "Исправление безопасности"
git push origin hotfix/1.1.x --follow-tags
Версионирование релизов: SemVer
Самая распространённая схема имён тегов — Semantic Versioning: MAJOR.MINOR.PATCH.
- MAJOR растёт при несовместимых изменениях API:
1.4.2 → 2.0.0. - MINOR — новая функциональность с сохранением совместимости:
1.4.2 → 1.5.0. - PATCH — исправления ошибок:
1.4.2 → 1.4.3. - Предрелизы помечаются суффиксом:
2.0.0-beta.1,2.0.0-rc.1.
Префикс v (v1.4.2) — распространённое соглашение для имён тегов, сам SemVer его не требует. Главное — выбрать один формат и придерживаться его, иначе сломаются скрипты, которые ищут последний релиз.
Теги в процессе релиза
Типичный процесс в команде выглядит так:
- Изменения сливаются в main, тесты зелёные.
- Обновляется CHANGELOG и номер версии в файлах проекта, если он там хранится.
- Ставится аннотированный тег:
git tag -a v1.5.0 -m "Релиз 1.5.0". git push origin main --follow-tags.- CI видит новый тег, собирает артефакты и публикует релиз; на GitHub и GitLab из тега создаётся страница Release с описанием и файлами.
Так любую версию в продакшене можно однозначно сопоставить с кодом: тег в имени сборки — и через git show видно, что в неё вошло. А git log v1.4.0..v1.5.0 --oneline даёт список изменений между релизами для заметок о выпуске.
Заметки к релизу из тегов
Если сообщения коммитов осмысленные, теги превращают историю в готовый список изменений. Последний и предыдущий релиз можно найти без ручного поиска:
LAST=$(git describe --tags --abbrev=0)
PREV=$(git describe --tags --abbrev=0 "$LAST^")
git log "$PREV..$LAST" --no-merges --pretty=format:"- %s (%h)"
# - feat: экспорт отчётов (7be41d0)
# - fix: таймаут запросов (1c02e8f)
Если команда пишет коммиты по соглашению Conventional Commits (feat:, fix:, feat!:), номер следующей версии можно вычислять автоматически: fix поднимает PATCH, feat — MINOR, восклицательный знак или BREAKING CHANGE — MAJOR. На этом построены популярные инструменты автоматических релизов: они сами ставят тег и пишут CHANGELOG.
Теги и CI/CD
Пайплайны обычно различают два события: push в ветку и появление тега. На ветку запускаются тесты и сборка тестового окружения, на тег — публикация. В GitHub Actions это выглядит так:
on:
push:
tags:
- "v*.*.*"
В GitLab CI аналогичную роль играет переменная CI_COMMIT_TAG и правило rules: - if: $CI_COMMIT_TAG. Имя тега внутри пайплайна становится номером версии Docker-образа или пакета, поэтому формат тегов должен быть строгим и предсказуемым.
Отсюда ещё одна причина не переносить теги: если пайплайн уже опубликовал пакет 1.2.0 в реестр, повторная публикация с тем же номером, как правило, запрещена, а образ с тегом 1.2.0 у пользователей останется старым.
Именование тегов: что допустимо
Имя тега подчиняется тем же правилам, что и имя ветки: без пробелов, без символов ~ ^ : ? * [ и двух точек подряд. Слэш допустим, поэтому в монорепозиториях встречаются теги вида api/v1.2.0 и web/v3.0.1 — по одному набору версий на каждый пакет. Проверить имя заранее можно командой git check-ref-format "refs/tags/v1.2.0": при ошибке она вернёт ненулевой код.
Защита тегов
В командных репозиториях релизные теги стоит защитить: GitHub и GitLab позволяют запретить создание, перезапись и удаление тегов по шаблону (например, v*) всем, кроме сопровождающих. Так случайный git push --tags с чужой машины не опубликует незапланированный релиз, а ошибочный git push origin --delete не сотрёт историю версий.
Типичные ошибки
- Тег не на сервере. Забыли
git push origin <тег>или--follow-tags. - Тег с тем же именем, что и ветка. Git выдаст предупреждение
refname is ambiguous; используйте префиксvдля тегов и разные имена. - Тег на незакоммиченном состоянии. Тег ставится на коммит, а не на файлы: незакоммиченные правки в релиз не попадут.
- Лёгкий тег для релиза. Нет автора, даты и сообщения,
git describeего по умолчанию не видит.
Итог
git tag фиксирует неизменяемую точку в истории: для релизов создавайте аннотированные теги через git tag -a, называйте их по SemVer и отправляйте на сервер явно — git push origin <тег> или --follow-tags. Опубликованные теги не переносите, а выпускайте новую версию. Теги вместе с rebase, stash и cherry-pick разобраны на практике в курсе по Git.
Частые вопросы
Чем тег отличается от ветки?
Ветка автоматически сдвигается на каждый новый коммит, а тег навсегда указывает на один и тот же коммит.
Можно ли поставить два тега на один коммит?
Да, количество тегов на коммите не ограничено, например v2.0.0 и latest-stable.
Удаляются ли теги при удалении ветки?
Нет, теги независимы от веток, и коммит с тегом остаётся доступен даже после удаления ветки.
Как узнать, в какой релиз попал коммит?
Выполните git tag --contains <хеш> — команда покажет все теги, в историю которых входит этот коммит.
Как получить последний тег в скрипте?
Используйте git describe --tags --abbrev=0 — он выведет имя ближайшего тега без суффикса с хешем.
Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».
Перейти к курсу