Tüm eğitimler
TEKNİK REHBER ARAÇLAR GIT

Git Internals —
Object Model & Plumbing.

commit, push, merge'ün arkasındaki graph — .git/ dizinini, objeleri ve pack file'ları anatomiye indir. Porcelain komutlarını bilen her mühendis için bir sonraki seviye.

00 Git nedir gerçekten

Git, temelde içerik-adresli bir dosya sistemidir; versiyon kontrolü bu filesytem'in üzerine inşa edilmiştir.

Çoğu geliştirici Git'i komutlar dizisi olarak öğrenir: git add, git commit, git push. Ama bu komutların arkasında ne olduğunu bilmeden merge conflict'leri çözmeye çalışmak, semptomları tedavi edip hastalığı bırakmak gibidir. Git'in iç modelini anlamak:

Hata çözümüNeden detached HEAD oluştu, neden merge conflict'te sahip olmadığın değişiklikler var — kökenini anlarsın.
Veri kurtarmaYanlışlıkla silinen commit'leri, stash drop'larını geri getirebilirsin. Çünkü Git nadiren gerçekten siler.
OtomasyonPlumbing komutları ile CI/CD script'leri, hook'lar ve araçlar yazabilirsin.
GüvenNe yapacağını bildiğinde force push'tan korkmak yerine ne zaman gerektiğini bilirsin.

Content-addressed filesystem

Git'te her şey içeriğinden hesaplanan bir SHA-1 hash ile adreslenir. Aynı içerik her zaman aynı hash'i üretir. İçerik değişirse hash değişir — bu integrity garantisi sağlar:

  İçerik  →  SHA-1(header + içerik)  →  .git/objects/ab/cdef1234...
  "hello"     →  8c7e5a667f1b771847ad9a31b3d9cc5f4b0cc3f5
    

DAG — Directed Acyclic Graph

Commit'ler arasındaki bağlantı, döngüsüz yönlü bir grafik (DAG) oluşturur. Her commit, parent commit'ine(lerine) işaret eder:

  A  ←  B  ←  C  ←  D  (main)
               ↑
               E  ←  F  (feature)
    

Merge commit, iki parent'a işaret eder. Bu yapı sayesinde hangi değişikliklerin hangi commit'ten geldiği her zaman izlenebilir.

Snapshot, not diff

SVN ve diğer sistemler dosyalar arasındaki farkı saklar. Git ise her commit'te tüm proje tree'sinin snapshot'ını saklar. Değişmeyen dosyalar için yeni obje oluşturmaz — sadece aynı blob'a işaret eder:

VerimlilikDeğişmeyen dosyalar için yeni obje oluşturulmaz. Sadece pointer (SHA) kaydedilir. Pack file'larda delta compression ayrıca yapılır.
HızHerhangi bir commit'in durumuna geçmek için diff hesaplamaya gerek yok. Doğrudan o snapshot'ın tree'si var.
BütünlükHer commit, tüm projenin o andaki durumunu bağımsız olarak temsil eder. Kayıp diff zinciri problemi yok.

.git/ — tüm repo burada

TEMEL KURAL

Tüm git verisi .git/ dizininde saklanır. Bu dizini bir yerden başka bir yere kopyalarsan, tam bir git repo elde edersin. Çalışma dizinini silersin, .git/'i tutarsan, tüm versiyon geçmişi oradadır — git checkout ile her şeyi geri getirebilirsin.

Bu bölümde

  • Git: content-addressed filesystem — içerik → SHA-1 hash
  • DAG: commit'ler arası döngüsüz yönlü graf yapısı
  • Snapshot modeli: her commit tam tree snapshot'ı, değişmeyen dosyalar paylaşılan blob
  • .git/ — tüm git verisi burada; taşısan repo gider

01 .git/ dizin anatomisi

.git/ altındaki her dosya ve dizin belirli bir görev üstlenir — bunları anlamak Git'in iç çalışmasını görünür kılar.

bash — .git/ içeriği
ls -la .git/

# Çıktı:
# drwxr-xr-x  HEAD
# drwxr-xr-x  config
# drwxr-xr-x  description
# drwxr-xr-x  hooks/
# -rw-r--r--  index
# drwxr-xr-x  info/
# drwxr-xr-x  logs/
# drwxr-xr-x  objects/
# drwxr-xr-x  packed-refs
# drwxr-xr-x  refs/

# HEAD'i oku
cat .git/HEAD
# → ref: refs/heads/main

# Detached HEAD durumunda:
# → a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9

Dosya ve dizin açıklamaları

HEADŞu anki konumu gösterir. Normal durumda ref: refs/heads/branchname formatında symbolic ref. Detached HEAD'de doğrudan SHA.
objects/Tüm git objeleri burada. Alt dizinler SHA'nın ilk iki hex karakteri. Örn: objects/a3/f8c2d1... Binary veya zlib compressed format.
refs/Branch, tag ve remote referansları. refs/heads/ → local branch'ler. refs/tags/ → tag'ler. refs/remotes/ → remote-tracking branch'ler.
indexStaging area'nın binary gösterimi. git add ile güncellenir. git commit bu dosyadan tree oluşturur.
configRepo-local git konfigürasyonu. user.email, remote URL'ler, branch tracking bilgisi burada.
logs/Reflog verileri. logs/HEAD ve logs/refs/heads/branchname dosyaları. Her ref hareketi kayıt altında.
packed-refsÇok sayıda ref içeren repolarda optimizasyon. Tüm ref'ler tek bir dosyada. git gc tarafından oluşturulur.
hooks/Git olaylarına bağlanan shell script'leri. pre-commit, post-commit, pre-push vb. .sample uzantılı örnekler var.
COMMIT_EDITMSGSon commit mesajı. git commit --amend için kullanılır.
ORIG_HEADmerge, rebase, reset gibi HEAD'i değiştiren operasyonlar öncesi HEAD değeri. Geri almak için: git reset --hard ORIG_HEAD
MERGE_HEADDevam eden merge işleminin kaynak commit'i. Merge tamamlandığında silinir.
CHERRY_PICK_HEADDevam eden cherry-pick işleminin kaynak commit'i.

refs/ yapısı

bash — refs inceleme
# Local branch'ler
ls .git/refs/heads/
# main  feature/auth  hotfix/crash

# Bir branch ref içeriği — tek satır SHA
cat .git/refs/heads/main
# a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9

# Remote-tracking branch'ler
ls .git/refs/remotes/origin/
# HEAD  main  develop

# Tag'ler
ls .git/refs/tags/
# v1.0.0  v1.1.0  v2.0.0

# objects/ alt dizin yapısı
ls .git/objects/ | head -5
# 1a  2b  3c  info  pack

ls .git/objects/a3/
# f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9  (SHA'nın geri kalanı)

Bu bölümde

  • HEAD: symbolic ref (refs/heads/main) veya detached SHA
  • objects/: tüm git objeleri SHA ile adresli, zlib compressed
  • refs/heads/, refs/tags/, refs/remotes/ hiyerarşisi
  • index: staging area binary dosyası — bir sonraki commit'in tree'si
  • ORIG_HEAD, MERGE_HEAD, CHERRY_PICK_HEAD — operasyon marker'ları

02 Object türleri: blob, tree, commit, tag

Git'te tam olarak dört obje tipi vardır — tüm versiyon geçmişi bu dört tip objenin ağından oluşur.

Dört obje tipi

TipNe saklarBoyutÖrnek kullanım
blob Dosya içeriği (ham baytlar) Dosya boyutu main.c, README.md, icon.png
tree Dizin listesi (dosya + alt dizin pointerları) Küçük src/, include/, proje kökü
commit Root tree SHA + parent + author + mesaj Küçük Her git commit komutu bir commit objesi üretir
tag Annotated tag: obje SHA + tagger + mesaj Küçük git tag -a v1.0.0 -m "Release"

Obje oluşturma ve inceleme

bash — hash-object ve cat-file
# Bir dosyadan blob oluştur, SHA döner (-w: git object db'ye yaz)
echo "hello embedded world" > hello.txt
git hash-object -w hello.txt
# → 8f9e3a2b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f

# -w olmadan: sadece hash hesapla, kaydetme
git hash-object hello.txt

# Obje tipini öğren
git cat-file -t 8f9e3a2b
# → blob

# Obje içeriğini oku
git cat-file -p 8f9e3a2b
# → hello embedded world

# Boyutunu öğren
git cat-file -s 8f9e3a2b
# → 21

Blob — dosya içeriği

Blob, dosyanın ham içeriğini saklar. Dosya adı bilgisi yoktur — bu bilgi tree objesindedir. Aynı içerikli iki farklı ada sahip dosya sadece tek bir blob oluşturur:

bash — blob içeriği
# Mevcut repo'dan blob oku
git cat-file -p HEAD:src/main.c
# → main.c dosyasının HEAD'deki içeriği

# Eski bir commit'teki halini oku
git cat-file -p HEAD~3:src/main.c

Tree — dizin listesi

bash — tree içeriği
# HEAD'in root tree'si
git cat-file -p HEAD^{tree}
# Çıktı (mode  type  sha             name):
# 100644 blob a3f8c2d1b4e5...  README.md
# 100644 blob 7b2e9f3c8d1a...  CMakeLists.txt
# 040000 tree 9c4e1f2b7a8d...  src
# 040000 tree 1d5f2e3c9b4a...  include

# src/ alt tree'si
git cat-file -p 9c4e1f2b7a8d
# 100644 blob b5c2a7d9e1f3...  main.c
# 100644 blob c6d3b8e0f2a4...  utils.c

Tree girdilerindeki mode değerleri Unix dosya izinlerinden gelir: 100644 normal dosya, 100755 executable, 040000 dizin (tree), 120000 symlink.

Commit objesi

bash — commit objesi içeriği
# HEAD commit'inin ham içeriği
git cat-file -p HEAD
# tree   9c4e1f2b7a8d5f3e2c1b0a9d8e7f6a5b4c3d2e1f
# parent 7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6
# author Emirhan Pehlevan <emirpehlevan@outlook.com> 1712345678 +0300
# committer Emirhan Pehlevan <emirpehlevan@outlook.com> 1712345678 +0300
#
# Add GPIO driver for STM32F4

# Merge commit — iki parent
git cat-file -p abc123
# tree   ...
# parent aaa111...   ← ilk parent (merge hedefi)
# parent bbb222...   ← ikinci parent (merge edilen branch)
# ...
NOT

İlk commit'te parent satırı yoktur. Orphan branch'ler (git checkout --orphan) da parent'sız commit ile başlar.

Bu bölümde

  • blob: dosya içeriği, adsız — ad bilgisi tree'de
  • tree: mode + type + SHA + name satırları — dizin snapshot'ı
  • commit: root tree + parent + author + committer + mesaj
  • git hash-object -w ve git cat-file -t / -p ile obje inceleme

03 SHA-1 hashing

Her git objesi, içeriğinden hesaplanan SHA-1 hash ile adreslenir — bu yapı hem veri bütünlüğünü hem de deduplication'ı sağlar.

Git object format

Git bir objeyi hashlemeden önce içeriğin başına bir header ekler. Format: type SP size NUL content. Yani blob için: "blob 5\0hello". Bu header + content SHA-1'e verilir:

bash — SHA-1 manuel hesaplama
# "hello" yazısının git blob SHA'sı nedir?
# Format: "blob " + boyut + NUL + içerik

# Python ile hesapla
python3 -c "
import hashlib
content = b'hello'
header = f'blob {len(content)}\0'.encode()
sha1 = hashlib.sha1(header + content).hexdigest()
print(sha1)
"
# → ce013625030ba8dba906f756967f9e9ca394464a

# Git'in kendi hesabıyla karşılaştır
echo -n "hello" | git hash-object --stdin
# → ce013625030ba8dba906f756967f9e9ca394464a  ← aynı!

# sha1sum ile (NUL byte için printf kullan)
printf "blob 5\0hello" | sha1sum
# → ce013625030ba8dba906f756967f9e9ca394464a  -

Objects dizin yapısı

SHA-1 hash 40 hex karakterdir. İlk 2 karakter dizin adı, kalan 38 karakter dosya adı olur. Bu yapı tek bir dizinde binlerce dosya birikmesini engeller (inode ve filesystem performansı için):

bash — objects dizin yapısı
# SHA: ce013625030ba8dba906f756967f9e9ca394464a
# Dosya: .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a

ls .git/objects/ce/
# 013625030ba8dba906f756967f9e9ca394464a

# Dosya içeriği zlib compressed — okunabilir değil
file .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
# → zlib compressed data

# git cat-file ile oku
git cat-file -p ce013625
# → hello

Neden SHA-1?

SHA-1 seçimi kasıtlıdır: hız + yeterli collision direnci (2000'lerin başında). İçerik değişirse hash değişir — bu git'in temel integrity mekanizmasıdır. SHA-1'i bilmeden hash'i değiştiremezsin, bu da tarih manipülasyonunu zorlaştırır.

SHAttered attack ve SHA-256 geçişi

DİKKAT

2017'de Google ve CWI SHA-1 collision ürettiler (SHAttered attack) — aynı SHA-1 hash'e sahip iki farklı PDF. Git için pratikte kritik risk düşük (blob içeriği değil commit graph'ı hedeflenmesi gerekir) ama teorik zayıflık var. Git 2.29+ (2020) ile SHA-256 objeli repo desteği geldi: git init --object-format=sha256. GitHub SHA-256 geçişini henüz tamamlamamıştır.

bash — SHA-256 repo oluşturma (Git 2.29+)
# SHA-256 object store ile yeni repo başlat
git init --object-format=sha256 myrepo-sha256
cd myrepo-sha256

# Artık 64 hex karakterli SHA-256 hash'ler kullanılır
echo "test" > file.txt && git add . && git commit -m "init"
git log --oneline
# → 3a8d9e2f1b4c7d5e6a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f

# Mevcut repo'nun hash formatını öğren
git rev-parse --show-object-format
# → sha1

Bu bölümde

  • Git object format: "type SP size NUL content" → SHA-1
  • Python ve printf ile manuel SHA-1 hesaplama ve git ile doğrulama
  • objects/ab/cdef... dizin yapısı — ilk 2 hex = dizin, kalan = dosya
  • SHAttered (2017) ve SHA-256 geçişi (git 2.29+, --object-format=sha256)

04 Plumbing komutları

Plumbing komutları, git'in düşük seviyeli mekanizmalarına doğrudan erişim sağlar — script ve araç yazımının temelidir.

Porcelain vs Plumbing

Porcelainİnsan odaklı yüksek seviye komutlar. add, commit, push, merge, pull, checkout, log, status. Kullanıcı dostu çıktı.
PlumbingScript odaklı düşük seviye komutlar. hash-object, cat-file, update-index, write-tree, commit-tree, update-ref. Stabil, makine okunabilir çıktı.
NOT

Porcelain komutları değişebilir — git yeni versiyonlarda git status çıktısını yeniden düzenleyebilir. Plumbing komutları ise stabil API olarak kabul edilir; script'lerde ve araçlarda plumbing kullan.

Sadece plumbing ile commit yapmak

Bir git commit komutu arka planda tam olarak bunları yapar — adım adım:

bash — plumbing ile sıfırdan commit
# ── Adım 1: Blob oluştur ────────────────────────────────────
echo "Merhaba, Git internals!" > hello.txt

# -w: nesneyi .git/objects/ içine yaz
BLOB_SHA=$(git hash-object -w hello.txt)
echo "Blob SHA: $BLOB_SHA"

# ── Adım 2: Index'e ekle ────────────────────────────────────
# --add: yeni dosya (henüz index'te yok)
# --cacheinfo mode,sha,path
# 100644 = normal dosya (rw-r--r--)
git update-index --add --cacheinfo 100644,$BLOB_SHA,hello.txt

# ── Adım 3: Tree yaz ────────────────────────────────────────
# Index'teki mevcut durumu tree objesine çevir
TREE_SHA=$(git write-tree)
echo "Tree SHA: $TREE_SHA"

# ── Adım 4: Commit oluştur ──────────────────────────────────
# commit-tree: tree SHA + opsiyonel -p parent
COMMIT_SHA=$(echo "İlk commit — plumbing ile" | \
  git commit-tree $TREE_SHA)
echo "Commit SHA: $COMMIT_SHA"

# ── Adım 5: Branch'i commit'e işaret et ────────────────────
git update-ref refs/heads/main $COMMIT_SHA

# ── Doğrula ─────────────────────────────────────────────────
git log --oneline
git show HEAD

İkinci commit — parent ile

bash — ikinci commit (parent bağlantısı)
echo "ikinci dosya" > world.txt
BLOB2=$(git hash-object -w world.txt)
git update-index --add --cacheinfo 100644,$BLOB2,world.txt
TREE2=$(git write-tree)

# -p: parent SHA — DAG zinciri oluşur
COMMIT2=$(echo "İkinci commit" | \
  git commit-tree $TREE2 -p $COMMIT_SHA)

git update-ref refs/heads/main $COMMIT2
git log --oneline
# → b9c0d1e  İkinci commit
# → a7b8c9d  İlk commit — plumbing ile

Plumbing komut referansı

git hash-objectStdin veya dosyadan obje hash'i hesapla. -w ile .git/objects/'a yaz. Obje türü için -t blob/tree/commit.
git cat-fileObje içeriğini oku. -t tip, -p içerik, -s boyut. --batch: stdin'den SHA listesi al.
git update-indexIndex'i doğrudan düzenle. --add yeni dosya, --remove dosya sil, --cacheinfo SHA + mode + path.
git write-treeMevcut index'ten tree objesi yaz ve SHA döner.
git commit-treeTree SHA'dan commit objesi oluştur. -p parent. Stdin'den mesaj alır.
git update-refRef dosyasını güncelle. Branch oluşturma ve güncelleme için. -d ile ref sil.
git ls-filesIndex içeriğini listele. --stage: mode + SHA + stage + path. --others: untracked dosyalar.
git rev-parseRevision/ref'i SHA'ya çevir. HEAD, main~3, HEAD^{tree} vb. --verify ile doğrula.
bash — ls-files ile index inceleme
# Index içeriği (stage area)
git ls-files --stage
# 100644 ce013625030ba8dba906f756967f9e9ca394464a 0	hello.txt
# 100644 8ab686eafeb1f44702738c8b0f24f2567c36da6d 0	world.txt
# Sütunlar: mode  SHA  stage-number  path
# Stage 0 = normal, 1 = ancestor, 2 = ours, 3 = theirs (merge conflict)

Bu bölümde

  • Porcelain (kullanıcı) vs Plumbing (script/araç) ayrımı
  • 5 adımda sıfırdan commit: hash-object → update-index → write-tree → commit-tree → update-ref
  • -p parent flag'i ile DAG zinciri oluşturma
  • git ls-files --stage ile index'in mode + SHA + stage içeriği

05 Refs sistemi

Ref, bir SHA'ya işaret eden basit bir dosyadır — branch, tag ve remote'lar bu mekanizma üzerine inşa edilmiştir.

Ref nedir?

Bir branch, aslında sadece bir SHA hash içeren küçük bir metin dosyasıdır. main branch'i oluşturmak demek .git/refs/heads/main dosyasını oluşturup içine commit SHA'sı yazmak demektir:

bash — ref dosyaları
# main branch hangi commit'i gösteriyor?
cat .git/refs/heads/main
# → a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9

# Symbolic ref: HEAD → main → commit SHA
cat .git/HEAD
# → ref: refs/heads/main

# Symbolic ref okuma
git symbolic-ref HEAD
# → refs/heads/main

# git update-ref ile yeni branch oluştur
git update-ref refs/heads/feature/new-driver a3f8c2d1
# → refs/heads/feature/new-driver dosyası oluşturuldu

# Rev-parse: istediğin revision'ı SHA'ya çevir
git rev-parse HEAD
git rev-parse main
git rev-parse main~3
git rev-parse HEAD^{tree}
git rev-parse HEAD^{commit}

Revision syntax

HEAD~NHEAD'den N commit geriye. HEAD~1 = bir önceki commit. HEAD~3 = 3 önceki.
HEAD^İlk parent. HEAD~1 ile eşdeğer. HEAD^2 = merge commit'in ikinci parent'ı.
HEAD@{N}Reflog'daki N. önceki konum. HEAD@{1} = bir önceki HEAD değeri.
main..featuremain'de olmayıp feature'da olan commit'ler. git log main..feature
main...featureHer ikisinde de olmayan commit'ler (symmetric difference). git diff için kullanışlı.
SHA^{tree}Commit veya tag objesinin içindeki tree SHA'sı.
:pathIndex'teki dosya. :0:path = stage 0, :1:path = ancestor (merge conflict).

Packed refs

Binlerce branch ve tag içeren büyük repolarda her ref için ayrı dosya tutmak verimsizdir. git gc ve git pack-refs bunları .git/packed-refs'e toplar:

bash — packed-refs
# Tüm ref'leri pack et
git pack-refs --all

cat .git/packed-refs
# # pack-refs with: peeled fully-peeled sorted
# a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9 refs/heads/main
# 7b2e9f3c8d1a4b5c6d7e8f9a0b1c2d3e4f5a6b7c refs/heads/feature
# 9c4e1f2b7a8d5f3e2c1b0a9d8e7f6a5b4c3d2e1f refs/tags/v1.0.0
# ^d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4  ← peeled: annotated tag'ın commit SHA'sı

Lightweight vs Annotated tags

Lightweight tagSadece bir ref dosyası — commit SHA'ya doğrudan işaret eder. git tag v1.0. İmza, tagger bilgisi yok.
Annotated tagKendi tag objesi var — tagger, tarih, mesaj, opsiyonel GPG imzası. git tag -a v1.0 -m "Release". Ref → tag objesi → commit.
bash — annotated tag inceleme
# Annotated tag oluştur
git tag -a v1.0.0 -m "v1.0.0: ilk stabil sürüm"

# Tag objesini incele
git cat-file -t v1.0.0
# → tag

git cat-file -p v1.0.0
# object a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9
# type   commit
# tag    v1.0.0
# tagger Emirhan Pehlevan <emirpehlevan@outlook.com> 1712345678 +0300
#
# v1.0.0: ilk stabil sürüm

Bu bölümde

  • Ref = SHA içeren metin dosyası. Branch = .git/refs/heads/branchname
  • Symbolic ref: HEAD → refs/heads/main → commit SHA
  • git rev-parse ile HEAD~3, HEAD^2, HEAD^{tree} revision syntax
  • packed-refs: binlerce ref'i tek dosyada topla (git pack-refs --all)
  • Lightweight (sadece ref) vs annotated (tag objesi) tag farkı

06 Index (staging area)

Index, bir sonraki commit'in ne içereceğini tutan binary bir önbellektir — working tree ile HEAD arasındaki tampon katman.

Index'in rolü

  Working tree  →  git add  →  Index  →  git commit  →  Commit (tree objesi)
  (disk dosyaları)              (binary)              (.git/objects/)
    

Index'te tam bir tree snapshot'ı vardır. git commit çalıştırdığında yeni blob veya tree objeleri oluşturulmaz — index'teki SHA'lar zaten obje db'de mevcuttur, sadece tree ve commit objeleri yazılır.

Index güncellenme mekanizması

bash — index operasyonları
# git add: blob oluştur + index güncelle
git add src/main.c
# → 1. src/main.c içeriğinden blob SHA hesaplanır ve objects'a yazılır
# → 2. Index'te src/main.c girişi yeni SHA ile güncellenir

# git diff: working tree vs index
git diff

# git diff --cached: index vs HEAD (ne commit edilecek)
git diff --cached
# Alias: git diff --staged

# Üç yönlü karşılaştırma
# Working tree vs HEAD:
git diff HEAD

# Index içeriğini göster
git ls-files --stage
# 100644 8ab686eafeb1f44702738c8b0f24f2567c36da6d 0	CMakeLists.txt
# 100644 ce013625030ba8dba906f756967f9e9ca394464a 0	README.md
# 100644 7b2e9f3c8d1a4b5c6d7e8f9a0b1c2d3e4f5a6b7c 0	src/main.c

Merge conflict — index stage numaraları

Merge conflict sırasında index, çakışan dosyayı üç farklı stage numarasıyla tutar:

Stage 0Normal durum — conflict yok. Tek bir SHA.
Stage 1Common ancestor (merge base). İki branch'in ayrıldığı noktadaki versiyon.
Stage 2"Ours" — HEAD'deki (mevcut branch) versiyon.
Stage 3"Theirs" — merge edilen branch'in versiyonu.
bash — merge conflict'te index inceleme
# Merge conflict durumunda ls-files --stage
git ls-files --stage src/config.c
# 100644 aaa111... 1	src/config.c  ← ancestor
# 100644 bbb222... 2	src/config.c  ← ours
# 100644 ccc333... 3	src/config.c  ← theirs

# Her versiyonun içeriğine bak
git cat-file -p :1:src/config.c  # ancestor
git cat-file -p :2:src/config.c  # ours
git cat-file -p :3:src/config.c  # theirs

# Conflict'i çözüp dosyayı stage 0'a al
git add src/config.c

--assume-unchanged performans hack

bash — assume-unchanged
# Büyük, nadiren değişen dosyaları git status taramasından çıkar
git update-index --assume-unchanged large-generated-file.bin

# Geri al
git update-index --no-assume-unchanged large-generated-file.bin

# Assume-unchanged olarak işaretli dosyaları listele
git ls-files -v | grep '^[a-z]'
# Küçük harf 'h' = assume-unchanged, büyük harf 'H' = normal tracked
DİKKAT

--assume-unchanged sadece performans içindir. Dosyayı gerçekten untrack etmez — git stash, git checkout veya git pull bu flag'i görmezden gelebilir. Gerçek anlamda takip etmemek için .gitignore veya --skip-worktree kullan.

Bu bölümde

  • Index = bir sonraki commit'in tree'si, binary format (.git/index)
  • git diff (working vs index) vs git diff --cached (index vs HEAD)
  • Merge conflict'te stage 1 (ancestor), 2 (ours), 3 (theirs)
  • git ls-files --stage ile index'in mode + SHA + stage içeriği
  • --assume-unchanged: büyük dosyaları status taramasından çıkarma (hack)

07 Pack files

Loose objects verimli değildir — pack file, binlerce objeyi delta compression ile tek bir dosyada toplar.

Loose objects vs Pack files

Loose objectsHer obje ayrı bir dosya: .git/objects/ab/cdef... Küçük ve yeni repolarda yeterli. Disk I/O ve inode sayısı açısından maliyetli.
Pack file.git/objects/pack/pack-SHA.pack + .idx index. Binlerce objeyi tek dosyada toplar, delta compression uygular. git gc ile oluşturulur.
  Loose objects  →  git gc  →  pack-SHA.pack  +  pack-SHA.idx
  (ayrı dosyalar)               (sıkıştırılmış)    (index)
    

Delta compression

Pack file, benzer objelerin yalnızca farkını (delta) saklar. Örneğin bir dosyanın 10 versiyonu varsa, Git bunların birbirinden farkını hesaplar ve en az yer kaplayan temel + delta zinciri seçer. Büyük binary dosyalar için bu optimizasyon minimal etki yapar; metin dosyaları için dramatik küçülme sağlar.

Pack file operasyonları

bash — pack file işlemleri
# Obje sayısına bak (loose vs packed)
git count-objects -v
# count:     24          ← loose object sayısı
# size:      96          ← toplam boyut (KB)
# in-pack:   4821        ← pack file içindeki obje sayısı
# packs:     1           ← pack file sayısı
# size-pack: 1842        ← pack file boyutu (KB)
# garbage:   0
# size-garbage: 0

# Manuel gc: loose objects'i pack'e taşı
git gc

# Agresif gc: daha iyi delta compression (yavaş)
git gc --aggressive

# Pack file'ları listele
ls .git/objects/pack/
# pack-a3f8c2d1...sha1.idx
# pack-a3f8c2d1...sha1.pack

# Pack içeriğini listele (tüm objeler)
git verify-pack -v .git/objects/pack/pack-*.idx | head -20
# SHA  type  size  packed-size  offset  depth  base-SHA
# a3f8c2d1  commit  230  185  12  0
# 7b2e9f3c  blob    1024  312  197  1  a3f8c2d1  ← delta

Shallow clone ve unshallow

bash — shallow clone
# Shallow clone: sadece son N commit
git clone --depth 1 https://github.com/user/repo.git

# Daha fazla history çek
git fetch --depth 50

# Tam history'ye dön (unshallow)
git fetch --unshallow

# Shallow mi değil mi?
cat .git/shallow   # varsa shallow, yoksa full clone
git rev-parse --is-shallow-repository
# → true / false

# Belirli bir tarihten bu yana tam history
git fetch --shallow-since="2024-01-01"

# CI'da hız için partial clone
git clone --filter=blob:none --no-checkout https://github.com/user/repo.git

Repack ile optimizasyon

bash — repack
# Tüm objeleri tek bir pack file'a topla
git repack -a -d -f
# -a: tüm objeler (mevcut pack'ler dahil)
# -d: eski pack'leri sil
# -f: delta'ları yeniden hesapla

# Sonucu karşılaştır
git count-objects -v -H   # -H: human-readable boyutlar

Bu bölümde

  • Loose objects: ayrı dosyalar. Pack file: tek dosya + delta compression
  • git gc: loose → pack. git gc --aggressive: daha iyi sıkıştırma
  • git count-objects -v: loose/packed obje ve boyut istatistikleri
  • git verify-pack -v: pack içeriği ve delta derinliği
  • --depth 1 shallow clone, --unshallow ile full history'ye geçiş

08 Reflog

Reflog, HEAD ve branch ref'lerinin her hareketini kaydeden yerel bir zaman çizelgesidir — sildiğini sandığın şeyi geri getirmenin anahtarı.

Reflog nedir?

Her git commit, git checkout, git reset, git rebase operasyonu bir reflog girdisi oluşturur. Reflog, bu hareketlerin kronolojik kaydını tutar:

bash — reflog kullanımı
# HEAD'in son hareketleri
git reflog
# a3f8c2d HEAD@{0}: commit: GPIO driver eklendi
# 7b2e9f3 HEAD@{1}: commit: UART init düzeltildi
# 9c4e1f2 HEAD@{2}: checkout: moving from feature to main
# 1d5f2e3 HEAD@{3}: reset: moving to HEAD~1
# b5c2a7d HEAD@{4}: commit: ilk commit

# Belirli bir branch'in reflog'u
git reflog show main

# Tüm ref'lerin reflog'u
git reflog --all

# Detaylı bilgi: tarih, yazar
git reflog --date=iso
# a3f8c2d HEAD@{2024-04-12 14:23:11 +0300}: commit: GPIO driver

Silinen commit'i geri getir

bash — kaybedilen commit'i kurtar
# Senaryo: yanlışlıkla git reset --hard HEAD~2 yaptın
# İki commit kaybedildi — ama reflog'da hâlâ var

# Adım 1: Kaybedilen commit'i reflog'da bul
git reflog
# a3f8c2d HEAD@{0}: reset: moving to HEAD~2   ← şu anki konum
# 7b2e9f3 HEAD@{1}: commit: önemli değişiklik  ← kaybolan (2)
# 9c4e1f2 HEAD@{2}: commit: kritik fix         ← kaybolan (1)

# Adım 2: Kaybedilen commit'i recover et
# Yöntem A: branch oluştur
git branch recovered-work 7b2e9f3

# Yöntem B: HEAD'i eski konuma taşı
git reset --hard HEAD@{2}

# Yöntem C: cherry-pick ile sadece o commit'i al
git cherry-pick 7b2e9f3

Reflog ile eski konuma dönme

bash — reflog ile reset
# 2 adım önceki HEAD konumuna git
git reset --hard HEAD@{2}

# Dün saat 15:00'deki konuma git
git checkout main@{"2024-04-12 15:00"}

# 3 gün önceki konuma git
git checkout main@{"3 days ago"}

Reflog expiry

gc.reflogExpireNormal reflog girdileri için TTL. Default: 90 gün. git config gc.reflogExpire 180 ile artır.
gc.reflogExpireUnreachableHiçbir branch'ten erişilemeyen commit'lerin reflog TTL'i. Default: 30 gün.
bash — reflog expiry ayarı
# Reflog expiry'yi artır
git config gc.reflogExpire "365 days"
git config gc.reflogExpireUnreachable "90 days"

# Reflog'u manuel temizle (normalde yapma)
git reflog expire --expire=now --all
DİKKAT

Reflog sadece lokaldir. Remote'a push edilmez, clone'da yer almaz, başka birinin makinesiyle paylaşılamaz. Reflog sayesinde kurtarabileceğin verinin zamanı sınırlıdır — git gc eski girdileri temizler. Önemli kaybolan commit'leri bulduktan hemen sonra yeni bir branch oluştur.

Bu bölümde

  • Reflog: HEAD ve branch ref'lerinin her hareketinin kronolojik kaydı
  • git reflog ve git reflog show main ile geçmişi görme
  • Kaybolan commit: git reflog → SHA → git branch recovered SHA
  • HEAD@{N}, HEAD@{"3 days ago"} syntax ile eski konuma dönme
  • Reflog sadece lokal — push edilmez, clone'da olmaz

09 fsck, dangling commits ve kurtarma

git fsck, object database bütünlüğünü kontrol eder ve hiçbir ref'ten ulaşılamayan "dangling" objeleri ortaya çıkarır.

git fsck — bütünlük kontrolü

bash — git fsck
# Obje database bütünlük kontrolü
git fsck
# Checking object directories: 100% (256/256), done.
# Checking connectivity: done.

# Unreachable (dangling) objeleri göster
git fsck --unreachable
# unreachable commit 7b2e9f3c8d1a4b5c6d7e8f9a0b1c2d3e4f5a6b7c
# unreachable blob   a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9
# unreachable tree   9c4e1f2b7a8d5f3e2c1b0a9d8e7f6a5b4c3d2e1f

# Unreachable objeleri .git/lost-found/ altına yaz
git fsck --lost-found
# → .git/lost-found/commit/ — dangling commit'ler
# → .git/lost-found/other/  — dangling blob ve tree'ler

ls .git/lost-found/commit/
ls .git/lost-found/other/

Senaryo 1: git reset --hard ile kaybedilen commit

bash — reset --hard kurtarma
# Senaryo: git reset --hard HEAD~3 yaptın, 3 commit kayboldu

# Adım 1: Reflog'da bul (en hızlı yol)
git reflog
# HEAD@{1}: commit: son değişiklikler  ← bu SHA'yı kullan

SHA=7b2e9f3c

# Adım 2: Yeni branch oluştur
git branch recover/reset-undo $SHA

# Adım 3: Branch'e geç ve kontrol et
git checkout recover/reset-undo
git log --oneline -5

# Adım 4: main'e merge et (değişiklikleri geri al)
git checkout main
git merge recover/reset-undo

Senaryo 2: yanlış git stash drop sonrası kurtarma

bash — stash drop kurtarma
# Senaryo: git stash drop ile stash'i yanlışlıkla sildin
# Stash aslında bir commit objesidir — hâlâ objects'ta olabilir

# Adım 1: Dangling commit'leri bul
git fsck --unreachable | grep commit
# unreachable commit 7b2e9f3c8d1a4b5c6d7e8f9a0b1c2d3e4f5a6b7c
# unreachable commit a3f8c2d1b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9

# Adım 2: Her birini incele — stash olup olmadığını anla
git show 7b2e9f3c
# commit 7b2e9f3c ...
# Merge: aaa111 bbb222  ← stash commit'i iki parent'lıdır
# Author: ...
# WIP on main: ...      ← stash mesajı genellikle "WIP on" ile başlar

# Adım 3: Stash'i geri yükle
git stash apply 7b2e9f3c

# Alternatif: branch olarak restore et
git checkout -b recovered-stash 7b2e9f3c

Senaryo 3: Bir dosyayı silip commit ettin

bash — silinen dosyayı blob'dan kurtar
# Senaryo: önemli bir dosyayı sildip commit ettin
# Dosyanın eski blob'u objects'ta hâlâ var

# Eski commit'lerde dosyayı ara
git log --all --full-history -- src/deleted_file.c
# commit abc123  Delete: src/deleted_file.c silindi
# commit def456  Add: src/deleted_file.c eklendi

# Silmeden önceki commit'teki haline bak
git show abc123~1:src/deleted_file.c

# Geri yükle
git checkout abc123~1 -- src/deleted_file.c

git maintenance — otomatik gc

bash — git maintenance
# Periyodik gc, commit-graph yenileme, prefetch etkinleştir
git maintenance start

# Etkin görevler
git config --list | grep maintenance
# maintenance.auto=false
# maintenance.strategy=incremental

# Manuel olarak tüm bakım görevlerini çalıştır
git maintenance run --auto

# Commit-graph: git log --graph hızlandırır
git commit-graph write --reachable
cat .git/objects/info/commit-graph | xxd | head

# Durdur
git maintenance stop

fsck ile corrupt repo tespiti

bash — corrupt repo tespiti
# Corrupt obje bul
git fsck
# error: sha1 mismatch 7b2e9f3c...  ← bozuk obje
# error: object file .git/objects/7b/2e9f3c... is empty

# Bozuk objeyi sil ve remote'dan geri getir
rm .git/objects/7b/2e9f3c...
git fetch origin

# Remote'u güvenilir kaynak olarak kullan
git remote prune origin
git fsck --no-dangling   # bütünlük kontrolü, dangling'i gizle
NOT

Git, git gc çalışana kadar ve reflog TTL dolmadan objeleri gerçek anlamda silmez. Bu yüzden git reset --hard, git stash drop, hatta git branch -D ile kaybettiğin şeylerin büyük çoğunluğu kurtarılabilir. Paniklemeden önce git reflog ve git fsck --unreachable komutlarını çalıştır.

Bu bölümde

  • git fsck: obje db bütünlük kontrolü. --unreachable ile dangling objeler
  • git fsck --lost-found: dangling objeleri .git/lost-found/'a yaz
  • git reset --hard kurtarma: reflog → SHA → git branch recover SHA
  • git stash drop kurtarma: git fsck --unreachable | grep commit → git stash apply SHA
  • git maintenance start: periyodik gc ve commit-graph yenileme
  • Git nadiren gerçekten siler — gc öncesi kurtarma mümkün