Ders 05 / 15
İleri Sarma ve Birleştirme İşlemesi
İki dalın birleşmesi tarihçeye ya hiçbir şey yazar ya da iki öncüllü bir işleme; on iki işlemelik gelişim birleştirme işlemesiyle on beşe çıkıp altı sorunun beşini, ileri sarmayla on ikide kalıp üçünü yanıtlar.
İçindekiler
Dal açmak ucuzdu: bir başvuru dosyasına yazılan tek bir işleme kimliği. Ad koymak düzen getirdi: dal adları okunduğunda kimin neyi geliştirdiği anlaşılır oldu. İki işlem de tarihçeyi büyütmedi — yalnız ona işaretçiler ekledi.
Bu dersin sorusu bundan sonrasıdır. İki dal ayrıldıktan sonra bir noktada birleşir (merge); peki birleşme tarihçeye ne yazar? Yanıt tek değildir. Aracın iki ayrı davranışı vardır ve hangisinin uygulandığı, aylar sonra tarihçeye sorulacak soruların kaçının yanıtlanabileceğini belirler.
Birleşmenin İki Durumu
Bir dalı ana dala almak, iki işleme kimliğinin ortak atasını bulmak ve aradaki farkı uygulamaktır. Aracın önünde iki ayrı durum vardır.
Birinci durum: dal ayrıldıktan sonra ana dala hiç işleme eklenmemiştir. O zaman dalın işlemeleri zaten ana dalın son işlemesinin üzerine kuruludur ve birleştirilecek bir şey yoktur. Aracın yapacağı tek iş, ana dalın işaretçisini dalın ucuna kaydırmaktır. Buna ileri sarma (fast-forward) denir.
İkinci durum: iki taraf da ayrıldıktan sonra işleme almıştır. Tarihçe gerçekten çatallanmıştır ve tek bir işaretçi kaydırmasıyla kapatılamaz. Araç iki tarafın içeriğini birleştirir ve sonucu iki öncüllü yeni bir işlemeye yazar: birleştirme işlemesi (merge commit).
# öğretilen komut, örnek döküm — çalıştırılmamıştır $ git merge olcum Updating 3f1a2b0..9c4d7e1 Fast-forward olcum.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) $ git merge rapor Merge made by the 'ort' strategy. rapor.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
İlk çağrı Fast-forward diyor: yeni bir işleme üretilmedi. İkincisi bir strateji adıyla
birleştirme yaptığını bildiriyor: yeni bir işleme üretildi. Aynı komut, iki ayrı sonuç.
Ortak Ata ve Üçlü Birleştirme
Aracın hangi durumda olduğunu anlaması için önce ortak atayı bulması gerekir: iki dalın uçlarından geriye gidildiğinde ulaşılan ilk ortak işleme. Ortak ata, iki dalın ayrıldığı noktadır. Birinci durumun tanımı ortak ata cinsinden yazılabilir: ortak ata ana dalın ucuna eşitse ana dal ayrılmadan beri hiç ilerlememiştir ve ileri sarma uygulanabilir.
Ortak ata ana dalın ucuna eşit değilse birleştirme üç dosya sürümü üzerinden yapılır: ortak atadaki sürüm, ana daldaki sürüm ve dallardaki sürüm. Buna üçlü birleştirme (three-way merge) denir ve üçüncü sürüm, yani atadaki hâl, ölçüyü veren şeydir. İki sürümü yan yana koymak yeterli olsaydı araç her farkı bir çatışma sayardı; ata sürümü sayesinde araç bir satırın kim tarafından değiştirildiğini bilir. Yalnız bir taraf değiştirmişse o tarafın sürümü alınır; iki taraf da aynı satırı ayrı biçimde değiştirmişse karar verilemez ve birleştirme çakışması doğar.
Ortak ata bir arama sonucudur, tarihçede yazılı bir alan değil. Bu, birleştirmenin maliyetinin dal ömrüyle birlikte arttığı anlamına gelir: ata ne kadar geride kalırsa iki taraf o kadar çok satırda ayrışmıştır.
İleri Sarmanın Yazdığı
İleri sarma tarihçeye hiçbir şey yazmaz. Ana dalın başvurusu yeni bir kimliği gösterir, o kadar. Dalın işlemeleri olduğu gibi ana dalın tarihçesinde belirir; ama tarihçede onların bir dalda geliştirildiğini söyleyen hiçbir kayıt kalmaz. Dal işaretçisi silindiğinde, o işlemelerin ayrı bir işe ait olduğu bilgisi de silinir.
Seçim aracın eline bırakılmak zorunda değildir. Varsayılan davranış uygulanabilir olduğunda
ileri sarar; --no-ff seçeneği ileri sarma mümkün olsa bile iki öncüllü bir işleme
üretmeye zorlar; --ff-only seçeneği bunun tersini yapar ve ileri sarma uygulanamıyorsa
birleştirmeyi yapmadan durur. Üçüncüsü ilk bakışta işe yaramaz görünür ama bir güvenlik
kilididir: tarihçesinin doğrusal kalmasını isteyen bir ekipte, farkında olmadan üretilmiş
bir birleştirme işlemesinin girmesini engeller. Üç ayarın hiçbiri öbüründen doğru değildir;
üçü de aynı sorunun üç ayrı yanıtıdır ve o soruyu bu dersin ölçümü sorar.
Birleştirme İşlemesinin Yazdığı
Birleştirme işlemesinin tek özel yanı iki öncülünün olmasıdır. Birinci öncül ana dalın o anki ucu, ikinci öncül birleştirilen dalın ucudur. Bu ikinci kenar, tarihçenin dal yapısını taşıyan tek şeydir.
# öğretilen komut, örnek döküm — çalıştırılmamıştır $ git log --oneline --graph * a1b2c3d rapor dalını birleştir |\ | * 7e8f9a0 rapor: özet satırını ekle | * 4d5e6f1 rapor: alan genişliğini düzelt |/ * 9c4d7e1 olcum: eşik değerini yazdır
Sol kenardaki çizim ikinci öncülden üretilir. Girintili iki satır rapor dalının
işlemeleridir; |/ işaretinin bulunduğu satır ayrılma noktasını gösterir. Bu çizim ileri
sarmayla alınmış bir dal için çizilemez, çünkü çizilecek kenar yoktur.
Buradaki kimlikler ve dosya adları kurgudur. Aynı komutu kendi deponuzda çalıştırdığınızda kimlikler farklı olur; sabit olan çıktının yapısıdır.
Ölçünün Kurulması
Kurs boyunca sürecek kurgu şudur: üç kişi aynı depoda üç ayrı işe başlıyor — olcum,
rapor ve kimlik. Her biri dört işleme yapıyor. Üçüncü adımda olcum ile rapor aynı
dosyaya, ayar.py dosyasına dokunuyor. rapor dalının ikinci işlemesi bir kusur
getiriyor. Gerçek gelişim 12 işlemedir ve gerçek sırası bilinir, çünkü kurguyu biz
yazdık.
Bu on iki işleme ayrı tümleştirme biçimleriyle (integration format) ana dala alınır. Biçimlerin hepsi aynı kodu üretir; ürettikleri tarihçe ayrıdır. Kurs bu farkı tek bir ölçütle ölçer: tarihçeye altı soru sorulur ve kaçını yanıtlayabildiği sayılır.
- Özellik gruplaması — hangi işlemeler hangi işe ait?
- Kusurun yalıtımı — kusuru getiren işleme tek başına bulunabiliyor mu?
- Gerçek sıra — işler hangi sırayla geliştirildi?
- Dosya izi — bir dosyaya dokunan her işleme ayrı ayrı bulunabiliyor mu?
- Dalın bütünlüğü — bir dalın işlemeleri bir arada mı duruyor?
- Çakışma kararı — çakışma nerede çözüldü ve hangi taraf seçildi?
Bu altı soru kursun sabitidir; sonraki derslerde tanımları değişmez, yalnız yanıtları değişir.
Ölçümün varsayımları:
- BR1 — Gerçek gelişim kurgudan üretilir; sırası ve kusurlu işlemesi bilinir, çünkü kurguyu biz yazdık. Tarihçenin bunu bilmesi gerekmez.
- BR2 — İleri sarma, dal ayrıldıktan sonra ana dala işleme eklenmemiş durumu temsil eder: işlemeler gerçek zaman sırasına dizilir ve dal kaydı tutulmaz.
- BR3 — Birleştirme işlemesi her dal için bir kapanış işlemesi ekler; bu işleme dosyaya dokunmaz ve kusur taşımaz, yalnız dal kaydını taşır.
- BR4 — Bir sorunun yanıtlanabilmesi, tarihçede o yanıtı veren kaydın bulunması demektir; kaydı okuyanın ne kadar uğraştığı ölçüme girmez.
- BR5 — Altıncı soru, tarihçede çakışmanın seçilen tarafını taşıyan bir alan aranarak ölçülür. Bu ders çakışmayı çözmez; alan yoktur ve soru bu derste iki biçimde de yanıtsızdır.
- BR6 — Ölçüm kodu üretir varsaymaz; iki biçimin ürettiği kod aynıdır, ölçülen yalnız tarihçedir.
- BR7 — Kümenin çözünürlüğü on iki işlemede 1/12, altı soruda 1/6; bundan küçük bir fark bu kurguyla savunulamaz.
Ölçüm
"""Iki tumlestirme bicimi: birlestirme islemesi ve ileri sarma. Bolum 1 - gercek gelisim: uc dal, her dalda dort isleme. Bolum 2 - iki bicimin biraktigi tarihceye alti soru sorulur. """ TOHUM = 20260813 DALLAR = ("olcum", "rapor", "kimlik") DOSYALAR = {"olcum": "olcum.py", "rapor": "rapor.py", "kimlik": "kimlik.py"} KUSURLU = ("rapor", 2) ORTAK_DOSYA = "ayar.py" def uretec(tohum): d = tohum % 2147483646 + 1 def r(n): nonlocal d d = (d * 48271) % 2147483647 return d % n return r def gelisim(): r, kayit, zaman = uretec(TOHUM), [], 0 for adim in range(1, 5): for dal in DALLAR: zaman += 1 + r(3) dosya = (ORTAK_DOSYA if adim == 3 and dal in ("olcum", "rapor") else DOSYALAR[dal]) kayit.append({"dal": dal, "sira": adim, "dosya": dosya, "kusurlu": (dal, adim) == KUSURLU, "zaman": zaman}) return kayit def tumlestir(kayit, bicim): t = [] if bicim == "birlestirme islemesi": for dal in DALLAR: for k in [x for x in kayit if x["dal"] == dal]: t.append({**k, "dal_kaydi": dal, "zaman_korundu": True}) t.append({"dal": dal, "sira": 0, "dosya": None, "kusurlu": False, "zaman": max(x["zaman"] for x in kayit if x["dal"] == dal), "dal_kaydi": dal, "birlesme": True, "zaman_korundu": True}) elif bicim == "ileri sarma": for k in sorted(kayit, key=lambda x: x["zaman"]): t.append({**k, "dal_kaydi": None, "zaman_korundu": True}) return t def s1_gruplama(t): return all(x.get("dal_kaydi") for x in t if not x.get("birlesme")) def s2_kusur(t): kusurlu = [x for x in t if x["kusurlu"]] return len(kusurlu) == 1 and not kusurlu[0].get("sikisik") def s3_sira(t): return all(x.get("zaman_korundu") for x in t) def s4_dosya(t): return all(isinstance(x["dosya"], str) or x.get("birlesme") for x in t) def s5_kapsam(t): yerler = {} for i, x in enumerate(t): if x.get("birlesme"): continue yerler.setdefault(x["dal"], []).append(i) return all(y[-1] - y[0] == len(y) - 1 for y in yerler.values()) def s6_cakisma(t): return any("secilen" in x for x in t) SORULAR = (("özellik gruplaması", s1_gruplama), ("kusurun yalıtımı", s2_kusur), ("gerçek sıra", s3_sira), ("dosya izi", s4_dosya), ("dalın bütünlüğü", s5_kapsam), ("çakışma kararı", s6_cakisma)) BICIMLER = ("birlestirme islemesi", "ileri sarma") kayit = gelisim() T = {b: tumlestir(kayit, b) for b in BICIMLER} print(f"gerçek gelişim: {len(kayit)} işleme, {len(DALLAR)} dal, ortak dosyaya " f"dokunan {sum(1 for k in kayit if k['dosya'] == ORTAK_DOSYA)}, " f"kusurlu işleme {sum(1 for k in kayit if k['kusurlu'])}") print() print(f"{'biçim':<22s} {'işleme':>6s} {'gelişimden':>10s} {'eklenen':>7s}") for b in BICIMLER: ek = sum(1 for x in T[b] if x.get("birlesme")) print(f"{b:<22s} {len(T[b]):6d} {len(T[b]) - ek:10d} {ek:7d}") print() print(f"{'soru':<20s} {'birlestirme islemesi':>20s} {'ileri sarma':>12s}") for ad, soru in SORULAR: print(f"{ad:<20s} {('evet' if soru(T[BICIMLER[0]]) else 'hayır'):>20s}" f" {('evet' if soru(T[BICIMLER[1]]) else 'hayır'):>12s}") print(f"{'yanıtlanan':<20s} {sum(s(T[BICIMLER[0]]) for _, s in SORULAR):20d}" f" {sum(s(T[BICIMLER[1]]) for _, s in SORULAR):12d}")
gerçek gelişim: 12 işleme, 3 dal, ortak dosyaya dokunan 2, kusurlu işleme 1 biçim işleme gelişimden eklenen birlestirme islemesi 15 12 3 ileri sarma 12 12 0 soru birlestirme islemesi ileri sarma özellik gruplaması evet hayır kusurun yalıtımı evet evet gerçek sıra evet evet dosya izi evet evet dalın bütünlüğü evet hayır çakışma kararı hayır hayır yanıtlanan 5 3
Üç İşlemenin Satın Aldığı
Üst tablo iki biçimi işleme sayısıyla karşılaştırıyor. Birleştirme işlemesi tarihçeyi 15 işlemeye çıkarıyor: on ikisi gerçek gelişimden, 3’ü aracın eklediği kapanış işlemeleri. İleri sarma 12’de kalıyor ve hiçbir şey eklemiyor.
Sezgi burada ileri sarmadan yana çalışır: aynı iş, daha az kayıt. Alt tablo bunun tersini söylüyor. Birleştirme işlemesi altı sorunun 5’ini, ileri sarma 3’ünü yanıtlıyor. Üç fazladan işleme, iki fazladan yanıt satın alıyor.
Satın alınan iki yanıt hangileri? Özellik gruplaması ve dalın bütünlüğü. İkisi de
aynı kayıptan doğuyor: ileri sarmada dal kaydı tutulmuyor. İşlemeler gerçek zaman sırasına
diziliyor, yani üç işin işlemeleri iç içe geçiyor — olcum dalının dört işlemesi ana
dalda ardışık durmuyor, aralarına öbür iki dalın işlemeleri giriyor. Tarihçe “bu işleme
hangi işe aitti” sorusuna yanıt veremiyor, çünkü işi adlandıran kayıt hiç yazılmamış.
Dördü ortak: kusurun yalıtımı, gerçek sıra ve dosya izi iki biçimde de evet. Kusurlu işleme iki tarihçede de tek başına duruyor; hiçbir biçim onu bir başkasıyla birleştirmedi. Zaman damgaları da korundu. Bu üç yanıt için fazladan bir kayda gerek yok.
Çakışma kararı iki biçimde de hayır. olcum ile rapor üçüncü adımda aynı dosyaya
dokunuyor ve bu bir birleştirme çakışması (merge conflict) doğuruyor; ama iki tarihçede
de çakışmanın nasıl çözüldüğünü söyleyen bir kayıt yok. Dikkat edilmesi gereken şudur:
birleştirme işlemesi çakışmanın olduğu yeri taşır — çözüm o işlemede yazılıdır — ama
hangi tarafın seçildiğini ayrı bir alan olarak tutmaz. Ölçüm bu ayrımı bir alan arayarak
yapıyor ve alan iki biçimde de yok. Bu sonuç dört biçimin dördü için de geçerlidir ve
sonraki ders bunu ödeyecektir.
Seçim Neye Bağlı
İki biçim arasındaki fark bir doğru–yanlış değil, bir takastır. Ölçülen kümede fark 2/6’dır: altı sorudan ikisi. Kümenin çözünürlüğü 1/6 olduğuna göre bu fark ölçüm bandının iki katıdır ve savunulabilir.
Ödenen bedel 3/12’dir: on iki işlemelik bir gelişime üç işleme eklenmiştir. Dal sayısı arttıkça bu bedel doğrusal büyür, çünkü her dal bir kapanış işlemesi ekler. On iki dallı bir tümleştirmede tarihçenin dörtte biri kapanış işlemesinden oluşur ve günlük çıktısı gerçek işi göstermek yerine kapanışları sayar.
Ölçüt şudur: dal kaydı sonradan sorulacak mı? Tek işlemelik bir düzeltmede sorulmaz ve ileri sarma kaybı sıfırdır. Dört işlemelik bir özellik dalında sorulur; oradaki üç işlemelik bedel, iki sorunun yanıtı için ödenmiş olur.
Eklenen üç işlemenin ikinci bir getirisi de vardır. Birleştirme işlemesinin birinci öncülü ana dalın kendi çizgisidir; günlük yalnız birinci öncülleri izleyecek biçimde çağrıldığında tarihçe, ölçümdeki on iki işleme yerine üç kapanış işlemesine iner. Aynı tarihçe iki ayrı çözünürlükte okunabilir hâle gelir: ne yapıldı sorusuna üç satır, nasıl yapıldı sorusuna on iki satır. İleri sarmada bu ikinci çözünürlük yoktur, çünkü ayıracak kenar yoktur — her okuma on iki satır verir.
# öğretilen komut, örnek döküm — çalıştırılmamıştır $ git log --oneline --first-parent a1b2c3d rapor dalını birleştir b2c3d4e olcum dalını birleştir 9c4d7e1 ilk kurulum
Bu iki çözünürlüğün ikisi de dal kaydına dayanır; kayıt yoksa ikisi çakışır ve ayrım kurulamaz.
Özet
- İleri sarma, ana dal ayrılma noktasından beri hiç ilerlememişse uygulanır ve tarihçeye hiçbir işleme yazmaz; yalnız dal işaretçisini kaydırır.
- Birleştirme işlemesi iki öncüllü bir işlemedir; ikinci öncül, tarihçenin dal yapısını taşıyan tek kayıttır ve günlük çizimi ondan üretilir.
- On iki işlemelik gelişim birleştirme işlemesiyle 15 işlemeye çıkar, ileri sarmayla 12’de kalır; eklenen 3 işleme dal başına bir kapanıştır.
- Birleştirme işlemesi altı sorunun 5’ini, ileri sarma 3’ünü yanıtlar; farkı yaratan iki soru özellik gruplaması ve dalın bütünlüğüdür.
- Kusurun yalıtımı, gerçek sıra ve dosya izi iki biçimde de yanıtlanır; çakışma kararı ikisinde de yanıtsızdır.
Sonraki Adım
Ölçümde olcum ile rapor aynı dosyaya dokundu ve iki tarihçe de bunun nasıl çözüldüğünü
kaydetmedi. Sonraki ders o çözümün kendisine bakacak: araç iki tarafı birleştiremediğinde
dosyaya ne yazar, çakışma işaretleri nasıl okunur, çözümün doğru olduğu nasıl doğrulanır —
ve çözüm bittiğinde tarihçeye tam olarak neyin yazıldığı, neyin yazılmadığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.