Version Kontrol (Git)
Versiyon kontrol sistemleri (VKS’leri), kaynak koddaki (veya diğer dosya ve klasörlerdeki) değişiklikleri izlemek için kullanılan araçlardır. Adından da anlaşılacağı gibi, bu araçlar değişiklik geçmişinin korunmasına yardımcı olur; ayrıca, işbirliğini de kolaylaştırırlar. VKS’ler bir klasördeki ve içeriğindeki değişiklikleri bir dizi snapshots’da (anlık görüntüde) izler, burada her snapshot (anlık görüntü), üst düzey bir dizindeki tüm dosya/klasör durumunu kapsar. Ayrıca VKS’ler oluşturulan tüm snapshot’larda bu snapshot’ı oluşturanın kim olduğunu ve snapshot ile ilişkilenmiş mesajlar ve benzeri meta verileri de beraberinde saklar.
Versiyon kontrolü neden yararlıdır? Kendi kendinize çalışırken bile, bir projenin eski anlık görüntülerine bakmanıza, belirli değişikliklerin neden yapıldığına dair kayıtlar tutmanıza, paralel gelişimtirme dallarında çalışmanıza ve çok daha fazlasına izin verebilir. Başkalarıyla çalışırken, diğer insanların neleri değiştirdiğini görmek ve eş zamanlı geliştirmedeki conflict’leri (çatışmaları) çözmek için paha biçilmez bir araçtır.
Ayrıca modern VKS’ler aşağıdaki soruları kolayca (ve genellikle otomatik olarak) cevaplamanızı sağlar:
- Bu modülü kim yazdı?
- Bu dosyanın bu satırı ne zaman düzenlendi? Kim tarafından? Neden düzenlendi?
- 1000’in üzerinde revizyondan sonra belirli bir birim testi (unit test) neden/ne zaman çalışmayı durdurdu?
Birçok VKS mevcut olsa da Git, versiyon kontrolü için fiili standarttır. Bu XKCD karikatürü Git’in izlenimini anlatır.
Çünkü Git arayüzleri bazen soyut kalıp kafa karışıklığına sebep olabiliyor. Buna başlarken seçtiğiniz arayüz sebep olabilir (komut satırı arayüzü ya da görsel arayüzlü arabirim). Bir avuç komutu ezberleyip onları büyülü sözler gibi düşünmek ve bir şeyler ters gittiğinde yukarıdaki karikatür gibi davranmak mümkündür.
Hiç kuşkusuz ki Git’in çirkin bir arayüzü olsa da altında yatan fikri ve tasarımı çok güzeldir. Çirkin bir arayüzün ezberlenmesi gerekirken, güzel bir tasarım anlaşılabilir. Bu nedenle, Git’in veri modelinden başlayıp daha sonra komut satırı arayüzüyle devam eden tepeden tırnağa bir anlatımını yapacağız. Veri modeli anlaşıldıktan sonra, komutların “temeldeki veri modelini nasıl manipüle ettikleri” daha iyi anlaşılabilir.
Git’in veri modelleri
Versiyon kontrolünde uygulayabileceğiniz birçok geçici yaklaşım vardır. Git, versiyon kontrolünün; versiyon geçmişini yönetebilmek, dallar (branch’lar) ile çalışmayı desteklemek ve iş birliği içinde çalışmayı mümkün kılmak gibi güzel özellikler sağlayan iyi düşünülmüş bir modele sahiptir.
Snapshots (Anlık görüntüler)
Git, dosya ve dizinlerdeki kolleksiyonların geçmişini bazı üst düzey dizinler içinde anlık görüntüler (snapshots) halinde modeller. Git terminolojisinde bir dosyaya “blob” denir ve bu sadece bir bayt’tır. Bir dizin “tree” olarak adlandırılır ve adları blob’larla veya tree’lerle eşleştirilir (böylece dizinler başka dizinler de içerebilir). Snapshot’lar (anlık görüntüler), izlenmekte olan en üst düzey tree’lerdir. Örneğin, aşağıdaki gibi bir ağacımız olabilir:
<root> (tree)
|
+- foo (tree)
| |
| + bar.txt (blob, contents = "merhaba dünya")
|
+- baz.txt (blob, contents = "git muhteşemdir")
Üst düzey tree iki eleman içerir. Bunlar; biri tree olan “foo” (bu da adı “bar.txt” olan bir blob element barındırır) ile bir blob olan “baz.txt” dir.
Geçmiş modellemesi: ilişkili anlık görüntüler (snapshot’lar)
Bir versiyon kontrol sistemi anlık görüntüleri nasıl ilişkilendirmelidir? Basit bir model doğrusal bir geçmişe sahip olurdu. Bu geçmiş, snapshot’ların zaman sıralamasına uygun şekilde bir listesi olurdu. Birçok nedenden dolayı Git böyle basit bir model kullanmaz.
Git’te geçmiş, anlık görüntülerin (snapshots’ların) yönlendirilmiş çevrimsel olmayan bir grafiğidir (DAG directed acyclic graph). Bu kulağa havalı bir matematik cümlesi gibi gelebilir ama sizi korkutmamalıdır. Tüm bunlar, Git’deki her anlık görüntünün (snapshot’ın) kendinden önceki bir dizi “ebeveyn’lerle” ilişkisi var demektir. Bu, tek bir ebeveyn yerine bir ebeveyn grubudur, çünkü bir anlık görüntü (snapshot) birden çok ebeveynden gelebilir (doğrusal bir tarihte olduğu gibi). Örneğin, iki paralel geliştirme dalının birleştirilmesi (merge) gibi.
Git, bu anlık görüntüleri (snapshot’ları) “commit” olarak adlandırır. Bir commit geçmişini görselleştirmek bu şekilde görünebilir:
o <-- o <-- o <-- o
^
\
--- o <-- o
Yukarıdaki ASCII sanatında, o
lar tekil commit’lere (snapshot’lara) karşılık gelir.
Oklar her bir commit’in ebeveynini işareteder (Bu “önce gelir” ilişkisidir; “sonra gelir” değil).
Üçüncü commit’den sonra dallanma geçmişi, iki ayrı dala (branch’a) ayrılıyor.
Bu iki ayrı özelliğin birbirinden bağımsız aynı anda geliştirilmesine örnek olabilir.
Bu brach’lar gelecekte her iki özelliği de barındıran yeni bir snapshot oluşturmak için birleştirilebilir (merge edilebilir).
Ve yeni üretilen bu geçmiş kalın puntolarla gösterilir:
o <-- o <-- o <-- o <---- o ^ / \ v --- o <-- o
Git’teki commit’ler değişmezler. Bu, hataların düzeltilemeyeceği anlamına gelmez. Şu var ki commit geçmişini düzenlemek için aslında tamamen yeni bir commit oluşturuyoruz ve referanslar (aşağıya bakınız) yenilerini gösterecek şekilde güncelleniyor.
Sözde kod olarak veri modeli
Git’in veri modelinin sözde kodla yazıldığını görmek öğretici olabilir:
// bir dosya bir sürü bayttır
type blob = array<byte>
// bir dizinde adlandırılmış dosyalar ve dizinler bulunur
type tree = map<string, tree | blob>
// bir commit'te ebeveynler, meta veriler ve en üst düzey ağaç(top-level tree) vardır
type commit = struct {
parent: array<commit>
author: string
message: string
snapshot: tree
}
Temiz, basit bir geçmiş modeli.
Nesneler ve içerik adresleme
Bir “nesne” bir blob, tree ya da commit’tir.
type object = blob | tree | commit
Git veri deposunda, tüm nesneler SHA-1 hash’leri tarafından içerik-adreslenir.
objects = map<string, object>
def store(object):
id = sha1(object)
objects[id] = object
def load(id):
return objects[id]
Blob’lar, tree’ler ve commit’ler bu şekilde birleştirilir ve hepsi nesnedir. Bunlar başka bir objeyi referans edeceklerinde aslında onları kendi disk gösterimlerinde direkt içermezler ama onları hash’lerle referans gösterirler.
Örnek olarak; Yukarıdaki örnek dizin yapısı için tree şöyle görünür: (görselleştirilmek için git cat-file -p 698281bc680d1995c5f4caaf3359721a5a58d48d
kullanıldı)
100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85 baz.txt
040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87 foo
Tree, içindeki bilgiler için işaretçilere (pointer’lara) sahiptir, baz.txt (blob) ve foo (tree). Eğer baz.txt’ye uyumlu hash tarafından adreslenmiş içeriklere git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85
ile bakarsak aşağıdakini elde ederiz:
git is wonderful
Referanslar
Şu an tüm snapshot’lar kendi SHA-1 hash fonksiyonları ile tespit edilebilirler. Bu elverişli değildir çünkü insanlar 40 karakterli hexadecimal sayıları hatırlamakta iyi değildirler.
Git’in bu soruna çözümü, SHA-1 hashleri yerine “referanslar” adı verilen, insanlar tarafından okunabilir
isimlerdir. Referanslar commit’leri işaret ederler. Değişmez olan obje’lerin aksine, referanslar
değiştirilebilirdir. (yeni bir commit’i işaret edecek şekilde güncellenebilir). Örneğin; master
referansı genellikle ana geliştirme branch’daki (daldaki) son commit’i işaret eder.
references = map<string, string>
def update_reference(name, id):
references[name] = id
def read_reference(name):
return references[name]
def load_reference(name_or_id):
if name_or_id in references:
return load(references[name_or_id])
else:
return load(name_or_id)
Bunla birlikte Git uzun hexadecimal string’ler yerine “master” gibi insan tarafından kolay okunabilen isimlerle geçmişteki bir snapshot’ı temsil edebilir.
Bir detay da genellikle geçmişte “şu an nerdeyiz” kavramını bilmek isteriz.
Bu sebeple yeni snapshot aldığımızda neyle ilişkili olduğunu biliriz.(commit’in parents
‘ını nasıl belirledik?)
Git’te “şu anda bulunduğumuz yer”, “HEAD” adı verilen özel bir referanstır.
Repo’lar
Son olarak Git repo’larını; veri objeleri
ve referanslar
olarak kabaca tanımlayabiliriz.
Diskte, tüm Git depoları nesneler ve referanslardan oluşmaktadır: Git’in veri modeli bundan ibarettir. Bütün git
komutları
objeler ekleyip ve referanslar ekleyip/güncelleyerek bazı commit DAG (directed acyclic graph) manipülasyonları ile ilişkilidir.
Herhangi bir komut yazarken, komutun grafik ve veri yapısının altında ne gibi bir değişiklik yaptığını düşünün. Buna karşılık,
commit DAG’de belli başlı bir değişiklik yapmaya çalışıyorsanız örnek olarak; “commit edilmemiş değişiklikleri atın ve 5d83f9
commit’ini işlemek için ‘master’ referans noktası olarak belirtin”. Muhtemelen, bunu uygulamak için bir komut vardır.
(Bu duruma örnek olarak git checkout master; git reset -- hard 5d83f9e
)
Staging area (Hazırlanma alanı)
Bu, veri modeline dikey olan başka bir konsepttir. Fakat commit oluşturmak için gereken arayüzün bir parçasıdır da.
Anlık görüntü uygulamasının yukarıda açıklandığı gibi uygulanacağını hayal etmenin bir yolu da, çalışma dizininin mevcut durumuna göre yeni bir anlık görüntü oluştur “anlık görüntü (snapshot) oluştur” komutuna sahip olmaktır. Bazı versiyon kontrol araçları bu şekilde çalışır, ama Git bu şekilde çalışmaz. Temiz anlık görüntüler isteriz ve mevcut durumdan anlık görüntü oluşturmak her zaman ideal olmayabilir. Örneğin, iki ayrı özellik uyguluyoruz; Birincisinin A özelliğini, diğerinin de B özelliğini tanıttığı iki ayrı commit oluşturmak istiyorsunuz ve bugfix’ler ile beraber kodunuzun her yerine hata ayıklama ekran çıktıları eklemek istiyorsunuz. Tüm bu hata ayıklama ekran çıktılarını göndermeden (discarding) bir yandan da bugfix’i commit’lemek istediğiniz bir senaryo düşünün.
Git “staging area” denen bir mekanizma ile bir dahaki snapshot’da hangi değişikliklerin olması gerektiğini belirlemenizi yarayacak senaryolar sağlar.
Git komut satırı arabirimi
Bilgilerin kopyalanmasını önlemek için aşağıdaki komutları ayrıntılı olarak açıklamayacağız. Daha fazla bilgi için şiddetle tavsiye edilen Pro Git‘e bakın veya ders videosunu izleyin.
Temeller
git help <command>
: bir git komutu için yardım alıngit init
: yeni bir git repo’su oluşturur, ilgili verileri.git
dizininde saklargit status
: neler olduğunu söylergit add <filename>
: dosyaları staging area’ya (sahne alanına) eklergit commit
: yeni bir commit oluşturur- Güzel commit mesajları yazın!
- Güzel commit mesajları yazmak için daha fazla neden!
git log
: commit geçmişini sade bir şekilde gösterirgit log --all --graph --decorate
: git geçmişini DAG’a göre görselleştirirgit diff <filename>
: son commit’ten bu yana yapılan değişiklikleri gösterirgit diff <revision> <filename>
: snapshot’lar arasındaki dosya farklılığını gösterirgit checkout <revision>
: HEAD’i ve mevcut brach’ı günceller
Dallanma (Branching) ve birleştirme (merging)
git branch
: brach’ları gösterirgit branch <name>
: bir branch oluştururgit checkout -b <name>
: bir branch oluşturur ve ona geçer- buna eşdeğerdir
git branch <name>; git checkout <name>
- buna eşdeğerdir
git merge <revision>
: mevcut dalla birleştirir (merge eder)git mergetool
: birleştirme çakışmalarını (merge conflict’lerini) çözmek için havalı bir araç kullanıngit rebase
: yeni base’e yamaları yükler
Remotes (Uzak repo)
git remote
: uzak depoları listelergit remote add <name> <url>
: uzak bir depo eklergit push <remote> <local branch>:<remote branch>
: nesneleri uzak depoya gönderir ve uzak depo referansını güncellergit branch --set-upstream-to=<remote>/<remote branch>
: yerel ve uzak branch’lar arasındaki yazışmaları ayarlargit fetch
: uzaktaki objeleri/referansları çekergit pull
: buna eşdeğerdirgit fetch; git merge
git clone
: uzaktaki repoyu indirir
Geri alma
git commit --amend
: bir commit’in içeriği/mesajını güncellergit reset HEAD <file>
: bir dosyayı stagin area’dan çıkarırgit checkout -- <file>
: değişiklikleri gözardı eder
Gelişmiş Git
git config
: Git son derece özelleştirilebilirdirgit clone --depth=1
: tüm versiyon geçmişi olmadan, yüzeysel bir klonlama yapargit add -p
: etkileşimli staginggit rebase -i
: etkileşimli rebasinggit blame
: kimin en son hangi satırı düzenlediğini göstertirgit stash
: çalışma dizinindeki değişiklikleri geçici olarak kaldırırgit bisect
: binary search’le geçmişi arar (örneğin ilişki yoklaması).gitignore
: bilinçli şekilde izlenmeyen dosyaları yoksayılan olarak belirtir
Çeşitli
- GUI’lar: Git için çok sayıda GUI istemcisi var. Şahsen biz bunları kullanmıyoruz. Bunların yerine komut satırı arayüzünü kullanıyoruz.
- Kabuk (Shell) entegrasyonu: Kabuğunuzun (shell’inizin) (zsh, bash) bir parçası olarak Git durumuna sahip olmak son derece kullanışlıdır. Genellikle Oh My Zsh gibi uygulama çatılarında (framework’lerde) bu, dahil olarak gelir.
- Editör entegrasyonu: Yukarıdakine benzer şekilde, birçok özelliğe sahip kullanışlı entegrasyonlar. fugitive.vim Vim için standart olandır.
- İş akışları : Size veri modelini ve bazı temel komutları öğrettik; fakat büyük projeler üzerinde çalışırken hangi uygulamaları takip edeceğinizi söylemedik. (ve birçok farklı yaklaşım var).
- GitHub: Git GitHub değildir. GitHub, pull requests adı verilen diğer projelere kod desteğinde bulunma imkanı sağlayan özel bir yola sahiptir.
- Diğer Git sağlayıcıları: Tek Git sağlayıcısı Github değildir. GitLab ve BitBucket gibi birçok Git repository sağlayıcıları vardır.
Kaynaklar
- Pro Git‘i okumanızı şiddetle tavsiye ediyoruz. Veri modellerini anladığınıza göre, 1-5 bölümlerinin üzerinden geçmek size Git’i verimli bir şekilde kullanmak için ihtiyaç duyduğunuz şeylerin çoğunu öğretecektir. Ayrıca gelecek bölümlerde bazı ilginç gelişmiş materyaller de var.
- Oh Shit, Git!?! yaygın Git hatalarından nasıl kurtulacağınız konusunda kısa bir rehberdir.
- Git for Computer Scientists, Git’in veri modelinin kısa bir açıklamasıdır ve bu ders notlarına göre daha az sözde kod ve daha havalı diyagramları vardır.
- Git from the Bottom Up, meraklılar için Git’in uygulama ayrıntılarının veri modellerinin ötesinde ayrıntılı bir açıklamasıdır.
- How to explain git in simple words
- Learn Git Branching, Git’i öğreten tarayıcı tabanlı bir oyundur.
Alıştırmalar
- Git ile ilgili geçmiş bir deneyiminiz yoksa, Pro Git’in ilk birkaç bölümünü okumayı deneyin veya Learn Git Branching gibi bir eğitimden faydalanın. Ve bunlar üzerinde çalışırken Git komutlarını veri modeliyle ilişkilendirmeye çalışın.
- Repoyu dersin sitesinden clone’layın.
- Versiyon geçmişini grafik olarak görselleştirp keşfedin.
README.md
‘de en son değişiklik yapan kişi kim? (İpucu: parametre ekleyerekgit log
‘u kullan)_config.yml
‘ıncollections:
satırına yapılan son değişiklik ile alakalı commit mesajı hangisidir? (İpucu:git blame
vegit show
‘u kullanın)
- Git’i öğrenirken yapılan yaygın hatalardan biri de git tarafından yönetilmemesi gereken büyük dosyaları commit’lemek veya hassas bilgileri eklemektir. Bir repoya dosya eklemeyi, bazı commit’ler oluşturmayı ve ardından o dosyayı geçmişten silmeyi deneyin (buna bakmak isteyebilirsiniz).
- GitHub’daki bazı depoları klonlayın ve mevcut dosyalarından birini değiştirin.
git stash
yaptığınızda ne olur?git log --all --oneline
‘ı çalıştırdığınızda ne görüyorsunuz?git stash
ile yaptıklarınızı geri almak içingit stash pop
komutunu çalıştırın. Hangi senaryoda bu yararlı olabilir? - Birçok komut satırı aracı gibi Git de
~/.gitconfig
adlı bir yapılandırma dosyası (veya dotfile) sağlar.git graph
komutunu çalıştırdığınızdagit log --all --graph --decorate --oneline
çıktısını almanız için~/.gitconfig
içinde bir takma ad (alias) oluşturun. git config --global core.excludesfile ~/.gitignore_global
komutunu çalıştırdıktan sonra~/.gitignore_global
içinde global yok sayma kalıplarını tanımlayabilirsiniz. Bunu yapın ve genel gitignore dosyanızı,.DS_Store
gibi işletim sistemine özgü veya metin editörlerine özgü geçici dosyaları yok sayacak şekilde ayarlayın.- Sınıfın web sitesinden repoyu clone’layın ve yapabileceğiniz bir iyileştirme bulun (yazım yanlışı gibi) ve Github’dan bir pull request gönderin.
CC BY-NC-SA lisansı ile lisanslanmıştır.