Bölüm 4 / 12
Uzak depolar
remote, fetch, pull, push, upstream, tag.
15 dakikalık okuma
Üç ders boyunca Git tek makinede çalıştı; şimdi ikinci depo
giriyor. 1. dersteki cümleyi hatırla: uzak depo teknik olarak ayrıcalıklı değildir, senin
klonunla aynı türden bir depodur. Bu ders o eşitliğin mekaniğini kurar: Git iki depo
arasında yalnız iki şey taşır — nesneler ve ref güncellemeleri — ve bunu
yapan yalnız iki komut vardır, fetch ve push. Geri kalan her şey
(pull, izleme dalları, origin/main, upstream) bu ikisinin
üstüne kurulmuş kolaylıklardır. En kritik bölüm, herkesin ilk haftasında yaşadığı
reddedilen push: doğru çözümü, --force'un neden yasak olduğu ve
--force-with-lease'in neden farklı olduğu. Sonda tag'ler (15-11'in SemVer
damgası) ve sunucudaki deponun neden "çıplak" olduğu.
01Ne öğreneceksin
- Remote kavramı,
origin'in ne olduğu, birden fazla remote (fork düzeni) - Uzak izleme dalları:
origin/mainyerel bir ref'tir ve bayatlayabilir fetch↔pull: pull = fetch + merge (ya da rebase); güvenli varsayılanlarpush'un gerçekte ne gönderdiği; upstream (-u) vepush.default- Reddedilen push'un doğru çözümü;
--forceyasağı ↔--force-with-lease - Tag: lightweight ↔ annotated; tag'lerin push'u; sürümleme bağı; tag taşıma yasağı
- Bare depo: sunucudaki deponun neden çalışma alanı olmadığı; HTTPS ↔ SSH
--prune, sığ klon ve CI'daki klonun farkı
02Remote: başka bir depoya verilen ad
Remote, başka bir deponun URL'sine verilmiş kısa addır; .git/config'te
yaşar. git clone klonladığı depoyu otomatik olarak origin diye
kaydeder — "origin" özel bir kelime değil, yalnız gelenektir:
$ git remote -v
origin https://git.kurum.gov.tr/basvuru/api.git (fetch)
origin https://git.kurum.gov.tr/basvuru/api.git (push)
$ git remote add upstream https://github.com/asil-proje/api.git
$ git remote show origin # dallar, izleme ilişkileri, bayat ref'ler
Birden fazla remote sıradan bir durumdur. En yaygın örüntü fork düzeni: yazma
yetkin olmayan bir projeyi kendi hesabına çatallarsın; origin senin çatalın,
upstream asıl projedir; asıl projeden fetch eder, kendi çatalına
push eder, PR ile (8. ders) katkı önerirsin. Kurum içinde de görülür: bir
geçiş döneminde eski ve yeni sunucu iki remote olarak yan yana durur.
Remote'un deponda bıraktığı iz uzak izleme dallarıdır: refs/remotes/origin/main
gibi ref'ler. Bunlar uzak deponun dallarının son gördüğün hâlinin yerel
kopyasıdır — sen yazamazsın, yalnız fetch günceller. Kritik cümle:
origin/main, uzak sunucudaki main değildir; senin en son fetch'inde
gördüğün main'dir. Fetch etmediğin sürece bayattır; "origin/main'de bu commit yok"
demek "son baktığımda yoktu" demektir.
03fetch ↔ pull
git fetch origin iki şey yapar: uzak depoda olup sende olmayan nesneleri
indirir ve origin/* izleme dallarını günceller. Yerel dallarına ve
çalışma alanına dokunmaz. Bu yüzden fetch her zaman güvenlidir — günde on kez, ne
yaptığın fark etmeksizin çalıştırabilirsin; hiçbir şeyi bozmaz, yalnız bilgini tazeler.
Fetch'ten sonra ne olduğunu görmek de senin elindedir:
git fetch
git log --oneline HEAD..origin/main # onlarda olup bende olmayan
git log --oneline origin/main..HEAD # bende olup onlarda olmayan
git diff HEAD...origin/main # ortak atadan bu yana onların değişikliği
git pull ise fetch + birleştirmedir: önce fetch,
sonra bulunduğun dalın upstream'ini (04. bölüm) yerel dala katar. Birleştirmenin nasıl
olacağı üç seçenekten biridir ve Git 2.27'den beri seçmediysen uyarır ("Pulling without
specifying how to reconcile divergent branches is discouraged"):
| Ayar | Davranış | Sonuç |
|---|---|---|
pull.rebase false (tarihsel varsayılan) | fetch + merge | Yerel commit'in varsa "Merge branch 'main' of ..." commit'i üretir — gürültü |
pull.rebase true | fetch + rebase | Yerel commit'lerin origin/main'in üstüne yeniden yazılır; düz geçmiş (6. ders) |
pull.ff only | fetch + yalnız ileri sar | Yerel commit'in varsa reddeder; ne yapacağına sen karar verirsin |
Öneri, 3. dersin --ff-only mantığıyla aynı: git config --global pull.ff only.
Pull, yalnız yerel dalın sunucunun gerisindeyse (tipik durum: sabah güncelleme) sessizce
ileri sarar; iki taraf da ilerlemişse durur ve seçimi sana bırakır — pull --rebase
ya da açık bir merge. Tesadüfi merge commit'leri geçmişten silinir ve "pull neden merge
penceresi açtı" şaşkınlığı biter. Kıdemli alışkanlığın özü: pull'u sihirli buton değil,
fetch + bilinçli birleştirme olarak düşün. IDE'lerin "senkronize et" düğmesi de
pull + push'tan ibarettir; ne yaptığını bilmeden basılmaz.
04push ve upstream
git push origin main şunu yapar: uzak depoda eksik olan nesneleri gönderir
ve uzak depodan refs/heads/main'i senin main'inin özetine güncellemesini
ister. Sunucu bu güncellemeyi yalnız ileri sarma ise kabul eder — 05. bölümün konusu.
Her push'ta remote ve dal adı yazmamak için dala bir upstream (izleme ilişkisi)
bağlanır:
git push -u origin feature/son-gun # ilk push: dalı oluştur ve upstream bağla
git push # sonrakiler: upstream'e
git branch -vv # her yerel dal: hangi uzak dalı izliyor, ahead/behind
git config --global push.autoSetupRemote true # 2.37+: ilk push'ta -u otomatik
Upstream ilişkisi .git/config'te branch.feature/son-gun.remote = origin
ve .merge = refs/heads/feature/son-gun satırlarıdır; 3. dersteki
status'un "ahead 1, behind 2" satırı bu ilişkiden hesaplanır.
push.default ayarı, çıplak git push'un ne yapacağını belirler;
Git 2.0'dan beri varsayılan simple'dır: bulunduğun dalı, aynı adlı
upstream'ine gönder — eski matching varsayılanının "tüm eşleşen dalları
birden gönderme" sürprizi ortadan kalktı.
05Reddedilen push ve --force meselesi
Sabah dalına iki commit attın, push ediyorsun:
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the remote contains work that you do not have locally.
Anlamı: sunucudaki main, senin son fetch'inden beri ilerlemiş (bir arkadaşın push etmiş); senin main'in onun ilerisinde değil, yanında — ileri sarılamaz. Sunucu haklı olarak reddediyor: kabul etseydi arkadaşının commit'leri main'den düşerdi. Doğru çözüm her zaman aynı üç adımdır: fetch → entegre et → tekrar push:
git fetch
git rebase origin/main # ya da: git merge origin/main (ekip kararı, 7. ders)
dotnet test # entegre sonuç hâlâ yeşil mi? (anlamsal çatışma, 3. ders)
git push
Yanlış çözüm git push --force'tur: sunucuya "ne olursa olsun ref'i benim
özetime çevir" der. Sunucu çevirir; arkadaşının commit'leri main'den düşer — o commit'ler
sunucuda erişilemez olur ve arkadaşının bir sonraki pull'u "diverged" hatasıyla biter.
Paylaşılan dala --force yasaktır; barındırma platformları bunu korumalı
dal (protected branch) kuralıyla sunucu tarafında engeller (8. ders).
Ama bazen zorla push meşrudur: kendi özellik dalını PR öncesi rebase -i ile
temizledin (6. ders); dal sende yeniden yazıldı, sunucudaki eski hâliyle uyuşmuyor,
yine de göndermen gerekiyor. Bunun aracı --force-with-lease'tir:
"sunucudaki dal hâlâ benim origin/dal'da gördüğüm özetteyse üstüne yaz; biri
bu arada push ettiyse reddet." Yani zorla push'a bir önkoşul ekler: senin
haberin olmayan işi ezemez. Kıdem uyarısı: --force-with-lease'in kiralık
sözü, izleme dalının bayat olmasına dayanır — IDE'nin arka planda otomatik fetch
yapması izleme dalını sessizce günceller ve korumayı boşa çıkarır. Bu yüzden Git 2.30
--force-if-includes'i ekledi (uzak dalın ucunun senin geçmişinde olduğunu
da doğrular); ikisi birlikte kullanılır. Ve kural değişmez: zorla push yalnız kendi
dalına, hiçbir zaman ekibin ortak dalına.
06Tag: sürümün adı
Tag, ilerlemeyen bir ref'tir (1. ders): bir commit'e verilmiş kalıcı ad. İki türü vardır ve fark, 1. dersin nesne modeliyle açıklanır:
| Lightweight | Annotated | |
|---|---|---|
| Komut | git tag v1.4 | git tag -a v1.4 -m "Sürüm 1.4" |
| Depoda ne var | Yalnız ref → commit | Ref → tag nesnesi → commit |
| Kim, ne zaman, neden | Yok | Etiketleyen, tarih, mesaj; -s ile imza |
| Kullanım | Kişisel yer imi | Sürüm damgası — her zaman bu |
Sürüm tag'i annotated olur çünkü "1.4'ü kim, ne zaman kesti" sorusu denetimde
sorulur (14-11); imzalı tag, dağıtılan sürümün kaynağını kanıtlar (14-04'ün imza kavramı).
Ad, 15-11'deki SemVer'dir: v1.4.0, v2.0.0-rc.1.
git describe bulunduğun commit'i en yakın tag'e göre adlandırır
(v1.4.0-12-g3f2a1c0: 1.4.0'dan 12 commit sonra) — derleme numarası üretmenin
standart yolu ve 18. hatta boru hattının sürüm bilgisini nereden aldığının cevabı.
Tag'ler push'ta kendiliğinden gitmez:
git push origin v1.4.0 # tek tag
git push --follow-tags # gönderilen commit'lere bağlı annotated tag'ler — önerilen
git push --tags # tüm tag'ler, lightweight dahil — deneme tag'lerin de gider
git push origin --delete v1.4.0 # uzaktan sil
Tag taşınmaz. Yayımlanmış bir tag'i silip başka commit'e koymak, sürümün
kimliğini değiştirmektir; fetch eden istemciler eski tag'i tutar (Git, değişmiş tag'i
varsayılan olarak güncellemez) ve ekipte "v1.4 hangisi" karmaşası başlar. Yanlış tag
atıldıysa doğru cevap yeni bir sürüm numarasıdır (v1.4.1).
07Bare depo ve protokoller
Sunucudaki depoya bakarsan tuhaf bir şey görürsün: proje dosyaları yok, yalnız
.git'in içeriği — objects/, refs/, HEAD.
Buna bare (çıplak) depo denir ve geleneksel adı .git ile biter:
git init --bare basvuru-api.git. Çalışma alanı olmamasının nedeni basittir:
kimse sunucuda dosya düzenlemez; depo yalnız push alır ve fetch verir. Üstelik bare
olmayan bir depoya, o an açık olan dala push etmek varsayılan olarak reddedilir
(receive.denyCurrentBranch): çalışma alanı ile HEAD'in uyumsuz kalmasını
önlemek için. GitHub, GitLab, Azure DevOps'un arkasındaki şey, erişim kontrolü ve web
arayüzü eklenmiş bare depolardır. git clone --mirror bir depoyu tüm ref'leriyle
bare kopyalar — sunucu taşıma ve yedekleme aracı.
İki protokol: HTTPS ve SSH. HTTPS'te kimlik, parola yerine kişisel erişim
belirteci (PAT) ile doğrulanır ve bir credential helper onu saklar (Windows'ta Git
Credential Manager; 13-10'un "sır nerede durur" sorusu burada da geçerli: belirteç
düz metin dosyada değil işletim sisteminin kasasında durmalı). SSH'ta kimlik bir anahtar
çiftidir: özel anahtar sende (~/.ssh/id_ed25519, parola korumalı), açık
anahtar sunucuda — 14-03'ün asimetrik kriptografisinin gündelik hâli. Kurum ağında
HTTPS çoğu kez vekil ve sertifika politikalarıyla daha uyumludur (14-04'ün kök
sertifika konusu 17. hatta konteynerde geri gelir); geliştirici makinesinde SSH,
belirteç yenileme derdini kaldırır. Her iki durumda da kural: kimlik bilgisi depoya
da URL'ye de yazılmaz.
08Bakım: prune, sığ klon, CI'daki klon
Uzakta silinen dal, sendeki origin/eski-dal'ı silmez; izleme dalları
birikir ve branch -a listesi yalan söylemeye başlar. git fetch --prune
(ya da kalıcı olarak fetch.prune true) uzakta olmayan izleme dallarını
kaldırır. Bu, "origin/main bayat olabilir" cümlesinin ikinci yüzü: izleme dalları
eksik olabildiği gibi fazla da olabilir; ikisinin de ilacı fetch'tir.
Büyük depolarda tam klon pahalıdır. git clone --depth 1 yalnız son
commit'i indirir (sığ klon) — geçmiş yok, bisect ve blame çalışmaz, ama CI için
(18. hat) çoğu zaman yeterlidir ve boru hattı süresini belirgin kısaltır.
--filter=blob:none (kısmi klon) tüm commit ve tree'leri alır, blob'ları
gerektiğinde indirir — geçmiş korunur, ilk indirme küçülür. 3. dersin notunu hatırla:
CI çoğu kez depoyu özetle, dalsız çeker (detached HEAD) ve sığdır; boru hattında
git describe ya da log tuhaf davranıyorsa nedeni klonun
derinliğidir (fetch-depth: 0 ayarı 18. hatta).
09Mini alıştırma — iki geliştirici, bir sunucu
Yaklaşık 20 dakika · terminal; sunucu yerine yerel bare depo
- Sunucu.
git init --bare sunucu.git; içine bak: çalışma alanı yok.git clone sunucu.git aysevegit clone sunucu.git can— iki geliştirici.ayse'degit remote -v. - İlk push.
ayse'de bir dosya + commit,git push -u origin main.can'dagit fetchardındangit log --oneline --all: commitorigin/main'de amamain'de değil — fetch'in yerel dala dokunmadığını gör;git pullile ileri sar. - Reddedilen push. İkisinde de aynı dosyanın farklı satırlarına commit
at.
aysepush eder;canpush eder → red. Mesajı oku.can'dagit fetch,git log --oneline --graph --allile ayrışmayı gör,git rebase origin/main, push. - pull.ff only.
can'dagit config pull.ff only.ayseyeni commit push etsin;canyerelde commit atıpgit pulldesin → red;git pull --rebaseile çöz. - Lease.
ayse'de son commit'i--amendile değiştir,git push→ red. Öncecan'dan yeni bir commit push ettir; sonraayse'degit push --force-with-lease→ red (biri push etti).git fetch+ rebase + tekrar lease ile push.--force'un bu adımda can'ın commit'ini nasıl düşüreceğini sözle açıkla. - Tag ve prune.
ayse'degit tag -a v0.1.0 -m "İlk sürüm",git push --follow-tags;can'da fetch +git describe.aysebir dal açıp push etsin, sonragit push origin --deleteile silsin;can'dagit branch -rhâlâ görüyor —git fetch --prune.
10Kendini sına
git fetch ile git pull arasındaki fark nedir? origin/main tam olarak nedir ve neden "bayat" olabilir? pull.ff only ayarı neden önerilir?
Fetch, uzak depoda olup sende olmayan nesneleri indirir ve uzak
izleme dallarını (refs/remotes/origin/*) günceller; yerel dallara ve
çalışma alanına dokunmaz — bu yüzden her zaman güvenlidir, yalnız bilgini tazeler.
Pull, fetch artı birleştirmedir: bulunduğun dalın upstream'ini yerel dala merge ya
da rebase ile katar. origin/main uzak sunucudaki main değil, senin son
fetch'inde gördüğün main'in yerel kopyasıdır; sen yazamazsın, yalnız fetch günceller;
fetch etmediğin sürece bayattır — "origin/main'de yok" demek "son baktığımda yoktu"
demektir; uzakta silinen dalın izleme dalı da kendiliğinden silinmez, fetch
--prune gerekir. pull.ff only, pull'un yalnız ileri sarılabildiğinde
(yerel dal geride) çalışmasını, iki taraf da ilerlemişse reddetmesini sağlar: tesadüfi
"Merge branch 'main' of ..." commit'leri geçmişten silinir ve birleştirme yöntemini
(rebase ya da açık merge) bilinçli seçersin. Git 2.27'den beri yöntem seçilmemişse
uyarı verilir; kıdemli alışkanlık pull'u sihirli buton değil, fetch + bilinçli
birleştirme olarak görmektir.
Push neden reddedilir ("non-fast-forward")? Doğru çözüm nedir? --force neden yasaktır ve --force-with-lease ondan nasıl farklıdır; lease'in zayıf noktası nedir?
Push, sunucudan uzak dalın ref'ini senin özetine güncellemesini
ister; sunucu bunu yalnız ileri sarma ise kabul eder. Senin son fetch'inden beri biri
push ettiyse senin dalın uzak dalın ilerisinde değil yanındadır; kabul edilseydi
diğerinin commit'leri daldan düşerdi — bu yüzden red. Doğru çözüm her zaman üç adımdır:
fetch, entegre et (rebase origin/main ya da merge — ekip kararı), testleri koş
(anlamsal çatışma), tekrar push. --force "ne olursa olsun ref'i benim
özetime çevir" der: başkasının commit'leri sunucuda erişilemez olur, onun sonraki
pull'u "diverged" hatasıyla biter; paylaşılan dala yasaktır ve platformlar korumalı dal
kuralıyla engeller. --force-with-lease, zorla push'a önkoşul ekler:
sunucudaki dal hâlâ senin izleme dalında (origin/dal) gördüğün özetteyse
üstüne yaz, biri bu arada push ettiyse reddet — haberin olmayan işi ezemezsin. Meşru
kullanımı kendi özellik dalını PR öncesi rebase ile temizledikten sonra göndermektir.
Zayıf noktası, önkoşulun izleme dalının bayat olmasına dayanmasıdır: IDE'nin arka
plandaki otomatik fetch'i izleme dalını sessizce günceller ve koruma boşa çıkar;
Git 2.30'un --force-if-includes'i uzak ucun senin geçmişinde olduğunu
da doğrular, ikisi birlikte kullanılır. Kural: zorla push yalnız kendi dalına.
Lightweight ve annotated tag arasındaki fark nedir; sürüm için hangisi ve neden? Tag'ler nasıl push edilir? Yayımlanmış tag neden taşınmaz?
Lightweight tag yalnız bir ref'tir: refs/tags/v1.4
doğrudan commit'i gösterir; kim, ne zaman, neden bilgisi yoktur — kişisel yer imi.
Annotated tag (-a -m) bir tag nesnesi üretir: etiketleyen, tarih, mesaj,
isteğe bağlı GPG imzası (-s), sonra commit'e işaret. Sürüm damgası her zaman
annotated olur: "1.4'ü kim, ne zaman kesti" sorusu denetimde sorulur ve imza dağıtılan
sürümün kaynağını kanıtlar; ad SemVer'dir (v1.4.0). git describe bulunduğun
commit'i en yakın tag'e göre adlandırır (v1.4.0-12-g3f2a1c0) — derleme numarasının
kaynağı. Tag'ler push'ta kendiliğinden gitmez: tek tag push origin v1.4.0;
önerilen push --follow-tags, gönderilen commit'lere bağlı annotated tag'leri
gönderir; --tags lightweight dahil hepsini gönderir ve deneme tag'lerini
de yayar. Silme push origin --delete v1.4.0. Yayımlanmış tag taşınmaz
çünkü sürümün kimliği değişir: fetch eden istemciler eski tag'i tutar — Git değişmiş
tag'i varsayılan olarak güncellemez — ve "v1.4 hangisi" karmaşası doğar; yanlış tag'in
cevabı yeni sürüm numarasıdır (v1.4.1).
Bare depo nedir, sunucudaki depo neden bare'dir? HTTPS ve SSH erişimi nasıl çalışır? Sığ klon ve --prune ne zaman gerekir?
Bare depo, çalışma alanı olmayan depodur: yalnız
.git'in içeriği (objects, refs, HEAD), geleneksel adı .git ile
biter (git init --bare). Sunucuda kimse dosya düzenlemez; depo yalnız push
alır ve fetch verir. Bare olmayan depoya o an açık dala push etmek varsayılan olarak
reddedilir (receive.denyCurrentBranch), çünkü çalışma alanı ile HEAD
uyumsuz kalır. GitHub/GitLab/Azure DevOps, erişim kontrolü ve arayüz eklenmiş bare
depolardır; clone --mirror tüm ref'lerle bare kopya alır — taşıma ve yedek.
HTTPS'te kimlik parola yerine kişisel erişim belirteciyle doğrulanır ve credential
helper (Windows'ta Git Credential Manager) onu işletim sistemi kasasında saklar;
SSH'ta anahtar çifti kullanılır — özel anahtar parola korumalı olarak sende, açık
anahtar sunucuda. Kurum ağında HTTPS vekil ve sertifika politikalarıyla daha uyumlu,
geliştirici makinesinde SSH belirteç yenileme derdinden kurtarır; kimlik bilgisi ne
depoya ne URL'ye yazılır. fetch --prune uzakta silinmiş dalların izleme
dallarını kaldırır; fetch.prune true ile kalıcı. Sığ klon
(--depth 1) yalnız son commit'i indirir — geçmiş yok, bisect ve blame
çalışmaz — CI için uygundur; kısmi klon (--filter=blob:none) geçmişi
korur, blob'ları gerektiğinde indirir. CI'da depo çoğu kez sığ ve detached HEAD ile
çekilir; describe tuhaf davranıyorsa nedeni derinliktir.
11Sınav köşesi
Yazılıda nasıl sorulur
(1) Git'te remote nedir? origin ne anlama gelir? (2) git fetch
ile git pull arasındaki fark nedir? (3) git push komutu ne
yapar; -u parametresi ne işe yarar? (4) Push işlemi hangi durumda
reddedilir, nasıl çözülür? (5) git push --force neden tehlikelidir?
(6) Tag nedir, hangi türleri vardır? (7) Uzak izleme dalı (remote-tracking branch)
nedir? (8) Bare repository nedir, nerede kullanılır? (9) Git'e HTTPS ve SSH ile
erişim arasındaki fark nedir? (10) git clone hangi işlemleri birleştirir?
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) Model — "Uzak depo, benim klonumla aynı türden bir depodur; Git iki
depo arasında yalnız nesne ve ref güncellemesi taşır, bunu fetch ve push yapar.
origin/main sunucudaki main değil, son fetch'imde gördüğüm hâlidir." (2) fetch ↔ pull
— "Fetch her zaman güvenlidir, yalnız izleme dallarını tazeler; pull fetch artı
birleştirmedir. Ben pull.ff only kullanırım: geride kalmışsam ileri sarar,
ayrışmışsam durur ve rebase mi merge mi bilinçli seçerim." (3) Reddedilen push —
"Sunucu ileri sarılamayan güncellemeyi reddeder; çözüm fetch, entegre et, test koş,
push. --force başkasının commit'lerini düşürür, paylaşılan dala yasaktır;
kendi dalımı temizledikten sonra --force-with-lease kullanırım — biri bu
arada push ettiyse reddeder." (4) Tag — "Sürüm damgası her zaman annotated tag'dir:
kim, ne zaman, imza; --follow-tags ile gönderirim; yayımlanmış tag
taşınmaz, yanlışsa yeni sürüm numarası." (5) Kıdem vuruşu — "Sunucudaki depo bare'dir,
çalışma alanı yoktur. Erişimde belirteç ya da SSH anahtarı kullanır, hiçbirini depoya
ya da URL'ye yazmam. IDE'nin otomatik fetch'inin lease korumasını boşa çıkarabildiğini
bilir, --force-if-includes ile birlikte kullanırım; izleme dallarının hem
eksik hem fazla olabildiğini bilir, fetch --prune ile temiz tutarım."
