İçeriğe geç
academia.sh

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.

  1. Özellik gruplaması — hangi işlemeler hangi işe ait?
  2. Kusurun yalıtımı — kusuru getiren işleme tek başına bulunabiliyor mu?
  3. Gerçek sıra — işler hangi sırayla geliştirildi?
  4. Dosya izi — bir dosyaya dokunan her işleme ayrı ayrı bulunabiliyor mu?
  5. Dalın bütünlüğü — bir dalın işlemeleri bir arada mı duruyor?
  6. Ç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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat