Kurs
Bir veri analizi projesi üzerinde çalışıyorsunuz ve ekibinizin başka bir depoda bakımını yaptığı ortak bir yardımcı araçlar kütüphanesini projeye dahil etmeniz gerekiyor. Kodu kopyalayıp yapıştırabilirsiniz, ancak bu durumda güncelleme yolunu kaybedersiniz. Git submodule kullanabilirsiniz, ama zor olduklarını duydunuz. Üçüncü bir seçenek var: git subtree.
git subtree, bir Git deposunu bir diğerinin içine bir alt dizin olarak gömmenizi sağlar; tam geçmişi korur ve güncellemeler için kolay bir yol sunar.
Bu rehber, git subtree’yi ne zaman kullanmanız gerektiğini, nasıl çalıştığını ve yaygın iş akışları için pratik örnekleri kapsar. Veri bilimi projelerinde gerçekten karşılaşabileceğiniz senaryoları kullanacağız.
git subtree Nedir?
Git subtree, projenizde belirli bir klasör altında başka bir Git deposunu, tüm commit geçmişini koruyarak, içerir. Kodu kopyalamaktan veya sembolik bağlantılar kullanmaktan farklı olarak, subtree özgün depoyla bir bağlantıyı sürdürür; böylece güncellemeleri çekebilir ve gerekirse değişiklikleri geri gönderebilirsiniz.
İşte onu kodu sadece kopyalamaktan ayıran noktalar:
Kopyala-yapıştır kod:
- Özgün depoyla bağlantı yok
- Kütüphane değiştiğinde güncelleme yolu yok
- Kodun nereden geldiğine dair geçmiş yok
Git subtree:
- Özgün depodan tam commit geçmişi
- Tek komutla güncellemeleri çekme
- Gerekirse değişiklikleri özgün depoya geri gönderme
- Her şey tek bir depo checkout’unda bulunur
Git submodule’dan temel fark: subtree içeriği doğrudan ana deponuza commit edilir. Birisi projenizi klonladığında, ek adım olmadan tek seferde her şeyi alır.
Bütün bunlar daha kolay işe alıştırma ve daha az “benim makinemde çalışıyor” sorununa dönüşür.
git subtree Nasıl Çalışır
Bir subtree eklediğinizde Git akıllı bir şey yapar: başka bir deponun geçmişini projenizin bir alt dizinine birleştirir. Olan şu:
- Git uzak deponun geçmişini çeker
- Bu commit’leri seçtiğiniz alt dizin altında görünecek şekilde yeniden yazar
- Yeniden yazılmış bu geçmişi mevcut dalınıza birleştirir
- Gelecekteki güncellemeler aynı modeli izler: fetch, rewrite, merge
Depo, sadece bir işaretçi veya referans değil, subtree’den gerçek dosyalar ve tam commit geçmişi içerir. Birisi projenizi klonladığında veya bir dalı checkout ettiğinde, tüm koda hemen sahip olur. Ayrı bir “submodule başlat” adımı yoktur.
Takas noktası: deponuz büyür; çünkü hem kendi kodunuzu hem de subtree’nin kodu ile tam geçmişini içerir. Buna değer mi? Duruma bağlı, ancak çoğu veri bilimi ekibi için, bence değer.
git subtree Nasıl Kullanılır (Yaygın Komutlar)
Artık subtree’nin ne yaptığını anladığınıza göre, en yaygın işlemleri adım adım inceleyelim. Gerçekçi bir senaryo kullanacağız: bir veri analizi projesi geliştiriyorsunuz ve ortak bir yardımcı kütüphaneyi dahil etmeniz gerekiyor.
Örnek kurulum:
-
Ana proje:
data-pipeline -
Paylaşılan kütüphane:
shared-utils(https://github.com/yourteam/shared-utils.gitadresinde mevcut) -
Bunu projenizde
libs/shared-utils/altında dahil etmek istiyorsunuz
git subtree add
add komutu, başka bir depoyu projenize ilk kez dahil eder.
Referans için temel sözdizimi şöyledir:
git subtree add --prefix=<directory> <remote-url> <branch> --squash
Gerçek örnek:
# shared-utils deposunu libs/shared-utils altına ekleyin
git subtree add --prefix=libs/shared-utils \
https://github.com/yourteam/shared-utils.git \
main \
--squash
Bunun yaptığı şey:
- libs/shared-utils dizinini oluşturur
- shared-utils’tan tüm dosyaları buraya kopyalar
- Her şeyi deponuza commit eder
- Gelecekteki güncellemeler için bağlantıyı kaydeder
Bayrakları anlama:
-
--prefix=libs/shared-utils: Subtree’nin projenizde yaşayacağı yer. Tüm gelecekteki işlemler için aynı öneki (prefix) tam olarak kullanmalısınız. Bunu bir kez değiştirirseniz, nedenlerin bozulduğunu 02:00’de Stack Overflow’da arıyor olabilirsiniz. -
--squash: Subtree’nin tüm commit geçmişini projenizde tek bir commit’e indirger. Bu, projenizin geçmişini daha temiz tutar. --squash olmadan, projenizin geçmişinde özgün depodaki her commit’i görürsünüz ki bu bunaltıcı olabilir.
Ne zaman squash yapılır: Özgün depodaki ayrıntılı commit atıflarını korumanız gerekmiyorsa --squash kullanın. Paylaşılan yardımcılar içeren çoğu veri projesinde, sıkıştırılmış (squashed) geçmiş daha temiz ve çalışması daha kolaydır. Ben her zaman kullanırım.
Bunu çalıştırdıktan sonra projenizde şu tarz bir iletisi olan yeni bir commit göreceksiniz: "Add 'libs/shared-utils/' from commit 'abc123'". libs/shared-utils dizini artık tüm dosyaları içerir ve hemen kullanabilirsiniz.
git subtree pull
pull komutu, subtree’nizi özgün depodaki değişikliklerle günceller. Paylaşılan kütüphane ekibi bir hatayı düzeltirse veya bir özellik eklerse, bu güncellemeleri böyle alırsınız.
Temel sözdizimi:
git subtree pull --prefix=<directory> <remote-url> <branch> --squash
Gerçek örnek:
# shared-utils’tan en son değişiklikleri çekin
git subtree pull --prefix=libs/shared-utils \
https://github.com/yourteam/shared-utils.git \
main \
--squash
Bunun yaptığı şey:
- Uzak depodan yeni commit’leri çeker
- Bunları libs/shared-utils dizininize birleştirir
- Projenizde bir merge commit’i oluşturur
Önek tam olarak eşleşmelidir. add sırasında libs/shared-utils kullandıysanız, pull için de libs/shared-utils kullanmalısınız. Farklı bir önek kullanmak işe yaramaz. Buna güvenin.
Squash tutarlılığı: add sırasında --squash kullandıysanız, her pull’da --squash kullanmalısınız. Sıkıştırılmış ve sıkıştırılmamış güncellemeleri karıştırmak kafa karıştırıcı bir geçmiş yaratır. Bunu zor yoldan öğrendim.
Çakışmaların ele alınması: libs/shared-utils içindeki dosyaları siz değiştirdiyseniz ve aynı dosyalar upstream’de de değiştiyse, merge çatışmaları oluşur. Bunları, herhangi bir Git birleştirme çatışmasını çözdüğünüz gibi çözün—dosyaları düzenleyin, aşamaya (stage) alın ve birleştirmeyi tamamlayın.
Ekibinizin README’sinde --squash kullanıp kullanmadığınızı belgeleyin ki herkes tutarlı kalsın.
git subtree push
push komutu, subtree klasöründe yaptığınız değişiklikleri özgün depoya geri gönderir.
Temel sözdizimi:
git subtree push --prefix=<directory> <remote-url> <branch>
Gerçek örnek:
# libs/shared-utils içindeki değişiklikleri özgün depoya geri gönderin
git subtree push --prefix=libs/shared-utils \
https://github.com/yourteam/shared-utils.git \
main
Bunun yaptığı şey:
- Sadece libs/shared-utils’i etkileyen commit’leri çıkarır
- Onları, sanki shared-utils’ın kökünde yapılmış gibi yeniden yazar
- Bu commit’leri özgün depoya iter
Ne zaman kullanılır: Ortak bir kütüphaneyi uygulama deponuzun içinde bakımını yapıyor ve iyileştirmeleri geri katkılamak istiyorsunuz. Diyelim ki data-pipeline projeniz üzerinde çalışırken shared-utils’a yararlı bir veri doğrulama işlevi eklediniz ve bundan diğer projeler de yararlanmalı.
Push’u, pull’un tersi gibi düşünün. Pull değişiklikleri içeri alır; push değişiklikleri dışarı gönderir. Git yalnızca ilgili geçmişi çıkarır ve onu özgün deponun yapısına çevirir. Oldukça akıllıca.
Özgün depoya yazma erişimine ihtiyacınız var. İzniniz yoksa, depoyu forklamanız, forka push etmeniz ve bir çekme isteği (pull request) oluşturmanız gerekir.
git subtree split
split komutu, bir alt dizinin geçmişini kendi dalına çıkarır.
Temel sözdizimi:
git subtree split --prefix=<directory> -b <new-branch-name>
Gerçek bir örnek:
# libs/shared-utils’ı kendi dalına çıkarın
git subtree split --prefix=libs/shared-utils -b shared-utils-extracted
Bunun yaptığı şey:
- Sadece libs/shared-utils’i etkileyen commit’leri içeren yeni bir dal oluşturur
- Bu dal, yalnızca o dizinden oluşan bağımsız bir depo gibi görünür
- Özgün dal değişmeden kalır
Yaygın kullanım durumları:
Bir monorepo’dan kütüphane çıkarmak: Veri işleme hattınız büyüdü ve yeniden kullanılabilir bir bileşeni bağımsız bir kütüphane olarak çıkarmak istiyorsunuz. Split, yalnızca o bileşenin geçmişini içeren bir dal oluşturur; bunu yeni bir depoya itebilirsiniz.
Bir alt dizini bağımsız bir proje olarak yayımlamak: Analiz projenizin içinde yararlı bir veri görselleştirme modülü oluşturdunuz ve bunu bağımsız bir paket olarak paylaşmak istiyorsunuz. Split, tam geçmişi korunmuş şekilde bunu çıkarır.
Yeni bir bağımsız depo oluşturma iş akışı örneği:
# Alt dizini çıkarın
git subtree split --prefix=libs/shared-utils -b shared-utils-standalone
# Yeni bir depo oluşturun ve ona push edin
git remote add shared-utils-origin https://github.com/yourteam/shared-utils-new.git
git push shared-utils-origin shared-utils-standalone:main
Artık shared-utils-new, yalnızca o alt dizinin tam geçmişine sahip eksiksiz bir depodur.
git subtree vs. submodule
İnsanların her zaman sorduğu soru şu: subtree mi submodule mü?
Her ikisi de dış kodu deponuza dahil etmenizi sağlar, ancak farkları vardır. Eğer kesin sürüm sabitlemeye ihtiyacınız varsa ya da depo boyutu kısıtlıysa, submodule kullanın. Ekibinizin Git becerileri karışıksa ve sadelik istiyorsanız, subtree kullanın. Aksi halde ikisi de işe yarar.
|
Boyut |
git subtree |
git submodule |
|
Kurulum karmaşıklığı |
Orta (doğrudan komutlar) |
Yüksek (ayrı |
|
Klonlama deneyimi |
Basit (tek klon, her şey çalışır) |
Karmaşık ( |
|
Depo boyutu |
Daha büyük (tam subtree içeriği + geçmişi dahil) |
Daha küçük (yalnızca başka bir depoya işaretçi) |
|
Geliştirici işe alıştırma |
Kolay (klondan sonra her şey çalışır) |
Daha zor (submodule iş akışını anlamak gerekir) |
|
CI/CD karmaşıklığı |
Basit (klonla ve devam et) |
Daha karmaşık (submodule’lar başlatılmalı) |
|
Sürüm sabitleme |
Daha az kesin (en son veya belirli commit’i birleştirir) |
Kesin (tam commit hash’i) |
|
Güncelleme iş akışı |
|
|
|
"Benim makinemde çalışıyor" riski |
Daha düşük (her şey commitlidir) |
Daha yüksek (submodule durumu sapabilir) |
|
Değişiklikleri geri gönderme |
|
Submodule içinde normal git push |
|
Sorumlulukların ayrımı |
Karışık (kod ana depoda yaşar) |
Net (submodule ayrı depodur) |
Şunlara ihtiyacınız olduğunda git submodule kullanın :
- Sıkı sürüm sabitleme (kütüphane Y’nin tam olarak X commit’i kullanılmalı)
- Projeler arasında net ayrım (gerçekten bağımsızlar)
- Aynı bağımlılığı farklı sürümlerde paylaşan birden çok proje
- İş akışınız için küçük ana depo boyutu önemliyse
Şunları istediğinizde git subtree kullanın:
- Ek başlangıç adımı olmasın
- Yeni ekip üyeleri bir kez klonlayıp sonra testleri çalıştırsın
- Ekibinizin öğrenmesi gereken daha az Git kavramı olsun
- Zaman zaman güncelleme çektiğiniz vendoring senaryoları
Hiçbiri her zaman daha iyi değildir. Seçim, sadeliğe mi yoksa sıkı sürüm kontrolüne mi değer verdiğinize bağlıdır. Benim görüşüm: Git’in iç detaylarından çok analize odaklanan veri bilimi ekipleri için subtree genellikle sürtünmeyi azaltır. Bağımlılık gereksinimlerini yöneten platform ekipleri için submodule mantıklıdır.

git subtree’yi Ne Zaman Kullanmamalı
git subtree’yi, depo boyutu sıkı bir kısıt olduğunda kullanmayın. Subtree, tam içerik ve geçmişi dahil ettiği için depo boyutunuzu şişirir. Git platformu limitlerine yakındaysanız veya ağ hızı çok önemliyse, submodule’lar daha hafiftir.
Ayrıca, sıkı sürüm sabitlemeye ihtiyacınız varsa git subtree kullanmayın. Kütüphanenin yalnızca 2.3.1 sürümünü ve başka hiçbir şeyi kullanmanız gerekiyorsa. Submodule’lar tam commit’lere sabitler; subtree commit aralıklarını birleştirir.
git subtree’yi harici depo sık değişiyorsa kullanmayın. Subtree her gün önemli değişiklerle güncelleniyorsa, sürekli güncelleme çekmek dağınık bir birleştirme geçmişi oluşturur. Gerçekten gömülü olmasına ihtiyacınız olup olmadığını veya bir paket yöneticisinin daha temiz olup olmayacağını değerlendirin.
Son olarak, kurumsal ayrım gerekiyorsa git subtree kullanmayın. Subtree içeriğinin farklı erişim kontrolleri, lisanslaması veya sahipliği varsa, onları ayrı depolarda tutmak sınırları daha net kılar.
git subtree’nin Avantajları
Tek depo klonlama deneyimi: git clone çalıştırın ve bitti. İhtiyacınız olan her şey orada. Bu, herkesin Git uzmanı olmadığı ekiplerde işe alıştırma sürtünmesini azaltır.
Submodule’lardan daha az hareketli parça: Ayrı başlatma adımı yok. Açıklanması gereken detached HEAD durumları yok. “Submodule’un uyumsuz” karmaşası yok. İş akışı standart Git işlemlerine (add, pull, push) daha yakındır.
CI/CD sadeliği: Sürekli entegrasyon betikleriniz daha basittir. Subtree ile klonlayıp testleri çalıştırırsınız. Submodule’larda klonlar, özyinelemeli başlatır, sonra testleri çalıştırırsınız. Bu ekstra adım unutulduğunda derlemeleri bozar.
Git konusunda farklı rahatlık seviyelerine sahip ekipler için iyi çalışır: Kıdemsiz veri uygulayıcılarının submodule mekaniğini anlaması gerekmez. Alışıldık Git komutlarını kullanırlar ve her şey çalışır.
Ara sıra güncellemeli vendoring için iyi: Bir kütüphanenin 1.2 sürümünü dahil edersiniz. Altı ay sonra, 1.3 sürümünde istediğiniz bir hata düzeltmesi vardır. Tek komutla içeri alın. Bakımı yapılmayan kopyalanmış kodla sıkışıp kalmazsınız; ama aynı zamanda sürekli upstream’i takip etmek zorunda da kalmazsınız.

git subtree’nin Sınırlamaları ve Tavizleri
Depo önemli ölçüde büyür: Deponuz, kendi kodunuzun yanı sıra subtree’nin kodunu ve tam commit geçmişini içerir (yardımcı olmakla birlikte --squash kullanmadıkça). 50 MB’lık bir subtree, deponuza yaklaşık 50 MB ekler.
Büyük veya birden çok subtree için bu birikir. Kolaylığın disk alanı ve klonlama süresine değip değmeyeceğini düşünün.
-
Geçmiş grafiği karmaşıklığı:
--squashile bile, subtree güncellemeleri için yapılan merge commit’leri Git geçmişinize karmaşıklık katar. Subtree güncellemelerini sık çekerseniz, commit grafiğiniz dağınık hale gelir. -
Upstream ayrışma kafa karışıklığı: Subtree dosyalarını siz değiştirir ve upstream de aynı dosyaları değiştirirse, birleştirme çatışmaları olur. Bir subtree’de çatışma çözmek kafa karıştırıcı olabilir; çünkü başkasının deposunu sizin alt dizininize birleştiriyorsunuz.
-
Push iş akışında dikkat edilmesi gerekenler: git subtree push geçmişi yeniden yazmayı gerektirir; bu, büyük subtree’lerde yavaş olabilir. Subtree klasöründe birçok commit yaptıysanız, push biraz sürebilir. Sabırlı olun.
-
Birleştirme çatışmaları yine olur: Siz ve upstream aynı dosyayı değiştirirseniz, çatışmaları çözmeniz gerekir. Bu subtree’ye özgü değildir; ancak subtree, Git çatışmalarını sihirli şekilde önlemez.
-
Disiplin gerektirir: Farklı
--prefixdeğerleri kullanmak veya tutarsız--squashbayrakları sorun yaratır. Ekibin aynı iş akışını belgelemesi ve takip etmesi gerekir.
Bu sınırlamalar ölümcül değildir, ancak en baştan bilmeye değerdir. Taviz şu: daha basit iş akışı karşılığında daha büyük depo boyutu ve potansiyel geçmiş karmaşıklığı.
git subtree Kullanırken Yaygın Hatalar
git subtree kullanmaya başladığınızda şu yaygın sorunlara dikkat edin. Haberdar olduktan sonra kaçınması kolaydır.
--squash’ın tutarsız kullanımı
Subtree’yi eklerken --squash kullandınız ama git subtree pull sırasında unuttunuz. Artık geçmişiniz, subtree’den hem sıkıştırılmış hem sıkıştırılmamış commit’ler içeriyor ve bu kafa karıştırıyor.
Çözüm, proje README’nizde bunu belgelemektir. Şu gibi notlar yazın: "shared-utils subtree ile her zaman --squash kullanın."
Yanlış veya değişen önek
Subtree’yi --prefix=libs/shared-utils ile eklediniz ama daha sonra --prefix=libs/utils ile çekmeye çalıştınız. Bu çalışmaz çünkü Git subtree’yi bulamaz.
Çözüm, her seferinde tam olarak aynı öneki kullanmaktır. Bunu bir betikte belgelemeyi düşünün, şöyle:
# scripts/update-subtree.sh
#!/bin/bash
git subtree pull --prefix=libs/shared-utils \
https://github.com/yourteam/shared-utils.git \
main \
--squash
Subtree’nin submodule gibi davranmasını beklemek
libs/shared-utils içine cd yapıp git checkout ile sürüm değiştirebileceğinizi sandınız. Bu çalışmaz; çünkü subtree içeriği, deponuzda sadece dosyalardır; ayrı bir Git deposu değildir.
Çözüm, subtree’nin belirli commit’leri birleştirdiğini anlamaktır. Bir subtree’yi “düşürmek” için eski bir commit’i bulup birleştirmeniz veya subtree güncelleme commit’lerini geri almanız gerekir.
İş akışını belgelememek
Ekip üyeleri --squash kullanılıp kullanılmayacağını, hangi uzak URL’nin kullanılacağını veya değişikliklerin ne zaman geri gönderileceğini bilmiyor. Herkes farklı yaparsa, tutarsız bir geçmiş oluşur.
Çözüm, README’nize bir bölüm eklemektir:
## shared-utils’ı güncelleme
## En son değişiklikleri çekmek için:
git subtree pull --prefix=libs/shared-utils https://github.com/yourteam/shared-utils.git main --squash
## Değişiklikleri geri göndermek için:
git subtree push --prefix=libs/shared-utils https://github.com/yourteam/shared-utils.git main
Son olarak, planlamadan subtree dosyalarını değiştirmek
Kendi projenize özel olarak libs/shared-utils içindeki dosyaları düzenliyorsunuz, ancak hiç geri göndermiyorsunuz. Altı ay sonra güncellemeleri çekiyorsunuz ve değişiklikleriniz üst akıştaki (upstream) değişikliklerle çakıştığı için çatışmalarla karşılaşıyorsunuz.
Çözüm şudur: Subtree dosyalarını değiştirirseniz, değişiklikleri herkes için yararlıysa shared-utils’a geri gönderin ya da güncellemeler sırasında çatışmaları yönetmeniz gerektiğini kabul edin.
Sonuç
Git subtree, depo boyutu ve geçmiş karmaşıklığını iş akışı sadeliğiyle takas eder. Git’in iç detaylarından çok analize odaklanan veri bilimi ekipleri için subtree sürtünmeyi azaltır. Yeni ekip üyeleri depoyu klonlar ve hemen çalışmaya başlar.
Git iş akışları hakkında daha fazla bilgi edinmek için Git’e Giriş kursumuzu inceleyin.
Yapay zekâ, makine öğrenimi ve veri bilimi alanlarında uzmanlaşmış teknik yazar; karmaşık fikirleri anlaşılır ve erişilebilir kılar.

