Soru bankası · Zanaat

Git ve sürüm kontrolü

Git'in veri modeli, merge/rebase/reset/revert farkları, geçmişi kurtarma ve yeniden yazma, branching stratejileri ve PR akışı; başlangıçtan ileri seviyeye mülakat soruları.

25 soru14 çoktan seçmeli
Konu özeti

Git'i her gün kullanmak ile mülakatta anlatabilmek farklı şeylerdir. Komutların neredeyse hepsi tek bir zihinsel modelle açıklanabilir: Git, içeriğe göre adreslenen nesnelerden oluşan bir veritabanı ve bu nesnelere işaret eden isimlerdir (ref). Bu modeli oturtunca "reset neyi geri alır", "rebase neden SHA'ları değiştirir", "reflog neden kurtarır" gibi soruların cevabı kendiliğinden çıkar.

Veri modeli. .git/objects altında dört tür nesne vardır. Blob bir dosyanın içeriğidir (adı ve izinleri değil, sadece baytları). Tree bir dizindir: isim, mod ve blob ya da alt tree işaretçilerinden oluşan liste. Commit bir kök tree'ye, sıfır ya da daha fazla parent commit'e, author/committer bilgisine ve mesaja işaret eder. Annotated tag bir nesneye isim, mesaj ve imza ekler. Her nesnenin adı içeriğinin hash'idir (varsayılan SHA-1; git init --object-format=sha256 ile SHA-256 da mümkündür). Hash, blob <boyut>\0 başlığı ile içeriğin birleşiminden hesaplanır; aynı içerikli iki dosya tek bir blob olarak saklanır. Commit parent'ının hash'ini içerdiği için geçmişteki tek bir baytı değiştirmek, o noktadan sonraki tüm commit'lerin hash'ini değiştirir: Git geçmişi bir Merkle zinciridir. Git kavramsal olarak her commit'te diff değil snapshot (tam bir tree) saklar; diskte yer kazanmak için git gc nesneleri packfile'larda delta ile sıkıştırır, ama bu bir depolama ayrıntısıdır. Boş dizinler takip edilmez (tree'de blob'suz girdi olmaz); bu yüzden .gitkeep geleneği vardır.

bash
git cat-file -p HEAD            # tree, parent, author, committer, mesaj
git cat-file -p HEAD^{tree}     # dizin listesi: blob ve tree hash'leri
cat .git/HEAD                   # ref: refs/heads/main

Ref, branch ve HEAD. Branch, .git/refs/heads/<isim> içinde (ya da packed-refs'te) tek bir commit hash'i tutan hareketli bir işaretçidir; kopya değildir. Bu yüzden branch açmak anlıktır. Commit attığında aktif branch'in işaretçisi yeni commit'e ilerler. HEAD "şu an neredeyim" bilgisidir ve normalde bir branch'e işaret eden sembolik bir ref'tir (ref: refs/heads/main). HEAD doğrudan bir commit'e işaret ediyorsa detached HEAD durumundasın: commit atabilirsin ama hiçbir branch onları tutmaz. Remote-tracking ref'ler (origin/main) son fetch anındaki remote durumunun yerel kopyasıdır. Tag, ilerlemeyen bir isimdir: lightweight tag sadece bir ref, annotated tag (git tag -a) ise kendi nesnesi olan, mesaj ve tarih taşıyan bir tag'dir; sürümler için annotated tercih edilir. git push tag'leri varsayılan olarak göndermez (git push origin v1.4.0 ya da --follow-tags).

Üç alan. Working tree diskte düzenlediğin dosyalardır. Index (staging area) bir sonraki commit'in taslağıdır; git add içeriği blob olarak yazar ve index'e koyar. Repository commit'lenmiş nesnelerdir. git commit index'ten bir tree üretir. Staging, bir dosyadaki değişikliklerin sadece bir kısmını (git add -p) commit'leyerek atomik commit kurmanı sağlar.

Birleştirme. Hedef branch, birleştirilen branch'in atası ise fast-forward olur: yeni commit oluşmaz, işaretçi ileri kayar. Branch'ler ayrıştıysa Git ortak ata (merge base), iki uç ile three-way merge yapar ve iki parent'lı bir merge commit üretir (varsayılan strateji ort). --no-ff fast-forward mümkünken bile merge commit zorlar; --ff-only mümkün değilse reddeder. Rebase ise commit'lerini yeni tabanın üzerine tek tek yeniden uygular; içerik aynı olsa da parent değiştiği için yeni SHA'lı yeni commit'ler oluşur, eskileri reflog'da kalır. Sonuç doğrusal geçmiştir; bedeli geçmişi yeniden yazmaktır. Altın kural: başkalarının üzerine iş kurmuş olabileceği, paylaşılmış commit'leri rebase etme. Interactive rebase (git rebase -i) ile commit'leri reword, squash, fixup, drop, edit ile düzenler, sıralarını değiştirirsin; git commit --fixup=<sha> ve git rebase -i --autosquash düzeltmeleri otomatik doğru yere taşır. Cherry-pick tek bir commit'in değişikliğini başka bir branch'e yeni bir commit olarak uygular (-x mesaja kaynağı yazar).

Geri alma komutları. git reset <commit> aktif branch'i o commit'e taşır; modu neyin ayrıca sıfırlanacağını belirler: --soft sadece branch'i taşır (değişiklikler staged kalır), --mixed (varsayılan) index'i de sıfırlar (değişiklikler working tree'de, unstaged), --hard working tree'yi de ezer (commit'lenmemiş değişiklikler kaybolur). git revert <commit> geçmişi değiştirmez; o commit'in tersini uygulayan yeni bir commit üretir, bu yüzden paylaşılmış branch'te güvenli yoldur. Merge commit'ini revert etmek için hangi parent'ın ana hat olduğunu söylemek gerekir (-m 1). Git 2.23 ile checkout'un iki işi ayrıldı: git switch branch değiştirir (-c ile oluşturur), git restore dosya geri yükler (--staged ile unstage eder). git stash commit'lenmemiş değişiklikleri bir yığına kaldırır; izlenmeyen dosyalar için -u gerekir, pop çatışırsa kayıt silinmez.

Remote. git fetch remote'taki yeni nesneleri indirir ve sadece origin/* ref'lerini günceller; senin branch'ine ve dosyalarına dokunmaz. git pull = fetch + entegrasyon (varsayılan merge). Yerel ve remote ayrışmışsa güncel Git sürümleri (burada 2.43 ile denendi) yapılandırma yoksa fatal: Need to specify how to reconcile divergent branches ile durur; ekip pull.rebase true ya da pull.ff only seçer. git pull --rebase yerel commit'lerini remote'un üzerine taşır ve gereksiz "Merge branch 'main' of ..." commit'lerini önler. git push sadece fast-forward ise kabul edilir. Geçmişi yeniden yazdıysan zorlaman gerekir: --force remote'ta ne varsa ezer; --force-with-lease remote ref'i senin bildiğin değerde değilse reddeder; --force-if-includes ise arka planda fetch edilmiş ama entegre etmediğin işlere karşı ek koruma sağlar.

Conflict ve kurtarma. Conflict, iki taraf aynı bölgeyi farklı değiştirdiğinde olur; Git dosyaya <<<<<<<, =======, >>>>>>> işaretleri koyar. merge.conflictStyle=zdiff3 ortak atanın içeriğini de gösterir ve hangi tarafın neyi değiştirdiğini anlamayı kolaylaştırır. Çöz, test et, git add, sonra git commit (merge) ya da git rebase --continue; her an --abort ile başa dönebilirsin. Reflog, HEAD'in ve her branch'in yerel hareket günlüğüdür: reset, rebase, amend sonrası "kaybolan" commit'ler buradan bulunur (git reflog, git branch kurtar HEAD@{2}). Yereldir, push edilmez; varsayılan olarak ulaşılamayan girdiler 30, diğerleri 90 gün tutulur. Hiç commit'lenmemiş değişiklik reflog'da yoktur. git bisect iyi ve kötü bilinen iki commit arasında ikili arama yapar; git bisect run <test> ile otomatikleşir.

Stratejiler ve PR. Git Flow main, develop, feature/*, release/*, hotfix/* ile planlı sürüm döngülerine uyar ama ağırdır. GitHub Flow tek uzun ömürlü branch (main, her zaman deploy edilebilir) ve kısa feature branch + PR'dır. Trunk-based development'ta herkes çok kısa ömürlü branch'lerle günde en az bir kez trunk'a entegre olur, bitmemiş iş feature flag arkasında kapalı durur (bkz. Kitap Bölüm 20). PR birleştirme yöntemleri: merge commit (branch'in commit'leri ve bir merge commit), squash merge (tüm PR tek commit; tek parent, PR başlığı commit mesajı olur), rebase merge (commit'ler tek tek ana hattın üzerine, doğrusal). İyi commit atomiktir, derlenir ve testleri geçer, mesajı "neden"i anlatır; Conventional Commits (feat:, fix:, ! ya da BREAKING CHANGE:) otomatik changelog ve semver'i besler (bkz. Kitap Bölüm 20).

Diğerleri. .gitignore sadece izlenmeyen dosyalar için geçerlidir; zaten commit'lenmiş bir dosyayı ignore'a eklemek onu takipten çıkarmaz (git rm --cached gerekir). Büyük binary'ler (jar, dump, model dosyası) için Git LFS repoda sadece küçük bir pointer dosyası tutar, içeriği ayrı bir sunucuda saklar. Secret commit edildiyse ilk iş secret'ı iptal edip yenilemektir (rotate); geçmişi temizlemek (git filter-repo) ikincildir, çünkü push edildiği an kopyalanmış, fork'lanmış ya da log'lanmış olabilir.

Sorular (25)

01BaşlangıçTahminitemeldvcs

Git nedir ve SVN gibi merkezi sürüm kontrol sistemlerinden farkı nedir?

02BaşlangıçTahminiindexstagingcommit

Working tree, staging area (index) ve repository nedir; commit ne yapar?

03BaşlangıçTahminibranchrefhead

Branch nedir? Git'te branch açmak neden ucuzdur?

04BaşlangıçTahminiremotefetchpull

git fetch ile git pull arasındaki fark nedir?

05BaşlangıçTahminiconflictmerge

Merge conflict nedir ve nasıl çözülür?

06BaşlangıçTahminiamendcommit

Son commit'in mesajını ya da içeriğini nasıl değiştirirsin?

07BaşlangıçTahministash

git stash ne işe yarar, ne zaman kullanırsın?

08BaşlangıçTahminigitignore

.gitignore nasıl çalışır? Daha önce commit'lenmiş bir dosyayı neden ignore etmiyor?

09BaşlangıçTahminitagrelease

Tag nedir? Lightweight ve annotated tag farkı nedir?

10BaşlangıçTahminicommit-mesajiconventional-commitsatomik-commit

İyi bir commit mesajı nasıl yazılır, atomik commit nedir?

11OrtaTahminimergerebasefast-forward

Merge ile rebase arasındaki fark nedir? Ne zaman hangisini kullanırsın?

12OrtaTahminiresetrevertrestore

git reset --soft, --mixed, --hard ve git revert arasındaki fark nedir?

13OrtaTahminibranchresetcherry-pick

Yanlış branch'e commit attım. Ne yaparım?

14OrtaTahminirevertforce-pushpaylasilan-gecmis

Push edilmiş bir commit'i nasıl geri alırsın? Neden reset + force push değil?

15OrtaTahminicherry-pickhotfix

cherry-pick ne zaman kullanılır, riskleri nelerdir?

16OrtaTahminirebaseinteractive-rebasefixup

Interactive rebase ile commit geçmişini nasıl temizlersin?

17OrtaTahminiheaddetached-headreflog

Detached HEAD nedir, orada attığın commit'lere ne olur?

18OrtaTahminipull-requestsquashmerge-stratejisi

Squash merge, merge commit ve rebase merge arasındaki fark nedir?

19OrtaTahminibranching-stratejisigit-flowtrunk-based

Git Flow, GitHub Flow ve trunk-based development arasındaki fark nedir? Hangisini ne zaman seçersin?

20İleriTahminiic-modelblobtreecommit

Git'in iç modeli: bir commit neyi işaret eder, SHA nasıl oluşur?

21İleriTahminiforce-pushforce-with-leaseremote

git push --force ile --force-with-lease arasındaki fark nedir?

22İleriTahminireflogkurtarmagc

Kaybolan bir commit'i reflog ile nasıl kurtarırsın?

23İleriTahminibisectdebug

git bisect ile hatayı getiren commit'i nasıl bulursun?

24İleriTahminiguvenliksecretfilter-repo

Yanlışlıkla bir secret'ı (şifre, API anahtarı) commit edip push ettim. Ne yaparım?

25İleriTahminimonoreposparse-checkoutpartial-clonelfs

Monorepo'da Git ile çalışırken nelere dikkat edersin?

Çoktan seçmeli (14)

01Başlangıç

git fetch komutu ne yapar?

Tek doğru cevap var.

02Başlangıç

Git'te bir branch teknik olarak nedir?

Tek doğru cevap var.

03Başlangıç

git add Foo.java yaptın, sonra Foo.java'yı tekrar değiştirdin ve git commit dedin. Commit'e ne girer?

Tek doğru cevap var.

04Başlangıç

Daha önce commit'lenmiş application.log dosyasını .gitignore'a ekledin ama git status hâlâ değişikliklerini gösteriyor. Neden?

Tek doğru cevap var.

05Başlangıç

Push edilmemiş son commit'inin mesajında yazım hatası var. En uygun komut hangisi?

Tek doğru cevap var.

06Başlangıç

git stash varsayılan olarak hangi değişiklikleri saklamaz?

Tek doğru cevap var.

07Başlangıç

git push origin main komutunun tag'lerle ilişkisi için hangisi doğrudur?

Tek doğru cevap var.

08Orta

main'den sonra kendi branch'inde 3 commit attın; git reset --soft HEAD~3 sonrası durum nedir?

Tek doğru cevap var.

09Orta

Rebase ile ilgili hangileri doğrudur? (Birden fazla seçenek doğru olabilir.)

Birden fazla doğru olabilir.

10Orta

main'e merge edilip push edilmiş bir merge commit'ini geri alman gerekiyor. Doğru komut hangisi?

Tek doğru cevap var.

11Orta

Bir PR squash merge ile main'e alındı. Hangisi doğrudur?

Tek doğru cevap var.

12İleri

Rebase ettiğin feature branch'ini push etmeden önce IDE arka planda git fetch yaptı ve bir takım arkadaşının yeni commit'i origin/feature'a geldi. Şimdi git push --force-with-lease (beklenen değer vermeden) çalıştırırsan ne olur?

Tek doğru cevap var.

13İleri

git reset --hard HEAD~1 yaptın. Silinen commit'in yanında, daha önce git add edilmiş ama commit'lenmemiş bir dosya ve hiç git add edilmemiş bir değişiklik vardı. Hangisi doğrudur?

Tek doğru cevap var.

14İleri

Production veritabanı şifresini içeren bir commit'i public olmayan şirket reposuna push ettin. İlk yapman gereken nedir?

Tek doğru cevap var.

Kaynaklar