Skip to content

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/main yerel bir ref'tir ve bayatlayabilir
  • fetch ↔ pull: pull = fetch + merge (ya da rebase); güvenli varsayılanlar
  • push'un gerçekte ne gönderdiği; upstream (-u) ve push.default
  • Reddedilen push'un doğru çözümü; --force yasağı ↔ --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"):

AyarDavranışSonuç
pull.rebase false (tarihsel varsayılan)fetch + mergeYerel commit'in varsa "Merge branch 'main' of ..." commit'i üretir — gürültü
pull.rebase truefetch + rebaseYerel commit'lerin origin/main'in üstüne yeniden yazılır; düz geçmiş (6. ders)
pull.ff onlyfetch + yalnız ileri sarYerel 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:

LightweightAnnotated
Komutgit tag v1.4git tag -a v1.4 -m "Sürüm 1.4"
Depoda ne varYalnız ref → commitRef → tag nesnesi → commit
Kim, ne zaman, nedenYokEtiketleyen, tarih, mesaj; -s ile imza
KullanımKişisel yer imiSü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

  1. Sunucu. git init --bare sunucu.git; içine bak: çalışma alanı yok. git clone sunucu.git ayse ve git clone sunucu.git can — iki geliştirici. ayse'de git remote -v.
  2. İlk push. ayse'de bir dosya + commit, git push -u origin main. can'da git fetch ardından git log --oneline --all: commit origin/main'de ama main'de değil — fetch'in yerel dala dokunmadığını gör; git pull ile ileri sar.
  3. Reddedilen push. İkisinde de aynı dosyanın farklı satırlarına commit at. ayse push eder; can push eder → red. Mesajı oku. can'da git fetch, git log --oneline --graph --all ile ayrışmayı gör, git rebase origin/main, push.
  4. pull.ff only. can'da git config pull.ff only. ayse yeni commit push etsin; can yerelde commit atıp git pull desin → red; git pull --rebase ile çöz.
  5. Lease. ayse'de son commit'i --amend ile değiştir, git push → red. Önce can'dan yeni bir commit push ettir; sonra ayse'de git 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.
  6. Tag ve prune. ayse'de git tag -a v0.1.0 -m "İlk sürüm", git push --follow-tags; can'da fetch + git describe. ayse bir dal açıp push etsin, sonra git push origin --delete ile silsin; can'da git branch -r hâ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."