← Kursa Dön
📄 Text · 30 min

References ve HEAD

Giriş — İsimsiz Nesnelere İsim Vermek

Önceki derste Git'in nesne modelini öğrendin: her şey SHA-1 hash'lerle adresleniyordu. Ama düşünsene, her gün "abc1234def5678ghi9012jkl3456mno78901" gibi 40 karakterlik hash'lerle mi çalışacaksın? Tabii ki hayır.

İşte referanslar (refs) bu sorunu çözer. Referanslar, karmaşık hash'lere insan tarafından okunabilir isimler veren dosyalardır. main, feature/login, v1.0.0, HEAD — bunların hepsi aslında bir commit hash'ine işaret eden referanslardır.

Bu derste referans sisteminin tamamını, HEAD'in ne olduğunu, detached HEAD durumunu ve hayat kurtaran reflog'u öğreneceksin.


Referans Nedir?

Analoji — Telefon Rehberi

Arkadaşının telefon numarasını ezbere bilmene gerek yok. Rehberinde "Ahmet" yazıyor, tıklıyorsun, arama yapılıyor. "Ahmet" ismi, +90 532 123 4567 numarasına işaret eden bir referans.

Git referansları da aynı şekilde çalışır:

Referans (isim)     →    Commit Hash (gerçek adres)
────────────────────     ─────────────────────────────
main                →    abc1234def5678ghi9012jkl3456
feature/login       →    def5678ghi9012jkl3456mno7890
v1.0.0              →    ghi9012jkl3456mno7890pqr1234
HEAD                →    refs/heads/main (symbolic)

Referanslar Birer Dosyadır

Referanslar .git/refs/ dizinindeki basit metin dosyalarıdır. Her dosya tek bir hash içerir:

# main branch'inin referansı:
cat .git/refs/heads/main
# abc1234def5678ghi9012jkl3456mno78901

# Bu, "main" dediğimizde Git'in abc1234... commit'ine gitmesi anlamına gelir
.git/refs/
├── heads/          ← Branch referansları
│   ├── main        ← main branch → commit hash
│   ├── develop     ← develop branch → commit hash
│   └── feature/
│       └── login   ← feature/login → commit hash
├── tags/           ← Tag referansları
│   ├── v1.0.0      ← v1.0.0 tag → commit/tag hash
│   └── v1.1.0
└── remotes/        ← Remote tracking branch'ler
    └── origin/
        ├── main    ← origin/main → commit hash
        └── develop

refs/heads — Branch'ler

Branch'ler sadece birer referanstır — bir commit'e işaret eden dosyalar. Branch'i "özel" yapan şey, yeni commit yaptığında otomatik olarak ilerlemesi.

# Branch referansını oku
git rev-parse main
# abc1234def5678ghi9012jkl3456mno78901

# Doğrudan dosyayı oku
cat .git/refs/heads/main
# abc1234def5678ghi9012jkl3456mno78901

# Aynı sonuç! Branch = dosyadaki hash

Branch Oluşturma = Dosya Oluşturma

# Bu iki komut eşdeğer:

# Porcelain (üst seviye):
git branch new-feature

# Plumbing (alt seviye):
echo "abc1234def5678ghi9012jkl3456mno78901" > .git/refs/heads/new-feature

Branch İlerleme Mekanizması

Commit öncesi:
  main → C  (abc1234)
  HEAD → refs/heads/main

Commit sonrası:
  main → D  (def5678)  ← main artık yeni commit'e işaret ediyor
  HEAD → refs/heads/main  ← HEAD hâlâ main'e işaret ediyor

Yani: git commit = yeni commit oluştur + branch referansını güncelle
# Commit öncesi:
cat .git/refs/heads/main
# abc1234...

# Commit yap:
git commit -m "New feature"

# Commit sonrası:
cat .git/refs/heads/main
# def5678...  ← Otomatik güncellendi!

refs/tags — Tag'ler

Tag referansları da .git/refs/tags/ dizinindedir:

# Lightweight tag:
cat .git/refs/tags/v1.0.0
# abc1234...  ← Doğrudan commit hash'ine işaret eder

# Annotated tag:
cat .git/refs/tags/v2.0.0
# def5678...  ← Tag nesnesinin hash'ine işaret eder (commit değil!)
#               Tag nesnesi ise commit'e işaret eder
Lightweight tag referansı:
  v1.0.0 ──► commit (abc1234)

Annotated tag referansı:
  v2.0.0 ──► tag object (def5678) ──► commit (ghi9012)

refs/remotes — Remote Tracking Branch'ler

Remote tracking branch'ler, remote repository'deki branch'lerin yerel kopyalarıdır:

# Remote tracking branch:
cat .git/refs/remotes/origin/main
# abc1234...  ← origin'deki main'in son bilinen commit'i

# Bu dosya ne zaman güncellenir?
# git fetch veya git pull yaptığında
Local branch:           refs/heads/main           → commit X
Remote tracking:        refs/remotes/origin/main   → commit Y
Remote (GitHub'daki):   (Git'in yerel bilmediği)   → commit Z

git fetch sonrası:
Local branch:           refs/heads/main           → commit X (değişmez)
Remote tracking:        refs/remotes/origin/main   → commit Z (güncellendi!)

💡 İpucu: Remote tracking branch'ler salt okunurdur. Direkt olarak checkout yapıp üzerlerinde çalışamazsın. git checkout origin/main dersen detached HEAD durumuna geçersin.


HEAD — "Sen Buradasın" İşareti

HEAD, Git'teki en önemli referanstır. "Şu an neredesin?" sorusunun cevabıdır.

HEAD Türleri

1. Normal HEAD (symbolic ref):
   HEAD → refs/heads/main → commit hash
   "main branch'indeyim"

2. Detached HEAD:
   HEAD → commit hash (doğrudan)
   "Hiçbir branch'te değilim, bir commit'in üzerindeyim"
# Normal durum:
cat .git/HEAD
# ref: refs/heads/main

# Detached HEAD:
cat .git/HEAD
# abc1234def5678ghi9012jkl3456mno78901

HEAD Nasıl Değişir?

# Branch değiştir → HEAD güncellenir
git checkout main
cat .git/HEAD
# ref: refs/heads/main

git checkout develop
cat .git/HEAD
# ref: refs/heads/develop

# Belirli bir commit'e checkout → Detached HEAD
git checkout abc1234
cat .git/HEAD
# abc1234def5678ghi9012jkl3456mno78901

HEAD Kısaltmaları

# HEAD = şu anki commit
git show HEAD

# HEAD~1 = bir önceki commit (parent)
git show HEAD~1

# HEAD~2 = iki önceki commit
git show HEAD~2

# HEAD^ = ilk parent (merge commit'te)
# HEAD^2 = ikinci parent (merge commit'te)

# HEAD~1 ve HEAD^ genellikle aynı
# Fark sadece merge commit'lerde ortaya çıkar:

#        HEAD
#         |
# A───B───M  (merge commit: M)
#      \ /
#       C
#
# HEAD^  = B (ilk parent, main branch'teki)
# HEAD^2 = C (ikinci parent, feature branch'teki)
# HEAD~1 = B (bir adım geri)
# HEAD~2 = A (iki adım geri, ilk parent zincirinde)

Detached HEAD — Kopuk Kafa

Ne Zaman Olur?

# 1. Belirli bir commit'e checkout
git checkout abc1234

# 2. Bir tag'e checkout
git checkout v1.0.0

# 3. Remote tracking branch'e checkout
git checkout origin/main

# Git seni uyarır:
# You are in 'detached HEAD' state. You can look around, make
# experimental changes and commit them, and you can discard any
# commits you make in this state without impacting any branches
# by switching back to a branch.

Detached HEAD'de Ne Olur?

Normal durum:
HEAD → main → C (commit)
Yeni commit: HEAD → main → D → C
Branch otomatik ilerler ✅

Detached HEAD:
HEAD → C (commit)  (branch yok!)
Yeni commit: HEAD → D → C
Ama D hiçbir branch'a bağlı değil! ⚠️
# Detached HEAD'de commit yaptın:
git checkout abc1234  # Detached HEAD
echo "test" > test.txt
git add .
git commit -m "Detached commit"
# Bu commit havada kalıyor — hiçbir branch'a bağlı değil!

# Başka bir branch'e geçersen:
git checkout main
# Yaptığın commit kaybolur!* (reflog'da kalır ama erişilmesi zor)

Detached HEAD'den Kurtulma

# Yöntem 1: Yeni branch oluştur (commit'leri kaybet.me)
git checkout -b save-my-work

# Yöntem 2: Mevcut branch'e dön (commit'lerden vazgeç)
git checkout main

# Yöntem 3: Kayıp commit'i kurtarma (reflog ile)
git reflog
# abc1234 HEAD@{1}: commit: Detached commit  ← Bu!
git branch rescue-branch abc1234

⚠️ Dikkat: Detached HEAD'de commit yapmak tehlikelidir çünkü hiçbir branch bu commit'i referans etmez. Git'in garbage collector'ı (gc) bir süre sonra bu "yetim" commit'leri silebilir. Commit yaptıysan, mutlaka bir branch oluştur!


Symbolic Ref

HEAD bir symbolic ref'tir — başka bir referansa işaret eden referans:

# Symbolic ref oku:
git symbolic-ref HEAD
# refs/heads/main

# Symbolic ref yaz (branch değiştirmek gibi):
git symbolic-ref HEAD refs/heads/develop
# Şimdi develop branch'indesin

# Detached HEAD durumunda symbolic-ref çalışmaz:
git symbolic-ref HEAD
# fatal: ref HEAD is not a symbolic ref

packed-refs — Sıkıştırılmış Referanslar

Çok sayıda branch ve tag olduğunda, her biri için ayrı dosya tutmak verimsiz olabilir. Git, bu referansları tek bir dosyada paketleyebilir:

cat .git/packed-refs
# pack-refs with: peeled fully-peeled sorted
abc1234def5678ghi9012jkl3456mno78901 refs/heads/main
def5678ghi9012jkl3456mno78901pqr12345 refs/heads/develop
ghi9012jkl3456mno78901pqr12345stu56789 refs/tags/v1.0.0
^jkl3456mno78901pqr12345stu56789vwx90123 refs/tags/v1.0.0 (peeled)
Git referans arama sırası:
1. .git/refs/heads/main dosyasına bak (loose ref)
2. Yoksa .git/packed-refs dosyasına bak (packed ref)

Loose ref varsa packed'ı ezer (override).
git gc sırasında loose ref'ler pack'lenir.

Reflog — Hayat Kurtaran Kayıt

Analoji — Tarayıcının Geri Tuşu

Web tarayıcında "Geri" butonuna basınca önceki sayfaya dönersin. Tarayıcı, ziyaret ettiğin her sayfayı kaydeder. Yanlışlıkla bir sayfayı kapattıysan, geçmişten geri alabilirsin.

Reflog (reference log), Git'teki "geri tuşu"dur. HEAD'in veya branch'lerin geçirdiği her değişikliği kaydeder. Commit, checkout, reset, rebase, merge — her şey reflog'da.

Reflog Görüntüleme

git reflog
abc1234 HEAD@{0}: commit: feat: Add search
def5678 HEAD@{1}: checkout: moving from feature to main
ghi9012 HEAD@{2}: commit: WIP: search component
jkl3456 HEAD@{3}: checkout: moving from main to feature
mno7890 HEAD@{4}: reset: moving to HEAD~2
pqr1234 HEAD@{5}: commit: feat: Add dashboard
stu5678 HEAD@{6}: commit: feat: Add login
vwx9012 HEAD@{7}: commit (initial): Initial commit

Her satır:

abc1234       HEAD@{0}:        commit:         feat: Add search
   │              │                │                  │
   │              │                │                  └── Açıklama
   │              │                └──────────────────── İşlem türü
   │              └───────────────────────────────────── Reflog index
   └──────────────────────────────────────────────────── Commit hash

Reflog ile Kurtarma Senaryoları

Senaryo 1: Yanlış git reset --hard

# Yanlışlıkla son 3 commit'i sildim!
git reset --hard HEAD~3

# Panik! Ama reflog var:
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~3
# def5678 HEAD@{1}: commit: feat: Important feature  ← BU!

# Geri al:
git reset --hard def5678
# Commit'ler geri geldi! 🎉

Senaryo 2: Yanlış Rebase Sonrası Kurtarma

# Rebase yaptım ama her şey bozuldu!
git rebase main  # Conflict çıktı, yanlış çözdüm...

# Reflog'da rebase öncesi durumu bul:
git reflog
# abc1234 HEAD@{0}: rebase (finish): refs/heads/feature
# def5678 HEAD@{1}: rebase (pick): feat: Step 2
# ghi9012 HEAD@{2}: rebase (start): checkout main
# jkl3456 HEAD@{3}: commit: feat: Step 2  ← REBASE ÖNCESİ!

# Rebase öncesine dön:
git reset --hard jkl3456

Senaryo 3: Silinen Branch'i Kurtarma

# Branch'i sildim ama commit'ler lazımdı!
git branch -D feature/important
# Deleted branch feature/important (was abc1234)

# Reflog'da bul:
git reflog
# ... abc1234 HEAD@{5}: commit: Last commit on feature/important

# Branch'i geri oluştur:
git branch feature/important abc1234

Senaryo 4: Detached HEAD'de Yapılan Commit'i Kurtarma

# Detached HEAD'de commit yaptım, sonra branch'e döndüm
# Commit kayboldu!

git reflog
# abc1234 HEAD@{3}: commit: My detached commit  ← BU!

git branch rescue abc1234
git checkout rescue

Branch Reflog'u

Reflog sadece HEAD için değil, branch'ler için de tutulur:

# main branch'inin reflog'u
git reflog show main
# abc1234 main@{0}: merge feature/login: Fast-forward
# def5678 main@{1}: commit: fix: Bug fix
# ghi9012 main@{2}: commit: feat: Add feature

Reflog Ömrü

# Reflog sonsuza kadar saklanmaz:
# Varsayılan ayarlar:
# - Erişilebilir commit'ler: 90 gün
# - Erişilemez commit'ler: 30 gün

# Ayarları gör:
git config gc.reflogExpire
# 90.days.ago

git config gc.reflogExpireUnreachable
# 30.days.ago

# Değiştirmek istersen:
git config --global gc.reflogExpire "180 days"

⚠️ Dikkat: Reflog sadece yerelde tutulur. git clone ile klonlanan repo'da eski reflog yok. Remote repo'nun reflog'u sende yok. Reflog bir güvenlik ağıdır ama kalıcı yedek değildir.


git rev-parse — Referans Çözümleme

git rev-parse herhangi bir referansı commit hash'ine çevirir:

# Branch adından hash
git rev-parse main
# abc1234...

# Tag'den hash
git rev-parse v1.0.0
# def5678...

# HEAD
git rev-parse HEAD
# ghi9012...

# Kısa hash
git rev-parse --short HEAD
# ghi9012

# HEAD~2
git rev-parse HEAD~2
# jkl3456...

# Stash
git rev-parse stash@{0}
# mno7890...

# Branch'in var olup olmadığını kontrol et
git rev-parse --verify feature/login
# pqr1234... (varsa hash döner, yoksa hata)

Referans Sözdizimi Özeti

# ═══════════════════════════════════════════
# REFERANS SÖZDİZİMİ
# ═══════════════════════════════════════════

HEAD              # Şu anki commit
HEAD~1            # 1 önceki commit (parent)
HEAD~3            # 3 önceki commit
HEAD^             # İlk parent (= HEAD~1, merge dışında)
HEAD^2            # İkinci parent (sadece merge commit'lerde)

main              # main branch'in son commit'i
main~3            # main'in 3 önceki commit'i
feature/login     # feature/login branch'i

v1.0.0            # Tag
v1.0.0^{commit}   # Tag'in işaret ettiği commit (annotated tag için)

origin/main       # Remote tracking branch

HEAD@{1}          # 1 adım önceki HEAD (reflog)
main@{yesterday}  # Dünkü main (reflog, zaman bazlı)
main@{2.days.ago} # 2 gün önceki main
main@{5}          # 5 adım önceki main (reflog index)

HEAD^{tree}       # HEAD commit'inin tree nesnesi
HEAD:README.md    # HEAD'deki README.md dosyasının blob'u

abc1234           # Kısa hash (minimum 4 karakter, benzersiz olmalı)
abc1234def5678    # Uzun hash

Pratik: Referans Sistemiyle Oynama

# Proje oluştur
mkdir refs-demo && cd refs-demo
git init

# Birkaç commit yap
echo "v1" > file.txt && git add . && git commit -m "Commit 1"
echo "v2" > file.txt && git add . && git commit -m "Commit 2"
echo "v3" > file.txt && git add . && git commit -m "Commit 3"

# ═══════════════════════════════════════
# REFERANSLARI İNCELE
# ═══════════════════════════════════════

echo "=== HEAD ==="
cat .git/HEAD
# ref: refs/heads/main

echo "=== main branch ==="
cat .git/refs/heads/main
# abc1234...

echo "=== Aynı hash mi? ==="
git rev-parse HEAD
git rev-parse main
# Evet, aynı!

# ═══════════════════════════════════════
# BRANCH OLUŞTUR (DÜŞÜK SEVİYE)
# ═══════════════════════════════════════

# Normal yol:
git branch test-branch

# Plumbing yol (aynı etkiyi yapar):
HASH=$(git rev-parse HEAD)
echo $HASH > .git/refs/heads/manual-branch

# İkisi de var:
git branch
# * main
#   manual-branch
#   test-branch

# ═══════════════════════════════════════
# DETACHED HEAD DENEYİ
# ═══════════════════════════════════════

# Detached HEAD'e geç
git checkout HEAD~1

cat .git/HEAD
# abc1234... (doğrudan hash — symbolic ref değil!)

# Commit yap
echo "detached" > detached.txt
git add .
git commit -m "Detached commit"

# Branch'e dön
git checkout main

# Detached commit'i reflog'da bul
git reflog
# ... HEAD@{1}: commit: Detached commit ...

# Kurtar!
git branch rescued HEAD@{1}
git log --oneline rescued
# def5678 Detached commit ← Kurtarıldı!

# ═══════════════════════════════════════
# REFLOG İNCELE
# ═══════════════════════════════════════

git reflog --all
# HEAD'in ve tüm branch'lerin reflog'u

git reflog show main
# Sadece main'in reflog'u

# Zaman bazlı sorgulama
git log -1 main@{yesterday}
# "main dün neredeydi?"

# Temizlik
git branch -D test-branch manual-branch rescued

Özet

Bu derste Git'in referans sistemini ve HEAD'i derinlemesine öğrendik:

  • Referanslar commit hash'lerine insan tarafından okunabilir isimler veren dosyalardır — refs/heads/ branch'ler, refs/tags/ tag'ler, refs/remotes/ remote tracking branch'ler

  • HEAD "şu an neredesin" sorusunun cevabıdır — normal durumda bir branch'e işaret eden symbolic ref, detached durumda doğrudan commit hash'i

  • Detached HEAD hiçbir branch'te olmadığın durumdur — burada yapılan commit'ler kaybolabilir, mutlaka branch oluştur

  • Reflog Git'in "geri tuşu"dur — HEAD ve branch'lerin her değişikliğini kaydeder. Yanlış reset, rebase, branch silme gibi felaketlerden kurtarır

  • packed-refs çok sayıda referansı tek dosyada saklar — performans optimizasyonu

  • git rev-parse herhangi bir referansı commit hash'ine çevirir — scriptlerde çok yararlı

Bir sonraki derste Git'in nesneleri nasıl sakladığını ve optimize ettiğini göreceğiz: packfile'lar ve garbage collection.