Git submodule, bir depoyu başka bir deponun içine belirli bir commit'e sabitleyerek yerleştirmenizi sağlar. Elinizde birden fazla projede kullandığınız ortak bir tema, bir yapılandırma seti ya da şirket içi bir kütüphane varsa, onu kopyala-yapıştır ile taşımak yerine submodule olarak bağlamak ilk bakışta çok doğru bir fikir gibi görünür. Çoğu zaman da öyledir; ancak submodule'ün nasıl çalıştığını bilmeden kullanan ekipler, bir süre sonra "klonladım ama klasör boş", "kimse hangi sürümde olduğumuzu bilmiyor" ve "CI'da eksik dosya hatası" üçlüsüyle tanışır.
Bu rehberde submodule'ün gerçekte ne sakladığını (bir dizin değil, bir commit işaretçisi), nasıl eklendiğini, depoyu klonlarken nasıl birlikte getirildiğini, sürümün nasıl ilerletildiğini ve temiz bir şekilde nasıl kaldırıldığını göstereceğim. Sonunda submodule'e alternatif olan subtree, paket yöneticisi ve tek depo (monorepo) yaklaşımlarını karşılaştırıp hangi durumda hangisinin daha az acı verdiğine dair net bir tavsiye bırakacağım.
Submodule Aslında Neyi Saklar#
En kritik kavram şudur: ana deponuz submodule'ün dosyalarını saklamaz. Yalnızca iki şey saklar. Birincisi depo kökündeki .gitmodules dosyası; bu dosya submodule'ün hangi yola, hangi uzak adresten bağlanacağını tanımlar ve normal bir metin dosyası gibi sürümlenir. İkincisi ise ağaçta özel bir "gitlink" girdisidir: submodule dizini, ana depoda tek bir commit SHA'sı olarak kaydedilir.
Bu tasarımın sonucu şudur: ana depodaki bir commit, "bu projede ortak kütüphanenin a3f91c2 commit'i kullanılıyordu" bilgisini taşır. Yani sürüm sabitlemesi (pinning) submodule'ün doğal davranışıdır. Alt depoda ne olursa olsun, siz açıkça ilerletmedikçe ana depo o commit'te kalır. Bunu doğrudan görebilirsiniz:
# Ana depoda submodule'ün hangi commit'e sabitlendiğini gör
git ls-tree HEAD ortak-tema
# 160000 commit a3f91c2b6d3e4f8091ac2d5e7b1f0c93a4d82e11 ortak-tema
# Tüm submodule'lerin durumu (başındaki - işareti "henüz getirilmedi" demektir)
git submodule status
# -a3f91c2b6d3e4f8091ac2d5e7b1f0c93a4d82e11 ortak-tema
160000 mod değeri normal bir dosya (100644) veya dizin (040000) değil, gitlink anlamına gelir. Bu tek satır, submodule ile ilgili yaşadığınız hemen her davranışın açıklamasıdır: dosyalar başka bir depoda durur, ana depo sadece bir işaretçi tutar.
Submodule Ekleme ve İlk Kurulum#
Eklemek tek komuttur. Hedef yolu ve uzak adresi verirsiniz; Git alt depoyu klonlar, .gitmodules dosyasını oluşturur veya günceller ve gitlink girdisini hazırlar.
cd /var/www/firmaniz-portal
# Ortak temayı vendor/ortak-tema yoluna submodule olarak ekle
git submodule add https://git.firmaniz.com/altyapi/ortak-tema.git vendor/ortak-tema
# Belirli bir dalı takip etmesini istiyorsan
git submodule add -b main https://git.firmaniz.com/altyapi/ortak-tema.git vendor/ortak-tema
git status
# new file: .gitmodules
# new file: vendor/ortak-tema
git commit -m "chore: ortak tema submodule olarak eklendi"
git push
Ortaya çıkan .gitmodules dosyası şuna benzer ve mutlaka depoya commit edilmelidir; bu dosya olmadan başka hiç kimse submodule'ü kuramaz:
[submodule "vendor/ortak-tema"]
path = vendor/ortak-tema
url = https://git.firmaniz.com/altyapi/ortak-tema.git
branch = main
Uzak adres seçimi küçük ama önemli bir karardır. HTTPS adresi herkesin klonlayabilmesi açısından esnektir; SSH adresi ise özel depolarda anahtar tabanlı erişim sağlar ama CI koşucusunda dağıtım anahtarı tanımlamayı gerektirir. Karma ekiplerde en az sürtünme yaratan yöntem .gitmodules içinde HTTPS kullanıp, SSH tercih edenlerin yerelde url.<base>.insteadOf ayarıyla kendi tercihini uygulamasıdır.
Depoyu Klonlarken Submodule'leri Getirmek#
Submodule ile ilgili en sık duyulan şikâyet burada doğar: bir geliştirici depoyu klonlar, vendor/ortak-tema dizinini açar ve bomboş bulur. Sebep basittir; sıradan git clone submodule içeriğini getirmez, sadece boş dizini ve işaretçiyi oluşturur.
Doğru komutlar şunlardır:
# Klonlarken submodule'leri de getir (önerilen)
git clone --recurse-submodules https://git.firmaniz.com/portal/firmaniz-portal.git
# Zaten klonlanmış bir depoda sonradan getirmek için
git submodule update --init --recursive
# Ağı hızlandırmak için paralel getirme
git submodule update --init --recursive --jobs 4
Bu adımı her seferinde hatırlamak zorunda kalmamak için Git'e alışkanlığı kalıcı olarak öğretebilirsiniz:
# checkout, pull, switch gibi komutlarda submodule'leri otomatik hizala
git config --global submodule.recurse true
Sürekli entegrasyon tarafında da aynı tuzak vardır: çoğu CI aracı varsayılan olarak submodule getirmez ve derleme "dosya bulunamadı" ile patlar. Kullandığınız aracın checkout adımında submodule seçeneğini açık şekilde etkinleştirin. Sunucuya kodu doğrudan Git ile çekiyorsanız aynı kural orada da geçerlidir; paylaşımlı ortamda dağıtım kurarken submodule'lerin ayrı bir adım gerektirdiğini hesaba katın — temel akış için cPanel Git Version Control yazısına bakabilirsiniz.
Submodule Güncelleme ve Sürüm İlerletme#
Submodule sabitlenmiş bir commit'e bakar; onu ilerletmek bilinçli bir işlemdir ve ana depoda yeni bir commit üretir. İki yol vardır.
Birinci yol, alt depoya girip istediğiniz commit'i seçmek:
cd vendor/ortak-tema
git fetch origin
git switch main # dikkat: submodule varsayılan olarak detached HEAD'dedir
git pull --ff-only
cd ../..
# Ana depoda işaretçinin değiştiğini gör
git status
# modified: vendor/ortak-tema (new commits)
git add vendor/ortak-tema
git commit -m "chore: ortak tema v2.3.0 sürümüne yükseltildi"
git push
İkinci yol, tek komutla takip edilen dalın ucuna atlamak:
# .gitmodules içindeki branch değerinin en son commit'ine ilerlet
git submodule update --remote vendor/ortak-tema
git add vendor/ortak-tema
git commit -m "chore: ortak tema güncellendi"
Buradaki en önemli ayrım şudur: git submodule update (uzaksız hâli) ana depodaki kayıtlı commit'e geri döner; --remote ekiyle çalıştırıldığında ise uzak daldaki en son commit'e ilerler. İkisini karıştırmak, "az önce yaptığım güncelleme neden geri gitti" sorusunun kaynağıdır.
Ekip için pratik bir kural: submodule sürümünü yalnızca bilinçli bir "yükseltme" commit'inde ilerletin ve commit mesajında hangi sürüme çıkıldığını yazın. Böylece git log -- vendor/ortak-tema komutu bağımlılık geçmişinizin okunabilir bir listesine dönüşür. Mesaj biçimini standartlaştırmak isterseniz conventional commits ve semantic versioning yazısındaki chore(deps): şeması bu iş için biçilmiş kaftandır.
Submodule'ü Temiz Bir Şekilde Kaldırma#
Kaldırma işlemi tek komut değildir ve yarım bırakıldığında depoda hayalet kayıtlar bırakır. Sırayı takip edin:
- Submodule'ü devre dışı bırakın ve çalışma kopyasını temizleyin.
- Git'in izleme kaydından çıkarın.
.git/modulesaltındaki iç kopyayı silin.- Değişikliği commit edin.
# 1) çalışma kopyasını kaldır ve .git/config kaydını temizle
git submodule deinit -f vendor/ortak-tema
# 2) izlemeden çıkar (bu adım .gitmodules'u da otomatik günceller)
git rm -f vendor/ortak-tema
# 3) iç depo kopyasını sil
rm -rf .git/modules/vendor/ortak-tema
# 4) commit et
git commit -m "chore: ortak tema submodule kaldırıldı"
Üçüncü adımı atlarsanız aynı yola ileride yeni bir submodule eklemek istediğinizde Git "already exists in the index" benzeri bir hata verir ve nedenini bulmak zaman alır. .gitmodules dosyası boş kaldıysa onu da silip commit edebilirsiniz.
Submodule Yerine Ne Kullanmalı#
Submodule tek doğru cevap değildir. Aşağıdaki tablo, aynı ihtiyaca yönelik dört yaklaşımı karşılaştırıyor.
| Yaklaşım | Sürüm sabitleme | Klonlama kolaylığı | Alt depoya katkı vermek | En uygun olduğu durum |
|---|---|---|---|---|
| Submodule | Commit düzeyinde, güçlü | Ek adım gerekir | Kolay, doğrudan alt depoda çalışılır | Ortak kütüphaneyi aynı anda geliştirdiğiniz durumlar |
| Subtree | Commit düzeyinde, ana depoya kopyalanır | Ekstra adım yok | Zahmetli, subtree push gerekir | Tüketicilerin kolay klonlaması öncelikliyse |
| Paket yöneticisi (Composer, npm) | Sürüm aralığı ve kilit dosyası | Ekstra adım yok, install yeterli | Ayrı yayın döngüsü gerekir | Olgun, sürümlenmiş kütüphaneler |
| Tek depo (monorepo) | Gereksiz, her şey aynı commit'te | En kolay | En kolay | Sıkı bağlı, birlikte yayınlanan projeler |
Pratik tavsiyem şudur: kütüphaneniz olgunlaşmış ve düzenli sürüm çıkarıyorsa paket yöneticisi her zaman daha az sürtünme yaratır; Composer veya npm zaten kilit dosyasıyla sabitleme yapar. Submodule'ü, henüz sürüm yayınlamadığınız ama iki projede birden aktif olarak geliştirdiğiniz şirket içi kod için saklayın. Subtree ise dış katkıcıların depoyu tek komutla klonlamasının kritik olduğu açık kaynak projelerde mantıklıdır.
Sık Yapılan Hatalar ve Tuzaklar#
Detached HEAD üzerinde commit atmak. Submodule dizinine girdiğinizde Git sizi bir dalın ucuna değil, sabitlenmiş commit'e koyar. Burada yaptığınız commit hiçbir dala ait olmaz ve git submodule update çalıştığınız anda erişilemez hâle gelir. Alt depoda çalışmadan önce mutlaka git switch main yapın.
.gitmodules dosyasını commit etmemek. İşaretçiyi commit edip .gitmodules'u unutmak, diğer herkes için "submodule mapping bulunamadı" hatası üretir. İki dosya her zaman aynı commit'te gitmelidir.
Submodule değişikliğini push etmeden ana depoyu itmek. Ana depoya ilerlettiğiniz işaretçi, alt depoda henüz itilmemiş bir commit'i gösteriyorsa, sizin makinenizde her şey çalışır ama başka hiç kimse o commit'i getiremez. Bunu yapısal olarak engelleyin:
# Ana depoyu iterken, submodule'de itilmemiş commit varsa push'u reddet
git push --recurse-submodules=check
# Ya da submodule'leri otomatik olarak önce it
git push --recurse-submodules=on-demand
Uzak adresi değiştirip senkronize etmemek. .gitmodules içindeki adresi güncelledikten sonra git submodule sync --recursive çalıştırmazsanız, yerel .git/config eski adresi kullanmaya devam eder ve nedensiz görünen erişim hataları alırsınız.
Submodule'ü yedek stratejisinin dışında bırakmak. Ana deponuzu yedeklerken submodule'ler ayrı depolardır ve ayrı yedeklenmeleri gerekir. Dağıtım paketleri hazırlarken de aynı şey geçerlidir; arşiv alırken git archive submodule içeriğini dâhil etmez. Depo ve sunucu yedekleme düzeninizi kurarken bunu kontrol listenize ekleyin ve gerekirse yedekleme çözümlerimize göz atın.
Sürüm ilerletmeyi otomatik sanmak. Alt depoda yeni commit çıkması, projenizde hiçbir şeyi değiştirmez — bu bir özelliktir, hata değil. Güncellemeyi bilinçli ve test edilmiş bir adım olarak planlayın.
Sıkça Sorulan Sorular#
Depoyu klonladım ama submodule klasörü boş, ne yapmalıyım#
Sıradan git clone submodule içeriğini getirmez. Mevcut klon üzerinde git submodule update --init --recursive komutunu çalıştırmanız yeterlidir. Bir daha unutmamak için baştan git clone --recurse-submodules <adres> kullanın ya da git config --global submodule.recurse true ayarını açarak Git'in bunu her checkout ve pull işleminde otomatik yapmasını sağlayın.
Git submodule ile subtree arasındaki fark nedir#
Submodule, alt deponun dosyalarını ana depoya kopyalamaz; yalnızca bir commit işaretçisi tutar, dolayısıyla klonlarken ek adım gerekir. Subtree ise alt deponun içeriğini gerçekten ana depoya kopyalar; klonlayan kişi hiçbir ek komut çalıştırmadan tüm dosyalara sahip olur ama alt depoya geri katkı vermek zorlaşır. Kısaca submodule katkı vermeyi, subtree tüketmeyi kolaylaştırır.
Submodule'ü belirli bir sürüme nasıl sabitlerim#
Submodule zaten doğası gereği sabittir: ana depodaki commit hangi alt commit'i işaret ediyorsa o kullanılır. Belirli bir etikete sabitlemek için alt depoya girip git checkout v2.3.0 yapın, ana depoya dönüp git add vendor/ortak-tema ve commit edin. Bu commit'i klonlayan herkes tam olarak o sürümü alır; siz açıkça ilerletmedikçe hiçbir şey değişmez.
CI/CD boru hattında submodule nasıl çekilir#
Neredeyse tüm CI araçları submodule getirmeyi varsayılan olarak kapalı tutar. Kullandığınız aracın checkout adımında submodule seçeneğini açın veya betiğe git submodule update --init --recursive satırını ekleyin. Özel bir depo söz konusuysa koşucuya erişim yetkisi de tanımlamanız gerekir: SSH adresi kullanıyorsanız dağıtım anahtarı, HTTPS kullanıyorsanız bir erişim jetonu.
Submodule içinde yaptığım değişiklikler neden kayboluyor#
Büyük olasılıkla değişiklikleri detached HEAD durumundayken commit ettiniz. Submodule dizini varsayılan olarak bir dala değil, sabitlenmiş commit'e bakar; orada atılan commit hiçbir dala bağlı değildir ve git submodule update çalıştığında görünmez olur. Çözüm, alt depoda çalışmaya başlamadan önce git switch main demek; kaybolduğunu düşündüğünüz commit'i ise git reflog ile geri bulabilirsiniz.
Submodule kullanmak zorunda mıyım, alternatifi var mı#
Zorunda değilsiniz. Bağımlılığınız düzenli sürüm çıkaran bir kütüphaneyse Composer veya npm gibi bir paket yöneticisi daha az sürtünme yaratır ve kilit dosyasıyla sabitlemeyi zaten yapar. Projeler birbirine çok sıkı bağlıysa ve hep birlikte yayınlanıyorsa tek depo yaklaşımı en basit çözümdür. Submodule'ü, henüz paketlenmemiş ama iki yerde birden aktif geliştirilen şirket içi kod için tercih edin.
Kapanış#
Submodule, doğru anlaşıldığında güçlü ve son derece öngörülebilir bir araçtır: ana depoda bir dizin değil, bir commit işaretçisi taşır ve o işaretçi siz istemedikçe kıpırdamaz. Aklınızda dört alışkanlık kalsın: klonlamayı her zaman --recurse-submodules ile yapın, submodule.recurse ayarını açın, alt depoda çalışmadan önce bir dala geçin ve ana depoyu --recurse-submodules=on-demand ile iterek eksik commit sorununu baştan engelleyin.
Bu tür çok depolu kurulumlarda asıl fark, kodun sunucuya nasıl indiğinde ortaya çıkar. Kendi Git sunucunuzu, CI koşucunuzu ve hazırlık ortamınızı barındırmak için tam root erişimli VDS veya sanal sunucu paketlerimizi kullanabilir, kurulum ve bakımı bizim üstlenmemizi isterseniz sunucu yönetimi hizmetimize bakabilirsiniz. Depolarınızın ve sunucunuzun düzenli kopyalarını almak için yedekleme sayfamız da işinize yarayacaktır.