Що таке Git теги і коли їх використовувати?
Git теги - це незмінні посилання на конкретні коміти, якими позначають точки релізу на кшталт v1.0.0.
Теорія
TL;DR
- Тег - це стікер на конкретному коміті: він залишається там назавжди, на відміну від гілки, яка рухається вперед з кожним новим комітом
- Два типи: lightweight (просто вказівник) і annotated (повний об'єкт з повідомленням, автором, датою)
- Annotated теги для релізів; lightweight для швидких локальних міток
- Теги не пушаться автоматично - потрібно робити це явно
Швидкий приклад
# Lightweight тег (просто вказівник)
git tag v1.0
# Annotated тег (об'єкт з метаданими)
git tag -a v1.1.0 -m "Стабільний реліз v1.1.0"
# Список тегів
git tag
# v1.0
# v1.1.0
# Запушити конкретний тег
git push origin v1.1.0Після git tag -a Git створює окремий об'єкт тегу: хто тегував, коли і з яким повідомленням. Lightweight теги пропускають все це і просто вказують на SHA коміту.
Головна різниця: теги проти гілок
Вказівник гілки рухається вперед з кожним комітом. HEAD на main сьогодні - це не той самий коміт що й HEAD на main минулого тижня. Тег ніколи не рухається. v1.0.0 вказуватиме на той самий коміт через рік. Саме ця незмінність робить теги корисними для релізів - завжди можна повернутись до точно того що пішло в продакшен.
Коли використовувати теги
- Позначення релізів: annotated теги для версій типу
v2.0.0з повідомленням про зміни - Хотфікси на старих версіях: спочатку тегуй базовий коміт, потім створюй гілку (
git checkout -b hotfix v1.0.0) - Тригери в CI/CD: пуш тегу запускає деплой на продакшен
- Локальні закладки: lightweight теги підходять для "треба сюди повернутись" під час розробки
Для роботи в процесі - тільки гілки, не теги.
Як це працює всередині
Git зберігає теги у .git/refs/tags/. Lightweight тег - це просто файл з SHA коміту. Annotated тег створює окремий об'єкт у базі Git зі своїм SHA, який вже вказує на коміт. Коли виконуєш git checkout v1.0.0, Git переходить у стан detached HEAD - HEAD вказує безпосередньо на хеш коміту, а не на ім'я гілки.
Типові помилки
Забувають запушити теги. git push не чіпає теги. Команда витягнула зміни і бачить порожній список тегів.
# Неправильно
git tag v1.0.0
git push # тег залишається локальним
# Правильно
git push origin v1.0.0
# або одразу всі
git push --tagsКоміти у стані detached HEAD після checkout тегу. Якщо зробити checkout тегу і почати комітити, ці коміти не належать жодній гілці. Перейдеш на іншу гілку - вони пропадуть.
# Неправильно
git checkout v1.0.0
# ... правки, коміт ...
git checkout main # коміти тепер "висять" без гілки
# Правильно
git checkout -b hotfix-v1.0.0 v1.0.0
# тепер ти на гілці, коміти в безпеціLightweight теги для релізів. Немає повідомлення, немає автора, немає дати. git show v1.0 просто показує сирий коміт. Annotated теги дають контекст для аудиту. Команди які використовують lightweight теги для релізів зазвичай жалкують про це через пів року - коли вже не зрозуміло хто і навіщо це тегував.
Видалення опублікованого тегу. Локальне видалення (git tag -d v1.0.0) не прибирає тег з remote. Видалення з remote (git push origin :refs/tags/v1.0.0) ламає CI-пайплайни і всіх хто прибиндився до цього тегу. Координуй з командою перед видаленням будь-якого опублікованого тегу.
Де зустрічається в реальних проектах
- npm:
npm version patchавтоматично створює коміт і тегv1.2.3за один крок - React на GitHub: теги типу
v18.2.0позначають точний коміт перед публікацією в npm - Ядро Linux: теги
v6.1,v6.2маркують merge window для збірок дистрибутивів - Docker Hub: пуш тегу може тригерити збірку Docker-образу автоматично
semantic-release: читає теги щоб обчислити наступну версію без ручного втручання
Питання на співбесіді
Q: Яка різниця між lightweight і annotated тегом?
A: Lightweight тег - файл з SHA коміту. Annotated тег - окремий об'єкт Git з ім'ям автора, email, датою і повідомленням. Для будь-чого спільного або пов'язаного з релізами - тільки git tag -a.
Q: Як запушити всі теги одразу?
A: git push --tags пушить усі локальні теги. git push origin --follow-tags (Git 2.16+) пушить тільки annotated теги досяжні з поточної гілки - зазвичай кращий варіант для більшості воркфлоу.
Q: Що таке detached HEAD при checkout тегу?
A: HEAD вказує безпосередньо на хеш коміту замість імені гілки. Будь-які нові коміти не потраплять у жодну гілку і можуть бути втрачені при переключенні.
Q: Чи можна перенести тег на інший коміт?
A: Локально: видали (git tag -d v1.0.0) і створи заново. На remote: спочатку видали (git push origin :refs/tags/v1.0.0), потім запуши знову. На спільних репозиторіях це болісна процедура - уникай з вже опублікованими тегами.
Q: У монорепозиторії з десятками пакетів як тегувати окремий пакет без тегування всього репо?
A: Для цього є Changesets або Lerna. Вони створюють теги у форматі @org/package@v1.0.0 і запускають тегування в CI, щоб не робити це вручну для кожного пакету окремо.
Приклади
Базовий: створення та перевірка annotated тегу
git checkout main
git pull origin main
# Annotated тег для релізу
git tag -a v2.0.0 -m "Мажорне оновлення: нова система авторизації"
# Перевіряємо об'єкт тегу
git show v2.0.0
# tag v2.0.0
# Tagger: Jane Dev <jane@example.com>
# Date: Mon Oct 14 11:30:00 2024
# Мажорне оновлення: нова система авторизації
#
# commit abc1234...
# Пушимо тег
git push origin v2.0.0Вивід git show містить метадані автора тегу - їх немає у lightweight тегів. Це важливо коли розбираєш що саме пішло в продакшен і хто це підтвердив.
Середній: тег як тригер деплою в Node.js проекті
# Типовий Express/Node воркфлоу
git checkout main
git pull origin main
# спочатку запускаємо тести
# npm version створює коміт і тег автоматично
npm version 4.18.2 -m "Release Express 4.18.2"
# створює тег v4.18.2
# Пушимо коміт і тег разом
git push origin main --follow-tags
# У конфігурації CI (GitHub Actions):
# on:
# push:
# tags:
# - 'v*'
# Деплой-джоб запускається тільки при пуші тегу версіїФлаг --follow-tags пушить і коміт гілки, і досяжні annotated теги за один раз. Не треба окремо пам'ятати про тег - він іде разом з кодом.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.