Tags sind die ortstreu Gegenstücke zu Branches – dauerhafte Markierungen spezifischer Punkte in der Git-Historie. Während Branches die Entwicklung steuern und sich mit jedem Commit bewegen, markieren Tags fertiggestellte Zustände: Releases, Meilensteine, wichtige Versionen. Sie sind das primäre Werkzeug für Release-Management und Production-Steuerung.
Technisch ist ein Tag entweder eine einfache Referenz (Lightweight Tag) oder ein vollwertiges Git-Objekt (Annotated Tag) – der vierte und letzte Objekttyp neben Blob, Tree und Commit. Diese Unterscheidung hat praktische und prozessuale Implikationen, die wir im Detail betrachten.
Wir haben bereits Blobs (Dateiinhalte), Trees (Verzeichnisstrukturen) und Commits (Snapshots mit Metadaten) kennengelernt. Tags sind der vierte Objekttyp im Git-Objektmodell.
Ein Lightweight Tag ist technisch identisch zu einem Branch – eine
Textdatei unter .git/refs/tags/, die einen Commit-Hash
enthält:
$ git tag v1.0.0
$ cat .git/refs/tags/v1.0.0
7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6
# Genau wie eine Branch-Datei
$ cat .git/refs/heads/main
7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6Der Unterschied liegt im Verhalten: Der Branch bewegt sich, der Tag bleibt. Technisch sind beide nur Pointer, aber semantisch ist der Tag immutabel.
Lightweight Tags sind schnell und einfach, haben aber keine zusätzlichen Metadaten. Sie sind Bookmarks, mehr nicht.
Ein Annotated Tag ist ein eigenständiges Git-Objekt mit eigener Struktur:
$ git tag -a v1.0.0 -m "Release 1.0.0: Initial stable version"
$ cat .git/refs/tags/v1.0.0
9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0
# Dieser Hash identifiziert das Tag-Objekt, nicht direkt den Commit
$ git cat-file -t 9f8e7d6c
tag
$ git cat-file -p 9f8e7d6c
object 7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6
type commit
tag v1.0.0
tagger John Doe <john@example.com> 1704124800 +0100
Release 1.0.0: Initial stable version
This release includes:
- User authentication system
- REST API v1
- Basic admin panel
- Comprehensive test suiteDie Struktur eines Tag-Objekts:
object: Der Hash des Objekts, auf das der Tag zeigt (typischerweise ein Commit)
type: Der Typ des referenzierten Objekts (meist
commit, kann auch blob oder tree
sein)
tag: Der Name des Tags
tagger: Wer hat den Tag erstellt, wann, und in welcher Zeitzone
Message: Die Tag-Message (nach Leerzeile) – kann beliebig lang sein
Das Tag-Objekt ist eine indirekte Referenz:
.git/refs/tags/v1.0.0 zeigt auf das Tag-Objekt, das
wiederum auf den Commit zeigt. Diese Indirektion ermöglicht die
zusätzlichen Metadaten.
| Eigenschaft | Lightweight | Annotated |
|---|---|---|
| Git-Objekt | Nein (nur Referenz) | Ja (eigenes Objekt) |
| Metadaten | Keine | Tagger, Date, Message |
| Nutzung | Private Bookmarks | Öffentliche Releases |
| Verifizierbar | Nein | Ja (GPG-Signatur möglich) |
| Best Practice | Temporäre Marker | Alle Production-Releases |
Regel: Lightweight für persönliche, temporäre Marker. Annotated für alles, was geteilt oder dokumentiert werden sollte.
Annotated Tags können GPG-signiert werden:
$ git tag -s v1.0.0 -m "Release 1.0.0"
$ git cat-file -p v1.0.0
object 7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6
type commit
tag v1.0.0
tagger John Doe <john@example.com> 1704124800 +0100
Release 1.0.0
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEE...
-----END PGP SIGNATURE-----Die Signatur garantiert: 1. Der Tag wurde vom angegebenen Tagger erstellt 2. Der Tag wurde nicht manipuliert 3. Der Tag zeigt auf den verifizierten Commit
Für Security-kritische Releases oder regulierte Umgebungen ist dies essentiell. Ein Angreifer kann einen Branch manipulieren, aber nicht einen signierten Tag (ohne den privaten Schlüssel).
Branches und Tags haben komplementäre Rollen im Entwicklungsprozess:
Branches organisieren aktive Entwicklung: -
main: Aktuelle stabile Version - develop:
Integration neuer Features - feature/*: Isolierte
Feature-Entwicklung - hotfix/*: Dringende Fixes
Branches sind mobil – sie bewegen sich mit der Entwicklung. Sie repräsentieren den Prozess, nicht das Ergebnis.
Tags markieren abgeschlossene Zustände: -
v1.0.0: Erstes Production-Release - v1.1.0:
Feature-Update - v1.1.1: Bugfix-Release -
rc-1.2.0: Release Candidate
Tags sind immutabel – sie markieren fixe Punkte. Sie repräsentieren das Ergebnis, nicht den Prozess.
Während der Entwicklung: Branches steuern. Features
werden in feature/* entwickelt, in develop
integriert, in main gemerged.
Beim Release: Tag setzen. Wenn main
release-ready ist: git tag -a v1.0.0. Dieser Tag triggert:
- Production-Deployment - Release-Notes-Generierung -
Artefakt-Publikation (Docker-Images, Binaries, Packages) -
Changelog-Update - Benachrichtigungen an Stakeholder
Nach dem Release: Der Tag bleibt. Branches
entwickeln sich weiter, aber v1.0.0 zeigt immer auf exakt
diesen Commit. Rollbacks, Hotfixes, Analyse – alles referenziert den
Tag.
Analogy: Branches sind wie Baustellen (aktive Arbeit), Tags sind wie fertige Gebäude (fixe Artefakte).
# Entwicklung auf develop
git checkout develop
git merge feature/new-payment-system
git push origin develop
# Wenn stabil: Prepare Release
git checkout main
git merge develop
# Intensive Testing, QA, Staging-Deployment
# ... alles OK ...
# Release-Tag setzen
git tag -a v1.2.0 -m "Release 1.2.0: Payment System
Features:
- Credit card payment integration
- PayPal support
- Invoice generation
Closes: #234, #245, #256"
git push origin v1.2.0
# Tag triggert CI/CD Pipeline:
# → Build Docker Image mit Tag v1.2.0
# → Run Full Test Suite
# → Deploy to Production
# → Create GitHub Release
# → Publish to Package Registry
# → Send NotificationsDer Tag ist der offizielle Release-Trigger. Nicht ein Branch-Merge, nicht ein Commit – der Tag signalisiert: “Diese Version ist production-ready.”
Semantic Versioning (SemVer) ist der de-facto Standard für Software-Versionierung. Er kodiert Kompatibilitäts-Informationen in der Versionsnummer.
v2.3.4
│ │ │
│ │ └─ PATCH: Bugfixes, rückwärtskompatibel
│ └─── MINOR: Neue Features, rückwärtskompatibel
└───── MAJOR: Breaking Changes, nicht rückwärtskompatibel
MAJOR: Inkompatible API-Änderungen - Entfernte Funktionen - Geänderte Funktionssignaturen - Breaking Changes im Verhalten
MINOR: Neue Funktionalität, kompatibel - Neue Endpoints, Methoden, Features - Deprecated Warnings für zukünftige Breaking Changes - Performance-Improvements
PATCH: Bugfixes, kompatibel - Fehlerbehebungen - Security-Patches - Kleine Verbesserungen
Pre-Release: v1.2.0-alpha.1,
v1.2.0-beta.2, v1.2.0-rc.3 -
alpha: Frühe Version, instabil, nur für Entwickler -
beta: Feature-complete, testing, für Early Adopters -
rc (Release Candidate): Potenzielle finale Version
Build-Metadata: v1.2.0+20240101.abc123
- Nach +: Metadaten, haben keine Precedence-Semantik -
Typischerweise: Build-Datum, Commit-Hash, Build-Nummer
Vollständige Version: v2.3.4-beta.2+20240101.7e8f9a0
Version 0.y.z: Initiale Entwicklung. Alles kann sich jederzeit ändern. Keine Stabilitätsgarantien.
Version 1.0.0: Erste stabile Public API. Ab hier gelten Kompatibilitätsregeln.
Patch: 1.2.3 → 1.2.4
# Nur Bugfixes, keine neuen Features
git tag -a v1.2.4 -m "Fix: Correct password validation regex"Minor: 1.2.4 → 1.3.0
# Neue Features, alte funktionieren weiter
git tag -a v1.3.0 -m "Feature: Add OAuth2 authentication"Major: 1.3.0 → 2.0.0
# Breaking Changes
git tag -a v2.0.0 -m "Breaking: Remove deprecated v1 API endpoints
BREAKING CHANGES:
- /api/v1/* endpoints removed
- Use /api/v2/* instead
- See migration guide: docs/v2-migration.md"SemVer ist essentiell für Package-Manager (npm, pip, cargo, Maven):
npm package.json:
{
"dependencies": {
"express": "^4.18.0", // ^: Minor und Patch updates OK
"lodash": "~4.17.21", // ~: Nur Patch updates OK
"react": "18.2.0" // Exakte Version
}
}^4.18.0 bedeutet: “≥4.18.0 und <5.0.0” – alle
Minor/Patch updates sind safe, weil kompatibel (laut
SemVer-Versprechen).
Ohne SemVer wäre automatisches Dependency-Management unmöglich. SemVer ist der Vertrag zwischen Library-Autoren und Nutzern.
Vor 1.0.0 gelten spezielle Regeln: - 0.y.z:
Alles instabil, keine Garantien - 0.1.0 → 0.2.0: Kann
Breaking Changes enthalten - 0.2.3 → 0.2.4: Sollte
kompatibel sein (aber nicht garantiert)
Viele Projekte bleiben jahrelang in 0.x.x, um sich
Flexibilität zu bewahren. Das ist legitim, signalisiert aber: “API noch
nicht stabil.”
Die Message eines Annotated Tags ist wertvolle Dokumentation. Sie sollte mindestens enthalten:
git tag -a v1.5.0 -m "Release 1.5.0: Performance and Security Update
Features:
- Redis caching for API responses (3x faster)
- Rate limiting per API key
- Batch processing for bulk operations
Improvements:
- Database query optimization (40% reduction)
- Frontend bundle size reduced by 200KB
Security:
- Fix XSS vulnerability in comment system (CVE-2024-1234)
- Update dependencies with known vulnerabilities
Breaking Changes:
- None
Migration:
- No action required for existing deployments
- New rate limits apply: 1000 req/hour/key (previously unlimited)
Closes: #345, #356, #367, #389
"Diese Tag-Message ist: - Selbstdokumentierend: Man versteht sofort, was in dieser Version ist - Referenzierbar: Issues, CVEs sind verlinkt - Actionable: Migration-Schritte sind dokumentiert - Archiviert: Bleibt permanent mit dem Tag verbunden
Viele Tools generieren Changelogs aus Tag-Messages:
# Alle Tags mit Messages anzeigen
git tag -n9
# Releases zwischen zwei Tags
git log v1.4.0..v1.5.0 --oneline
# Tag-Message für Changelog extrahieren
git tag -l -n99 v1.5.0GitHub, GitLab generieren automatisch Release-Pages aus Tag-Messages. Die Message wird zur öffentlichen Dokumentation.
Während SemVer Versionen semantisch macht, macht Conventional Commits Commit-Messages semantisch. Die beiden Standards ergänzen sich.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Types (standardisiert): - feat: Neues
Feature (→ MINOR bump) - fix: Bugfix (→ PATCH bump) -
docs: Nur Dokumentation - style:
Code-Formatierung (keine Logic-Änderung) - refactor:
Code-Umstrukturierung (kein neues Feature, kein Fix) -
perf: Performance-Improvement - test: Tests
hinzufügen/ändern - build: Build-System-Änderungen -
ci: CI-Konfiguration - chore:
Maintenance-Tasks
Breaking Change: BREAKING CHANGE: im
Footer oder ! nach Type:
feat!: remove deprecated API endpoints
BREAKING CHANGE: All /api/v1/* endpoints are removed.
Use /api/v2/* instead.
Feature (Minor):
feat(auth): add OAuth2 authentication
Implements OAuth2 flow for Google and GitHub providers.
Users can now login using their existing accounts.
Closes: #234
Bugfix (Patch):
fix(api): correct validation error in user registration
Email validation was too restrictive, rejecting valid addresses
with plus signs. Updated regex to RFC 5322 compliance.
Fixes: #456
Breaking Change (Major):
feat(api)!: migrate to REST API v2
BREAKING CHANGE: The v1 API has been removed.
All endpoints have moved from /api/v1/* to /api/v2/*.
Response format has changed from XML to JSON.
See migration guide: docs/v2-migration.md
Chore (No Version Bump):
chore(deps): update dependencies
- express: 4.17.1 → 4.18.2
- lodash: 4.17.20 → 4.17.21
No API changes.
Tools wie semantic-release oder
standard-version lesen Commit-Messages und bestimmen
automatisch die nächste Version:
# Commits seit letztem Tag:
feat: add payment system # → MINOR bump
fix: correct email validation # → PATCH bump
docs: update README # → no bump
# Nächste Version: 1.2.0 → 1.3.0 (wegen feat)Ein BREAKING CHANGE in irgendeinem Commit → MAJOR
bump.
Conventional Commits ermöglichen strukturierte Changelogs:
# Changelog
## v1.3.0 (2024-01-15)
### Features
- **auth**: add OAuth2 authentication (#234)
- **api**: add bulk import endpoint (#245)
### Bug Fixes
- **api**: correct validation error in registration (#456)
- **ui**: fix date picker timezone handling (#467)
### Documentation
- update API documentation for v2 endpointsDiese Struktur ist automatisch aus Commit-Messages generiert. Keine manuelle Changelog-Pflege.
Commit häufig mit klaren Messages:
git commit -m "feat(api): add user search endpoint"
git commit -m "test(api): add tests for user search"
git commit -m "docs(api): document user search endpoint"Scope für Kontext: Hilft zu verstehen, welcher Teil
betroffen ist: - feat(auth), fix(payment),
refactor(database)
Imperative Mood: “add feature”, nicht “added feature” oder “adds feature”
Body für Details: Wenn der Commit komplex ist, nutze den Body für Erklärungen
Footer für Referenzen: Closes: #123,
Fixes: #456, Refs: #789
Tags sind der primäre Trigger für Production-Deployments in modernen CI/CD-Systemen.
stages:
- build
- test
- deploy
# Läuft auf allen Commits
build:
stage: build
script:
- npm run build
test:
stage: test
script:
- npm test
# Läuft nur bei Tags
deploy_production:
stage: deploy
script:
- docker build -t myapp:${CI_COMMIT_TAG} .
- docker push myapp:${CI_COMMIT_TAG}
- kubectl set image deployment/myapp myapp=myapp:${CI_COMMIT_TAG}
only:
- tags
except:
- branches
# Nur bei Release-Tags (nicht Prereleases)
publish_artifacts:
stage: deploy
script:
- npm publish
only:
- /^v[0-9]+\.[0-9]+\.[0-9]+$/ # Regex: v1.2.3 formatEnvironment-Variable: CI_COMMIT_TAG
enthält den Tag-Namen (v1.2.0)
name: Release
on:
push:
tags:
- 'v*' # Trigger bei allen Tags beginnend mit 'v'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: npm run build
- name: Create Release
uses: actions/create-release@v1
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
body: |
See CHANGELOG.md for details
draft: false
prerelease: falseBlue-Green Deployment:
deploy_blue:
script:
- deploy.sh blue ${CI_COMMIT_TAG}
only:
- tags
when: manual
deploy_green:
script:
- deploy.sh green ${CI_COMMIT_TAG}
only:
- tags
when: manual
switch_traffic:
script:
- switch.sh ${ENVIRONMENT}
only:
- tags
when: manualCanary Deployment:
deploy_canary:
script:
- deploy.sh canary ${CI_COMMIT_TAG} --traffic=5%
only:
- tags
promote_canary:
script:
- deploy.sh production ${CI_COMMIT_TAG} --traffic=100%
only:
- tags
when: manual
needs: [deploy_canary]Tags ermöglichen einfache Rollbacks:
# Aktuell deployed: v1.5.0
# Problem entdeckt
# Rollback zu vorheriger stabiler Version
kubectl set image deployment/myapp myapp=myapp:v1.4.0
# Oder: Hotfix-Tag auf alten Commit setzen
git tag -a v1.5.1 v1.4.0^{commit} -m "Rollback: Revert to stable 1.4.0"
git push origin v1.5.1
# Pipeline deployed v1.5.1 (identisch zu v1.4.0)Konsistent: Wähle ein Schema und bleibe dabei - ✓
v1.0.0, v1.1.0, v2.0.0 - ✗
1.0.0, v1.1.0, version-2.0.0
Präfix: v ist Standard-Konvention
(obwohl optional)
Pre-Releases: Klare Labels -
v1.2.0-alpha.1, v1.2.0-beta.1,
v1.2.0-rc.1
Build-Metadata: Nach + -
v1.2.0+20240101.abc123
Keine Lightweight Tags für Releases: Immer Annotated
# ✗ Schlecht
git tag v1.0.0
# ✓ Gut
git tag -a v1.0.0 -m "Release 1.0.0: Initial stable release"Signierte Tags für kritische Releases:
git tag -s v1.0.0 -m "Release 1.0.0"Tags dokumentieren:
# Tag-Message sollte Release-Notes enthalten
git tag -a v1.2.0 -F release-notes.mdTags pushen:
# Einzelnen Tag
git push origin v1.0.0
# Alle Tags
git push origin --tags
# Nur Annotated Tags (sichere)
git push origin --follow-tagsTags sollten immutabel sein, aber Fehler passieren:
# Lokalen Tag löschen
git tag -d v1.0.0
# Remote Tag löschen
git push origin :refs/tags/v1.0.0
# Neuen Tag erstellen
git tag -a v1.0.0 <correct-commit> -m "Release 1.0.0"
git push origin v1.0.0Warnung: Nur bei Tags tun, die noch nicht veröffentlicht/deployed sind. Veröffentlichte Tags zu ändern, bricht Trust und Reproduzierbarkeit.
Git Describe: Generiere Versions-String aus Tags
$ git describe
v1.2.0-5-g7e8f9a0
# │ │
# │ └─ Short commit hash
# └─── 5 commits seit v1.2.0
$ git describe --tags --always
v1.2.0-5-g7e8f9a0
# In Build-Scripts nutzen
VERSION=$(git describe --tags --always)
docker build -t myapp:${VERSION} .Semantic-Release: Automatisches Versioning
# Analysiert Commits seit letztem Tag
# Bestimmt nächste Version (MAJOR/MINOR/PATCH)
# Erstellt Tag und Release
npx semantic-releaseTags sind mehr als Bookmarks – sie sind das fundamentale Werkzeug für Release-Management und Production-Steuerung:
Technisch: Entweder simple Referenzen (Lightweight) oder vollwertige Git-Objekte (Annotated) mit Metadaten und optionaler Signatur.
Prozessual: Der Trigger für Production-Deployments, die Dokumentation von Releases, die Referenz für Rollbacks.
Semantisch: Durch SemVer kodieren Tags Kompatibilitäts-Information. Durch Conventional Commits werden Tags automatisch generierbar.
Immutabel: Während Branches die Bewegung der Entwicklung sind, sind Tags die Fixpunkte der Historie.
Die Kombination aus: - Branches für Entwicklungs-Steuerung - Tags für Release-Steuerung - SemVer für Versions-Semantik - Conventional Commits für Message-Semantik - CI/CD für Automatisierung
… bildet ein robustes, skalierbares Release-Management-System.
Ein gut getaggtes Repository erzählt seine Release-Geschichte. Jeder Tag ist ein Kapitel: Was wurde released, wann, warum, von wem. Diese Geschichte ist wertvoll – für Debugging, für Compliance, für Verständnis.
Nutze Annotated Tags. Schreibe aussagekräftige Messages. Folge SemVer. Nutze Conventional Commits. Automatisiere mit CI/CD. Deine Future Self (und deine Kollegen, und deine Nutzer) werden es dir danken.
Tags sind nicht optional oder “nice to have”. Sie sind fundamental für professionelles Software-Engineering. Master sie, und Release-Management wird von chaotisch zu systematisch.