I tag in Git: come usare git tag e versionare le release
git tag mette un'etichetta permanente su un commit preciso, di solito per indicare una release, ad esempio v1.2.0. Un tag annotato si crea con git tag -a v1.2.0 -m "messaggio", mentre i tag si inviano al server separatamente: git push origin v1.2.0 o git push --tags.
git tag è un'etichetta con nome su un commit preciso. A differenza di un branch, il tag non si sposta con i nuovi commit: v1.2.0 tra un anno punterà alla stessa istantanea del codice. Per questo i tag si usano per le release, e i sistemi CI/CD spesso avviano la build e la pubblicazione proprio quando compare un tag.
Tag leggeri e annotati
In Git esistono due tipi di tag, e la differenza conta.
| Leggero (lightweight) | Annotato (annotated) | |
|---|---|---|
| Creazione | git tag v1.0.0 | git tag -a v1.0.0 -m "..." |
| Cos'è | un semplice puntatore a un commit | un oggetto separato nel database di Git |
| Autore, data, messaggio | no | sì |
| Firma GPG/SSH | no | possibile (-s) |
Considerato da git describe per impostazione predefinita | no | sì |
| Quando usarlo | etichette personali temporanee | release e tutto ciò che viene pubblicato |
Per le release usate i tag annotati: registrano chi ha pubblicato la versione e quando, e si possono firmare.
Come creare un tag
Sul commit corrente
git tag -a v1.2.0 -m "Release 1.2.0: esportazione report, corretto il timeout dell'API"
Senza -m Git apre l'editor per il messaggio: comodo se volete scrivere l'elenco delle modifiche.
Su un commit vecchio
Avete dimenticato di mettere il tag al momento della release? Nessun problema, il tag si può applicare a qualsiasi commit:
git log --oneline -5
# 7be41d0 feat: esportazione report
# 1c02e8f fix: timeout delle richieste
git tag -a v1.1.0 1c02e8f -m "Release 1.1.0"
Tag firmato
git tag -s v1.2.0 -m "Release 1.2.0"
git tag -v v1.2.0 # verificare la firma
Serve una chiave GPG configurata (o SSH con gpg.format=ssh). GitHub e GitLab mostrano questi tag con il contrassegno di verifica.
Come vedere i tag
git tag # tutti i tag in ordine alfabetico
git tag -l "v1.*" # per pattern
git tag -n # con la prima riga del messaggio
git tag --sort=-v:refname # per versione, i più recenti in alto
git show v1.2.0 # dati del tag e commit
git describe --tags # tag più vicino: v1.2.0-3-g7be41d0
Fate attenzione all'ordinamento: per impostazione predefinita v1.10.0 finisce prima di v1.9.0, perché le stringhe vengono confrontate carattere per carattere. L'opzione --sort=v:refname ordina come versioni.
L'output di git describe si legge così: tre commit dopo il tag v1.2.0, commit attuale 7be41d0 (la lettera g sta per git). È una comoda fonte del numero di versione per le build tra una release e l'altra.
Come inviare un tag al server
Il normale git push non invia i tag: è il motivo frequente della domanda «perché il tag non c'è su GitHub».
git push origin v1.2.0 # un solo tag
git push --tags # tutti i tag locali
git push --follow-tags # commit + tag annotati che puntano a essi
--follow-tags è più sicuro di --tags: non invierà i tag leggeri casuali che avete messo per uso personale. Si può attivare come predefinito: git config --global push.followTags true.
Per ottenere i tag dal server: git fetch --tags. Anche il normale git fetch scarica i tag che puntano ai commit scaricati.
Come eliminare o spostare un tag
Eliminazione
git tag -d v1.2.0 # in locale
git push origin --delete v1.2.0 # sul server
# vecchio equivalente: git push origin :refs/tags/v1.2.0
Spostare un tag su un altro commit
git tag -a -f v1.2.0 <nuovo-hash> -m "Release 1.2.0"
git push origin -f v1.2.0
Tecnicamente è possibile, ma nei progetti pubblici non conviene spostare un tag già pubblicato. Chi lo ha già scaricato non lo vedrà aggiornarsi da solo: Git non sovrascrive i tag locali esistenti durante il fetch senza --force. Alla fine «v1.2.0» corrisponderà a codice diverso per persone diverse. È più corretto pubblicare v1.2.1.
Come sapere quali commit sono entrati in una release
I tag sono comodi come estremi di intervallo in tutti i comandi che leggono la storia:
git log v1.1.0..v1.2.0 --oneline # commit tra due release
git diff v1.1.0 v1.2.0 --stat # quali file sono cambiati
git tag --contains 1c02e8f # in quali release c'è questo commit
git merge-base --is-ancestor 1c02e8f v1.2.0 && echo "è nella release"
Gli ultimi due comandi rispondono a una domanda frequente del supporto: «la correzione è già arrivata agli utenti o no?» Se la correzione è finita solo in main e la release è stata creata prima, git tag --contains mostrerà una lista vuota, e sarà chiaro che serve un nuovo tag.
Un altro trucco: scaricare i sorgenti di una versione specifica senza la storia: git archive --format=zip -o app-1.2.0.zip v1.2.0. Così si preparano gli archivi da consegnare a un cliente o da compilare in un ambiente isolato.
Come passare a una versione tramite il tag
git switch --detach v1.1.0
# HEAD is now at 1c02e8f fix: timeout delle richieste
Vi troverete in stato detached HEAD: potete guardare e compilare, ma i nuovi commit non apparterranno a nessun branch. Se dovete correggere una versione vecchia, create un branch dal tag:
git switch -c hotfix/1.1.x v1.1.0
# modifiche, commit
git tag -a v1.1.1 -m "Correzione di sicurezza"
git push origin hotfix/1.1.x --follow-tags
Versionare le release: SemVer
Lo schema di nomi dei tag più diffuso è il Semantic Versioning: MAJOR.MINOR.PATCH.
- MAJOR aumenta con modifiche dell'API non compatibili:
1.4.2 → 2.0.0. - MINOR: nuove funzionalità mantenendo la compatibilità:
1.4.2 → 1.5.0. - PATCH: correzioni di bug:
1.4.2 → 1.4.3. - Le pre-release si indicano con un suffisso:
2.0.0-beta.1,2.0.0-rc.1.
Il prefisso v (v1.4.2) è una convenzione diffusa per i nomi dei tag, SemVer in sé non lo richiede. L'importante è scegliere un formato e mantenerlo, altrimenti si rompono gli script che cercano l'ultima release.
I tag nel processo di release
Il processo tipico in un team è questo:
- Le modifiche vengono unite in main, i test sono verdi.
- Si aggiornano il CHANGELOG e il numero di versione nei file del progetto, se è conservato lì.
- Si crea un tag annotato:
git tag -a v1.5.0 -m "Release 1.5.0". git push origin main --follow-tags.- La CI vede il nuovo tag, compila gli artefatti e pubblica la release; su GitHub e GitLab dal tag si crea una pagina Release con descrizione e file.
Così ogni versione in produzione si può associare in modo univoco al codice: il tag è nel nome della build, e con git show si vede cosa contiene. E git log v1.4.0..v1.5.0 --oneline fornisce l'elenco delle modifiche tra le release per le note di rilascio.
Note di rilascio a partire dai tag
Se i messaggi dei commit sono significativi, i tag trasformano la storia in un elenco di modifiche pronto. L'ultima release e la precedente si trovano senza cercarle a mano:
LAST=$(git describe --tags --abbrev=0)
PREV=$(git describe --tags --abbrev=0 "$LAST^")
git log "$PREV..$LAST" --no-merges --pretty=format:"- %s (%h)"
# - feat: esportazione report (7be41d0)
# - fix: timeout delle richieste (1c02e8f)
Se il team scrive i commit secondo la convenzione Conventional Commits (feat:, fix:, feat!:), il numero della versione successiva si può calcolare automaticamente: fix incrementa PATCH, feat incrementa MINOR, il punto esclamativo o BREAKING CHANGE incrementano MAJOR. Su questo si basano i diffusi strumenti di release automatica: creano da soli il tag e scrivono il CHANGELOG.
Tag e CI/CD
Le pipeline di solito distinguono due eventi: il push su un branch e la comparsa di un tag. Sul branch partono i test e la build dell'ambiente di test, sul tag la pubblicazione. In GitHub Actions si presenta così:
on:
push:
tags:
- "v*.*.*"
In GitLab CI lo stesso ruolo lo svolgono la variabile CI_COMMIT_TAG e la regola rules: - if: $CI_COMMIT_TAG. Il nome del tag dentro la pipeline diventa il numero di versione dell'immagine Docker o del pacchetto, quindi il formato dei tag deve essere rigoroso e prevedibile.
Ecco un altro motivo per non spostare i tag: se la pipeline ha già pubblicato il pacchetto 1.2.0 nel registry, una nuova pubblicazione con lo stesso numero di solito è vietata, e l'immagine con tag 1.2.0 presso gli utenti resterà quella vecchia.
Nomi dei tag: cosa è ammesso
Il nome di un tag segue le stesse regole del nome di un branch: niente spazi, niente caratteri ~ ^ : ? * [ né due punti consecutivi. La barra è ammessa, quindi nei monorepo si trovano tag come api/v1.2.0 e web/v3.0.1, una serie di versioni per ogni pacchetto. Potete verificare il nome in anticipo con git check-ref-format "refs/tags/v1.2.0": in caso di errore restituisce un codice diverso da zero.
Proteggere i tag
Nei repository di team conviene proteggere i tag di release: GitHub e GitLab permettono di vietare la creazione, la sovrascrittura e l'eliminazione dei tag che corrispondono a un pattern (ad esempio v*) a tutti tranne i maintainer. Così un git push --tags accidentale dal computer di qualcun altro non pubblicherà una release non prevista, e un git push origin --delete sbagliato non cancellerà la storia delle versioni.
Errori comuni
- Il tag non è sul server. Avete dimenticato
git push origin <tag>o--follow-tags. - Un tag con lo stesso nome di un branch. Git mostrerà l'avviso
refname is ambiguous; usate il prefissovper i tag e nomi diversi. - Tag su uno stato non committato. Il tag si applica a un commit, non ai file: le modifiche non committate non entreranno nella release.
- Tag leggero per una release. Mancano autore, data e messaggio, e
git describeper impostazione predefinita non lo vede.
In sintesi
git tag fissa un punto immutabile nella storia: per le release create tag annotati con git tag -a, nominateli secondo SemVer e inviateli al server esplicitamente, con git push origin <tag> o --follow-tags. Non spostate i tag pubblicati: rilasciate una nuova versione. I tag, insieme a rebase, stash e cherry-pick, si affrontano nella pratica nel corso su Git.
Domande frequenti
Che differenza c'è tra un tag e un branch?
Il branch si sposta automaticamente su ogni nuovo commit, mentre il tag punta per sempre allo stesso commit.
Si possono mettere due tag sullo stesso commit?
Sì, il numero di tag su un commit non è limitato, ad esempio v2.0.0 e latest-stable.
Eliminando un branch si eliminano anche i tag?
No, i tag sono indipendenti dai branch, e il commit con il tag resta raggiungibile anche dopo l'eliminazione del branch.
Come capire in quale release è entrato un commit?
Eseguite git tag --contains <hash>: il comando mostrerà tutti i tag nella cui storia è presente quel commit.
Come ottenere l'ultimo tag in uno script?
Usate git describe --tags --abbrev=0: stamperà il nome del tag più vicino senza il suffisso con l'hash.
Questo argomento è trattato in modo approfondito, con pratica, nel corso «Git da zero a sviluppatore sicuro».
Vai al corso