Git Komutlarını Değil, Kavramlarını Öğrenin

Gizem Korkmaz
28 min readJul 14, 2022

--

Bu yazı Nico Riedmann’ın Learn git concepts, not commands adlı makalesinin bir çevirisidir.

Bu, size yalnızca hangi komutları kullanacağınızı değil, git’in nasıl çalıştığını da öğretmek amacıyla yazılmış etkileşim içeren öğretici bir makaledir.

Git kullanmak istiyorsunuz, değil mi?

Ama sadece komutları öğrenmeyi değil, kullandığınız şeylerin ne olduğunu da anlamak istiyorsunuz?

O halde bu yazı tam size göre!

Haydi, başlayalım.

Bu makale, Rachel M. Carmena’nın How to teach Git adlı blog postunun temel kavramlarına dayanmaktadır.

İnternette bulduğum git eğitimlerinin birçoğunun, git’in nasıl çalıştığından ziyade ne yaptığına daha fazla odaklanmış olmasının yanı sıra her iki durum için de en değerli kaynaklardan ikisi git Book ve Reference page’dir (bu eğitim için de kaynak olarak kullanıldılar).

O halde, buradaki işiniz bittiğinde bunlara da bir göz atın. Umuyorum ki bu yazıdaki farklı kavramlar, detaylı bir şekilde açıklanmış git özelliklerini öğrenirken sizlere yardımcı olacaktır.

Genel Bir Bakış

Aşağıdaki görselde dört kutu görüyorsunuz. Bunlardan bir tanesi diğerlerinden ayrı, kalanları ise Development Environment adı vereceğimiz bir alanda toplanmışlar.

Tek başına olandan başlayalım. Remote Repository yaptığınız değişiklikleri başkaları ile paylaşmak istediğinizde gönderdiğiniz ve başkalarının yaptığı değişiklikleri aldığınız yerdir. Eğer farklı versiyon kontrol sistemleri kullandıysanız burada pek de ilgi çekici bir şey yok.

Development Environment ise local makinenizde olan şeydir. Buradaki üç bölüme Working Directory, Staging Area ve Local Repository denir. Bunları git’i kullanmaya başladığımız zaman daha detaylı öğreneceğiz.

Development Environment’ı oluşturmak için bir yer seçin. Home klasörünüze gidin ya da projeleri nereye koymayı seviyorsanız orada oluşturun. Dev Environment için ayrı bir klasör oluşturmanıza gerek yok.

Remote Bir Repository Almak

Şimdi bir Remote Repository’yi alıp içindekileri makinenize aktarmak istiyoruz.

Bu makaleyi şu an GitHub’dan okumuyorsanız şu linki (https://github.com/UnseenWizzard/git_training.git) kullanmanızı öneririm.

Bunu yapmak için git clone https://github.com/UnseenWizzard/git_training.git komutunu kullanabiliriz.

Bu eğitim boyunca Dev Environment içerisinde yaptığımız değişiklikleri, Remote Repository’ye göndermemiz gerekecek ancak GitHub herhangi birinin repo’suyla bu şekilde oynamamıza izin vermez. Bu yüzden en iyisi bir fork işlemi yapalım. Sayfanın sağ üst köşesinde bir buton var.

Şimdi kendinize ait bir Remote Repository olduğuna göre sırada bunu kendi makinenize almak var.

Bunun için kullanacağımız komut git clone https://github.com/{YOUR USERNAME}/git_training.git olacak.

Aşağıdaki şemada da gördüğünüz gibi bu, Remote Repository’yi iki farklı yere kopyalar; Working Directory ve Local Repository’ye. Burada git’in dağıtık bir versiyon kontrol sistemi olduğunu görebilirsiniz. Local Repository aslında Remote’un bir kopyasıdır ve tıpkı onun gibidir. Tek farkı bunu kimseyle paylaşamamanızdır.

git clone'un yaptığı bir diğer şey de çağırdığınız yerde yeni bir klasör oluşturmasıdır. Şimdi bir git_training klasörünüz olmalı. Bunu açın.

Yeni Şeyler Eklemek

Birisi Remote Repository’ye bir dosya eklemiş bile. Adı da Alice.txt ve biraz yalnız duruyor. Haydi, yeni bir dosya oluşturalım ve ona Bob.txt diyelim.

Yaptığınız şey dosyayı Working Directory’ye eklemek oldu. Working Directory içerisinde iki tür dosya vardır. Git’in haberi olan tracked files (takip edilen dosyalar) ve git’in (henüz) haberinin olmadığı untracked files (takip edilmeyen dosyalar).

Working Directory içerisinde neler olduğuna bir bakmak için size hangi branch'te olduğunuzu, Local Repository’nin Remote’tan farklı olup olmadığını ve tracked ve untracked dosyalarınızın durumunu söyleyecek olan git status komutunu çalıştırın.

Burada Bob.txt'in untracked olduğunu göreceksiniz,git status size bunu nasıl değiştireceğinizi de söyleyecek.

Aşağıdaki görselde verilen tavsiyeye uyup git add Bob.txt yazdığınız zaman neler olduğunu görebilirsiniz: Dosyayı, Repository’ye koymak istediğiniz değişiklikleri toparlayan Staging Area’ya eklediniz.

Tüm değişikliklerinizi eklediğiniz zaman (mevcut durumda sadece Bob’u ekledik) Local Repository’de yaptığınız şeyi commit’lemeye hazırsınız.

Commitlediğiniz toplu değişiklikler anlamlı bir çalışma yığınıdır, bu yüzden git commit komutunu çalıştırdığınızda bir text editor’ü açılacak ve yaptığınız şeyleri açıklamanız için bir mesaj yazabileceksiniz. Bunu kaydedip kapattığınızda commit’iniz Local Repository’ye eklenmiş olacak.

git commit'i şu şekilde yazarsanız commit mesajınızı doğrudan da ekleyebilirsiniz: git commit -m “Add Bob". Ancak iyi bir commit mesajı yazmak istiyorsanız gerçekten zaman ayırıp editor’ü kullanmalısınız.

Şimdi, değişikliklerinize henüz birilerinin ihtiyacı yoksa ya da paylaşmaya hazır değilseniz bunun için uygun bir yer olan Local Repository’de duruyorlar.

Commitlerinizi Remote Repository ile paylaşmak istiyorsanız onları pushlamalısınız.

git push yaptığınız zaman değişiklikler Remote Repository’ye gönderilecektir. Aşağıdaki şemada push işleminden sonraki durumu görebilirsiniz.

Değişiklikler Yapma

Şu ana dek sadece yeni bir dosya ekleme işlemi yaptık. Ancak versiyon kontrolünün daha eğlenceli olan kısmı tabii ki dosyaları değiştirmektir.

Alice.txt dosyasına bir bakın.

Bir yazı içeriyor, ancak Bob.txt'in içi boş. Bunu değiştirelim ve içerisine Hi!! I’m Bob. I’m new here. yazalım.

Eğer git status komutunu çalıştırırsanız Bob.txt'in değiştirildiğini fark edeceksiniz. Mevcut durumda değişiklikler yalnızca Working Directory içerisindedir.

Eğer Working Directory içerisinde nelerin değiştiğini görmek isterseniz git diff komutunu çalıştırabilirsiniz, böylece şunu göreceksiniz:

diff --git a/Bob.txt b/Bob.txt
index e69de29..3ed0e1b 100644
--- a/Bob.txt
+++ b/Bob.txt
@@ -0,0 +1 @@
+Hi!! I'm Bob. I'm new here.

Devam edin ve daha önce yaptığınız gibi git add Bob.txt yazın. Bildiğimiz gibi bu, değişikliklerinizi Staging Area’ya taşıyacak.

Stage’e koyduğumuz değişiklikleri görmek istiyorum, bu yüzden tekrar git diff yazalım! Bu sefer çıktının boş olduğunu fark edeceksiniz. Bunun sebebi git diff'in yalnızca Working Directory içerisindeki değişiklikleri yönetmesinden kaynaklanır.

Hali hazırda stage’e alınmış değişiklikleri görmek için git diff --staged yazarak bir önceki çıktıyı görebiliriz.

‘Hi’ yazdıktan sonra iki ünlem koyduğumuzu fark ettim. Hoşuma gitmedi, bu yüzden Bob.txt'i tekrar değiştirip bunu sadece ‘Hi’ yapalım.

Tekrar git status yazarsak artık iki değişiklik olduğunu göreceğiz, biri yazı eklediğimiz, hali hazırda stage edilmiş hali; diğeri ise şimdi yaptığımız, hala working directory’de olan değişiklik.

Değişikliklerimizin bir commit için hazır olduğunu hissettiğimiz andan itibaren nelerin değiştiğini göstermek için Working Directory ile Staging Area arasındaki farka git diff yazarak bir göz atabiliriz.

diff --git a/Bob.txt b/Bob.txt
index 8eb57c4..3ed0e1b 100644
--- a/Bob.txt
+++ b/Bob.txt
@@ -1 +1 @@
-Hi!! I'm Bob. I'm new here.
+Hi! I'm Bob. I'm new here.

Değişiklikler istediğimiz gibi olduğuna göre dosyanın mevcut durumunu stage’e almak içingit add Bob.txt yazalım.

Şimdi yaptığımız şeyleri commitlemeye hazırız. Ben git commit -m "Add text to Bob" olarak yazdım çünkü bu denli küçük bir değişiklik için tek satır yeterli hissettirdi.

Bildiğimiz gibi yaptığımız değişiklikler şu an Local Repository’de. Hala yeni commitlediğimiz değişiklikleri ve bunların daha önceki durumlarına bakmak isteyebiliriz.

Bunu commitleri karşılaştırarak yapabiliriz. Git’teki her bir commit’in, kendisine referans olan, kendisine özgü tek bir hash değeri vardır.

Eğer git log’a bakarsak sadece Author, Date ve hash değerleri ile beraber listelenmiş commitleri değil, aynı zamanda Local Repository’mizin durumunu ve remote branchlerin en son local bilgilerini görürüz.

Şimdilik git log'umuz bu şekilde gözükecek:

commit 87a4ad48d55e5280aa608cd79e8bce5e13f318dc (HEAD -> master)
Author: {YOU} <{YOUR EMAIL}>
Date: Sun Jan 27 14:02:48 2019 +0100

Add text to Bob

commit 8af2ff2a8f7c51e2e52402ecb7332aec39ed540e (origin/master, origin/HEAD)
Author: {YOU} <{YOUR EMAIL}>
Date: Sun Jan 27 13:35:41 2019 +0100

Add Bob

commit 71a6a9b299b21e68f9b0c61247379432a0b6007c
Author: UnseenWizzard <nicola.riedmann@live.de>
Date: Fri Jan 25 20:06:57 2019 +0100

Add Alice

commit ddb869a0c154f6798f0caae567074aecdfa58c46
Author: Nico Riedmann <UnseenWizzard@users.noreply.github.com>
Date: Fri Jan 25 19:25:23 2019 +0100

Add Tutorial Text

Changes to the tutorial are all squashed into this commit on master, to keep the log free of clutter that distracts from the tutorial

See the tutorial_wip branch for the actual commit history

Burada ilgi çekici birkaç şey var:

  • İlk iki commit benim tarafımdan oluşturulmuş.
  • Bob’u eklediğiniz ilk commit Remote Repository’de master branch’inin mevcut HEAD’idir. Buna branchlerden ve remote değişikliklerden bahsederken tekrar bakacağız.
  • Local Repository’deki en son commit en son yaptığımız değişiklik ve artık onun hash değerini biliyoruz.

Commitlerinizin hash değerlerinin sizin için farklı olacağını unutmayın. Eğer git’in bu uyarlama IDleri tam olarak nasıl verdiğini merak ediyorsanız, şu ilginç makaleye bir göz atın.

Bir commit ile ondan önceki başka bir commit’i karşılaştırmak için git diff <commit>^! yazabiliriz, ^! git’e bir önceki commit ile karşılaştırma yapmasını söyler. Bu sebeple mevcut durumda git diff 87a4ad48d55e5280aa608cd79e8bce5e13f318dc^! komutunu çalıştıracağım.

Aynı sonuç ve genel olarak iki farklı commit’i karşılaştırmak için de git diff 8af2ff2a8f7c51e2e52402ecb7332aec39ed540e 87a4ad48d55e5280aa608cd79e8bce5e13f318dc yapabiliriz. Buradaki formatın git diff <önceki commit> <sonraki commit> olduğuna dikkat edin, yani yeni commit’imiz sonra gelecek.

Aşağıdaki şemada bir değişimin farklı adımlarını ve dosyanın konumuna göre farklı diff komutlarını görüyorsunuz.

Yapmak istediğimiz değişikliği yaptığımıza emin olduğumuza göre devam edin ve git push yazın.

Branching

Git’i harika yapan bir başka şey de branchlerle çalışmanın oldukça kolay ve beraber çalışmanın ayrılmaz bir parçası olmasıdır.

Biz henüz başladığımızdan beri aynı branch’te çalışıyoruz.

Remote Repository’yi Dev Environment’ınıza cloneladığınızda repositoryler otomatik olarak main ya da master branch’inde başlatılır.

Git ile planlanan pek çok çalışma akışı master ile merge etmeden önce değişikliklerinizi bir branch’te yapmayı kapsar.

Genellikle master ile merge olmak üzere yaptığınız değişiklikleriniz konusunda emin olana kadar kendi branch’inizde çalışıyor olursunuz.

GitLab ve GitHub gibi repository yöneticileri de branchlerin güvende olmalarını sağlar, bu da herhangi birinin burada değişiklikler yapıp pushlamasını engeller. Master ise varsayılan olarak korunur durumdadır.

Endişelenmeyin, bunlara ihtiyacımız olduğu zaman daha detaylı olarak inceleyeceğiz.

Şimdilik üzerinde değişiklikler yapabileceğimiz bir branch oluşturmak istiyoruz. Belki tek başınıza bir şeyler denemek istiyorsunuzdur ve master branch’inde çalışan durumu bozmak istemiyorsunuzdur ya da master’a push işlemi yapma yetkiniz yoktur.

Branchler Local ve Remote Repositoryler içerisinde yaşarlar. Yeni bir branch oluşturduğunuz zaman, branchlerin içerikleri üzerinde çalıştığınız branch her ne ise onun mevcut commitlenmiş state’inin bir kopyası olacaktır.

Haydi Alice.txt üzerinde bazı değişiklikler yapalım. İkinci satıra biraz yazı eklemeye ne dersiniz?

Bu değişikliği kaydetmek istiyoruz ancak doğrudan master’a dahil etmek istemiyoruz, bu yüzden git branch <branch name> komutu ile bir branch oluşturalım.

change_alice adında bir branch oluşturmak için git branch change_alice yazabiliriz.

Bu, Local Repository’ye yeni bir branch ekleyecektir.

Working Directory ya da Staging Area branchlerinizle pek ilgilenmiyor olsalar da siz her zaman mevcut branch’inizde commit işlemi yaparsınız.

Branchleri bir dizi commit’i işaret eden göstergeler gibi düşünebilirsiniz. Bir commit işlemi yaptığınızda o sırada neyi işaret ediyorsanız oraya eklersiniz.

Sadece bir branch eklemek sizi oraya götürmez, bu yalnızca bir gösterge oluşturur.

Local Repository’nizdeki mevcut durum, HEAD adı verilen, hangi branch’te ve commit’te olduğunuzu işaret eden başka bir gösterge ile de görüntülenebilir.

Eğer tüm bunlar kulağa karışık geliyorsa umuyorum ki aşağıdaki şemalar bu karışıklığı biraz giderecektir.

Working Directory’nin değişmediğini fark edeceksiniz. Bunun sebebi değiştirdiğimiz Alice.txt içinde bulunduğumuz branch ile henüz ilişkili değil.

Şimdi, daha önce masterda yaptığımız gibi Alice.txt'deki değişiklikleri add ve commit ile ekleyeceğiz, bu da onları stage alanına alacak (bu noktada hala branch ile alakaları yok) ve son olarak değişiklikleriniz change_alice branch’ine commitlenecek.

Henüz yapamadığınız tek bir şey kaldı. Değişikliklerinizi git push yaparak Remote Repository’ye eklemeye çalışın.

Aşağıdaki hatayı göreceksiniz ve git her zamanki gibi yardımıza hazır bir şekilde bu sorunu çözmek için bir tavsiye veriyor olacak:

fatal: The current branch change_alice has no upstream branch.
To push the current branch and set the remote as upstream, use

git push --set-upstream origin change_alice

Gözü kapalı öylece söylenileni yapmayın. Neler olup bittiğini anlamak için buradayız. O halde upstream branchler ve remote nedir?

Bir süre önce Remote Repository’yi cloneladığımızı hatırlıyorsunuz, değil mi? O noktada sadece bu eğitimi ve Alice.txt'yi içermiyordu, aslında içerisinde iki branch vardı.

Biri doğrudan üzerinde çalışmaya başladığımız master ve diğeri ise benim “tutorial_wip” adını verdiğim, bu eğitimi oluşturmak için tüm değişiklikleri commitlediğim branch.

Remote Repository içerisindekileri Dev Environment’ınıza kopyaladığımız zaman perde arkasında birkaç şey daha oldu.

Git, Local Repository’nizin remote’unu, cloneladığınız Remote Repository olarak ayarladı ve ona varsayılan ismi olan origin'i verdi.

Local Repository birkaç farklı remote’u takip edebilir ve bunların farklı isimleri olabilir ancak biz bu eğitim içerisinde sadece origin ismini kullanacağız.

Ardından iki remote branch’i de Local Repository’nize kopyaladı ve son olarak master’a sizin için checked out yaptı.

Burada üstü kapalı bir şey daha gerçekleşiyor. Remote branchlerde birebir eşi olan yeni bir branch ismine checkout yaptığınız zaman remote branchinize bağlı yeni bir local branch oluşturursunuz. Remote branch’iniz local’dekinin upstream branch’i olur.

Aşağıdaki şemalarda sahip olduğunuz local branchleri görebilirsiniz. git branch kodu ile de local branch listenizi görebilirsiniz.

Eğer Local Repository’nizin tüm remote branchlerini görmek istiyorsanız git branch -a yazarak hepsini listeleyebilirsiniz.

Şimdi önerilen git push --set-upstream origin change_alice komutunu çalıştırabilir ve branch’teki değişikliklerimizi yeni remote’a pushlayabiliriz. Bu, Remote Repository’de yeni bir change_alice branch’i oluşturacak ve local chance_alice'in bu yeni branch’i takip etmesi için ayarlayacak.

Branch’imizin Remote Repository’de hali hazırda var olan bir şeyi takip etmesini istiyorsak bunun bir başka yolu daha var. Biz local branch’imizde alakalı şeylerle uğraşırken belki bir iş arkadaşınız bazı değişiklikler pushladı ve iki tarafı da bir araya getirmek istedik. O halde chance_alice branch’inin upstream’ini git branch --set-upstream-to=origin/change_alice yazarak yeni bir remote’a bağlayabiliriz ve buradan remote branch’i takip edebiliriz.

Tüm bunlardan sonra GitHub’daki Remote Repository’nize bir göz atın; branch’iniz insanların görebileceği ve üzerine çalışabileceği şekilde duruyor olacaktır.

Başkalarının yaptığı değişiklikleri nasıl Dev Environment’ınıza alacağınızdan birazdan bahsedeceğiz ancak önce Remote Repository’den yeni şeyler aldığımızda ortaya çıkan tüm kavramları tanıtmak üzere branchler üzerine biraz daha çalışacağız.

Merging

Herkes branchler üzerinde çalışacağı için değişiklikleri bir branch’ten başka bir branch’e mergeleyerek nasıl alabileceğimizden de bahsetmeliyiz.

change_alice branch'indeki Alice.txt dosyasını yeni değiştirdik ve yaptığımız değişikliklerin içime sindiğini söyleyebilirim.

Eğer git checkout master derseniz diğer branch üzerinde yaptığımız commit işleminin orada olmadığını göreceksiniz. Değişiklikleri master’a almak için change_alice branch’ini master içine mergelememiz gerekiyor.

Her zaman branchleri o an hangi branch’teyseniz onun içerisine mergelediğinizi unutmayın.

Fast-Forward Merging

Hali hazırda master’a checked out yaptığımız için şimdi git merge change_alice diyebiliriz.

Alice.txt'e herhangi bir conflict çıkmadığı ve master’da da hiçbir şey değiştirmediğimiz için hiçbir sorun olmadan fast-forward merge denilen bir şekilde birleşecektir.

Aşağıdaki şemalarda, bu durumun master göstergesinin change_alice neredeyse doğrudan oraya ilerleyebileceği anlamına geldiğini görebilirsiniz.

İlk şema merge işleminden önceki, yani master’ın eski commit’teki halini ve üzerine bir commit daha eklediğimiz diğer branch’i gösteriyor.

Diğer şema ise merge işlemi ile beraber olan değişimleri gösteriyor.

Farklı Branchler ile Merge

Biraz daha karmaşık bir şey deneyelim.

master'da Bob.txt'ye yeni bir satır yazı ekleyelim ve commitleyelim.

Ardından git checkout change_alice diyelim ve Alice.txt’i değiştirip commitleyelim.

Aşağıdaki şemada commit geçmişimizin nasıl gözüktüğünü görebilirsiniz. Hem master hem change_alice aynı commit’ten oluşturulmuş ancak ayrıldıkları için her ikisinin de kendine ait ayrı commitleri bulunmakta.

Şimdi git merge change_alice derseniz fast-forward merge mümkün olmayacak. Bunun yerine favori text editor’ünüz açılacak ve git'in gerçekleştireceği merge commit'in mesajını değiştirmenizi sağlayacak; böylece iki branch bir araya gelebilecek. Bu noktada varsayılan mesaj ile devam edebilirsiniz. Aşağıdaki şemada git geçmişimizin merge işleminden sonraki son halini görebilirsiniz.

Yeni commit change_alice branch’i üzerindeki yaptığımız değişiklikleri master’a dahil edecek.

Daha önceden de hatırladığınız gibi, git üzerindeki düzenlemeler sadece dosyalarınız üzerindeki anlık görüntüler değil, aynı zamanda bu değişikliklerin nereden geldiğine dair bilgiler de içerir. Her bir commit bir ya da daha fazla parent commit’e sahiptir. Yeni merge commit’imiz hem master’daki son commit’i hem de parent’ı olduğu için diğer branch’teki commit’i içeriyor.

Conflictleri (Çakışmaları) Çözmek

Şu ana dek değişikliklerimiz birbirleri ile çakışmadılar.

Bir conflict çıkaralım ve ardından bunu çözelim.

Yeni bir branch oluşturup checkout yapın. Nasıl yapıldığını biliyorsunuz ancak belki git checkout -b yazarak işleri kolaylaştırabilirsiniz.

Ben kendiminkine bobby_branch dedim.

Branch üzerinde Bob.txt'i değiştireceğiz.

İlk satır hala Hi!! I'm Bob. I'm new here. olmalı. Onu Hi!! I'm Bobby. I'm new here. ile değiştirelim.

Stage’e alalım ardından değişiklikleri tekrar master’a checkout yapmadan önce commitleyelim. Burada aynı satırı Hi!! I'm Bob. I've been here for a while now. olarak değiştirelim ve değişiklikleri commitleyelim.

Şimdi sıra yeni branch’i master’a mergelemekte.

Bunu denediğiniz zaman şu çıktıyı göreceksiniz.

Auto-merging Bob.txt
CONFLICT (content): Merge conflict in Bob.txt
Automatic merge failed; fix conflicts and then commit the result.

Her iki branch’te de aynı satır değiştirildi ve git bunu tek başına çözemiyor.

Eğer git status derseniz nasıl devam etmeniz konusunda yardımcı olacak talimatları göreceksiniz.

Önce conflict’i kendi ellerimizle çözmemiz gerekiyor.

Bunun gibi kolay bir conflict’i favori text editor’ünüz kolaylıkla çözecektir. Çok fazla değişiklik içeren büyük dosyalar için daha güçlü bir araç işleri kolaylaştırır ve sanıyorum ki favori IDE’niz içerisinde versiyon kontrol araçları ve merge işlemleri için hoş bir görüntüsü vardır.

Eğer Bob.txt dosyasını açarsanız aşağıdakine benzer bir şeyler göreceksiniz (ikinci satırdan önce koyabileceğimiz kısmı kısalttım):

<<<<<<< HEAD
Hi! I'm Bob. I've been here for a while now.
=======
Hi! I'm Bobby. I'm new here.
>>>>>>> bobby_branch
[... whatever you've put on line 2]

En üstte mevcut HEAD’de Bob.txt'te neyin değiştiğini görüyorsunuz, altında ise merge’lediğimiz branch’te nelerin değiştiği gösteriliyor.

Conflict’i ellerimizle çözmek için makul bir içerik elde ettiğinizden ve git’in dosya içerisine eklediği özel satırların olmadığından emin olmalıyız.

O halde devam edin ve dosyayı şöyle bir hale getirin:

Hi! I'm Bobby. I've been here for a while now.
[...]

Bu noktadan sonra herhangi bir değişiklik için ne yapıyorsak aynı şeyi yapıyoruz. add Bob.txt yazarak stage’e alıyoruz ve commitliyoruz.

Değişiklikleri yaptığımız commit’in conflict’i çözdüğünü biliyoruz. Merge işlemini yaparken görünen her zaman merge commit olacaktır.

Olur da conflictleri çözerken yarı yolda merge işlemine devam etmek istemezseniz git merge --abort diyerek işlemi iptal edebilirsiniz.

Rebasing

Git’te iki branch’i bir araya getirmenin rebase adı verilen başka temiz bir yolu daha vardır.

Bir branch’in her zaman başka bir branch’e bağlı olduğunu söylemiştik. Bir branch oluşturduğumuzda onu başka bir yerden dallandırıyorsunuz (branching).

Basit merge örneğimizde belirli bir commit’teki master branch’inden yeni bir branch oluşturduk ve değişiklikleri hem master’da hem de change_alice branch’inde commitledik.

Bir branch bağlı olduğu branch’ten ayrıldığında ve yapılan son değişiklikleri mevcut branch’e geri bağlamak istediğinizde rebase size merge işleminden daha temiz bir yol sunar.

Gördüğümüz gibi merge iki geçmişi tekrar bir araya getirdiği için bir merge commit oluşturur.

Basitçe rebase, sadece branch’inizi baz alan geçmişteki bir noktayı (commit) değiştirir.

Bunu denemek için önce master branch’ine tekrar bir göz atalım, ardından bunu baz alan yeni bir branch oluşturup checkout yapalım.

Ben kendiminkine add_patrick adını verdim ve Patrick.txt adında yeni bir dosya ekleyip “Add Patrick” mesajı ile commitledim.

Branch’e yeni bir commit eklediğiniz zaman master’a geri dönün, bir değişiklik yapın ve commitleyin. Ben Alice.txt'e biraz daha yazı ekledim.

Merge örneğimizde olduğu gibi bu iki branch’in geçmişi aşağıdaki şemada da görüldüğü gibi ortak bir noktada ayrışır.

Şimdi tekrarcheckout add_patrick diyelim ve master üzerinde yapılan değişikliği çalıştığımız branch üzerine alalım!

git rebase master dediğimiz zaman add_patrick branch’imize baz olarak tekrar master’ın mevcut halini alıyoruz.

Bu komutun çıktısı bize içeride neler olduğuna dair hoş bir ipucu veriyor:

First, rewinding head to replay your work on top of it...
Applying: Add Patrick

Hatırladığımız gibi HEAD, Dev Environment’ımızdaki mevcut commit’in bir göstergesiydi.

add_patrick'in rebase işlemi başlamadan önce gösterdiği yeri işaret ediyor. Rebase için tekrar baz almasını istediğimiz branch’in mevcut HEAD’ine gitmeden önce ortak geçmişe dönüyor.

Yani HEAD 0cfc1d2 commit’inden, master’ın HEAD’indeki 7639f4b commit’ine gidiyor.

Ardından rebase add_patrick üzerinde yaptığımız tüm commitleri buna dahil ediyor.

Daha açık olmak gerekirse, HEAD’i tekrar branchlerin ortak geçmişine taşıdıktan sonra git’in yaptığı şey branch üzerinde yaptığınız her bir commit’in parçalarını saklamak (değişikliklerin diff'i ve commit mesajları, kullanıcı isimleri vs.)

Bundan sonra rebase işlemi yaptığınız branch’in en son commit’ine checkout yapar, ardından tüm bu saklanan değişimleri yeni bir commit olarak üzerine ekler.

Yani orijinal basitleştirilmiş görüntümüzde rebase işleminden sonra 0cfc1d2 commit’i artık ortak geçmişi işaret etmez, bunun yerine master’ın HEAD’ini işaret eder.

Aslında 0cfc1d2 commit’i artık gitmiştir ve add_patrick branch geçmişi master’ın en son commit’i olan yeni bir 0ccaba8 commit’i ile başlar.

add_patrick'i mevcut master’ın eski bir versiyon olarak değil, onu baz alıyor gibi gösterdik ancak bu şekilde yaparak branch’in geçmişini baştan yazmış olduk.

Bu eğitimin sonunda geçmişi tekrar yazma konusunda daha detaya inip bunu yapmanın ne zaman uygun ne zaman uygun olmadığını öğreneceğiz.

master gibi ortak bir branch’e bağlı kendi development branch’inizde çalışıyorsanız rebase son derece güçlü bir araçtır.

Rebase’i kullanarak başka insanların yaptıkları ve master’a pushladıkları değişiklikleri sıkça dahil edebilir, aynı zamanda paylaşımlı branchlere fast-forward merge yapmanızı sağlayan temiz, doğrusal bir geçmiş tutabilirsiniz.

Doğrusal bir geçmiş tutmak aynı zamanda commit loglarınıza bakmayı ya da okumayı (git log --graph yazmayı deneyin ya da GitHub ya da GitLab’te bir branch görüntüsüne bakın) çoğunlukla varsayılan mesajlar içeren merge commitlerle dolu geçmişlerden çok daha faydalı hale getirir.

Conflictleri Çözmek

Tıpkı merge işleminde olduğu gibi bir dosyadaki aynı yerleri değiştiren iki commit işlemi yaparsanız conflictlerle karşılaşabilirsiniz.

Ancak rebase işlemi esnasında bir conflict ile karşılaşırsanız bunu ekstra bir merge commit’inde düzeltmezsiniz, bunun yerine uygulanan commit üzerinde kolayca çözebilirsiniz.

Bunu yine değişikliklerinizde orijinal branch’in mevcut halini baz alarak yaparsınız.

Aslında rebase ile conflictleri çözmek tıpkı merge ile yaptığınız gibidir, bu yüzden nasıl yapılacağı konusunda emin değilseniz o bölüme tekrar bakabilirsiniz.

İkisi arasındaki tek fark burada bir merge commit verilmediği için sonucunuzu commitlemeye ihtiyacınız yoktur. Değişikliklerinizi doğrudan add diyerek Staging Environment’a alabilir, ardından git rebase --continue diyebilirsiniz. Düzenlenen commit içerisinde conflict de çözülecektir.

Merge işleminde de olduğu gibi git rebase --abort yazarak durdurup, yaptığınız her şeyi iptal edebilirsiniz.

Remote Değişikliklerle Dev Environment’ı Güncellemek

Buraya kadar sadece değişiklik yapıp bu değişiklikleri nasıl paylaşacağımızı gördük.

Bu, yalnızca tek başınızına çalışıyorsanız sizin için yeterli olur ancak genellikle aynı işlemi yapan birçok insan olacak ve yapılan bu değişiklikleri Remote Repository’den alıp Dev Environment’a bir şekilde çekmek isteyeceğiz.

Üzerinden biraz zaman geçtiği için git’in bileşenlerine tekrar bir bakalım:

Dev Environment’ınızda olduğu gibi herkes aynı kod kaynağında kendi kodu ile çalışır.

Buradaki her bir Dev Environment’ının kendine ait çalışan ve stage’e alınmış, bir noktada Local Repository’ye commitlenmiş ve Remote’a pushlanmış değişiklikleri vardır.

Örneğimizde, biz çalışırken başka birini remote’ta değişiklikler yapmış gibi göstermek için GitHub’ın sunduğu online araçları kullanacağız.

Devam edin ve bu repoyu github.com’da forklayın ve Alice.txt dosyasını açın.

Edit butonunu bulun ve web sayfası üzerinden değişiklikler yapıp commitleyin.

Bu repository içerisindeki fetching_changes_sample adında bir branch’te Alice.txt dosyasına remote bir değişiklik ekledim, ancak repository’nin sizdeki halinde dosyanızı doğrudan master'da değişitirebilirsiniz.

Değişiklikleri Almak (Fetching)

git push dediğiniz zaman Local Repository’de yaptığınız değişiklikleri Remote Repository’ye senkronize ettiğimizi hatırlıyoruz.

Remote içerisinde yaptığımız değişiklikleri Local Repository’mize almak için git fetch komutunu kullanıyoruz.

Bu remote üzerindeki tüm değişiklikleri -commitlerle beraber branchleri de- Local Repository’nize alacaktır.

Bu noktada değişikliklerin henüz local branchlere ve bu sebeple de Working Directory ile Staging Area’ya katılmadığını unutmayın.

Şimdi git status derseniz, tam olarak neler olup bittiğini size açıklayan harika bir git komutu örneği göreceksiniz:

git status
On branch fetching_changes_sample
Your branch is behind 'origin/fetching_changes_sample' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)

Değişikleri Çekmek (Pulling)

Working’de ya da stage’e alınmış değişiklerimiz olmadığı için Repository’deki tüm değişiklikleri git pull diyerek doğrudan çalışma alanımıza alabiliriz.

Pull işlemi dolaylı olarak Remote Repository’de fetch işlemi de yapmış olacak ancak bazen fetch'i kendi başına kullanmak da iyi bir fikir olabilir.

Örneğin herhangi remote bir branch’i senkronize etmek ya da origin/master gibi bir yerde git rebase yapmadan önce Local Repository’nizin güncel olduğundan emin olmak için kullanabilirsiniz.

pull demeden önce dosyayı localde değiştirip neler olduğuna bir bakalım.

Aynı zamanda Alice.txt'i Working Directory’de değiştirelim.

Şimdi git pull derseniz aşağıdaki hatayı göreceksiniz:

git pull
Updating df3ad1d..418e6f0
error: Your local changes to the following files would be overwritten by merge:
Alice.txt
Please commit your changes or stash them before you merge.
Aborting

pull yaparkenki commitlerle de değişmiş olan Working Directory içerisinde düzenlemeler olduğu sürece herhangi bir değişikliğe pull işlemi yapamıyorsunuz.

Bunun bir yolu değişikliklerinizi kendinizden emin olduğunuz bir noktaya getirip commitlemeden önce Staging Environment’a add işlemi ile eklemeniz. Burası başka harika bir araç olan git stash'ı öğrenmek için iyi bir zaman.

Değişiklikleri Zulalamak (Stashing)

Eğer bir noktada henüz commitlemek istemediğiniz local değişiklikleriniz varsa ya da bir problemi farklı bir bakış açısı ile incelemek isterseniz mevcut değişiklikleri stash ile zulalayabilirsiniz.

git stash temelde Working Directory içerisindeki değişiklikleri depoladığınız bir yığındır.

Sıklıkla kullanacağınız komutlar, Working Directory içerisindeki herhangi bir değişimi tuttuğunuz git stash ve zuladaki son değişikliği alıp tekrar Working Directory’ye uygulayan git stash pop olacaktır.

Tıpkı adlarını aldığı stack komutları gibi git stash pop da uygulamayı yapmadan önce zuladaki son değişikliği silecektir.

Ancak zulaladığınız değişiklikleri tutmak istiyorsanız, git stash apply komutunu kullanabilirsiniz, böylece uygulamadan önce onları silmeyecektir.

Mevcut stash'ı ve her bir girdiyi incelemek için git stash list'i ve stash'deki son değişikliklerin girdisini göstermek için git stash show'u kullanabilirsiniz.

Bir başka işe yarar komut ise değişiklikleri stash’e aldıktan sonra HEAD’den başlayan bir branch oluşturan ve ardından stash’teki değişiklikleri bu branch'e taşıyan git stash branch {BRANCH ADI}’dır.

Artık git stash'i öğrendiğimize göre bunu, local değişikliklerimizi Working Directory’den Alice.txt'e alıp silmek için kullanalım; böylece devam edebilir ve web sayfası aracılığı ile yaptığımız değişiklikleri git pull yaparak çekebiliriz.

Bundan sonra git stash pop diyerek değişiklikleri geri alalım. Hem pull yaparak oluşturduğumuz commit hem de Alice.txt'deki stash'e alınmış değişiklikler sebebiyle oluşan conflict’i tıpkı merge ya da rebase ile yaptığınız gibi çözmeniz gerekecek.

Değişiklikleri tamamladığınızda add ve commit diyebilirsiniz.

Conflictlerle Beraber Pull

Artık Remote değişiklikleri nasıl Dev Environment’a fetch ve pull yaptığımızı öğrendiğimize göre sıra bazı conflictler çıkarmakta!

Alice.txt dosyasını değiştiren commit’i pushlamayın ve github.com’daki Remote Repository’nize dönün.

Burada da Alice.txt dosyasını değiştirip değişiklikleri commitleyeceğiz.

Şimdi Local ve Remote Repositorylerde iki tane conflict oluştu.

Doğrudan pull demeden önce git fetch diyerek remote değişikliklere bakmayı unutmayın.

git status derseniz iki branch’in de diğerinden farklı olan birer commit’i olduğunu görecekseniz.

git status
On branch fetching_changes_sample
Your branch and 'origin/fetching_changes_sample' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)

Bu commitlerle aynı dosyayı değiştirmiş olmamızın yanı sıra çözmemiz gereken bir merge conflict’i ile karşı karşıyayız.

Local ve Remote Repository’lerde farklılıklar varken git pull dediğiniz zaman iki branch’i merge yapmaya çalıştığınız zaman olan şeyin aynısı olur.

Buna ek olarak bunu Remote’daki ve Local Repository’deki iki branch’in arasındaki ilişkiyi bir branch’i başka birine temel alarak oluşturulan özel bir durum olarak düşünebilirsiniz.

Local branch, Remote’un en son fetch işlemi uyguladığınız halini temel alır.

Bu şekilde düşündüğünüzde remote değişiklikleri almanın iki yolu mantıklı gelmeye başlar:

git pull yaptığınız zaman branch’in Local ve Remote versiyonları merge olacak. Tıpkı iki branch’i merge yapıyormuşuz gibi bu bir merge commit oluşturacak.

Tüm local branchler kendilerinin remote versiyonlarını baz aldığı için rebase de yapabiliriz, böylece localde yaptığımız herhangi bir değişiklik sanki Remote Repository’deki mevcut en son halini baz almış gibi duracaktır. Bunu yapmak için git pull --rebase (ya da kısaca git pull -r) diyebiliriz.

Rebase’in detaylı bölümünde anlatıldığı gibi temiz doğrusal bir geçmiş tutmanın bazı faydaları vardır, bu sebeple ne zaman git pull yaparsanız bir de git pull -r yapmanızı şiddetle tavsiye ederim.

Aynı zamanda git’e git pull yaptığınız zamanlarda merge yerine rebase'i varsayılan olarak kullanmak istediğinizi söyleyebilirsiniz. pull.rebase‘i git config --global pull.rebase true gibi bir komutla ayarlayabilirsiniz.

Eğer birkaç paragraf önce bahsettiğim git pull'u hala çalıştırmadıysanız şimdi git pull -r yazarak remote değişiklikleri alıp yeni commit’imizi onlardan hemen sonra gelmiş gibi gösterebiliriz.

Tabii ki normal bir rebase (ya da merge) ile olduğu gibi git pull işlemini tamamlamak için conflict’i çözmeniz gerekecek.

Cımbızlamak (cherry picking)

Tebrikler! Daha ileri seviye özelliklere kadar geldiniz!

Şimdiye kadar sıradan git komutlarını kullanmayı daha da önemlisi nasıl işlediklerini öğrendiniz.

Umarım bu ileriki kavramları doğrudan hangi komutları yazmanızı söylememden ziyada onları anlamanızı kolaylaştıracaktır.

O halde hemen commitleri nasıl cherry-pick yapabileceğimizi öğrenmeye başlayalım!

İlk bölümlerde bahsettiğimiz, bir commit'i oluşturan şeyleri hatırlıyorsunuz, değil mi?

Ve bir branch’i rebase yaparken commitlerinizin değişmiş hali ve mesajı ile yeni commitler olarak uygulandığını da hatırlıyorsunuz?

Ne zaman bir branch’teki değişikliklerden birkaçını seçip, bunları bir başka branch’e uygulamak isterseniz bu commitlere cherry-pick yapar ve onları kendi branch’inize eklersiniz.

Bu tam olarak git cherry-pick'in ya tek commit ile ya da bir dizi commit ile yapmanızı sağladığı şeydir.

Tıpkı rebase'de olduğu gibi bu commitlerdeki değişiklikleri alıp mevcut branch’inizde yeni bir commit’e çevirecek.

Bir ya da birkaç commit'e yapılancherry-pick örneğine bakalım:

Aşağıdaki şema hiçbir şey yapmadan önceki halleriyle üç branch’i gösteriyor. Haydi add_patrick branch’inden change_alice branch’ine bazı değişiklikleri almak istediğimizi varsayalım. Maalesef ikisi de henüz master’a dahil olmadılar, bu yüzden bu değişiklikleri (ve istemesek bile diğer branchlerdeki diğer değişiklikleri) almak için master’a rebase yapamıyoruz.

O halde 63fc421 commitine git cherry-pick yapalım. Aşağıdaki şema git cherry-pick 63fc421 yazdığımızda neler olduğunu görselleştiriyor:

Gördüğünüz gibi istediğimiz değişikliklerle birlikte yeni commit’imiz branch’te gözüküyor.

Bu noktada daha önce gördüğümüz tüm değişiklikleri alma yolları gibi cherry_pick için de çıkacak tüm conflictler komut çalışmadan önce tarafımızdan çözülmek zorunda.

Ayrıca tıpkı diğer komutlarda olduğu gibi cherry-pick için de conflictler çözüldüğünde --continue ya da tamamen iptal etmek için --abort diyebiliriz.

Aşağıdaki görsel tek bir commit yerine bir dizi commit olduğu zaman yapılan cherry-pick işlemini gösteriyor. Bunu doğrudan git cherry-pick <from>..<to> komutunu çağırarak yapabilir ya da aşağıdaki örnekte olduğu gibi git cherry-pick 0cfc1d2..41fbfa7 diyebiliriz.

Geçmişi Tekrar Yazmak

Kendimi tekrar etmeye başladım ancak rebase’i hatırlıyorsunuz, değil mi? Değilse geçmişi değiştirmeyi öğrenirken hali hazırda öğrendiklerimizi kullanacağımız için devam etmeden önce hızlıca o bölüme bir bakın!

Bildiğiniz gibi bir commit değişikliklerini, mesajını ve birkaç şeyi daha içerir.

Bir branch’in “geçmişi” ise bu commitlerin bir araya gelmesiyle oluşur.

Yeni bir commit oluşturduğunuzu ancak bir dosyayı eklemeyi unuttuğunuzu ya da bir yazım hatası yaptığınız için bozuk bir kodunuz olduğunu varsayalım.

Bunu düzeltmek için iki şeye kısaca bakacağız ve bunlar hiç olmamış gibi göstermeye çalışacağız.

Haydi git checkout -b rewrite_history yazarak yeni bir branch’e geçelim.

Şimdi hem Alice.txt'de hem de Bob.txt'de değişiklikler yapalım ve git add Alice.txt diyelim.

Ardından “Bu geçmiş.” gibi bir commit mesajı ile git commit diyelim ve tamamız.

Bir dakika, tamamız mı dedim? Hayır, açıkça görünüyor ki bazı hatalar yaptık.

Son Commit’i Düzenlemek

Bu ikisini düzeltmenin bir yolu yaptığımız bu commit’e amend işlemi yapmaktır.

En son commit’e amend işlemi yapmak yeni bir tane yapıyormuşuz gibi işler.

Hiçbir şey yapmadan önce git show {COMMIT} yazarak en son commit’e bir göz atın. Ya commit’in hash’ını (bunu muhtemelen git commit‘ten ya da git log yazarak görebilirsiniz) ya da sadece HEAD yazın.

Tıpkı git log'da olduğu gibi mesajı, mesajı yazan kişiyi, tarihi ve tabii ki değişiklikleri göreceksiniz.

Şimdi bu committe yaptığımız şeyleri amend ile düzenleyelim.

Değişiklikleri Staging Area’ya almak için git add Bob.txt yazalım ve ardından git commit --amend diyelim.

Ardından en son commit’iniz listeden silinir, Staging Area’daki yeni değişiklikleriniz mevcut olana dahil edilir ve commit mesajı için editor açılır.

Editor’de bir önceki commit mesajını göreceksiniz.

Bunu daha iyi bir şeyle değiştirebilirsiniz.

Tamamladıktan sonta git show HEAD yazarak en son commit’inize son bir kez bakın.

Tam olarak beklediğiniz gibi commit hash’ınız artık farklı. Orijinal commit gitti ve yerine değişiklikleri ve yeni commit mesajı ile yeni bir tanesi geldi.

Orijinal commit’teki commit mesajını yazan kişinin ve tarihin değişmediğine dikkat edin. Eğer istiyorsanız bunları da amen işlemi esnasında --author={AUTHOR} ve --date={DATE} flagleri ile değiştirebilirsiniz.

Tebrikler! Başarılı bir şekilde ilk geçmişi tekrar yazma işleminizi tamamladınız!

İnteraktif Rebase

Genellikle git rebase dediğimizde bir branch’e rebase yaparız. git rebase origin/master gibi bir şey yaparsak, aslında o branch’in HEAD’ine rebase olur.

Aslında istersek herhangi bir commit’e rebase yapabiliriz.

Unutmayın, bir commit kendinden önceki geçmişin de bilgisini içerir.

Pek çok başka komutta olduğu gibi git rebase'in de interaktif modu vardır.

Pek çoğunun aksine, interaktif rebase geçmişi istediğiniz kadar değiştirmenize müsaade ettiği için sıklıkla kullanacağınız bir şeydir.

Özellikle de değişikliklerinizi küçük commitler halinde oluşturduğunuz bir iş akışını takip ediyorsanız, bir hata yaptığınızda kolayca geri dönmenizi sağladığı için interaktif rebase sıkı bir dostunuz olacaktır.

Yeteri kadar konuştuk! Haydi, bir şeyler yapalım!

Tekrar master branch’ine dönün ve git checkout ile çalışacağınız yeni branch’e geçin.

Daha önceki gibi hem Alice.txt'de hem de Bob.txt'de değişiklikler yapacağız ve ardından git add Alice.txt diyeceğiz.

Ardından “Add text to Alice” mesajı ile git commit diyeceğiz.

Şimdi bu commit’i silmek yerine git add Bob.txt diyeceğiz ve bu değişikliği de git commit ile ekleyeceğiz. Bunun için de “Add Bob.txt” mesajını kullandım.

İşleri daha da ilginç hale getirmek için Alice.txt'de bir başka değişiklik yapacağız ve git add ile git commit diyeceğiz. Mesaj olarak da “Add more text to Alice” diyeceğiz.

Şimdi git log ile (ya da daha hızlı bir bakış için tercihen git log --oneline) branch’in geçmişine bakarsak master’da her ne varsa onun üzerinde üç commitimizi göreceğiz.

Bende şu şekilde gözüküyorlar:

git log --oneline
0b22064 (HEAD -> interactiveRebase) Add more text to Alice
062ef13 Add Bob.txt
9e06fca Add text to Alice
df3ad1d (origin/master, origin/HEAD, master) Add Alice
800a947 Add Tutorial Text

Burada farklı şeyleri öğrenmek adına düzeltmek istediğimiz iki şey var, bir önceki amed bölümünden biraz farklı olacak.

  • Alice.txt'deki her iki değişikliği de tek bir commit’e almak.
  • İsimlendirmelerde sürekliliği sağlamak ve Bob.txt ile ilgili mesajdaki .txt uzantısını silmek.

Üç commiti değiştirmek için onlardan bir önceki commit’e rebase etmemiz gerekiyor. Bu commit benim için df3ad1d ancak bunu aynı zamanda mevcut HEAD’deki üçüncü commit olduğu için HEAD~3 olarak da yazabiliriz.

İnteraktif rebase’i başlatmak için git rebase -i {COMMIT} komutunu kullanıyoruz, bu yüzden haydi git rebase -i HEAD~3 diyelim.

Seçtiğiniz editörün şöyle bir şey gösterdiğini göreceksiniz:

pick 9e06fca Add text to Alice
pick 062ef13 Add Bob.txt
pick 0b22064 Add more text to Alice
# Rebase df3ad1d..0b22064 onto df3ad1d (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Git’in herhangi bir komut çalıştırdıktan sonra yapabileceğiniz şeyleri o esnada açıkladığına dikkat edin.

Muhtemelen en çok kullanacağınız komutlar reword, squash ve drop olacak (Bir de pick ancak bu varsayılan olarak mevcut).

Bir dakikanızı ayırarak gördüğünüz şeyi biraz inceleyin ve bu noktadan sonra hedeflerimize ulaşmak için ne kullacağımızı düşünün. Bekleyeceğim.

Bir planınız var mı? Harika!

Değişiklikleri yapmaya başlamadan önce commitlerin eskiden yeniye doğru listelendiğini ve bu yüzden git log çıktısının ters yönünde olduğu gerçeğini aklınızda tutun.

Kolay olan değişiklikle başlayıp yapacağım, böylece ortadaki commit’in mesajını değiştirebileceğiz.

pick 9e06fca Add text to Alice
reword 062ef13 Add Bob.txt
pick 0b22064 Add more text to Alice
# Rebase df3ad1d..0b22064 onto df3ad1d (3 commands)
[...]

Şimdi Alice.txt'in iki değişikliğini tek bir commit’e alıyoruz.

Tabii ki bizim asıl yapmak istediğimiz ikincisini ilki ile squash yapmak, bu yüzden Alice.txt dosyasını değiştiren ikinci commit’e pick yerine bu komutu koyalım. Örnekte benim için bu 0b22064.

pick 9e06fca Add text to Alice
reword 062ef13 Add Bob.txt
squash 0b22064 Add more text to Alice
# Rebase df3ad1d..0b22064 onto df3ad1d (3 commands)
[...]

Tamam mıyız? Bu, yapmak istediğimiz şeyi yapacak mı?

Yapmayacak, değil mi? Dosyadaki yorumların da bize dediği gibi:

# s, squash = use commit, but meld into previous commit

Şimdiye kadar yaptığımız şey ikinci Alice commit’inin değişikliklerini Bob commit’i ile birleştirmek oldu. Bu istediğimiz şey değil.

İnteraktif rebase ile yapabildiğimiz bir başka şey de commitlerin sırasını değiştirmek.

Eğer yorumların size söylediklerini dikkatlice okuduysanız zaten nasıl yapacağınızı biliyorsunuz. Sadece satırların yerini değiştirin!

Neyse ki favori text editor’ünüzdesiniz, bu yüzden devam edin ve ikinci Alice commit’inin yerini ilki ile değiştirin.

pick 9e06fca Add text to Alice
squash 0b22064 Add more text to Alice
reword 062ef13 Add Bob.txt
# Rebase df3ad1d..0b22064 onto df3ad1d (3 commands)
[...]

Bu sorunu halledecektir, o yüzden editor’ü kapatın ve git'e komutları çalıştırmasını söyleyin.

Ardından olan şey sıradan bir rebase işleminin aynısı: başlarken referans aldığınız commitle başlayarak listelediğiniz her bir commit birbiri ardına uygulanacaktır.

Şimdi olmayacak ancak gerçek kod değişimlerini yeniden sıraladığınızda rebase esnasında bazı conflictler çıkabilir. Sonuçta birbiri üzerine inşa etmeye çalıştığımız değişiklikleri karıştırmış olabilirsiniz.

Bu conflictleri de her zaman yaptığınız gibi çözün.

İlk commitinizi attıktan sonra editor açılacak ve Alice.txt'in değişikliklerini bir araya getiren commit için yeni bir mesaj yazmanıza izin verecek. Ben her iki commit için de döktürerek “Add a lot of very important text to Alice” yazdım.

Bu commit’i bitirmek için editor’ü kapattığınızda Add Bob.txt'nin mesajını değiştirmeniz için tekrar açılacak. .txt uzantısını silin ve editor’ü kapatarak devam edin.

Bu kadardı! Geçmişi bir kere daha tekrar yazdınız. Bu sefer amend'den daha ciddi bir şekilde.

Eğer tekrar git log derseniz daha önceden üç tane olan yerde iki yeni commit göreceksiniz. Fakat artık rebase'in yaptığı şeye hakimsiniz ve bunu bekliyordunuz.

git log --oneline
105177b (HEAD -> interactiveRebase) Add Bob
ed78fa1 Add a lot very important text to Alice
df3ad1d (origin/master, origin/HEAD, master) Add Alice
800a947 Add Tutorial Text

Public Geçmiş, Neden Tekrar Yazmamalısınız ve Yine de Nasıl Güvenli Bir Şekilde Yapabilirsiniz

Daha önce değindiğimiz gibi geçmiş değiştirmek, çalışırken ufak tefek birçok commit dahil edilen iş akışlarının son derece faydalı bir parçasıdır.

Tüm bu küçük atomik değişiklikler, her seferinde tüm testlerinizin geçtiğinden emin olmanızı ve eğer geçmiyorlarsa silmenizi ya da belirli değişiklikler yapmanızı sağladığı için son derece faydalı olsa da HelloWorld.java gibi bir dosyaya eklediğiniz 100 commit insanlarla çok da paylaşmak isteyeceğiniz bir şey olmayacaktır.

Büyük bir ihtimalle paylaşmak isteyeceğiniz şey, iş arkadaşlarınızda neyi neden yaptığınızı açıklayan ve hoş commit mesajları içeren, güzelce oluşturulmuş commitler olacaktır.

Bu küçük commitler sizin Dev Environment’ınızda olduğu sürece güvenle git rebase -i diyebilir ve dilediğiniz yere kadar geçmişinizi değiştirebilirsiniz.

İşler Public History olduğu zaman karışıyor ve başka insanların branchleri bu geçmişe bağlı olabiliyor. Bu da durumu işleri çok karıştırmak istemeyeceğiniz bir şey haline getiriyor.

Genel bir mantra olarak “Asla public geçmişi değiştirmeyin!” diyebilirim ve bunu tekrar vurgulasam da yine de itiraf etmeliyim, public geçmişi değiştirmek isteceğiniz makul miktarda durumlar da vardır.

Tüm bu durumlarda geçmiş tam anlamıyla “public” değildir. Tabii ki open source bir projenin master branch’inde ya da şirkenizin release branch’inde asla değişiklik yapmak istemezsiniz.

İş arkadaşlarınızla paylaşmak için henüz pushladığınız branchlerin geçmişleri üzerinde değişiklik yapmayı isteyebilirsiniz.

Trunck-based development tarzında geliştirme yapıyor ancak henüz compile edilmemiş bir şey paylaşmak istiyor olabilirsiniz, bu sebeple tabii ki bunu bile isteye main branch’e koymazsınız.

Ya da feature branchlerini paylaştığınız bir iş akışınız vardır.

Umuyorum ki, özellikle feature branchlerini sık sık mevcut master'a rebase yapıyorsunuzdur. Ancak bildiğimiz gibi git rebase branch’in commitlerini baz aldığımız yerin üzerine yeni commitler olarak ekliyor. Bu da geçmişi değiştirmektir. Ve bu durumda paylaşılan feature branch’i geçmişi değiştirmiş olur.

O halde “Asla public geçmişi değiştirmeyin!” mantrasını dinlersek ne yapmamız gerekecek?

Branch’imizi asla rebase yapmayacağız ancak yine de en sonunda master ile merge olmasınız mı umacağız?

Paylaşılan feature branchlerini kullanmayacak mıyız?

Aslında ikincisi gayet mantıklı bir cevap ancak yine de bunu yapmanız mümkün olmayabilir. Bu yüzden yapabileceğiniz tek şey geçmişi değiştirmeyi kabullenmek ve bu değiştirilmiş geçmişi Remote Repository’ye pushlamak.

Doğrudan git push derseniz local branch’iniz remote’tan ayrı olduğu için bunu yapmaya yetkiniz olmadığını göreceksiniz.

Değişikliklerinizi force ile pushlamanız gerekecek ve böylece remote’u local versiyonu ile değiştirmiş olacaksınız.

Vurguladığım gibi muhtemelen şimdi git push --force yapmaya hazırsınız. Ancak public geçmişi güvenli bir şekilde değiştirmek istiyorsanız bunu yapmamalısınız!

--force'un daha dikkatli olan kardeşi --force-with-lease komutunu kullansanız çok daha iyi olacaktır!

--force-with-lease pushlamadan önce remote branch’in local versiyonu ile asıl remote’un eşleşip eşleşmediğine bakacaktır.

Böylece siz geçmişi değiştirirken başka biri yeni değişiklikler yapıppushladıysa onları yanlışlıkla silmediğinizden emin olursunuz.

Bu konuda size hafif değiştirilmiş yeni bir mantra sunacağım:

“Ne yaptığınızdan emin değilseniz public geçmişi değiştirmeyin. Eğer eminseniz güvenliği elden bırakmayın ve force-with-lease’i kullanın.

Geçmişi Okumak

Dev Environment’ınızdaki — özellikle de Local Repository — farklılıkları ve commitler ile geçmişin nasıl çalıştığını bilmek ya da rebase işlemleri yapmak sizin için korkutucu olmamalı.

Yine de bazen işler yolunda gitmeyebilir. Bir rebase yapmış olabilirsiniz ve bir conflict’i çözerken dosyanın yanlış bir versiyonunu kabul etmişsinizdir.

Eklediğiniz dosya yerine, sadece iş arkadaşlarınızın bir dosyadaki log satırı eklenmiştir.

Neyse ki git gömülü bir güvenlik özelliği olan Reference Logs yani bir diğer adıyla reflog ile yanınızda.

Local Repository’nizde, branch’inizdeki bir referans güncellendiğinde bir Reference Log’u girdisi eklenir.

Bu yüzden ne zaman bir commit atarsanız, reset yaparsanız ya da HEAD'e giderseniz bunların birer kaydı oluşur.

Bu postu buraya kadar okumuş olduğunuza göre bu durumun rebase ile işleri karıştırdığımızda işimize yarayacağını görebiliyorsunuz, değil mi?

rebase'in, branch HEAD'inin yerini, baz aldığımız yere göre değiştirdiğini ve değişiklikleri uyguladığını biliyoruz. İnteraktif base de benzer şekilde işler ancak bu commitlere squash ya da reword işlemleri de yapabiliyorsunuz.

Eğer hala interaktif branch’e baktığımız branch’te değilseniz, burada biraz daha çalışma yapacağımız için buraya tekrar dönün.

Bu branch’te neler yaptığımızın bir reflog'una bakacağız; doğru tahmin ediyorsunuz, git reflog komutunu kullanarak.

Muhtemelen birçok çıktı göreceksiniz ancak en tepedeki birkaç satır şuna benzer bir şey olmalı:

git reflog
105177b (HEAD -> interactiveRebase) HEAD@{0}: rebase -i (finish): returning to refs/heads/interactiveRebase
105177b (HEAD -> interactiveRebase) HEAD@{1}: rebase -i (reword): Add Bob
ed78fa1 HEAD@{2}: rebase -i (squash): Add a lot very important text to Alice
9e06fca HEAD@{3}: rebase -i (start): checkout HEAD~3
0b22064 HEAD@{4}: commit: Add more text to Alice
062ef13 HEAD@{5}: commit: Add Bob.txt
9e06fca HEAD@{6}: commit: Add text to Alice
df3ad1d (origin/master, origin/HEAD, master) HEAD@{7}: checkout: moving from master to interactiveRebase

İşte. Branchleri değiştirmemizden tutun yaptığımız rebaselere kadar her şey burada.

Yaptığımız şeyleri görmek oldukça güzel ancak her satırın başındaki referanslar olmasaydı ve bir yerlerde hata yapmış olsaydık bunlar tek başına bir işe yaramazdı.

Reflog çıktısını log’a en son baktığımız hali ile karşılaştırırsanız, bu noktaların commit referansları ile ilgili olduğunu görecesiniz ve bunları böylece kullanabiliriz.

Örneğin aslında rebase yapmak istemediğimizi düşünelim. O zaman yapmak istediğimiz değişikliklerden nasıl kurtulacağız?

HEAD'i git reset 0b22064 ile başlayan rebase yapmadan önceki noktaya taşıyoruz.

0b22064 benim rebase yapmadan bir önceki commit’im.

Bunu daha genel anlamda HEAD@{4} aracılığı ile dört değişiklik önceki HEAD olarak da referans verebilirsiniz. Branchler arası geçişler ya da günlük loglar oluşturan başka bir şeyler yaptıysanız burada çok daha yüksek sayılar göreceğinizi unutmayın.

Şimdi log'a bir göz atarsanız geri gelen üç ayrı commit’in orijinal halini görebilirsiniz.

Ancak bunun istediğimiz şey olmadığını varsayalım. rebase'de bir problem yoktu, sadece Bob commit’inin değiştirdiğimiz mesajından hoşlanmadık.

Mevcut durumda, normalde nasıl yapıyorsak doğrudan başka bir rebase -i yapabiliriz.

Ya da reflog kullanarak rebase’den sonraki yere gelir ve commit’e burada amend işlemi yaparız.

Ancak artık bunların ikisinin de nasıl yapıldığını biliyorsunuz, bu yüzden bunu size bırakacağım. Ek olarak, yine biliyorsunuz ki, reflog size yanlışlıkla yaptığınız şeyleri geri alma imkanı da sunuyor.

--

--