CVS’den GIT’e taşınma hikayesi

Mustafa Sadedil
SabancıDx
Published in
10 min readMay 27, 2019
Photo by Shripal Daphtary on Unsplash

Bu yazıda, SabancıDx’deki bazı projelerde uzun süre kullanılan ve çok eski bir versiyon kontrol sistemi olan CVS’ten GIT’e nasıl taşındığımızın hikayesini anlatmaya çalışacağım.

Burada “taşınma” derken, yeni bir GIT reposu açıp mevcut kodların bu yeni repoya tek seferde eklenmesinden ziyade, kodun tüm tarihçesi (revision history) ile birlikte taşınmasından bahsedeceğim.

Yani bu yazı, bir migration (göç) hikayesi.

CVS ve GIT

Öncelikle iki versiyon sistemine de kısaca değinerek başlayalım.

CVS (Concurrent Versions System), ilk sürümü 90'ların başında çıkmış bir versiyon kontrol sistemidir. Server / Client mimarisi üzerine geliştirilmiştir. Ancak bu günlerde, kaynak bulmakta bile zorlanabileceğiniz kadar eski ve atıl kalmıştır.

GIT ise, temelleri 2005 yılında Linus Torvalds tarafından atılmış bir dağıtık versiyon kontrol sistemidir. İlk çıkış amacı Linux kernel kodlarını versiyonlamak olan GIT’in yayılması, Torvalds’ın açık kaynak dünyasındaki bilinirliğinin de etkisiyle çok kolay olmuştur. GIT günümüzde versiyon kontrol sistemleri arasında bir de facto standardına dönüşmüştür.

Bu arada iki sistem de açık kaynak kodlu olarak dağıtılmaktadır.

Peki neden taşındık?

Bu konuda harekete geçmek için birçok nedenimiz vardı. Bu nedenlerden birkaçını özetlemek gerekirse:

  • CVS’in son kararlı sürümünün 2008 yılında yayınlanmış olması, dolayısıyla kaynak, online hosting servisi ya da herhangi bir tool bulmakta bile sıkıntı yaşamak
  • Bu kadar eski bir teknolojiyi bilen kişi bulmanın zorluğu, motivasyon eksikliği
  • GIT’in sunduğu “dağıtık” yapının avantajları (branch oluşturma maliyetinin sıfıra yakın olması, offline çalışabilme, vs.)
  • GIT’in community ve tool desteğinin yılarca artarak iyi bir noktaya gelmesi
  • ve daha birçoğu…

Hangi kısımlarda zorlandık?

Taşınma yöntemleri arasında şu iki seçeneği masaya yatırdık

  1. GIT’te boş bir repository açıp, tüm kodları buraya göndermek (push) ve beş dakika sonra hayatımıza kaldığı yerden devam etmek.
  2. Yıllardır insanların parça parça CVS’ye eklediği kodları, aynı hikaye setini koruyarak GIT’e taşınmak.

Her ne kadar ilk seçeneği seçip kestirmeden gitmek cazip gözükse de kod tarihçesini korumayı önemsedik ve 2. seçenek ile yola devam ettik.

Aslında ilk seçeneği seçip, eski repository’yi bir yerlerde read-only olarak saklamak da kulağa hoş geliyor. Bu şekilde, ihtiyacı olan yazılımcı istediği zaman kod tarihçesine buradan bakabilir diye düşünülebilir.

Ancak önceki tecrübelerimizden yola çıkarak, bu sistemin pratikte işlemediğini biliyoruz. Birçok yazılımcının üşengeçlik ile yaratıcılık arasında ince bir çizgide gidip geldiğini hesaba katmakta da yarar var ¯\_(ツ)_/¯

Ayrıca bu yoldan gitmek bizi CVS’ye bağımlı kılacaktı ve yazılımcıların bilgisayarlarına halen CVS client vb. araçlar kurması gerekecekti. Bir yandan da CVS sunucuyu tamamen kapatma şansımız elimizden gidecekti.

İşte zorluklar tam da bu noktada başladı…

Ne kadar zor olabilir ki?

Her zaman yaptığım gibi “kesin bunun için yazılmış bir tool vardır” diye düşünüp aramaya başladım. Haksız da sayılmazdım. Basit bir arama ile birkaç tane command-line aracını bulabildim:

  • git cvsimport (link)
  • cvs2git (link)
  • cvs-fast-export (bizim kullandığımız araç)(link)

Ancak bu araçların hiçbirisi için, Windows üzerinde nasıl çalışacağı ile ilgili bir bilgiye erişemedim. Verilen tüm örnekler, yazılan tüm dokümanlar UNIX sistemler özelindeydi.

Elimde hali hazırda şirketteki kodlara erişebileceğim bir Mac cihazım olmadığından macOS üzerinde çalışıp çalışmadığını deneme şansım olmadı.

Aklıma ilk gelen çözüm, VirtualBox ya da benzeri bir sanallaştırma aracı üzerine bir Linux dağıtımı yüklemekti ancak tam bu yoldan gidecekken, WSL (Windows Subsystem for Linux) üzerinden daha basit bir şekilde bu denemeyi yapabileceğimi düşündüm, ve sağ olsun WSL beni yarı yolda bırakmadı.

WSL (Windows Subsystem for Linux)

WSL’i, Windows 10 üzerinde Linux komut satırı çalıştırmaya yarayan bir altyapı olarak düşünebiliriz. WSL, varsayılan olarak aktif olmadığından bir kereliğine bu özelliği Windows 10'a eklemek gerekiyor.

Windows 10 üzerinde WSL’i aktif hale getirme

WSL’i etkinleştirdikten sonra, Microsoft Store üzerinden Arch Linux, Alpine, Ubuntu, Fedora, Debian gibi birçok Linux dağıtımını indirebilirsiniz. Ben kendi örneğimde Ubuntu 18.04 LTS sürümünü kullandım.

Microsoft Store’da WSL üzerinde kullanabileceğiniz Linux dağıtımlarını ararken “WSL” anahtar kelimesini kullanmak yeterli olacaktır.

WSL 2

Bu arada konusu açılmışken bahsetmekte yarar var. Microsoft, Build 2019 etkinliğinde WSL 2'yi tanıttı. WSL 2 yenilenen mimarisiyle tam system call uyumluluğunu destekliyor. Buna bağlı olarak da performansta dramatik bir artış ve daha fazla Linux uygulamasının (Docker gibi) çalışması desteklenmiş olacak.

WSL 2'nin Haziran 2019 sonu gibi yayınlanması bekleniyor.

Ubuntu üzerindeki hazırlıklar

Konuyu daha fazla dağıtmadan asıl meselemize dönelim. Sıradaki işlem WSL üzerine kurduğumuz Ubuntu dağıtımı üzerinde cvs-fast-export isimli aracı yüklemek.

Burada yaptığım bir hatadan bahsetmek istiyorum. İlk önce Ubuntu üzerinde apt-get ile cvs-fast-export’u yüklemeye çalıştım ve “paket bulunamadı” benzeri bir hata aldım. Bu yüzden, bu uygulama için hazır bir paket olmadığını düşünüp kendim derleme işine giriştim. Yaklaşık birkaç GB boyutundaki ekstra bağımlılıkların indirilmesi ve birkaç saat içerisinde yüklenmesi sonrasında kendi cvs-fast-export’umu derlemeyi başardım. Ancak bu işleme hiç gerek olmadığını sonradan fark ettim (╯°□°)╯︵ ┻━┻

Aşağıda bu konudan bahsetmeyip, basit yoldan kurulumu anlatacağım.

WSL üzerinde yeni kurulmuş olan Ubuntu’muzu açıp aşağıdaki iki satırı sırasıyla çalıştırdıktan sonra, kullanacağımız uygulamalar (cvs-fast-export ve cvsconvert) hazır hale gelmiş oluyor.

$> sudo apt-get update
$> sudo apt-get install cvs-fast-export

Kurulum işlemini teyit etmek için which cvs-fast-export komutu kullanılabilir. Ben çalıştırdığımda /usr/local/bin/cvs-fast-export şeklinde yanıt alıyorum. Kurulumdan sonra cvs-fast-export’u bir daha PATH’e eklemek gibi bir ihtiyaç oluşmuyor.

Sıra geldi CVS -> GIT dönüşümüne

Artık dönüşüm işlemi için kullanacağımız aracımız da elimizde olduğuna göre işe başlayabiliriz.

Dönüşüm: İlk adım — Kodları hazırlama

Bir CVS repository’sini cvs checkout komutu ile bilgisayarımıza indirdiğimizde, kodun sadece son hali bilgisayara indirmiş oluyoruz. Yani projemiz bir Java projesi ise .java uzantılı dosyaları klasör içerisinde görebiliriz. Ancak biz tüm history ile birlikte dönüşüm gerçekleştirmek istediğimizden, repository’nin bu hali yeterli olmayacaktır. Bunun için CVS Server’ın bulunduğu bilgisayara uzaktan masaüstü vs. yollarla bağlanıp direkt olarak CVS klasörünü elde etmemiz gerekiyor.

Örnek olarak, benim kodları indirmek için kullandığım CVS Root adresi şu şekildeydi:

:pserver:mustafa.sadedil@MyCompanyCvsServer:C:/MyCompanyCvsRoot

Bu durumda, öncelikle Windows Explorer üzerinden \\MyCompanyCvsServer\C$\MyCompanyCvsRoot adresine erişip ihtiyacım olan CVS repository’sini bilgisayarıma kopyaladım. (Bu noktadan sonra, proje klasörümün adı MyJavaProject gibi devam edelim.)

CVS server’dan kopyaladığım klasörün içerisindeki kodlar tüm tarihçeyi içeriyor. Bunu teyit etmek için klasöre girdiğinizde dosya.java gibi dosyalar yerine dosya.java,v şeklinde, sonu ,v ile biten dosyaları görüyor olmanız gerekiyor.

Buradaki klasörü, kendi bilgisayarımızdaki C:\conversion\MyJavaProject altına kopyalayıp yolumuza devam edelim.

Dönüşüm: İkinci adım — ,v dosyalarına elle müdahale (opsiyonel)

Önemli not: Dönüşüm sırasında kullandığım araç, ,v dosyaları içerisinde username ve kopt keywordlerinin geçtiği yerlerde hata verdiği için, araya böyle bir adım eklemek zorunda kaldım. Sizin dönüşümünüzde aynı adıma gerek olmayabilir.

Örnek olarak MyJavaClass.java,v dosyasının ilk birkaç satırını aşağıya ekledim. Yukarıda bahsettiğim gibi, bu blokta bold olarak işaretlediğim bilgiler, dönüşüm esnasında ignoring 4 cvs-fast-export fatal: parse error syntax at; şeklinde kriptik bir hata veriyordu.

head 1.20;
access;
symbols;
locks; strict;
comment @// @;
1.20
date 2018.08.09.08.26.58; author john.doe; state Exp;
branches;
next 1.19;
deltatype text;
kopt kv;
permissions 666;
commitid 52e05b6cfad20cf8;
username john.doe;
filename MyJavaClass.java;
1.19
date 2017.02.20.16.40.31; author jane.doe; state Exp;
branches;
next 1.18;
deltatype text;
kopt kv;
permissions 666;
commitid b88581b1bfc3d88;
username jane.doe;
filename MyJavaClass.java;

Sebebini bulamasam da, bu satırları ortadan kaldırdığımda, dönüşümün çalıştığını ve hiçbir bilgi kaybetmediğimi gördüm. İlk bakışta, tarihçedeki username bilgisini kaybettiğimi düşünebilirsiniz ancak, aynı bilgi author kısmında da mevcut, dolayısıyla bir şey kaybetmiyoruz.

Bu bold alanların silinmesi sorunu çözüyordu, ancak elimde birkaç bin tane ,v dosyası olduğundan bu işi (bir kereliğine de olsa) otomatize etme ihtiyacı çıktı. Burada çok detayına girmeyeceğim ama yazdığım ufak bir C# kodu ve basit bir iki Regex Replace yöntemi ile tüm bu dosyalardaki ilgili alanları kaldırdım. Değişimi aynı klasörde yapmamak için yeni dosyaları C:\conversion\MyJavaProject-cleaned klasörüne taşıdım. Artık yolumuza bu klasör ile devam edeceğiz.

// Merak edenler için, kullandığım iki Regex (C#) şu şekilde:Regex UsernameRegex = new Regex(@"^username\s*[\w\.]*;$");
Regex KoptRegex = new Regex(@"^kopt\s*.*;$");

Dönüşüm: Üçüncü adım — Kodları dönüştürme

Artık repository’miz de dönüşüme hazır. Burada, cvs-fast-export bize iki yöntem sunuyor:

1. cvsconvert

cvsconvert, az önce apt-get ile yüklediğimiz cvs-fast-export uygulaması içerisinden çıkan bir wrapper scriptten ibaret. Yani tek başına bir anlamı yok. Sadece cvs-fast-export’u kullanırken manuel olarak yapılması gereken birkaç işi bizim yerimize yapıyor.

2. cvs-fast-export

Bu araç zaten yazının başından beri bahsettiğimiz, dönüşüm işini asıl yapacak olan araç. Doğrudan kullanmaya çalıştığımızda birkaç işi elle yapmak zorunda kalacağız.

Dönüşüm hikayesini iki yöntemi kullanarak ayrı ayrı anlatacağım.

Yöntem 1: cvsconvert

Buraya kadar yazılan her şey ortak olmasına rağmen, dönüşüm için Yöntem 1 ya da Yöntem 2 arasından yalnızca birisini tercih etmek yeterli olacaktır.

Ben biraz daha uzun olması rağmen Yöntem 2 ile ilerledim, çünkü Yöntem 1'de eski kullanıcı bilgilerini yenilerine dönüştüren authormap dosyasından yararlanmanın bir yolunu bulamadım.

Dönüşümü gerçekleştirmek için WSL üzerine yüklediğimiz Ubuntu komut satırını açıp, aşağıdaki komut ile conversion klasörüne gidelim.

$> cd /mnt/c/conversion
Windows 10 üzerindeki WSL üzerinde çalışan Ubuntu

Burada artık cvsconvert komutunu çalıştırabiliriz.

$/mnt/c/conversion> cvsconvert -v -p MyJavaProject-cleanedReading file list...done, 867727.247KB in 6797 files (3.014sec)
Analyzing masters with 16 threads...5150 of 6797(75%)
Analyzing masters with 16 threads...done, 30223 revisions (5.399sec)
Make DAG branch heads...6797 of 6797(100%) (0.007sec)
Sorting...done (0.001sec)
Compute branch parent relationships...5 of 5(100%) (0.001sec)
Merge common branches...5 of 5(100%) (0.855sec)
Find tag locations...done (0.000sec)
Compute tail values...done (0.001sec)
Generating snapshots...done (9.330sec)0%)
Saving in fast order: done (1.189sec)100%)
after parsing: 8.417 27148KB
after branch merge: 9.288 49216KB
total: 19.809 329420KB
11575 commits/2717.208M text, 26119 atoms at 584 commits/sec.
Checking out files: 100% (4733/4733), done.
MyJavaProject-cleaned branch master: trees matched as expected
cvsconvert: conversion is in MyJavaProject-cleaned-git

Parametreleri kısaca açıklamak gerekirse:

-v: Detaylı çıktı alabilmek için (verbose)
-p: Dönüşüm esnasındaki ilerlemeyi takip etmek için (progress)

Daha detaylı bilgi için cvsconvert manpage’i inceleyebilirsiniz.

İşlem tamam.

Komut çıktısının son satırında görebileceğimiz gibi, cvsconvert bizim için MyJavaProject-cleaned-git adında bir klasör daha oluşturdu. Bu klasör artık tüm tarihçesi içerisinde bir GIT reposu. İçerisinde dosya.java,v gibi dosyalar yerine dosya.java kod dosyalarımız var.

Yöntem 2: cvs-fast-export

İkinci hatırlatma: Yöntem 1'i uygulamaya karar verdiyseniz, buradaki adımları uygulamaya ihtiyacınız olmayacaktır. Bu kısım tam olarak Yöntem 1'e alternatif olarak yazılmıştır.

Daha önce de bahsettiğim gibi ikinci yöntem biraz daha manuel ilerliyor. Adım adım anlatmaya çalışayım.

1/4: Bu güne kadar commit yapan kişilerin listesini alalım

$/mnt/c/conversion> find MyJavaProject-cleaned -type f | cvs-fast-export -a

Bu komutun çıktısı aşağıdaki gibi olacaktır. Bu çıktı bize distinct commit yapan kullanıcı listesi’ni verecek.

john.doe
jane.doe
...

Bu noktadan sonra bizim yapmamız gereken authormap isimli bir text dosya hazırlayıp, aşağıdaki formata getirmek. Bu dosyayı bir sonraki aşamada kullanacağız. Böylelikle oluşacak yeni GIT repository’mizdeki author isimler bir standarda sahip olacak.

Aşağıda, hazırladığım authormap dosyasının içeriğini bulabilirsiniz.

john.doe = John DOE <john.doe@sabancidx.com>
jane.doe = Jane DOE <jane.doe@sabancidx.com>

2/4: GIT’in anlayacağı formatta bir fast-import dosyası hazırlayalım

Bu dönüşüm işleri ile birlikte, daha önce hiç duymadığım bir GIT komutu öğrendim: git fast-import

Bu komut, herhangi bir GIT repository’sine bulk olarak veri yazmak için kullanılıyor. Yani dosyanın nereden geldiğinin bir önemi olmadan, herhangi bir uygulama ile bu formatta dosya hazırlayabilir ve GIT ile bunu import edebilirsiniz.

Aslında, yazının başından beri anlattığım cvs-fast-export aracının tek görevi bu formatta bir dosya oluşturmak. Daha sonrasındaki kısmı GIT’in kendi komutları ile halledeceğiz.

Neyse, daha da uzatmadan bu fast-import-file dosyamızı oluşturalım.

$/mnt/c/conversion> find MyJavaProject-cleaned -type f | cvs-fast-export -A author-map > fast-import-file

Bu işlem hakkında birkaç bilgi vermek gerekirse;

  • Bu komut sonrasındaki tek çıktımız, fast-import-file isimli dosya oldu.
  • Bu dosyanın adının ya da uzantısının hiçbir önemi yok, fast-import-file yerine dosya.bak da yazabilirdik.
  • Benim dönüştürdüğüm iki farklı proje için oluşan fast-import-file dosyalarının boyutları sırasıyla 2.5 GB ve 6 GB oldu. Büyük olan projede 50K civarı commit vardı.
  • Komutu çalıştırırken verdiğimiz -A parametresi ile, 1. adımda oluşturduğumuz author-map isimli dosyayı kullanmış olduk. Artık author-map dosyasına da ihtiyaç kalmadı.
  • Hatta bu noktadan sonra, cvs-fast-export isimli araçla bile bir işimiz kalmadı. Yolumuza GIT’in kendi komutları ile devam edeceğiz.

3/4: Boş bir GIT repository’si oluşturalım

Bu noktada işimiz kolay. Yeni bir klasör oluşturup, içerisinde git init komutu çalıştıracağız.

$/mnt/c/conversion> mkdir MyJavaProject-cleaned-git
$/mnt/c/conversion> cd MyJavaProject-cleaned-git
$/mnt/c/conversion/MyJavaProject-cleaned-git> git init

Yukarıdaki gibi bir komut yazdığımda > işaretinin solundaki kısım, komutun çalıştığı dizini, sağındaki kalın olarak yazılmış kısım ise komutun kendisini gösteriyor. Buna dikkat etmek gerekiyor, çünkü örneğin git init komutu hangi klasörde çalıştırıldığına bağlı olarak, o klasörü bir GIT repository’si haline getirecektir.

4/4: fast-import-file dosyamızı import edelim

Aşağıdaki komut ile, 2. adımda oluşturulan fast-import-file dosyasını, 3. adımda oluşturduğumuz boş repository’ye import edeceğiz.

$/mnt/c/conversion/MyJavaProject-cleaned-git> git fast-import < ../fast-import-file

Bu komutun çalışması, verdiğimiz dosyanın büyüklüğüne göre birkaç dakika sürecektir. İçerisinde 50000 civarı commit olan dosya için bu süre yaklaşık 5 dakikaydı. Yani uzun sürerse paniğe gerek yok.

Son olarak aşağıdaki iki komut ile dönüşüm işlemini sonlandırıyoruz.

$/mnt/c/conversion/MyJavaProject-cleaned-git> git checkout master
$/mnt/c/conversion/MyJavaProject-cleaned-git> git gc --prune=now

Bunlardan ilki zorunlu, çünkü bunu çalıştırmazsak, MyJavaProject-cleaned-git klasörü içerisinde sadece .git klasörünü görebiliriz. Bu komut ile master branchini aktif hale getirmiş ve kodların son halini ilgili klasöre çıkartmış oluyoruz.

İkinci komut ise, GIT’in standart temizlik (housekeeping) komutlarından. Çalıştırmazsak bir zararı olur mu? Sanmıyorum. Ancak dokümanlarından okuduğum kadarıyla, böyle bir import sonrası bu komutu çalıştırmakta fayda var.

Bu noktadan sonra tek yapmamız gereken, repository’mize yeni bir git remote ekleyip push yapmak. GIT’te yeniyseniz, local bir repository’yi uzaktaki bir sunucuya gönderme konusunda buradan bilgi alabilirsiniz.

Son

Ve nihayet — beklediğimden çok daha uzun ve çetrefilli olan — bir maceranın sonuna geldik. SabancıDx olarak dönüşüm sürecimiz yeni başlıyor diyebilirim. Ufukta gözüken işlerimiz arasında;

  • GIT branching stratejisi seçimi
  • CI/CD pipeline’larının oluşturulması
  • .NET Core dönüşümü
  • Cloud Native uygulama geliştirme

gibi birçok konu var.

Yeri geldikçe hepsi hakkında teknik yazılar paylaşmak da hedeflerimiz arasında.

Şimdilik görüşmek üzere. Sağlıcakla kalın.

Kaynaklar

--

--