← Kursa Dön
📄 Text · 30 min

Tag ve Release

Giriş — Sürüm Kavramı

Bir yazılım projesi geliştirirken düşün: commit'lerin akıyor, branch'ler açılıp kapanıyor, her gün onlarca değişiklik yapılıyor. Ama bir noktada "Tamam, bu sürüm yayınlanabilir" diyorsun. Kullanıcılara sunduğun, production'a deploy ettiğin, "v1.0 budur" dediğin o ana bir işaret koymalısın.

İşte tag bu işaretin ta kendisi. Commit tarihçesindeki belirli bir noktaya yapıştırılan kalıcı bir etiket. Branch'ler hareket eder (her commit'le ilerler), ama tag'ler sabit kalır. "Bu commit v1.0'dır" — nokta.

Bu derste tag'lerin ne olduğunu, nasıl oluşturulacağını, semantic versioning kavramını ve GitHub Releases ile tag'leri nasıl birleştireceğini öğreneceksin.


Tag Nedir?

Analoji — Kitaptaki Sayfa İmi

Bir kitap okuyorsun (commit tarihçesi). Sayfalar ilerliyor (commit'ler). Önemli bir sayfaya geldiğinde bir ayraç koyuyorsun — "Burası önemli, sonra dönmem gerekebilir."

Tag tam olarak bu ayraç. Commit akışındaki belirli bir noktayı işaretler:

Commit tarihçesi:
A───B───C───D───E───F───G───H───I───J───K
        ↑           ↑               ↑
      v0.1        v1.0            v2.0
      (beta)    (release)       (major update)

Tag'ler branch'lerden farklıdır:

Branch (dal):  Büyüyen, hareket eden bir işaretçi
               Her commit'le ilerler
               Geçici — iş bitince silinir

Tag (etiket):  Sabit bir işaretçi
               Bir commit'e yapışır, hareket etmez
               Kalıcı — sürümü temsil eder

İki Tür Tag

Git'te iki tür tag var: lightweight (hafif) ve annotated (açıklamalı).

Lightweight Tag

Sadece bir commit'e işaret eden basit bir işaretçi. Ek bilgi içermez.

# Lightweight tag oluştur
git tag v1.0

# Belirli bir commit'e tag koy
git tag v0.9 abc1234
# Lightweight tag'in yapısı:
# Sadece bir ref dosyası: .git/refs/tags/v1.0
# İçeriği: commit hash'i

cat .git/refs/tags/v1.0
# abc1234def5678ghi9012jkl3456mno78901

Annotated Tag

Tag'e ek bilgi ekler: kim oluşturdu, ne zaman, açıklama mesajı. Git veritabanında ayrı bir tag nesnesi olarak saklanır.

# Annotated tag oluştur
git tag -a v1.0 -m "Version 1.0: İlk stabil sürüm"

# Belirli bir commit'e annotated tag
git tag -a v0.9 -m "Beta release" abc1234

# Tag bilgilerini görüntüle
git show v1.0

git show v1.0 çıktısı:

tag v1.0
Tagger: Ahmet <ahmet@email.com>
Date:   Sun Mar 2 14:30:00 2025 +0300

Version 1.0: İlk stabil sürüm

commit abc1234def5678
Author: Ahmet <ahmet@email.com>
Date:   Sat Mar 1 18:00:00 2025 +0300

    feat: Finalize v1.0 features

diff --git a/...

Lightweight vs Annotated — Hangisini Kullanmalı?

┌──────────────────┬───────────────────┬───────────────────┐
│    Özellik       │   Lightweight     │    Annotated      │
├──────────────────┼───────────────────┼───────────────────┤
│ Oluşturan bilgisi│ Yok               │ Var (Tagger)      │
│ Tarih            │ Yok               │ Var               │
│ Mesaj            │ Yok               │ Var               │
│ GPG imzalama     │ Yok               │ Var               │
│ Git nesnesi      │ Hayır (sadece ref)│ Evet (tag object) │
│ Komut            │ git tag v1.0      │ git tag -a v1.0   │
│ Kullanım         │ Geçici, kişisel   │ Resmi sürümler    │
└──────────────────┴───────────────────┴───────────────────┘
Kural: Resmi sürümler için HER ZAMAN annotated tag kullan.
Lightweight tag'ler sadece geçici, kişisel işaretler için.

💡 İpucu: git describe komutu sadece annotated tag'lerle çalışır. CI/CD pipeline'larında sürüm numarası almak için git describe --tags kullanılır — bu yüzden annotated tag önemlidir.


Tag Yönetimi

Tag'leri Listeleme

# Tüm tag'ler
git tag
# v0.1
# v0.2
# v1.0
# v1.1
# v2.0

# Pattern ile filtreleme
git tag -l "v1.*"
# v1.0
# v1.1

# Detaylı listeleme (tarih ve mesajla)
git tag -l -n1
# v0.1    Beta release
# v1.0    İlk stabil sürüm
# v1.1    Bug fix release
# v2.0    Major update with new UI

# Daha fazla mesaj satırı
git tag -l -n5

Tag Silme

# Yerel tag silme
git tag -d v1.0
# Deleted tag 'v1.0' (was abc1234)

# Remote'tan tag silme
git push origin --delete v1.0
# veya
git push origin :refs/tags/v1.0

Tag Push'lama

Tag'ler otomatik olarak push edilmez! Manuel olarak göndermelisin:

# Tek bir tag'i push et
git push origin v1.0

# Tüm tag'leri push et
git push origin --tags

# Sadece annotated tag'leri push et (önerilen)
git push origin --follow-tags

⚠️ Dikkat: git push --tags hem lightweight hem annotated tüm tag'leri gönderir. Production ortamında --follow-tags tercih et — sadece annotated tag'leri gönderir, geçici lightweight tag'leri göndermez.

Varsayılan Olarak Tag Push'lama

# Her push'ta annotated tag'leri otomatik gönder
git config --global push.followTags true

Semantic Versioning (SemVer)

Sürüm Numarası Nasıl Belirlenir?

"v1.0" dedik ama bu numara nasıl belirlenir? Rastgele mi? Hayır. Yazılım dünyasında Semantic Versioning (SemVer) adında bir standart var.

SemVer Formatı

MAJOR.MINOR.PATCH
  │     │     │
  │     │     └── Bug düzeltmeleri (geriye uyumlu)
  │     └──────── Yeni özellikler (geriye uyumlu)
  └────────────── Kırılma değişiklikleri (geriye uyumsuz!)

Ne Zaman Hangi Numara Artar?

┌──────────────────────────────────────────────────────────┐
│              SEMANTIC VERSIONING KURALLARI                │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  PATCH (1.0.X): Bug fix, güvenlik yaması                │
│  ─────────────────────────────────────                   │
│  • Mevcut API değişmez                                   │
│  • Kullanıcıların kodu bozulmaz                         │
│  • Örnek: 1.0.0 → 1.0.1                                │
│  • "Login'deki typo düzeltildi"                         │
│                                                          │
│  MINOR (1.X.0): Yeni özellik (geriye uyumlu)            │
│  ─────────────────────────────────────                   │
│  • Yeni fonksiyonlar/endpoint'ler eklenir                │
│  • Mevcut API bozulmaz                                   │
│  • Örnek: 1.0.1 → 1.1.0                                │
│  • "Dark mode eklendi"                                   │
│  • PATCH sıfırlanır!                                     │
│                                                          │
│  MAJOR (X.0.0): Kırılma değişikliği (breaking change)   │
│  ─────────────────────────────────────                   │
│  • API değişir, eski kod çalışmayabilir                  │
│  • Kullanıcıların kodunu güncellemesi gerekir            │
│  • Örnek: 1.1.0 → 2.0.0                                │
│  • "Tüm API endpoint'leri /v2/ altına taşındı"          │
│  • MINOR ve PATCH sıfırlanır!                            │
│                                                          │
└──────────────────────────────────────────────────────────┘

SemVer Örnekleri

1.0.0  → İlk stabil sürüm
1.0.1  → Bug fix: login hatası düzeltildi
1.0.2  → Bug fix: güvenlik yaması
1.1.0  → Yeni özellik: arama fonksiyonu eklendi (PATCH sıfırlandı)
1.1.1  → Bug fix: arama sonuçlarında sıralama hatası
1.2.0  → Yeni özellik: dark mode eklendi
2.0.0  → Breaking change: API v2'ye geçiş (MINOR ve PATCH sıfırlandı)
2.0.1  → Bug fix: API v2'de küçük düzeltme

Pre-release ve Build Metadata

1.0.0-alpha      → Alfa sürüm (erken geliştirme)
1.0.0-alpha.1    → İkinci alfa
1.0.0-beta       → Beta sürüm (test aşaması)
1.0.0-beta.2     → İkinci beta
1.0.0-rc.1       → Release Candidate (son test)
1.0.0            → Stabil sürüm

Sıralama: alpha < beta < rc < release

Build metadata (bilgi amaçlı, sıralamayı etkilemez):
1.0.0+build.123
1.0.0+20250301

v Öneki

# Teknik olarak SemVer "v" öneki içermez
# Ama pratikte çoğu proje "v" kullanır:
git tag v1.0.0  # Yaygın tercih ✅
git tag 1.0.0   # Teknik olarak doğru ama daha az yaygın

# Projenizde tutarlı olun — ya hep v kullanın ya hiç

Git Describe — Otomatik Sürüm Bilgisi

git describe, en yakın annotated tag'e göre mevcut commit'in konumunu açıklar:

git describe
# v1.2.0-5-gabc1234
#   │      │    │
#   │      │    └── Commit hash'inin kısaltması
#   │      └─────── Son tag'den bu yana 5 commit geçmiş
#   └────────────── En yakın annotated tag
# Eğer tam tag üzerindeysen:
git describe
# v1.2.0

# Tag'ler dahil (lightweight da)
git describe --tags

# Her zaman uzun format
git describe --long
# v1.2.0-0-gabc1234  (0 commit uzakta = tam tag üzerinde)

Bu bilgi CI/CD'de çok yararlı:

# Docker image'a sürüm numarası ver
VERSION=$(git describe --tags --always)
docker build -t myapp:$VERSION .

# Package.json'daki sürümü güncelle
npm version $(git describe --tags --abbrev=0)

GitHub Releases

GitHub Releases, tag'leri kullanıcı dostu bir arayüzle sunar. Tag'in üzerine açıklama, değişiklik listesi ve dosya ekleri (binary, installer) ekleyebilirsin.

Release vs Tag

Tag:     Git seviyesinde bir işaretçi (commit'e referans)
Release: GitHub seviyesinde bir paket (tag + açıklama + dosyalar)

Her release bir tag'e dayanır.
Ama her tag bir release olmak zorunda değil.

Release Oluşturma (Web Arayüzü)

1. Repo → Releases → "Draft a new release"
2. Tag seç (veya yeni oluştur)
3. Release başlığı: "v1.0.0 — İlk Stabil Sürüm"
4. Açıklama yaz (Changelog)
5. Dosya ekle (binary, installer, documentation)
6. Pre-release mı? (alpha/beta için)
7. "Publish release"

Release Oluşturma (GitHub CLI)

# Basit release
gh release create v1.0.0 --title "v1.0.0" --notes "İlk stabil sürüm"

# Detaylı release
gh release create v1.0.0 \
  --title "v1.0.0 — İlk Stabil Sürüm" \
  --notes "## Yenilikler
- ✨ Kullanıcı kayıt sistemi
- 🔐 JWT authentication
- 📊 Dashboard

## Bug Düzeltmeleri
- 🐛 Login redirect sorunu çözüldü
- 🐛 Şifre sıfırlama maili düzeltildi

## Breaking Changes
- Yok

Full Changelog: https://github.com/user/repo/compare/v0.9...v1.0.0"

# Dosya ekleyerek release
gh release create v1.0.0 ./dist/app-linux.tar.gz ./dist/app-macos.zip \
  --title "v1.0.0" \
  --notes-file CHANGELOG.md

# Pre-release (alpha/beta)
gh release create v2.0.0-beta.1 --prerelease \
  --title "v2.0.0 Beta 1" \
  --notes "Bu bir beta sürümüdür. Production'da kullanmayın."

# Draft release (yayınlamadan hazırla)
gh release create v1.1.0 --draft \
  --title "v1.1.0" \
  --notes "Yayınlanmayı bekliyor..."

Auto-generated Release Notes

GitHub, PR'lardan otomatik release notes oluşturabilir:

# GitHub'ın otomatik release notes'unu kullan
gh release create v1.1.0 --generate-notes

# Bu, son release'den bu yana merge edilen PR'ları listeler:
# ## What's Changed
# * feat: Add dark mode by @ahmet in #45
# * fix: Resolve login issue by @ayse in #47
# * docs: Update API documentation by @ali in #48
# 
# ## New Contributors
# * @ali made their first contribution in #48
# 
# **Full Changelog**: https://github.com/user/repo/compare/v1.0.0...v1.1.0

Release Notes Yapılandırma

# .github/release.yml dosyası ile kategorileri özelleştir
cat > .github/release.yml << 'EOF'
changelog:
  exclude:
    labels:
      - skip-changelog
    authors:
      - dependabot
  categories:
    - title: 🚀 Yeni Özellikler
      labels:
        - enhancement
        - feature
    - title: 🐛 Bug Düzeltmeleri
      labels:
        - bug
        - fix
    - title: 📝 Dokümantasyon
      labels:
        - documentation
    - title: ⬆️ Bağımlılık Güncellemeleri
      labels:
        - dependencies
    - title: 🏗️ Diğer Değişiklikler
      labels:
        - "*"
EOF

Bu yapılandırma ile auto-generated release notes şöyle görünür:

## 🚀 Yeni Özellikler
* feat: Add dark mode by @ahmet in #45
* feat: Add search functionality by @ayse in #50

## 🐛 Bug Düzeltmeleri
* fix: Resolve login issue by @ali in #47

## 📝 Dokümantasyon
* docs: Update API docs by @veli in #48

CHANGELOG.md — Değişiklik Günlüğü

Release'lerle birlikte, projenin kök dizininde bir CHANGELOG.md dosyası tutmak yaygın bir pratiktir.

CHANGELOG Formatı (Keep a Changelog)

# Changelog

Tüm önemli değişiklikler bu dosyada belgelenir.

Bu proje [Semantic Versioning](https://semver.org/) kullanır.

## [Unreleased]
### Added
- Bildirim sistemi (PR #55)

## [1.1.0] - 2025-03-01
### Added
- Dark mode desteği (#45)
- Arama fonksiyonu (#50)
### Fixed
- Login redirect sorunu (#47)
### Changed
- Dashboard layout güncellendi (#49)

## [1.0.0] - 2025-02-01
### Added
- Kullanıcı kayıt sistemi
- JWT authentication
- Dashboard
- Profil sayfası

## [0.9.0] - 2025-01-15
### Added
- İlk beta sürümü
- Temel CRUD işlemleri

[Unreleased]: https://github.com/user/repo/compare/v1.1.0...HEAD
[1.1.0]: https://github.com/user/repo/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/user/repo/compare/v0.9.0...v1.0.0
[0.9.0]: https://github.com/user/repo/releases/tag/v0.9.0

CHANGELOG Kategorileri

Added      — Yeni özellikler
Changed    — Mevcut fonksiyonalitedeki değişiklikler
Deprecated — Yakında kaldırılacak özellikler
Removed    — Kaldırılan özellikler
Fixed      — Bug düzeltmeleri
Security   — Güvenlik yamaları

💡 İpucu: CHANGELOG'u elle yazmak yerine, conventional commits kullanarak otomatik oluşturabilirsin. standard-version, conventional-changelog, release-please gibi araçlar commit mesajlarından otomatik CHANGELOG üretir.


Tag Workflow — Gerçek Dünya Akışı

Basit Sürüm Çıkma Akışı

# 1. Tüm değişiklikler main'de (veya release branch'te)
git checkout main
git pull origin main

# 2. Son testleri çalıştır
npm test
npm run build

# 3. Sürüm numarasını güncelle (package.json vb.)
npm version minor  # veya major, patch
# Bu otomatik olarak:
# - package.json'daki version'ı günceller
# - Commit oluşturur: "1.1.0"
# - Tag oluşturur: v1.1.0

# 4. Push et (commit + tag)
git push origin main --follow-tags

# 5. GitHub Release oluştur
gh release create v1.1.0 --generate-notes

Release Branch ile Sürüm Çıkma

main ────A───B───C───D───E───F───G───H
                  \                   
release/1.0  ──── C'──D'──v1.0.0──v1.0.1
                                   ↑
                              hotfix

Bu yaklaşımda:
1. main'den release branch açılır
2. Release branch'te son düzeltmeler yapılır
3. Hazır olunca tag konur ve release yapılır
4. Hotfix'ler de bu branch'e uygulanır
5. Değişiklikler main'e geri merge edilir
# Release branch oluştur
git checkout -b release/1.0 main

# Son düzeltmeleri yap
git commit -m "fix: Final adjustments for v1.0"

# Tag koy
git tag -a v1.0.0 -m "Release v1.0.0"

# Push
git push origin release/1.0 --follow-tags

# Main'e geri merge
git checkout main
git merge release/1.0

Pratik: Tam Sürüm Döngüsü

# ═══════════════════════════════════════
# PROJEYİ HAZIRLA
# ═══════════════════════════════════════

mkdir version-demo && cd version-demo
git init

# İlk commit
echo '{ "name": "my-app", "version": "0.1.0" }' > package.json
echo "# My App" > README.md
git add .
git commit -m "Initial commit"

# İlk tag: v0.1.0 (alpha)
git tag -a v0.1.0 -m "Alpha release"

# ═══════════════════════════════════════
# GELİŞTİRME
# ═══════════════════════════════════════

# Yeni özellik ekle
echo "console.log('Hello!');" > index.js
git add .
git commit -m "feat: Add main entry point"

echo "function login() {}" > auth.js
git add .
git commit -m "feat: Add authentication"

# ═══════════════════════════════════════
# İLK STABIL SÜRÜM
# ═══════════════════════════════════════

# package.json güncelle
cat > package.json << 'EOF'
{ "name": "my-app", "version": "1.0.0" }
EOF
git add .
git commit -m "chore: Bump version to 1.0.0"

# Annotated tag
git tag -a v1.0.0 -m "v1.0.0 — İlk Stabil Sürüm

Yenilikler:
- Ana giriş noktası eklendi
- Kullanıcı kimlik doğrulama sistemi
- Temel proje yapısı"

# ═══════════════════════════════════════
# BUG FIX
# ═══════════════════════════════════════

echo "function login(email, password) {}" > auth.js
git add .
git commit -m "fix: Add parameters to login function"

cat > package.json << 'EOF'
{ "name": "my-app", "version": "1.0.1" }
EOF
git add .
git commit -m "chore: Bump version to 1.0.1"
git tag -a v1.0.1 -m "v1.0.1 — Bug fix: login parametreleri eklendi"

# ═══════════════════════════════════════
# YENİ ÖZELLİK
# ═══════════════════════════════════════

echo "function search(query) {}" > search.js
git add .
git commit -m "feat: Add search functionality"

cat > package.json << 'EOF'
{ "name": "my-app", "version": "1.1.0" }
EOF
git add .
git commit -m "chore: Bump version to 1.1.0"
git tag -a v1.1.0 -m "v1.1.0 — Arama özelliği eklendi"

# ═══════════════════════════════════════
# KONTROL
# ═══════════════════════════════════════

# Tüm tag'ler
git tag -l -n1
# v0.1.0   Alpha release
# v1.0.0   v1.0.0 — İlk Stabil Sürüm
# v1.0.1   v1.0.1 — Bug fix: login parametreleri eklendi
# v1.1.0   v1.1.0 — Arama özelliği eklendi

# Şu an neredeyiz?
git describe
# v1.1.0

# Grafik tarihçe
git log --oneline --graph --decorate
# * abc1234 (HEAD -> main, tag: v1.1.0) chore: Bump version to 1.1.0
# * def5678 feat: Add search functionality
# * ghi9012 (tag: v1.0.1) chore: Bump version to 1.0.1
# * jkl3456 fix: Add parameters to login function
# * mno7890 (tag: v1.0.0) chore: Bump version to 1.0.0
# * pqr1234 feat: Add authentication
# * stu5678 feat: Add main entry point
# * vwx9012 (tag: v0.1.0) Initial commit

# İki sürüm arasındaki değişiklikler
git log v1.0.0..v1.1.0 --oneline
# abc1234 chore: Bump version to 1.1.0
# def5678 feat: Add search functionality
# ghi9012 chore: Bump version to 1.0.1
# jkl3456 fix: Add parameters to login function

Tag ile İlgili İleri Konular

GPG İmzalı Tag'ler

Güvenlik açısından tag'leri GPG anahtarınla imzalayabilirsin. Bu, tag'in gerçekten senden geldiğini kanıtlar:

# GPG anahtarını ayarla
git config --global user.signingkey YOUR_GPG_KEY_ID

# İmzalı tag oluştur
git tag -s v1.0.0 -m "Signed release v1.0.0"

# İmzayı doğrula
git tag -v v1.0.0
# gpg: Signature made Sun Mar  2 14:30:00 2025
# gpg: Good signature from "Ahmet <ahmet@email.com>"

Tag'i Taşımak (Önerilmez ama Bazen Gerekli)

# Yanlış commit'e tag koyduysan:
git tag -d v1.0.0                        # Yerel tag'i sil
git push origin --delete v1.0.0          # Remote tag'i sil
git tag -a v1.0.0 DOĞRU_COMMIT -m "..."  # Doğru commit'e koy
git push origin v1.0.0                   # Tekrar push et

⚠️ Dikkat: Yayınlanmış tag'leri taşımak, bu tag'e bağlı CI/CD pipeline'ları, Docker image'ları ve kullanıcı referanslarını bozabilir. Mümkünse yeni bir patch sürüm çıkar (v1.0.1).

Belirli Bir Tag'e Checkout

# Tag'in işaret ettiği commit'e git
git checkout v1.0.0
# "Detached HEAD" durumuna geçersin — branch'te değilsin!

# Bu noktadan yeni bir branch oluşturmak istersen:
git checkout -b hotfix/v1.0.0-fix v1.0.0

Yaygın Hatalar

1. Tag'i Push Etmeyi Unutmak

# ❌ Tag oluşturdun ama push etmedin
git tag -a v1.0.0 -m "Release"
git push origin main
# Tag remote'a gitmedi!

# ✅ Tag'i de push et
git push origin v1.0.0
# veya
git push origin --follow-tags

2. Yanlış Sürüm Numarası

# ❌ Breaking change yaptın ama minor sürüm artırdın
# v1.0.0 → v1.1.0  (Yanlış! Breaking change = MAJOR)

# ✅ Doğrusu:
# v1.0.0 → v2.0.0  (Breaking change = MAJOR artmalı)

3. CHANGELOG Güncellemesini Unutmak

# ❌ Tag ve release var ama CHANGELOG güncellenmedi
# Kullanıcılar ne değişti bilmiyor

# ✅ Tag koymadan ÖNCE CHANGELOG'u güncelle
# veya conventional-changelog gibi araçlarla otomatize et

Özet

Bu derste sürüm yönetiminin tüm boyutlarını öğrendik:

  • Tag, commit tarihçesindeki belirli bir noktayı kalıcı olarak işaretler — branch'ler hareket eder, tag'ler sabit kalır

  • Lightweight tag sadece bir işaretçi iken, annotated tag kim oluşturduğu, ne zaman ve neden bilgisini de içerir — resmi sürümler için her zaman annotated tag kullan

  • Semantic Versioning (MAJOR.MINOR.PATCH) ile sürüm numaralarını anlamlı kıl — breaking change = MAJOR, yeni özellik = MINOR, bug fix = PATCH

  • GitHub Releases, tag'lerin üzerine açıklama ve dosya ekleri ekler — --generate-notes ile PR'lardan otomatik release notes oluşturabilirsin

  • CHANGELOG.md ile değişiklikleri belgelemen gerekir — "Keep a Changelog" formatını takip et

  • `git describe` ile mevcut commit'in en yakın tag'e göre konumunu öğrenirsin — CI/CD'de sürüm bilgisi almak için kritik

Bir sonraki bölümde Git'in motorunun kapağını açıyoruz: Git Internals — blob, tree, commit nesneleri ve Git'in aslında nasıl çalıştığı.