Инструкция

Теги в Git: как работать с git tag и версионировать релизы

Обновлено: · Skillaria.Top

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.0git 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 его не требует. Главное — выбрать один формат и придерживаться его, иначе сломаются скрипты, которые ищут последний релиз.

Теги в процессе релиза

Типичный процесс в команде выглядит так:

  1. Изменения сливаются в main, тесты зелёные.
  2. Обновляется CHANGELOG и номер версии в файлах проекта, если он там хранится.
  3. Ставится аннотированный тег: git tag -a v1.5.0 -m "Релиз 1.5.0".
  4. git push origin main --follow-tags.
  5. 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 &lt;хеш&gt; — команда покажет все теги, в историю которых входит этот коммит.

Как получить последний тег в скрипте?

Используйте git describe --tags --abbrev=0 — он выведет имя ближайшего тега без суффикса с хешем.

Разберите тему на видео

Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».

Перейти к курсу