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 deposuna dönüştürülmesi
- Deponun doğru dönüştüğünün kontrolü ve gerekli ek düzenlemelerin yapılması
- Deponun 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 deponun çalışanlarda clone’lanması, test ve öğrenme amaçlı denemeler yapılmasına izin verilmesi
- Varsa sürüm betiğinin yeni depoyu kullanır hale getirilmesi
- CI/CD araçlarının yeni depoyu kullanır hale getirilmesi
- 6, 7 ve 8. adımlar tamamlandığında arada güncelleme olduysa deponun 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 depoları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 depo 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 deposunu oluştururken subprojects altındaki bar kendi trunk/branches/tags dizinleriyle beraber bar diye ayrı bir depo olacak.
deps altındaki baz ise trunk/branches dizinlerine sahip olmamasına rağmen ayrı bir depo 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 deposunu 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.)
subgit

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 deposuna almak istiyoruz. (Depo büyük olduğu için depoyu 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 deposunu yarattı.
Sonradan bu adreste belirttiği gibi önce depoyu mirror olarak clone’layı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 depoları (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://svndepoadresi/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://svndepoadresi/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 depo içinde zorunlu olarak en azından bir trunk belirtmeni bekliyor. Bu yüzden sanki depo adresi deps’miş ve baz dizini trunk’mış gibi göstererek bunu aştım.
trunk vb. dizinleri olmayan bir başka depo 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

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.svn" "authors read <qux.map" "sourcetype svn" "prefer git" "script qux.lift" "legacy write >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 depo 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.svn" "authors read <moo.map" "sourcetype svn" "prefer git" "script moo.lift" "legacy write >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. Deponun 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 clone’lanan 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 depoyu 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.
Deponun son halinin boyutunu azaltmak için şu komutu kullanmak oldukça işe yarıyor:
$ cd foo-clone $ git gc --aggressive
5. Deponun Git’de oluşturulan karşılığına push edilmesi ve proje çalışanları için erişimlerin açılması
Biz Git deposu olarak Bitbucket kullandık.
Bu adımı yapabilmek için, ilgili Bitbucket projesi içinde önce depoyu oluşturmuş olmak gerekiyor. Örneğin foo için depo adresi:
ssh://git@gitdeposu/foo.git
Sonrasında subgit ile oluşturduğum ve mirror’layıp SVN revizyonları ekledikten sonra git gc ile küçülttüğüm clone üzerinde şu işlemleri yapıp Bitbucket’a depoyu atmış oldum:
$ cd foo-clone $ git remote rm origin $ git remote add origin ssh://git@gitdeposu/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 depodan çalışmaya hazır durumdayız. Buna engel olan tek şey SVN deposuna 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 clone’ladığı 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 deponun çalışanlarda clone’lanması, test ve öğrenme amaçlı denemeler yapılmasına izin verilmesi
Bitbucket üzerinde artık dönüştürülmüş bir git depomuz 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 depo kullanılabilir. Ekip, bu depodan kendi yereline clone’layı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 depo 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 depoyu kullanır hale getirilmesi
Bu aşamada amaç, SVN kullanımına bağlı çalışan yerlerin de artık yeni git deposunu 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 deponun kullanılabileceğine karar verdikten sonra (ve 9. adım da gerekiyorsa yapıldıktan sonra) SVN deposunun kullanımı bırakılır ve ekipçe Bitbucket kullanmaya başlanır. Kod tabancısı SVN deposunun 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 depoyu 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ı kullanmak için mantıklı ama migration 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 deposunun 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 depolar 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 depoyla ilerlemeye karar verdim ancak reposurgeon’ı diğer depolarla 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 depolarda subgit, SVN revizyon numarası ekleme işi ve git gc işi hariç hiç ek iş bırakmadı.
moo’yu tek depo 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
