Tags in Git: mit git tag arbeiten und Releases versionieren
git tag setzt eine dauerhafte Markierung auf einen bestimmten Commit, meist um ein Release zu kennzeichnen, z. B. v1.2.0. Ein annotierter Tag wird mit git tag -a v1.2.0 -m "Nachricht" erstellt, zum Server werden Tags separat übertragen: git push origin v1.2.0 oder git push --tags.
git tag ist eine benannte Markierung auf einem bestimmten Commit. Anders als ein Branch bewegt sich ein Tag bei neuen Commits nicht: v1.2.0 zeigt auch in einem Jahr noch auf denselben Code-Stand. Deshalb verwendet man Tags für Releases, und CI/CD-Systeme starten Build und Veröffentlichung häufig genau dann, wenn ein Tag auftaucht.
Leichtgewichtige und annotierte Tags
Git kennt zwei Arten von Tags, und der Unterschied ist wichtig.
| Leichtgewichtig (lightweight) | Annotiert (annotated) | |
|---|---|---|
| Erstellen | git tag v1.0.0 | git tag -a v1.0.0 -m "..." |
| Was es ist | nur ein Zeiger auf einen Commit | ein eigenes Objekt in der Git-Datenbank |
| Autor, Datum, Nachricht | nein | ja |
| GPG-/SSH-Signatur | nein | möglich (-s) |
Standardmäßig von git describe berücksichtigt | nein | ja |
| Wann verwenden | persönliche, temporäre Markierungen | Releases und alles, was veröffentlicht wird |
Verwenden Sie für Releases annotierte Tags: Sie halten fest, wer die Version wann veröffentlicht hat, und lassen sich signieren.
Tag erstellen
Auf dem aktuellen Commit
git tag -a v1.2.0 -m "Release 1.2.0: Berichtsexport, API-Timeout behoben"
Ohne -m öffnet Git einen Editor für die Nachricht – praktisch, wenn Sie eine Liste der Änderungen eintragen möchten.
Auf einem älteren Commit
Beim Release vergessen, einen Tag zu setzen? Kein Problem, ein Tag lässt sich an jeden beliebigen Commit hängen:
git log --oneline -5
# 7be41d0 feat: Berichtsexport
# 1c02e8f fix: Timeout bei Anfragen
git tag -a v1.1.0 1c02e8f -m "Release 1.1.0"
Signierter Tag
git tag -s v1.2.0 -m "Release 1.2.0"
git tag -v v1.2.0 # Signatur prüfen
Dafür brauchen Sie einen eingerichteten GPG-Schlüssel (oder SSH bei gpg.format=ssh). GitHub und GitLab zeigen solche Tags mit einem Verifizierungsvermerk an.
Tags anzeigen
git tag # alle Tags alphabetisch
git tag -l "v1.*" # nach Muster
git tag -n # mit der ersten Zeile der Nachricht
git tag --sort=-v:refname # nach Version, neueste zuerst
git show v1.2.0 # Tag-Daten und Commit
git describe --tags # nächstgelegener Tag: v1.2.0-3-g7be41d0
Achten Sie auf die Sortierung: Standardmäßig landet v1.10.0 vor v1.9.0, weil Strings zeichenweise verglichen werden. Die Option --sort=v:refname sortiert als Versionen.
Die Ausgabe von git describe liest sich so: drei Commits nach dem Tag v1.2.0, aktueller Commit 7be41d0 (das g steht für git). Eine bequeme Quelle für die Versionsnummer von Builds zwischen zwei Releases.
Tag zum Server pushen
Ein normales git push überträgt keine Tags – häufiger Grund für die Frage „Warum ist mein Tag nicht auf GitHub?“.
git push origin v1.2.0 # ein Tag
git push --tags # alle lokalen Tags
git push --follow-tags # Commits + annotierte Tags, die darauf zeigen
--follow-tags ist sicherer als --tags: Es überträgt keine zufälligen leichtgewichtigen Tags, die Sie nur für sich gesetzt haben. Sie können es als Standard aktivieren: git config --global push.followTags true.
Tags vom Server holen: git fetch --tags. Auch ein normales git fetch lädt Tags, die auf die heruntergeladenen Commits zeigen.
Tag löschen oder verschieben
Löschen
git tag -d v1.2.0 # lokal
git push origin --delete v1.2.0 # auf dem Server
# ältere Schreibweise: git push origin :refs/tags/v1.2.0
Tag auf einen anderen Commit verschieben
git tag -a -f v1.2.0 <neuer-hash> -m "Release 1.2.0"
git push origin -f v1.2.0
Technisch ist das möglich, in öffentlichen Projekten sollten Sie einen veröffentlichten Tag aber nicht verschieben. Bei allen, die den Tag bereits geholt haben, aktualisiert er sich nicht von selbst: Git überschreibt vorhandene lokale Tags beim fetch ohne --force nicht. Am Ende steht „v1.2.0“ bei verschiedenen Leuten für unterschiedlichen Code. Besser ist es, v1.2.1 zu veröffentlichen.
Welche Commits sind in einem Release enthalten?
Tags eignen sich hervorragend als Bereichsgrenzen in allen Befehlen, die die Historie lesen:
git log v1.1.0..v1.2.0 --oneline # Commits zwischen Releases
git diff v1.1.0 v1.2.0 --stat # welche Dateien sich geändert haben
git tag --contains 1c02e8f # in welchen Releases dieser Commit steckt
git merge-base --is-ancestor 1c02e8f v1.2.0 && echo "im Release enthalten"
Die letzten beiden Befehle beantworten eine häufige Support-Frage: „Ist der Fix schon bei den Nutzern angekommen?“ Ist der Fix nur in main gelandet und das Release vorher gebaut worden, zeigt git tag --contains eine leere Liste – und es ist klar, dass ein neuer Tag nötig ist.
Noch ein Trick: den Quellcode einer bestimmten Version ohne Historie herunterladen: git archive --format=zip -o app-1.2.0.zip v1.2.0. So erstellt man Archive für die Auslieferung an Kunden oder für Builds in isolierten Umgebungen.
Auf eine Version per Tag wechseln
git switch --detach v1.1.0
# HEAD is now at 1c02e8f fix: Timeout bei Anfragen
Sie befinden sich dann im Zustand detached HEAD: Ansehen und bauen ist möglich, neue Commits gehören aber zu keinem Branch. Wenn Sie eine alte Version korrigieren müssen, legen Sie einen Branch vom Tag an:
git switch -c hotfix/1.1.x v1.1.0
# Änderungen, Commit
git tag -a v1.1.1 -m "Sicherheitsfix"
git push origin hotfix/1.1.x --follow-tags
Releases versionieren: SemVer
Das verbreitetste Namensschema für Tags ist Semantic Versioning: MAJOR.MINOR.PATCH.
- MAJOR steigt bei inkompatiblen API-Änderungen:
1.4.2 → 2.0.0. - MINOR – neue Funktionen bei erhaltener Kompatibilität:
1.4.2 → 1.5.0. - PATCH – Fehlerbehebungen:
1.4.2 → 1.4.3. - Vorabversionen werden mit einem Suffix gekennzeichnet:
2.0.0-beta.1,2.0.0-rc.1.
Das Präfix v (v1.4.2) ist eine verbreitete Konvention für Tag-Namen, SemVer selbst schreibt es nicht vor. Entscheidend ist, sich auf ein Format festzulegen und dabei zu bleiben – sonst brechen Skripte, die nach dem neuesten Release suchen.
Tags im Release-Prozess
Ein typischer Ablauf im Team sieht so aus:
- Änderungen werden nach main gemergt, die Tests sind grün.
- CHANGELOG und – falls dort gepflegt – die Versionsnummer in den Projektdateien werden aktualisiert.
- Ein annotierter Tag wird gesetzt:
git tag -a v1.5.0 -m "Release 1.5.0". git push origin main --follow-tags.- Die CI erkennt den neuen Tag, baut die Artefakte und veröffentlicht das Release; auf GitHub und GitLab entsteht aus dem Tag eine Release-Seite mit Beschreibung und Dateien.
So lässt sich jede Version in Produktion eindeutig dem Code zuordnen: Der Tag steckt im Build-Namen, und git show zeigt, was enthalten ist. git log v1.4.0..v1.5.0 --oneline liefert die Liste der Änderungen zwischen zwei Releases für die Release Notes.
Release Notes aus Tags erzeugen
Sind die Commit-Nachrichten aussagekräftig, machen Tags aus der Historie eine fertige Änderungsliste. Das letzte und das vorherige Release lassen sich ohne manuelle Suche ermitteln:
LAST=$(git describe --tags --abbrev=0)
PREV=$(git describe --tags --abbrev=0 "$LAST^")
git log "$PREV..$LAST" --no-merges --pretty=format:"- %s (%h)"
# - feat: Berichtsexport (7be41d0)
# - fix: Timeout bei Anfragen (1c02e8f)
Schreibt das Team Commits nach der Konvention Conventional Commits (feat:, fix:, feat!:), lässt sich die nächste Versionsnummer automatisch berechnen: fix erhöht PATCH, feat MINOR, ein Ausrufezeichen oder BREAKING CHANGE MAJOR. Darauf bauen beliebte Tools für automatisierte Releases auf: Sie setzen den Tag selbst und schreiben das CHANGELOG.
Tags und CI/CD
Pipelines unterscheiden meist zwei Ereignisse: einen Push in einen Branch und das Erscheinen eines Tags. Beim Branch laufen Tests und der Build für die Testumgebung, beim Tag die Veröffentlichung. In GitHub Actions sieht das so aus:
on:
push:
tags:
- "v*.*.*"
In GitLab CI übernehmen die Variable CI_COMMIT_TAG und die Regel rules: - if: $CI_COMMIT_TAG diese Rolle. Der Tag-Name wird in der Pipeline zur Versionsnummer des Docker-Images oder Pakets, daher sollte das Tag-Format streng und vorhersehbar sein.
Daraus ergibt sich ein weiterer Grund, Tags nicht zu verschieben: Hat die Pipeline das Paket 1.2.0 bereits in eine Registry veröffentlicht, ist eine erneute Veröffentlichung unter derselben Nummer in der Regel verboten, und das Image mit dem Tag 1.2.0 bleibt bei den Nutzern das alte.
Tag-Namen: was erlaubt ist
Für Tag-Namen gelten dieselben Regeln wie für Branch-Namen: keine Leerzeichen, keine Zeichen ~ ^ : ? * [ und keine zwei Punkte hintereinander. Ein Schrägstrich ist erlaubt, daher findet man in Monorepos Tags wie api/v1.2.0 und web/v3.0.1 – ein eigener Versionssatz pro Paket. Einen Namen können Sie vorab mit git check-ref-format "refs/tags/v1.2.0" prüfen: Bei einem Fehler liefert der Befehl einen Exit-Code ungleich null.
Tags schützen
In Team-Repositorys sollten Release-Tags geschützt werden: GitHub und GitLab erlauben es, das Erstellen, Überschreiben und Löschen von Tags nach Muster (z. B. v*) allen außer den Maintainern zu verbieten. So veröffentlicht ein versehentliches git push --tags von einem fremden Rechner kein ungeplantes Release, und ein irrtümliches git push origin --delete löscht nicht die Versionshistorie.
Typische Fehler
- Tag ist nicht auf dem Server.
git push origin <tag>oder--follow-tagsvergessen. - Tag mit demselben Namen wie ein Branch. Git warnt mit
refname is ambiguous; verwenden Sie das Präfixvfür Tags und unterschiedliche Namen. - Tag auf nicht committetem Stand. Ein Tag zeigt auf einen Commit, nicht auf Dateien: Nicht committete Änderungen landen nicht im Release.
- Leichtgewichtiger Tag für ein Release. Kein Autor, kein Datum, keine Nachricht, und
git describesieht ihn standardmäßig nicht.
Fazit
git tag markiert einen unveränderlichen Punkt in der Historie: Erstellen Sie für Releases annotierte Tags mit git tag -a, benennen Sie sie nach SemVer und pushen Sie sie ausdrücklich zum Server – git push origin <tag> oder --follow-tags. Veröffentlichte Tags verschieben Sie nicht, sondern bringen eine neue Version heraus. Tags sowie rebase, stash und cherry-pick üben Sie praktisch im Git-Kurs.
Häufige Fragen
Was ist der Unterschied zwischen einem Tag und einem Branch?
Ein Branch wandert mit jedem neuen Commit automatisch weiter, ein Tag zeigt dauerhaft auf denselben Commit.
Kann ich zwei Tags auf denselben Commit setzen?
Ja, die Anzahl der Tags pro Commit ist unbegrenzt, z. B. v2.0.0 und latest-stable.
Werden Tags gelöscht, wenn ich einen Branch lösche?
Nein, Tags sind unabhängig von Branches, und ein getaggter Commit bleibt auch nach dem Löschen des Branches erreichbar.
Wie finde ich heraus, in welchem Release ein Commit enthalten ist?
Führen Sie git tag --contains <hash> aus – der Befehl zeigt alle Tags, in deren Historie dieser Commit liegt.
Wie bekomme ich den neuesten Tag in einem Skript?
Verwenden Sie git describe --tags --abbrev=0 – es gibt den Namen des nächstgelegenen Tags ohne Hash-Suffix aus.
Dieses Thema wird im Kurs „Git vom Anfänger zum versierten Entwickler“ ausführlich mit Praxis behandelt.
Zum Kurs