Kod tabanlarının SVN’den Git’e Dönüştürülmesi (Migration)

Bu yazıda Kartaca’da SVN’den Git’e geçiş sürecimizde kod tabanlarının dönüştürülmesinde izlediğimiz yöntemi, kullandığımız araçları ve edindiğimiz tecrübeleri paylaşacağım.
Temel Yöntem
- ‘authors’ dosyasının oluşturulması
- Standart dizin yapısından daha karmaşık kod tabanlarının hangi yapıda geçirileceğinin belirlenmesi
- Belirlenen kod tabanlarının bir araçla git reposuna dönüştürülmesi
- Reponun doğru dönüştüğünün kontrolü ve gerekli ek düzenlemelerin yapılması
- Reponun Git’de oluşturulan karşılığına push edilmesi, proje çalışanları için erişimlerin açılması
- Proje ekibinin isteğine göre yeni oluşturulan reponun çalışanlarda klonlanması, test ve öğrenme amaçlı denemeler yapılmasına izin verilmesi
- Varsa sürüm betiğinin yeni repoyu kullanır hale getirilmesi
- CI/CD araçlarının yeni repoyu kullanır hale getirilmesi
- 6, 7 ve 8. adımlar tamamlandığında arada güncelleme olduysa reponun silinip 3–5 arasındaki adımların tekrarlanması
- SVN kod tabanının kullanıma kapatılması
1. ‘authors’ dosyasının oluşturulması
SVN repolarında loglarda yazar bilgisi olarak kullanıcı adları görünür, git’e dönüştürme için kullanıcı adlarına karşılık gelen e-posta ve isimleri içeren bir map dosyası hazırlamamız gerekli. Bu dosya örneğin şöyle satırlar içermeli:
serdar = Serdar Yüksel <serdar.yuksel@geemail.com>
Bu adreste bu map dosyasının nasıl hazırlanabileceğine dair bir yol gösterilmiş.
2. Dizinlerin belirlenmesi
Temel mantık, trunk/branches/tags alt dizinlerine sahip her dizini ayrı bir repo olacak şekilde belirlemek. Normalde bu şekilde mevcuttaki kullanımdan pek bir farkı olmuyor, kullanılan yerlerde sadece adres değişmiş oluyor.
Örnek:Kod tabanı => foo
foo kod tabanında mevcut trunk/branches/tags dizinleri foo reposunu oluştururken subprojects altındaki bar kendi trunk/branches/tags dizinleriyle beraber bar diye ayrı bir repo olacak.
deps altındaki baz ise trunk/branches dizinlerine sahip olmamasına rağmen ayrı bir repo olarak açılacak.
3. Dönüşümle ilgili önemli konular
- Boş dizinler: Git değişiklikleri yöneten bir araç, bir dosya sistemi değil. Bu yüzden boş dizinlerin git’de bir manası yok, dolayısıyla dönüşme sırasında da çoğu araç bunları getirmiyor. git-svn kullanırken özellikle belirtilirse geliyor ama çok daha yavaş olmasına neden oluyormuş. (Konuyla ilgili bu yazıyı okuyabilirsiniz.) Boş dizinleri getirmek zorundaysanız, yazıda belirtildiği şekilde sonradan oluşturmak da gayet basit.
- Branch’ler ve tag’ler: Çoğu araç branch ve tag’leri yeterince düzgün oluşturamıyor, sonradan bunlarla ilgili düzenleme yapmak gerekebiliyor. Benim gördüğüm kadarıyla, subgit bu işi başarabiliyor.
- Ignore dosyaları: Çoğu araç bunlara da bakmıyor. Fakat reposurgeon ve subgit bunları da dönüştürüyor. Ancak dikkat etmek gerek, SVN reposunu git-svn ile kullanıp projede .gitignore oluşturduysanız subgit dönüştürme sırasında bunu ezecektir, bunu da hesaba katmak gerekli.
- SVN revizyon numaraları: Bilgi amaçlı olarak, SVN’deki revizyon numaralarının commit loglarında gözükmesi istenebilir (ben isterim mesela). Bunun için reposurgeon’da write komutuna legacy parametresini vermek gerekiyor, subgit’de ise revizyon numaralarını ek bir adım olarak şu dokümanda bahsettiği şekilde ekleyebiliyoruz.
- Loglarda gözüken timezone bilgisi: Dönüşümden sonra commit loglarında maalesef (SVN’de olmasına rağmen) timezone bilgisi yok oluyor. İstenilirse reposurgeon ile bütün commit loglarına offset eklemek mümkün ancak ben yapmadım. git log — date=local deyince kendi yerel timezone bilgisine göre tarihlere bakabiliyoruz.
- Aşağılara doğru “Süreçle İlgili Genel Notlar” kısmına benim dönüşümle ilgili ilerleyişimi özet olarak koydum oradan kullandığım araçlarla ilgili ve daha fazlası konusunda fikir edinebilirsiniz.
** Kullandığım iki araç için kullandığım komutları şimdi detaylandıracağım. (git-svn başarısız olduğu için onu eklemiyorum.)
foo isimli kod tabanında trunk/branches/tags dizinleri ve onun dışında subprojects altında bar ve deps altında baz mevcut.
Öncelikle trunk/branches/tags dizinlerini foo olarak ayrı bir git reposuna almak istiyoruz. (Repo büyük olduğu için repoyu dosya sistemine koydum ancak aslında bu pek gerekli değil)
/home/serdar/local/subgit-3.2.6/bin/subgit import \
--svn-url file:///home/serdar/svn-to-git/svn/foo/ \
--authors-file ../kartaca_authors.txt \
--trunk trunk \
--branches branches \
--tags tags \
foo.git
Bu komut foo.git dizininde dönüştürülmüş git reposunu yarattı.
Sonradan bu adreste belirttiği gibi önce repoyu mirror olarak klonlayıp üzerinde SVN revizyon numaralarını ekleyebilmek için düzenleme yaptım:
$ git clone --mirror foo.git foo-clone $ cd foo-clone $ git filter-branch --msg-filter ' REV=$(git log --format="%N" $GIT_COMMIT -1) cat echo "\n" echo -n "$REV" ' -- --branches --tags
foo içindeki diğer repoları (subprojects/bar ve deps/baz) da şu şekilde dönüştürdüm:
$ /home/serdar/local/subgit-3.2.6/bin/subgit import --svn-url https://svnrepoadresi/foo/subprojects/bar/ --authors-file ../kartaca_authors.txt --trunk trunk --branches branches --tags tags bar.git $ /home/serdar/local/subgit-3.2.6/bin/subgit import --svn-url https://svnrepoadresi/foo/deps/ --authors-file ../kartaca_authors.txt --trunk baz baz.git
baz‘da trunk vb. dizinler olmadığı için tek branch şeklinde almam gerekiyordu ancak subgit repo içinde zorunlu olarak en azından bir trunk belirtmeni bekliyor. Bu yüzden sanki repo adresi deps‘miş ve baz dizini trunk’mış gibi göstererek bunu aştım.
trunk vb. dizinleri olmayan bir başka repo olan moo’yu da sadece master şeklinde almam gerekiyordu ancak bir üst dizini olmadığı için bir trunk belirtemedim, bu yüzden onu ancak reposurgeon ile çevirebildim.
Reposurgeon‘ı inceleyin.
Bu aracı kullanmak için ($PATH’e reposurgeon dizini eklenmiş varsayımı ile) önce bir dizin oluşturup içinde:
$ mkdir qux-reposurgeon $ cd qux-reposurgeon $ repotool initialize qux
komutunu çalıştırınca reposurgeon dönüşüm için gerekli dosyaları oluşturuyor. Makefile dosyasını editleyip REMOTE_URL adresini düzeltiyoruz, authors map dosyasını kopyalıyoruz ve make çalıştırıyoruz:
$ cp /home/serdar/svn-to-git/kartaca_authors.txt qux.map $ make
Araç qux-git isminde dönüştürülmüş dizini oluşturuyor. Sonrasında tag ve branch’lerde temizlik yapmak gerekebiliyor.
SVN revizyon numaraları eklensin istiyorsak Makefile’da (reposurgeon’a komutlar verildiği yerde en sondaki) write komutuna legacy parametresi vermek gerekiyor, örnek:
# Build the second-stage fast-import stream from the first-stage stream dump
qux.fi: qux.svn qux.opts qux.lift qux.map $(EXTRAS)
$(REPOSURGEON) $(VERBOSITY) "script qux.opts" "read $(READ_OPTIONS) qux.fo" "write --legacy >qux.fi"
Bu eklenen parametreyle commit loglarının altına “Legacy-ID: XXX” şeklinde revizyon numaraları geliyor.
moo gibi sadece master şekilde alınması gereken bir repo için read komutundan önce branchify nobranch komutunu kullanmam gerekti (yoksa zorunlu olarak trunk dizini arıyor bulamayınca da hiçbir şey almıyor):
# Build the second-stage fast-import stream from the first-stage stream dump
moo.fi: moo.svn moo.opts moo.lift moo.map $(EXTRAS)
$(REPOSURGEON) $(VERBOSITY) "script moo.opts" "branchify nobranch" "read $(READ_OPTIONS) moo.fo" "write >moo.fi"
reposurgeon’ın dökümantasyonu çok uzun, ama gerektiğinde başvurulabilir. (Umarım gerekmez ama yine de buraya ekliyorum)
4. Reponun doğru dönüştüğünün kontrolü ve gerekli ek düzenlemelerin yapılması
Doğrulama için hem otomatik, hem de düzgün bir yöntem bulamadım. Commit loglarını, tag ve branch’leri, dosya dizin yapısı ve dosyaların son hallerini (diff -r ile svn’den checkout edilen dizin ile git’den klonlanan dizine bakılabilir) gözle kontrol ederek gittim.
Her halukarda diff -r ile mevcut svn checkout edilmiş dizin ile dönüşmüş git working tree arasındaki farklara bakmak gerekli, boş dizinleri almadığını düşünürsek neleri almadığını ve gerekip gerekmediğine burada karar vermek gerekli. Gerekiyorsa boş dizinleri eklemek için şu adresteki yöntem uygulanabilir:
subgit kullanılırsa sonradan geriye sadece SVN revizyon numaralarının eklenmesi kalıyor (isteniyorsa), onun dışında repoyu push etmeden önce yapılacak başka bir iş kalmıyor.
reposurgeon kullanılırsa boş commit’ler için oluşturduğu tag’leri temizlemek ve silinmemiş branch’lerin tekrar silinmesini sağlamak gerekiyor.
reponun son halinin boyutunu azaltmak için şu komutu kullanmak oldukça işe yarıyor:
$ cd foo-clone $ git gc --aggressive
5. Reponun Git’de oluşturulan karşılığına push edilmesi ve proje çalışanları için erişimlerin açılması
Biz Git reposu olarak Bitbucket kullandık.
Bu adımı yapabilmek için, ilgili Bitbucket projesi içinde önce repoyu oluşturmuş olmak gerekiyor. Örneğin foo için repo adresi:
ssh://git@gitrepository/foo.git
Sonrasında subgit ile oluşturduğum ve mirror’layıp SVN revizyonları ekledikten sonra git gc ile küçülttüğüm klon üzerinde şu işlemleri yapıp Bitbucket’a repoyu atmış oldum:
$ cd foo-clone $ git remote rm origin $ git remote add origin ssh://git@gitreposu/foo.git/scm/cdt/foo.git $ git push --all origin $ git push --tags origin
Kartaca’da proje erişimleri LDAP’dan geliyor ve bu erişimleri projeler açılırken hazırladık. Yani, ekibin mevcut kod tabanlarına ve projelere erişimleri, aynı şekilde devam etti, yani erişim açmakla ilgili ek olarak bir şey yapılmadı.
Bu noktadan sonra artık neredeyse SVN kullanmayı bırakıp Bitbucket üzerindeki repodan çalışmaya hazır durumdayız. Buna engel olan tek şey SVN reposuna bağımlı çalışan şeyler olabilir, örneğin Bamboo yapılandırması ya da sürüm betiği (7 ve 8. maddeler), ya da kod tabancısı, ekibin önce biraz tecrübe kazanmasını isteyebilir ve 6. maddeyi uygulamaya karar verir.
Eğer ekipte henüz commit etmediği değişiklikleri olan birileri varsa çalıştığı dizindeki bütün dosyaları yeni klonladığı dizinin içine kopyalaması (üzerine yazması) ve .svn dizinlerini silmesi şeklinde geçişi yapabilir.
6. Proje ekibinin isteğine göre yeni oluşturulan reponun çalışanlarda klonlanması, test ve öğrenme amaçlı denemeler yapılmasına izin verilmesi
Bitbucket üzerinde artık dönüştürülmüş bir git repomuz olduktan ve proje ekibi için gerekli erişimleri açtırdıktan sonra istenirse SVN ile çalışmaya devam edip test amaçlı bu yeni repo kullanılabilir. Ekip, bu repodan kendi yereline klonlayıp üzerinde öğrenme amaçlı denemeler yapabilir.
Bahsettiğim dönüştürme yöntemleri tek seferlik olduğu için bu süreç boyunca SVN’de yapılacak güncellemeler nedeniyle testlerin bittiğine karar verildikten sonra dönüştürme işleminin tekrar yapılması gerekecektir. Yani 3, 4 ve 5. adımların tekrar etmesi gerekir (Bitbucket’da proje içindeki repo silinip tekrar oluşturulabilir). Dönüştürme işlemi normal şartlarda uzun süren bir işlem olmadığı için tekrar baştan dönüştürmenin de bir sakıncası yok.
7. ve 8. Varsa sürüm betiği ve CI/CD araçlarının yeni repoyu kullanır hale getirilmesi
Bu aşamada amaç, SVN kullanımına bağlı çalışan yerlerin de artık yeni git reposunu kullanacak hale getirilmesini sağlamak.
İlk akla gelen, sürüm betiği ve bizim CI/CD aracı olarak kullandığımız Bamboo’nun yapılandırması oluyor ancak projeye göre farklı konular da olabilir. Biz, farklı olabilecek durumları kod tabancısının bildiğini varsayarak ilerledik.
Bamboo yapılandırması Ürün ve Sistem Yöneticisi’nin kontrolünde olduğu için burada onunla da koordine olmak gerekecektir. Bu aşamalar tamamlanmadan geçiş yapılamaz.
10. SVN kod tabanının kullanıma kapatılması
Kod tabancısı ve Ürün ve Sistem Yöneticileri beraberce yeni reponun kullanılabileceğine karar verdikten sonra (ve 9. adım da gerekiyorsa yapıldıktan sonra) SVN reposunun kullanımı bırakılır ve ekipçe Bitbucket kullanmaya başlanır. Kod tabancısı SVN reposunun arşivlenebileceğine dair Ürün Sistem Yöneticileri’ne durumu bildirir.
Süreçle İlgili Genel Notlar
Bitbucket kullanacağımız için ilk başta rehber olarak Bitbucket Migration Guide dokümanını takip ettim. Bu dokümanda gösterilen yöntemde git-svn kullanılıyor.
foo kod tabanını git-svn ile geçirmeye kalktığımda yaklaşık 4 saat sürdü ve hatayla sonuçlandı. Ancak baktığımda repoyu oluşturmuş gibi görünüyordu. Ben de Atlassian’a ait araçla temizleme adımına geçtim (tag ve branch’lerin oluşturulması vs), ancak işe yaramadı.
Git kitabında yer alan komutlardan devam edecektim ki; biraz araştırınca Eric S. Raymond’ın başını çektiği bir görüşe rastladım. Bu görüşe göre:
“git-svn köprü amaçlı kullanım için mantıklı ama taşıma için çok mantıklı değil.”
Bu yüzden başta isteksiz de olsam bu iş için yazılmış farklı araçlara yöneldim.
İlk bakışta Eric Raymond’ın reposurgeon aracı baya karmaşık geldi. Bu yüzden önce hem kolay olan, hem de daha önce git’den svn’e çevirmek için kullanıp memnun kaldığım subgit’i denedim. 3200 revizyona ve çok sayıda branch’e sahip foo reposunun dönüşmesi 7 saatin (!) sonunda başarılı bir şekilde (süreyi saymazsak) tamamlandı. Daha sonra çok sayıda revizyona sahip başka başka repolar nedense bu kadar sürmedi.
Ben de bu arada Eric Raymond’ın migration için yazdığı rehberi ve piyasadaki diğer araçlarla ilgili yazdıklarını okudum. Dediğine göre subgit, yazdığı testlerin yarısından fazlasından geçememiş (ama zamanla değişmiş olabilir.)
Kendisine duyduğum saygıdan dolayı, yazdıklarını dikkate alarak reposurgeon ile ilerlemeye karar verdim. Diğerlerine göre gerçekten inanılmaz hızlı çalıştı ancak maalesef foo kod tabanını geçirmeyi tamamlayamadan hata verip çıktı ve ben bir türlü neden hata verdiğini çözemedim.
subgit ile dönüşen foo kodtabanında pek bir sorun göremedim bu yüzden o dönüşen repoyla ilerlemeye karar verdim ancak reposurgeon’ı diğer repolarla da denedim. Bir tanesi yaklaşık 8000 revizyon içeren bir kaç tane kod tabanını dakikalar içerisinde çevirebildim. Kod tabanlarından birindeki yoğun branch ve ve tag kullanımı nedeniyle dönüşüm sonrası bir miktar daha iş olduğunu gördüm (Branch’lerin silinmesi dizin silmekten ibaret olduğundan, o tür commit’ler için tag oluşturuyor, oluşturduğu tag’lerden dizin silen ya da branch silen commit’ler görülebiliyor).
foo kod tabanı dışında birçok kod tabanını da subgit ile sorunsuzca dakikalar içinde dönüştürdüm (foo’nun kendisini neden o kadar uzun sürede dönüştürdüğünü çözemedim). Bütün bu repolarda subgit, SVN revizyon numarası ekleme işi ve git gc işi hariç hiç ek iş bırakmadı.
moo’yu tek repo olarak branch olmadan dönüştürmeye çalıştım, bunu subgit ile yapamadım, reposurgeon ile başta branchify komutu kullanarak yapabildim.
Sonuç
subgit bana en sorunsuz araç gibi geldi (Bu arada import ve gateway işleri için Bitbucket ve Gitlab de subgit kullanıyorlar.) Mümkünse subgit ile, olmazsa reposurgeon veya diğerleriyle ilerlemenizi öneririm.
Yayınlanma Tarihi: 23.10.2018