Git Submodule ile Bağımlı Depo Yönetimi
Monorepo mu, çoklu repo mu tartışması yıllardır sürüp gidiyor. Ama gerçek dünyada her iki yaklaşımın da yetersiz kaldığı senaryolar var: birden fazla bağımsız repo’yu koordineli şekilde yönetmek zorundasınız, ancak bunları tek bir dev monorepo altında birleştirmek de mümkün değil. Belki farklı ekipler farklı erişim haklarıyla çalışıyor, belki açık kaynak bir kütüphaneyi projenize entegre ediyorsunuz, belki de CI/CD pipeline’larınız ayrı repo yaşam döngüleri gerektiriyor. İşte bu noktada Git Submodule devreye giriyor ve doğru kullanıldığında son derece güçlü bir araç haline geliyor.
Git Submodule Nedir ve Neden Var?
Submodule, özünde bir Git reposunun içine başka bir Git reposunu “iç içe geçirme” mekanizmasıdır. Ana repo (superproject), alt reponun sadece belirli bir commit’e işaret eden bir referans tutar. Dosyaların kendisini değil, bir pointer’ı takip edersiniz.
Bu mimari şu senaryolarda gerçek anlamda hayat kurtarır:
- Şirket içi paylaşılan kütüphaneleri birden fazla projede kullanıyorsunuz
- Üçüncü parti bir açık kaynak projesini belirli bir sürümde sabitlemek istiyorsunuz
- Frontend ve backend ayrı ekipler tarafından yönetiliyor ama deployment tek repo’dan tetikleniyor
- Embedded sistemlerde vendor kodu ana kod tabanından izole tutulması gerekiyor
Submodule’ün npm, pip veya Maven gibi paket yöneticilerinden farkı şu: kaynak kodu doğrudan repo tarihçenizin bir parçası haline geliyor, ikili paket değil. Dependency hell yerine tam kaynak kontrolü alıyorsunuz.
Temel Kurulum: İlk Submodule Ekleme
Diyelim ki bir web uygulaması geliştiriyorsunuz ve şirketinizin ortak UI bileşen kütüphanesini projeye dahil etmeniz gerekiyor.
# Ana projenizin root dizininde
git submodule add https://github.com/sirketiniz/ui-components.git libs/ui-components
# Sonucu kontrol edin
git status
Bu komut çalıştıktan sonra iki şey oluşur: libs/ui-components dizini ve .gitmodules dosyası. .gitmodules dosyasına bir göz atalım:
cat .gitmodules
# [submodule "libs/ui-components"]
# path = libs/ui-components
# url = https://github.com/sirketiniz/ui-components.git
Şimdi bu değişiklikleri commit’leyelim:
git add .gitmodules libs/ui-components
git commit -m "feat: ui-components submodule eklendi"
git push origin main
Ana repo’da libs/ui-components dizini artık gerçek dosyalar içermiyor gibi görünür. Git orada sadece bir commit hash saklıyor. git log --oneline çıktısında da göreceğiniz gibi, submodule dizini aslında bir “gitlink” nesnesidir.
Ekip Arkadaşlarının Repoyu Klonlaması
Burada çok yaygın bir tuzak var. Yeni bir takım arkadaşı projeyi klonladığında standart git clone submodule içeriklerini indirmez:
# Yanlış yaklaşım - submodule dizini boş gelir
git clone https://github.com/sirketiniz/ana-proje.git
# Doğru yaklaşım - tek seferde her şeyi çek
git clone --recurse-submodules https://github.com/sirketiniz/ana-proje.git
Eğer normal clone yaptıysanız panik yapmayın, sonradan da düzeltebilirsiniz:
# Submodule'leri initialize et ve çek
git submodule update --init --recursive
--recursive bayrağı kritik, çünkü submodule’ün içinde de başka bir submodule olabilir (nested submodules). Bunu atlarsanız beklenmedik build hataları alırsınız ve nedenini bulmak için saatler harcarsınız.
Ben bu durumu ilk kez bir Jenkins pipeline’ında yaşadım. Pipeline submodule’ü initialize etmeden build almaya çalışıyordu, her seferinde “header file not found” hatası geliyordu. Yarım gün compile flag’lerine baktık, sorun tamamen başka bir yerdeydi.
Submodule Güncellemeleri: Asıl Zorluk Burası
Submodule yönetiminde en sık karşılaşılan sorunlar güncelleme aşamasında ortaya çıkıyor. Upstream’de (submodule’ün orijinal reposunda) bir değişiklik olduğunu ve bunu projenize almak istediğinizi düşünelim:
# Submodule dizinine gir
cd libs/ui-components
# Upstream değişikliklerini çek
git fetch origin
git checkout main
git pull origin main
# Ana dizine dön
cd ../..
# Ana repo submodule'ün yeni commit'ini görecek
git status
# modified: libs/ui-components (new commits)
# Bu değişikliği commit'le
git add libs/ui-components
git commit -m "chore: ui-components v2.3.1 sürümüne güncellendi"
git push origin main
Burada kritik bir nokta var: submodule dizinine girip orada çalışmak, ayrı bir repo’da çalışmak gibidir. Ana repo’nun komutları submodule içini otomatik güncellemez. Her zaman bilinçli bir karar vermeniz gerekiyor.
Tüm submodule’leri tek seferde güncellemek istiyorsanız:
# Tüm submodule'leri upstream'in varsayılan branch'ından güncelle
git submodule update --remote --merge
# Sadece belirli bir submodule'ü güncelle
git submodule update --remote --merge libs/ui-components
--merge yerine --rebase de kullanabilirsiniz. Fark şu: --merge submodule’deki yerel değişikliklerinizi korurken merge commit oluşturur, --rebase ise tarihçeyi daha temiz tutar ama conflict durumunda daha dikkatli olmanızı gerektirir.
Submodule Branch Takibi
Varsayılan davranışta submodule belirli bir commit’e “sabitlenmiş” durumdadır, bir branch’ı takip etmez. Ama özellikle geliştirme ortamlarında belirli bir branch’ı takip etmek isteyebilirsiniz:
# Submodule'ün takip edeceği branch'ı ayarla
git config -f .gitmodules submodule.libs/ui-components.branch develop
# .gitmodules dosyasını kontrol et
cat .gitmodules
# [submodule "libs/ui-components"]
# path = libs/ui-components
# url = https://github.com/sirketiniz/ui-components.git
# branch = develop
# Değişikliği commit'le
git add .gitmodules
git commit -m "config: ui-components develop branch'ini takip edecek sekilde ayarlandi"
Artık git submodule update --remote komutunu çalıştırdığınızda, submodule develop branch’ının son commit’ini alacak.
Gerçek Dünya Senaryosu: Mikroservis Mimarisi
Şöyle bir senaryo düşünelim: Bir e-ticaret platformu geliştiriyorsunuz. Sisteminizde şunlar var:
platform-core: Ana platform reposu (deployment, config, docs)auth-service: Kimlik doğrulama servisiproduct-service: Ürün katalogu servisishared-schemas: Ortak JSON schema ve protobuf tanımları
shared-schemas her iki serviste de kullanılıyor ve platform-core da tüm servisleri bir arada tutuyor.
# platform-core reposunda
git submodule add [email protected]:sirket/auth-service.git services/auth
git submodule add [email protected]:sirket/product-service.git services/product
git submodule add [email protected]:sirket/shared-schemas.git libs/schemas
# auth-service reposunda da shared-schemas submodule olarak ekli
# Nested submodule durumu
git clone --recurse-submodules [email protected]:sirket/platform-core.git
# Tüm yapıyı initialize et
git submodule update --init --recursive
Bu yapıda deployment şöyle çalışabilir:
# Belirli bir environment için tüm servislerin durumunu gör
git submodule status
# +a1b2c3d4 services/auth (v1.2.3)
# +e5f6g7h8 services/product (v2.1.0-4-ge5f6g7h)
# 9i0j1k2l libs/schemas (v1.0.0)
+ işareti o submodule’ün kayıtlı commit’ten farklı bir commit’te olduğunu gösterir. - işareti ise henüz initialize edilmediğini. Bu çıktıyı CI/CD pipeline’ınızda parse ederek deployment kararları verebilirsiniz.
Submodule’de Değişiklik Yapma ve Geri Katkı Sağlama
İşte burada çoğu insanın takıldığı nokta geliyor. Submodule içinde değişiklik yaptınız ve bunu upstream’e göndermek istiyorsunuz:
# Submodule dizinine gir
cd libs/ui-components
# Detached HEAD durumundan çık, gerçek bir branch'a geç
git checkout main
# ya da yeni bir feature branch aç
git checkout -b fix/button-hover-state
# Değişikliklerinizi yapın
# ...
# Submodule içinde commit
git add .
git commit -m "fix: button hover state düzeltildi"
# Submodule'ün kendi remote'una push et
git push origin fix/button-hover-state
# Ana dizine dön
cd ../..
# Ana repo yeni commit'i görecek
git add libs/ui-components
git commit -m "fix: ui-components button hover düzeltmesi dahil edildi"
git push origin main
Kritik uyarı: Submodule içinde değişiklik yapıp sadece ana repoyu push’ladıysanız, diğer ekip üyeleri git submodule update yaptığında o commit’e ulaşamaz. Submodule’ün kendi remote’una push etmeyi unutmayın. Bu “push sırası” hatası production’da ciddi sorunlara yol açabilir.
Submodule Silme
Submodule silmek sanıldığından biraz daha zahmetli bir işlem:
# 1. Adım: submodule'ü deinitialize et
git submodule deinit -f libs/ui-components
# 2. Adım: .git/modules dizininden temizle
rm -rf .git/modules/libs/ui-components
# 3. Adım: Dosya sisteminden kaldır ve git'ten çıkar
git rm -f libs/ui-components
# 4. Adım: Değişikliği commit'le
git commit -m "chore: ui-components submodule kaldirildi"
Bu adımları eksik yaparsanız sonraki git submodule add işlemlerinde “already exists in the index” gibi kafa karıştırıcı hatalar alabilirsiniz.
CI/CD Entegrasyonu
GitHub Actions veya GitLab CI kullanıyorsanız submodule’leri pipeline’da doğru yapılandırmanız gerekiyor.
GitHub Actions için:
# .github/workflows/build.yml
name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout with submodules
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GITHUB_TOKEN }}
# Private submodule varsa PAT kullanmaniz gerekebilir
# token: ${{ secrets.PAT_TOKEN }}
GitLab CI için:
# .gitlab-ci.yml
variables:
GIT_SUBMODULE_STRATEGY: recursive
build:
stage: build
script:
- git submodule status
- make build
GIT_SUBMODULE_STRATEGY değişkeni için üç seçenek var:
- none: Submodule’leri çekme (varsayılan, dikkat edin)
- normal: Sadece bir seviye derinlikte submodule’leri initialize et
- recursive: Nested submodule’ler dahil hepsini initialize et
Private submodule’ler için SSH key yönetimini de doğru yapmanız gerekiyor. Özellikle CI ortamlarında deploy key veya machine user kullanımını öneririm.
Yaygın Hatalar ve Çözümleri
“You are in ‘detached HEAD’ state” uyarısı
Submodule’e girdiğinizde git sizi her zaman belirli bir commit’e koyar, bir branch’a değil. Bu normaldir. Değişiklik yapmak istiyorsanız mutlaka bir branch’a geçin:
cd libs/ui-components
git status
# HEAD detached at a1b2c3d
git checkout main
# ya da
git checkout -b benim-branch
Submodule URL değişikliği
Repo migrate edildi veya URL değişti:
# .gitmodules dosyasını düzenle
git config -f .gitmodules submodule.libs/ui-components.url https://yeni-url.com/ui-components.git
# Lokal konfigürasyonu senkronize et
git submodule sync
# Submodule'leri yeniden initialize et
git submodule update --init
Merge conflict’lerde submodule pointer çakışması
İki branch farklı submodule commit’lerine işaret ediyorsa merge sırasında conflict çıkar. Bu durumda hangi versiyonu kullanmak istediğinize karar vermeniz gerekiyor:
# Conflict'i görmek için
git diff
# Kendi versiyonunuzu seçmek için
git checkout --ours libs/ui-components
git add libs/ui-components
# Ya da karşı tarafın versiyonunu
git checkout --theirs libs/ui-components
git add libs/ui-components
Submodule Alternatiflerine Kısa Bir Bakış
Submodule her zaman doğru araç değildir. Bazı durumlarda şu alternatifleri değerlendirin:
- git subtree: Submodule’e göre daha basit bir bağımlılık modeli, history birleştirme destekli. Ama upstream’e katkı yapmak daha karmaşık.
- Paket yöneticisi: Node.js projesiyse npm/yarn workspace, Python ise pip editable install. Kaynak kontrolü gerekmiyorsa bu yolu tercih edin.
- Artifact registry: Maven Central, Docker Hub, npm registry gibi. Binary bağımlılıklar için submodule kullanmak çoğu zaman yanlış yaklaşım.
Submodule’ün gerçekten mantıklı olduğu durumlar: kaynak kodun tam kontrolünü istiyorsunuz, bağımlılığın spesifik bir commit’e sabitlenmesi kritik, ve ekibiniz git workflow’unu iyi biliyor.
Faydalı Alias’lar ve Konfigürasyonlar
Günlük kullanımı kolaylaştırmak için global git config’e ekleyebileceğiniz bazı ayarlar:
# Submodule durumunu her zaman göster
git config --global status.submoduleSummary true
# diff'lerde submodule içeriğini göster
git config --global diff.submodule log
# fetch sırasında submodule'leri de otomatik güncelle
git config --global fetch.recurseSubmodules on-demand
# Faydalı alias'lar
git config --global alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
git config --global alias.spush 'push --recurse-submodules=on-demand'
git config --global alias.supdate 'submodule update --remote --merge'
spush alias’ı özellikle değerli: git push --recurse-submodules=on-demand komutu push işleminden önce submodule’lerin de push edilmesini sağlar. “Submodule’ü push’lamayı unuttum” sendromunun panzehiri bu.
Sonuç
Git submodule, öğrenmesi biraz zahmetli ama mastered edildiğinde çok güçlü bir araç. Asıl sorun submodule’ün karmaşık olması değil, yeterince anlaşılmadan kullanılması. “Detached HEAD”, “boş dizin”, “push sırası” gibi klasik tuzaklar hep yetersiz anlayıştan kaynaklanıyor.
Ekip olarak submodule kullanmaya başlayacaksanız birkaç temel kural koyun: clone her zaman --recurse-submodules ile yapılır, submodule’de değişiklik yapıldığında önce submodule sonra ana repo push’lanır, CI/CD pipeline’larında submodule stratejisi açıkça tanımlanır.
Doğru senaryoda doğru araçla kullanıldığında, submodule çoklu ekiplerin bağımsız ama koordineli çalışmasını sağlayan zarif bir çözüm sunuyor. Monorepo’nun overhead’i olmadan, ayrı repo’ların kaotikliği olmadan. Tam ortasında, kontrollü bir nokta.
