← Kursa Dön
📄 Text · 30 min

Commit Derinlemesine

Giriş: Neden Commit'i Bu Kadar Detaylı Öğreniyoruz?

Commit, Git'in atomu. Her şey commit'lerden oluşur: geçmiş, branch'ler, merge'ler, rebase'ler — hepsi commit zincirleri. Commit'i yüzeysel bilmek, kimyayı atomları anlamadan öğrenmeye benzer.

Bu derste üç kritik soruyu cevaplayacağız:

  1. Bir commit'in içinde gerçekten ne var? (tree, blob, SHA-1)

  2. İyi bir commit mesajı nasıl yazılır? (Conventional Commits)

  3. Yanlış bir commit'i nasıl düzeltiriz? (--amend)


🎬 Analoji: Kargo Paketi

Bir commit'i bir kargo paketi gibi düşün:

┌──────────────────────────────────────────┐
│              KARGO PAKETİ                │
│                                          │
│  📦 İçerik (tree + blob'lar)            │
│     Paket içindeki eşyalar              │
│     Hangi dosyalar, hangi içerikler      │
│                                          │
│  🏷️ Etiket (commit mesajı)              │
│     "Kış kıyafetleri — 3 kazak, 2 mont" │
│     Ne gönderdiğini açıklayan not        │
│                                          │
│  📋 Kargo fişi (metadata)               │
│     Gönderen: Ali (author)               │
│     Tarih: 1 Mart 2026                   │
│     Takip no: a1b2c3d (SHA-1 hash)       │
│     Önceki kargo: d4e5f6g (parent)       │
│                                          │
└──────────────────────────────────────────┘

Her kargo, kendinden öncekine referans verir. Kargoları sırayla takip edersen, tüm gönderim geçmişini görürsün.


Commit Anatomisi: İç Yapı

Git'in Dört Nesne Türü

Git, tüm verisini dört tür nesne olarak saklar. Her nesne SHA-1 hash ile tanımlanır:

Git Nesne Türleri:

1. blob (Binary Large Object)
   → Dosyanın içeriği. Sadece içerik — dosya adı yok!
   
2. tree (Ağaç)
   → Klasör yapısı. Hangi dosya hangi blob'a işaret ediyor.
   
3. commit
   → Snapshot. Tree referansı + parent + author + mesaj
   
4. tag (İleriki derslerde)
   → Annotated tag. Commit'e isim ve açıklama ekler.

Bir Commit'in İçine Bakmak

Somut bir örnekle görelim. Diyelim ki projemizde şu dosyalar var:

web-projem/
├── index.html    ("<!DOCTYPE html>...")
├── style.css     ("body { margin: 0; }")
└── src/
    └── app.js    ("console.log('hi')")

Bu projeyi commit'lediğimizde Git, şu nesneleri oluşturur:

                    Commit abc123
                    ├── tree: def456    (root tree)
                    ├── parent: (none)  (ilk commit)
                    ├── author: Tolgahan
                    ├── date: 2026-03-01
                    └── message: "İlk commit"
                         │
                         ▼
                    Tree def456 (kök dizin)
                    ├── blob 111aaa → index.html
                    ├── blob 222bbb → style.css
                    └── tree ghi789 → src/
                                       │
                                       ▼
                                  Tree ghi789 (src/)
                                  └── blob 333ccc → app.js

Bunu gerçek komutlarla görelim:

# Commit nesnesinin içeriği
$ git cat-file -p HEAD
tree def456789...
author Tolgahan Kaya <email> 1709283600 +0300
committer Tolgahan Kaya <email> 1709283600 +0300

İlk commit

# Tree nesnesinin içeriği
$ git cat-file -p def456789
100644 blob 111aaabbb...    index.html
100644 blob 222bbbccc...    style.css
040000 tree ghi789jkl...    src

# Blob nesnesinin içeriği (dosya)
$ git cat-file -p 111aaabbb
<!DOCTYPE html>
<html>
<head><title>Web Projem</title></head>
</html>

# Nesne türünü öğren
$ git cat-file -t abc123
commit
$ git cat-file -t def456
tree
$ git cat-file -t 111aaa
blob

SHA-1 Hash Nasıl Hesaplanır?

Git, her nesnenin hash'ini şöyle hesaplar:

SHA-1( nesne_türü + boşluk + boyut + null_byte + içerik )

Örneğin "hello" içerikli bir blob için:

# Git'in yaptığını elle yapalım
$ echo -n "hello" | git hash-object --stdin
ce013625030ba8dba906f756967f9e9ca394464a

# Doğrulama
$ printf "blob 5\0hello" | sha1sum
ce013625030ba8dba906f756967f9e9ca394464a

Neden önemli? Aynı içerik = aynı hash. Bu, Git'e birçok güç verir:

  1. Deduplication: İki farklı commit'te aynı dosya varsa, blob bir kere saklanır

  2. Bütünlük kontrolü: Hash değişirse, içerik bozulmuş demektir

  3. Hızlı karşılaştırma: İki dosyanın aynı olup olmadığını hash'e bakarak anla (içeriği okumaya gerek yok)

Aynı içerikli dosyalar tek blob olarak saklanır:

Commit 1:                    Commit 2:
├── index.html → blob A      ├── index.html → blob A  (AYNI!)
├── style.css  → blob B      ├── style.css  → blob C  (değişmiş)
└── app.js     → blob D      └── app.js     → blob D  (AYNI!)

Sadece blob C yeni oluşturuldu. A ve D tekrar kullanılıyor.

💡 İpucu: Git'in "snapshot" sakladığı doğru ama her seferinde tüm dosyaları kopyaladığı yanlış. Değişmeyen dosyalar için aynı blob referansı kullanılır. Bu yüzden Git hem snapshot avantajını yaşar hem depolama alanında tutumlu olur.


İyi Commit Mesajı Yazmak

Neden Önemli?

6 ay sonra şu log'a baktığını düşün:

# 😱 Kötü geçmiş
$ git log --oneline
f7g8h9i fix
e6f7g8h update
d5e6f7g misc changes
c4d5e6f asdf
b3c4d5e WIP
a2b3c4d initial

# 😍 İyi geçmiş
$ git log --oneline
f7g8h9i fix: Login sayfasında null pointer hatası düzeltildi
e6f7g8h feat: Kullanıcı profil fotoğrafı yükleme eklendi
d5e6f7g refactor: Veritabanı bağlantı katmanı yeniden yapılandırıldı
c4d5e6f docs: API dokümantasyonu güncellendi
b3c4d5e test: Ödeme modülü birim testleri eklendi
a2b3c4d feat: Proje iskelet yapısı oluşturuldu

Hangisinde 3 ay önceki bir bug'ı bulmak daha kolay?

Commit Mesajı Yapısı

Bir commit mesajı üç bölümden oluşur:

<başlık>           ← Kısa özet (50 karakter veya altı)
                   ← Boş satır
<gövde>            ← Detaylı açıklama (72 karakter genişlik)
                   ← Boş satır
<footer>           ← İlişkili issue'lar, breaking changes

Örnek:

feat: Kullanıcı kayıt formu eklendi

Yeni kullanıcıların sisteme kayıt olabilmesi için kayıt formu
oluşturuldu. Form, e-posta ve şifre validasyonu içerir.
Başarılı kayıt sonrasında onay e-postası gönderilir.

- E-posta format kontrolü eklendi
- Şifre güçlülük kontrolü eklendi (min 8 karakter, büyük/küçük harf)
- Captcha entegrasyonu yapıldı

Closes #42

Conventional Commits

Modern projelerde yaygın olarak kullanılan bir commit mesajı standardı:

<tür>(kapsam): <açıklama>

Türler:
feat:     Yeni özellik
fix:      Bug düzeltme
docs:     Dokümantasyon
style:    Kod formatlama (mantık değişikliği yok)
refactor: Refactoring (ne bug fix ne yeni özellik)
test:     Test ekleme/düzenleme
chore:    Build, CI, bağımlılık güncelleme
perf:     Performans iyileştirmesi
ci:       CI/CD değişiklikleri

Örnekler:

git commit -m "feat: Kullanıcı profil sayfası eklendi"
git commit -m "fix: Login'de şifre hatası düzeltildi"
git commit -m "docs: README'ye kurulum adımları eklendi"
git commit -m "style: Kod formatlama düzeltmeleri"
git commit -m "refactor: Auth modülü yeniden yapılandırıldı"
git commit -m "test: Payment modülü unit testleri"
git commit -m "chore: Node.js 20'ye güncellendi"
git commit -m "perf: Anasayfa sorgusu optimize edildi"

Kapsam (scope) isteğe bağlı ama faydalı:

git commit -m "feat(auth): İki faktörlü doğrulama eklendi"
git commit -m "fix(cart): Sepet toplamı yanlış hesaplanıyordu"
git commit -m "docs(api): Endpoint listesi güncellendi"

Altın Kurallar

✅ İYİ commit mesajları:
─────────────────────────
1. Emir kipi kullan: "Eklendi" değil "Ekle" veya "Add"
2. İlk harf büyük (Türkçe: küçük de olabilir, tutarlı ol)
3. Başlık sonuna nokta koyma
4. 50 karakter altında tut (başlığı)
5. Neyi VE neden yaptığını açıkla

❌ KÖTÜ commit mesajları:
─────────────────────────
- "fix"
- "update"  
- "asdf"
- "WIP"
- "misc"
- "a"
- "."
- "aaaaa"
- "Birçok şey değişti"

⚠️ Dikkat: Commit mesajı yazma disiplini, kariyerinin en başından itibaren edinmen gereken bir alışkanlık. Code review'da commit mesajlarına bakılır. Open source projelere katkıda bulunurken, kötü commit mesajları PR'ının reddedilmesine neden olabilir.


git commit --amend: Son Commit'i Düzeltme

Yanlış bir commit mesajı yazdın veya bir dosya eklemeyi unuttun? --amend kurtarır:

Mesajı Düzeltme

$ git commit -m "feat: Lgin sayfası eklendi"
# Oops, yazım hatası!

$ git commit --amend -m "feat: Login sayfası eklendi"
[main abc1234] feat: Login sayfası eklendi
 Date: Mon Mar 1 10:00:00 2026 +0300
 1 file changed, 15 insertions(+)

Unutulan Dosyayı Ekleme

$ git add index.html
$ git commit -m "feat: Login sayfası eklendi"
# Oops, style.css'i eklemeyi unuttum!

$ git add style.css
$ git commit --amend --no-edit
# --no-edit: Mesajı değiştirme, dosyayı ekle

$ git log --oneline -1
abc1234 feat: Login sayfası eklendi
# Tek commit, iki dosya da içinde

Amend Nasıl Çalışır?

--amend aslında son commit'i silip yerine yenisini koyar:

Öncesi:
[A] ← [B] ← [C]     (C yanlış)
                ▲
              HEAD

git commit --amend sonrası:
[A] ← [B] ← [C']    (C' düzeltilmiş yeni commit)
                ▲
              HEAD

C hâlâ var ama artık hiçbir branch onu göstermiyor.
Hash değişti: C ≠ C' (içerik/mesaj değişti → hash değişti)

⚠️ Dikkat: --amend commit hash'ini değiştirir. Eğer commit'i zaten push ettiysen, amend yapıp tekrar push'lamak sorun çıkarır (force push gerekir). Kural: Sadece push'lanmamış commit'lerde amend kullan.


Author ve Committer Farkı

Her commit'te iki farklı kimlik bilgisi var:

$ git cat-file -p HEAD
tree abc123...
parent def456...
author Ali Yılmaz <ali@example.com> 1709283600 +0300
committer Tolgahan Kaya <tolgahan@example.com> 1709283600 +0300

feat: Login eklendi
  • Author: Değişikliği yazan kişi

  • Committer: Değişikliği commit'leyen kişi

Genelde aynı kişidir. Ama farklı olabilir:

  • Ali bir patch gönderdi, Tolgahan onu commit'ledi

  • git cherry-pick veya git rebase yapıldığında committer değişir

  • git commit --amend yapıldığında committer güncellenir

Author Bilgisini Değiştirmek

# Bu commit için farklı author kullan
$ git commit --author="Ali Yılmaz <ali@example.com>" -m "feat: Ali'nin katkısı"

# Son commit'in author'unu değiştir
$ git commit --amend --author="Ali Yılmaz <ali@example.com>" --no-edit

Commit ile İlgili Faydalı Komutlar

Boş Commit

Bazen CI/CD tetiklemek veya test amacıyla boş commit atmak istersin:

$ git commit --allow-empty -m "ci: Pipeline yeniden tetiklendi"

Detaylı Commit (Editör ile)

$ git commit
# Editör açılır, detaylı mesaj yazabilirsin:

feat: Kullanıcı profili güncelleme özelliği

- Profil fotoğrafı yükleme eklendi (max 5MB, JPG/PNG)
- Bio alanı eklendi (max 160 karakter)
- Sosyal medya linkleri eklenebilir
- Form validasyonu client-side ve server-side

Closes #127
Reviewed-by: Ayşe Demir

Tarih Değiştirme

# Belirli bir tarihte commit at (çok nadir kullanılır)
$ git commit --date="2026-01-15T10:00:00" -m "eski tarihli commit"

# Author tarihi VE committer tarihini değiştir
$ GIT_COMMITTER_DATE="2026-01-15T10:00:00" git commit --date="2026-01-15T10:00:00" -m "mesaj"

Pratik Senaryo: Commit Workflow

# Yeni proje
$ mkdir commit-lab && cd commit-lab && git init

# Dosya oluştur
$ echo "# Commit Lab" > README.md
$ echo "console.log('v1');" > app.js

# İlk commit
$ git add .
$ git commit -m "feat: Proje başlatıldı"

# Bazı değişiklikler yap
$ echo "console.log('v2');" > app.js
$ echo "body { color: #333; }" > style.css

# Parça parça commit'le
$ git add app.js
$ git commit -m "refactor: App versiyonu güncellendi"

$ git add style.css
$ git commit -m "style: Temel stiller eklendi"

# Oops, son commit mesajı yanlış
$ git commit --amend -m "feat: Temel stil dosyası eklendi"

# Bir dosya eklemeyi unuttum
$ echo "p { line-height: 1.6; }" >> style.css
$ git add style.css
$ git commit --amend --no-edit

# Geçmişe bak
$ git log --oneline
c3d4e5f (HEAD -> main) feat: Temel stil dosyası eklendi
b2c3d4e refactor: App versiyonu güncellendi
a1b2c3d feat: Proje başlatıldı

# Commit detaylarını incele
$ git show b2c3d4e
# diff çıktısı ile ne değiştiğini görebilirsin

# İç yapıyı incele
$ git cat-file -p HEAD
# tree, parent, author, committer ve mesaj

Signed Commits — GPG ile İmzalama

Commit'lerin gerçekten sizden geldiğini kanıtlamak için GPG imzalama kullanılır. Özellikle açık kaynak projelerde ve kurumsal ortamlarda önemlidir:

# GPG key oluştur
$ gpg --full-generate-key
# RSA and RSA, 4096 bit, uzun süre (veya süresiz)

# Key ID'nizi bulun
$ gpg --list-secret-keys --keyid-format=long
# sec   rsa4096/ABC123DEF456 2026-01-01 [SC]
#       FINGERPRINT
# uid   Tolgahan Kaya <tolgahan@email.com>

# Git'e tanıt
$ git config --global user.signingkey ABC123DEF456
$ git config --global commit.gpgsign true  # Tüm commit'leri otomatik imzala

# İmzalı commit at
$ git commit -S -m "feat: Güvenli commit"

# İmzaları doğrula
$ git log --show-signature
commit abc1234 (HEAD -> main)
gpg: Signature made Mon 01 Mar 2026 12:00:00 PM +03
gpg: Good signature from "Tolgahan Kaya <tolgahan@email.com>"

# GitHub'da GPG public key'i ekle:
# Settings → SSH and GPG keys → New GPG key
$ gpg --armor --export ABC123DEF456
# Çıktıyı kopyala ve GitHub'a yapıştır

GitHub'da imzalı commit'ler "Verified" badge'i ile gösterilir. Bu, commit'in gerçekten o kullanıcıdan geldiğini kanıtlar.

Verified vs Unverified:

✅ Verified   → GPG imzalı, GitHub doğruladı
⚠️ Partially  → Web UI ile yapılmış (GitHub imzalamış)
❌ Unverified → İmza yok veya doğrulanamadı

💡 İpucu: GitHub, web arayüzünden yapılan commit'leri (PR merge, dosya düzenleme) otomatik olarak GitHub'ın GPG key'i ile imzalar. Ama local commit'lerinizi siz imzalamalısınız.


git commit --fixup ve --autosquash

İleride rebase dersinde detaylı göreceğiz ama preview olarak: --fixup ile önceki bir commit'e düzeltme ekleyebilirsin:

# Bir commit yaptın
$ git commit -m "feat: User profili eklendi"  # (abc1234)

# Bir hata fark ettin — düzeltmeyi ayrı commit yap ama işaretle
$ git add fix.js
$ git commit --fixup=abc1234
# Otomatik mesaj: "fixup! feat: User profili eklendi"

# Sonra rebase ile otomatik birleştir
$ git rebase -i --autosquash main
# fixup commit, otomatik olarak abc1234'ün altına yerleşir
# ve ikisi birleşir

Bu, code review sırasında "şu commit'teki hatayı düzelt" isteklerini temiz bir şekilde karşılamak için mükemmeldir.


Commit Best Practices: Profesyonel İpuçları

1. Atomik Commit'ler

Her commit tek bir şeyi yapmalı. "Login eklendi ve footer CSS'i düzeltildi" → iki ayrı commit olmalı.

# ❌ Yanlış: İki farklı iş tek commit'te
$ git add .
$ git commit -m "Login eklendi ve footer düzeltildi"

# ✅ Doğru: Ayrı commit'ler
$ git add src/login.js src/auth.js
$ git commit -m "feat: Login sayfası eklendi"

$ git add styles/footer.css
$ git commit -m "fix: Footer hizalama sorunu düzeltildi"

2. Sık Commit At

Gün sonunda dev bir commit yerine, anlamlı parçalarda commit at.

3. Çalışan Kod Commit'le

Her commit, projenin çalışır bir versiyonunu temsil etmeli. Yarım kalmış, derleme hatası veren kod commit'leme (stash kullan).

4. Mesajda "Ne" ve "Neden" Olsun

# ❌ Sadece ne
$ git commit -m "Timeout değeri 30'a çıkarıldı"

# ✅ Ne ve neden
$ git commit -m "fix: API timeout 30s'ye çıkarıldı

Yoğun saatlerde veritabanı sorguları 10s'yi aşıyordu ve
timeout hatası alınıyordu. 30s ile yeterli zaman tanındı.
Root cause: slow query — ayrı issue'da optimize edilecek.

Fixes #89"

Özet

  • Commit üç nesneden oluşur: blob (dosya içeriği), tree (klasör yapısı), commit (snapshot + metadata)

  • Her nesne SHA-1 hash ile tanımlanır — aynı içerik = aynı hash (deduplication)

  • İyi commit mesajı: kısa başlık (50 kar.), boş satır, detaylı açıklama — Conventional Commits standardını kullan

  • git commit --amend ile son commit'in mesajını veya içeriğini düzeltebilirsin — sadece push'lanmamış commit'lerde kullan

  • Atomik commit: Her commit tek bir mantıksal değişikliği temsil etmeli

  • Commit mesajında ne yaptığını ve neden yaptığını açıkla — gelecekteki sen teşekkür edecek



💡 Son İpucu: Commit, yazılım geliştirmenin "cümleleridir". Nasıl iyi bir yazar her cümlesini özenle kuruyorsa, iyi bir geliştirici de her commit'ini özenle oluşturur. Başlığı net, gövdesi açıklayıcı, kapsamı dar ve atomik. Bu alışkanlığı kariyerinin başından itibaren edin — gelecekteki sen (ve ekip arkadaşların) teşekkür edecek.

*Bir sonraki derste commit geçmişini derinlemesine incelemeyi — git log, git diff, git show — öğreneceğiz!*