İçeriğe geç
academia.sh

Ders 06 / 15

Çakışma Çözümü

Araç iki tarafı birleştiremediğinde dosyaya çakışma işaretleri yazar; çözüm elle verilir ama dört tümleştirme biçiminin dördü de seçilen tarafı kaydetmez, çözümün gerekçesi işleme mesajına yazılmazsa hiçbir yerde durmaz.

İçindekiler

Önceki dersin ölçümünde olcum ile rapor dalları üçüncü adımda aynı dosyaya dokundu. İki tümleştirme biçimi de bu dosyayı doğru sonuçla ana dala aldı, ama iki tarihçe de çakışmanın nasıl çözüldüğünü kaydetmedi. Ölçümde bu, altıncı sorunun iki biçimde de yanıtsız kalması demekti.

Bu ders o çözümün kendisine bakar. Araç iki tarafı birleştiremediğinde dosyaya ne yazar, yazdığı işaretler nasıl okunur, verilen çözümün doğru olduğu nasıl doğrulanır — ve iş bittiğinde tarihçeye tam olarak neyin yazıldığı, neyin yazılmadığı.

Aracın Karar Veremediği Yer

Üçlü birleştirme üç sürüm okur: ortak atadaki hâl, ana daldaki hâl, gelen daldaki hâl. Bir satırı yalnız bir taraf değiştirmişse karar kendiliğinden verilir — değişen taraf alınır. Çakışma, iki tarafın da aynı satırı ata sürümünden ayrı biçimde değiştirdiği yerde doğar. Araç burada bir tercih üretmez; iki değişiklik de kasıtlıdır ve hangisinin doğru olduğu ancak işin anlamı bilinerek söylenebilir.

Çakışmalar yalnız satır içeriğinden doğmaz. Bir taraf dosyayı silmişken öbür taraf onu değiştirmişse, bir taraf yeniden adlandırmışken öbürü aynı yola yazmışsa, ya da iki taraf aynı yolda ayrı içerikle yeni birer dosya oluşturmuşsa da karar verilemez. Bu durumların ortak yanı şudur: ata sürümüyle karşılaştırma iki taraf için de bir değişiklik gösterir ve ikisi bir arada uygulanamaz.

Çakışma bir hata değildir. Aracın “burada karar bende değil” demesidir; birleştirme durdurulur, çalışma alanı yarım bırakılır ve karar insana devredilir.

Çakışma İşaretleri

Araç kararı devrederken dosyayı boş bırakmaz: iki tarafın da içeriğini dosyaya yazar ve aralarına ayraç koyar. Bunlara çakışma işareti (conflict marker) denir.

# öğretilen komut, örnek döküm — çalıştırılmamıştır

$ git merge rapor
Auto-merging ayar.py
CONFLICT (content): Merge conflict in ayar.py
Automatic merge failed; fix conflicts and then commit the result.

$ cat ayar.py
<<<<<<< HEAD
ESIK = 40
||||||| ortak ata
ESIK = 25
=======
ESIK = 25
BIRIM = "mm"
>>>>>>> rapor

<<<<<<< ile ||||||| arası bulunduğunuz taraftır: birleştirmeyi başlattığınız dal, yani HEAD. ||||||| ile ======= arası ortak atadaki hâldir. ======= ile >>>>>>> arası gelen taraftır. Üç bölümlü bu gösterim varsayılan değildir; iki bölümlü gösterimde ata sürümü yazılmaz ve yalnız iki taraf görünür. Ata bölümü ölçüyü verir: yukarıdaki dökümde ata ESIK = 25 diyor, yani eşik değerini değiştiren taraf HEAD, satırı olduğu gibi bırakıp altına yeni bir satır ekleyen taraf rapor. Bu bilgi olmadan iki taraf da eşit görünür ve seçim körleme yapılır.

İşaretler dosyanın kendisine yazılır, dosya çalışma alanında kalır ve o hâliyle derlenemez. Aracın kendi kaydında ise aynı çakışma üç ayrı aşama olarak durur: ata, bulunduğunuz taraf ve gelen taraf. Dosyayı elle düzenlemeden önce üç sürümü ayrı ayrı görmek bu aşamalardan yapılır.

# öğretilen komut, örnek döküm — çalıştırılmamıştır

$ git status --short
UU ayar.py
M  olcum.py

$ git diff --base ayar.py
$ git diff --ours ayar.py
$ git diff --theirs ayar.py

UU iki tarafın da aynı yolu değiştirdiğini gösterir; silme ve yeniden adlandırma çakışmaları başka harf çiftleriyle işaretlenir. Üç diff çağrısı çalışma alanındaki dosyayı sırasıyla ata, bulunulan taraf ve gelen tarafla karşılaştırır.

Çözümün Verilmesi ve Doğrulanması

Çözmek, dosyayı istenen son hâline getirmek ve işaretleri silmektir. Sonuç iki taraftan birinin aynısı olabileceği gibi ikisinden de farklı, elle yazılmış bir üçüncü içerik de olabilir — kurgudaki gibi hem yeni eşiğin hem yeni birimin durduğu bir hâl. Dosya hazırlandıktan sonra hazırlık alanına eklenmesi, aracın gözünde “bu yol çözüldü” demektir; bütün yollar çözüldüğünde birleştirme işlemesi yazılır.

Araç bir kısayol da sunar: çakışan yolu tamamen bir tarafın sürümüyle doldurmak. Bu kısayol makine tarafından üretilen dosyalarda yerindedir, çünkü orada iki taraftan biri yeniden üretilebilir. Elle yazılmış bir dosyada ise kısayol, öbür tarafın o dosyadaki bütün değişikliklerini sessizce atar; atılan değişiklik dosyada görünmez olur ve sınamalar o tarafı kapsamıyorsa hiçbir yerde belirti vermez.

Doğrulamanın üç ayrı katmanı vardır ve üçü de gereklidir.

Birincisi mekanik: dosyada işaret kalmadığından emin olmak. Kalan bir <<<<<<< satırı çoğu dilde sözdizimi hatası verir, ama biçimlendirilmiş metin ya da veri dosyalarında sessizce durabilir. Ağaçta işaret dizisi araması bunu yakalar.

İkincisi bütünlük: çözülmüş dosyanın iki tarafın da amacını taşıdığını görmek. Çakışmanın dar bakılan tehlikesi budur — birleştirmeyi yapan kişi genellikle iki taraftan yalnız birini yazmıştır ve öbürünün ne yapmak istediğini bilmez. Bu yüzden seçim, iki tarafın kodunu birleştirmekten çok iki niyeti uzlaştırmaktır.

Üçüncüsü davranışsal: sınamaların çalıştırılması. Çözülmüş dosya derlenebilir olabilir ve yine de yanlış olabilir: iki taraf ayrı ayrı doğruyken birleşiği yanlış olan bir hâl gayet olağandır. Çakışma çözümü, sınamaların en gerekli olduğu yerdir.

Çözümden geri dönmenin güvenli yolu birleştirmeyi iptal etmektir; araç bunun için ayrı bir seçenek taşır ve çalışma alanını birleştirme öncesindeki hâline döndürür. Yarım kalmış bir birleştirmeyi elle temizlemeye çalışmak yerine iptal edip baştan başlamak her zaman ucuzdur.

Aracın çözümü hatırlayan bir yeteneği de vardır: aynı çakışma daha sonra yeniden karşınıza çıktığında önceki çözümü kendiliğinden uygular. Bu yetenek, uzun ömürlü bir dalın aynı çakışmayı defalarca doğurduğu durumda emek kazandırır. Ölçüm açısından önemli olan yanı şudur: kaydettiği çözüm deponun yerel bir dizininde durur, tarihçeye yazılmaz ve kimseyle paylaşılmaz. Çözüm bilgisinin tarihçenin dışında yaşadığı ilk yer budur.

Ölçümün varsayımları:

  • BR8 — Çakışma, üçüncü adımda olcum ile rapor dallarının aynı dosyaya dokunmasından doğar. Kurgu bunu bilir; tarihçenin bilmesi gerekmez.
  • BR9 — Ölçüm dört tümleştirme biçimini de kurar. Biçimlerin tanımı ortak kurgudaki tanımdır ve bu derste değiştirilmez.
  • BR10 — Altıncı soru, tarihçede seçilen tarafı taşıyan bir alan aranarak ölçülür. Alanın adı değil, varlığı ölçülür.
  • BR11 — Çözüm kaydı üç alandan oluşur: seçilen taraf, gerekçe ve kararı veren rol. Üçü de aracın kendiliğinden yazdığı bir alan değildir.
  • BR12 — Sıkıştırmada bir işlemenin dosya alanı tek ad değil, ad listesidir: ortak dosyaya dokunulduğu okunur, dokunan işleme ayrı ayrı okunamaz.
  • BR13 — Gerekçenin işleme mesajına yazılması ölçümde tek bir değişikliktir: birleştirme işlemesine üç alan eklenir, başka hiçbir şey değişmez.
  • BR14 — Kümenin çözünürlüğü on iki işlemede 1/12, altı soruda 1/6.

Ölçüm

"""Cakismanin cozumu tarihceye ne yaziyor.

Bolum 1 - cakismayi doguran iki isleme dort bicimde nerede duruyor.
Bolum 2 - altinci soru dort bicimde de aranir.
Bolum 3 - tek degisiklik: cozumun gerekcesi isleme mesajina yazilir.
"""
TOHUM = 20260813
DALLAR = ("olcum", "rapor", "kimlik")
DOSYALAR = {"olcum": "olcum.py", "rapor": "rapor.py", "kimlik": "kimlik.py"}
KUSURLU = ("rapor", 2)
ORTAK_DOSYA = "ayar.py"
COZUM_ALANLARI = ("secilen", "gerekce", "karar_veren")


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 == "yeniden temellendirme":
        for dal in DALLAR:
            for k in [x for x in kayit if x["dal"] == dal]:
                t.append({**k, "dal_kaydi": None, "zaman_korundu": False})
    elif bicim == "sikistirma":
        for dal in DALLAR:
            grup = [x for x in kayit if x["dal"] == dal]
            t.append({"dal": dal, "sira": 0,
                      "dosya": sorted({x["dosya"] for x in grup}),
                      "kusurlu": any(x["kusurlu"] for x in grup),
                      "zaman": max(x["zaman"] for x in grup), "dal_kaydi": dal,
                      "zaman_korundu": False, "sikisik": len(grup)})
    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 s4_dosya(t):
    return all(isinstance(x["dosya"], str) or x.get("birlesme") for x in t)


def s6_cakisma(t):
    return any("secilen" in x for x in t)


def dokunan(t, ad):
    """Tarihceden ad dosyasina dokundugu okunabilen isleme sayisi."""
    return sum(1 for x in t if x["dosya"] == ad
               or (isinstance(x["dosya"], list) and ad in x["dosya"]))


def ayri_okunabilir(t, ad):
    """Yalniz o dosyaya dokunan tek bir isleme olarak okunabilenler."""
    return sum(1 for x in t if x["dosya"] == ad)


def mesaja_yaz(t):
    """Tek degisiklik: cozumun gerekcesi bir isleme mesajina yazilir."""
    y = [dict(x) for x in t]
    for x in y:
        if x.get("birlesme") and x["dal"] == "rapor":
            x.update({"secilen": "olcum tarafi", "gerekce": "esik alani",
                      "karar_veren": "birlestiren"})
    return y


BICIMLER = ("birlestirme islemesi", "yeniden temellendirme", "sikistirma",
            "ileri sarma")
kayit = gelisim()
T = {b: tumlestir(kayit, b) for b in BICIMLER}
catisan = [(k["dal"], k["sira"]) for k in kayit if k["dosya"] == ORTAK_DOSYA]

print(f"gerçek gelişim: {len(kayit)} işleme | {ORTAK_DOSYA} dosyasına dokunan "
      f"{len(catisan)} işleme: {catisan}")
print()
print(f"{'biçim':<24s} {'işleme':>6s} {'dokunan':>7s} {'ayrı':>5s}"
      f" {'dosya izi':>9s} {'çakışma kararı':>14s}")
for b in BICIMLER:
    print(f"{b:<24s} {len(T[b]):6d} {dokunan(T[b], ORTAK_DOSYA):7d}"
          f" {ayri_okunabilir(T[b], ORTAK_DOSYA):5d}"
          f" {('evet' if s4_dosya(T[b]) else 'hayır'):>9s}"
          f" {('evet' if s6_cakisma(T[b]) else 'hayır'):>14s}")
print(f"altıncı soruyu yanıtlayan biçim: "
      f"{sum(s6_cakisma(T[b]) for b in BICIMLER)}/{len(BICIMLER)}")
print()
M = mesaja_yaz(T["birlestirme islemesi"])
print(f"{'tarihçe':<34s} {'çözüm alanı taşıyan':>19s} {'çakışma kararı':>14s}")
for ad, t in (("araç ne yazdıysa", T["birlestirme islemesi"]),
              ("gerekçe işleme mesajına yazılınca", M)):
    tasiyan = sum(1 for x in t if any(a in x for a in COZUM_ALANLARI))
    print(f"{ad:<34s} {f'{tasiyan}/{len(t)}':>19s}"
          f" {('evet' if s6_cakisma(t) else 'hayır'):>14s}")
gerçek gelişim: 12 işleme | ayar.py dosyasına dokunan 2 işleme: [('olcum', 3), ('rapor', 3)]

biçim                    işleme dokunan  ayrı dosya izi çakışma kararı
birlestirme islemesi         15       2     2      evet          hayır
yeniden temellendirme        12       2     2      evet          hayır
sikistirma                    3       2     0     hayır          hayır
ileri sarma                  12       2     2      evet          hayır
altıncı soruyu yanıtlayan biçim: 0/4

tarihçe                            çözüm alanı taşıyan çakışma kararı
araç ne yazdıysa                                  0/15          hayır
gerekçe işleme mesajına yazılınca                 1/15           evet

Dördü de Yanıtlayamaz

Üst tablonun son sütunu bu kursun üçüncü iddiasını ödüyor: çakışma kararı dört biçimde de hayır. Yanıtlayan biçim sayısı 0/4. Bu, biçimlerden birinin öbürlerinden kötü olduğu anlamına gelmez; ölçülen dört biçimin hiçbirinde böyle bir alanın bulunmadığı anlamına gelir.

Tarihçenin ne kaydettiğine bakıldığında ayrım netleşir. Ortak dosyaya dokunan iki işleme üç biçimde de 2 olarak okunuyor: çakışmanın hangi dosyada doğduğu ve hangi iki işin oraya dokunduğu bulunabiliyor. Sıkıştırmada dosya adı ayrı bir işlemede değil, bir işleme grubunun dosya listesi içinde duruyor; dokunulduğu görülüyor ama ayrı okunabilen işleme sayısı 0 ve dosya izi sorusu bu biçimde hayır veriyor.

Yani tarihçe çakışmanın olduğunu kaydediyor — çakışmanın doğduğu yer üç biçimde bulunabiliyor. Kaydetmediği şey çözümün kendisidir: hangi tarafın seçildiği, hangi satırların elle yazıldığı, kararı kimin verdiği ve neden verdiği. Bu dört bilginin hiçbiri araç tarafından yazılan bir alanda durmuyor.

Bunun bir araç eksikliği olmadığını görmek için kaydın ne olduğunu düşünmek yeterli. Bir işleme, ağacın bir hâlini ve öncüllerini taşır. Birleştirme işlemesinin taşıdığı ağaç, çözülmüş dosyayı zaten içerir — sonuç kayıtlıdır. Kayıtlı olmayan şey süreçtir: iki adaydan hangisinin neden seçildiği, sonucun kendisinden okunamaz. Araç yalnız sonucu saklar, çünkü sakladığı şey ağaçtır.

Gerekçenin Tek Yeri

Alt tablo bir sonraki adımı ölçüyor. Ölçümde değiştirilen tek şey şu: çözümün gerekçesi birleştirme işlemesinin mesajına yazılıyor. Üç alan — seçilen taraf, gerekçe, kararı veren rol — o tek işlemeye ekleniyor.

Sonuç, on beş işlemelik tarihçenin 1’inde çözüm kaydı bulunması ve altıncı sorunun evet vermesidir. Oran 1/15: kümenin çözünürlüğünün üzerinde, ama tarihçenin tamamına göre küçük bir yer. Bu küçüklük tesadüf değildir; çözüm kaydı ancak çakışmanın kapatıldığı işlemede anlamlıdır.

Buradan kursun en pratik sonucu çıkar: çakışma çözümünün gerekçesi işleme mesajına yazılmazsa hiçbir yerde yoktur. Araç onu yazmaz, dosya onu taşımaz, sınamalar onu anlatmaz. Çözümü hatırlayan yerel yetenek bile onu yalnız o depoda tutar ve kimseyle paylaşmaz. Aylar sonra “bu satır neden böyle” diye soran kişinin bakabileceği tek yer mesajdır ve mesaj boşsa yanıt kimsede değildir.

Bunun somut karşılığı şudur: birleştirme işlemesinin varsayılan mesajı yalnız hangi dalın birleştirildiğini yazar ve boş bırakılırsa öyle kalır. Çakışmalı bir birleştirmede o mesajın üç şeyi taşıması gerekir — hangi dosyada çakışıldığı, hangi tarafın hangi gerekçeyle seçildiği ve elle yazılmış bir içerik varsa onun ne olduğu. Üçü de üç satırdır ve üçü de yazılmadığında geri getirilemez.

Özet

  • Çakışma, iki tarafın da aynı satırı ata sürümünden ayrı biçimde değiştirdiği yerde doğar; araç tercih üretmez, kararı devreder ve birleştirmeyi yarıda bırakır.
  • Çakışma işaretleri bulunulan tarafı, ata sürümünü ve gelen tarafı dosyaya yazar; ata bölümü hangi tarafın gerçekten değişiklik yaptığını gösterir ve seçimi körlemeden çıkarır.
  • Çözüm üç katmanda doğrulanır: işaret kalmadığı, iki tarafın amacının korunduğu ve sınamaların geçtiği. Geri dönmenin güvenli yolu birleştirmeyi iptal etmektir.
  • Ortak dosyaya dokunan 2 işleme üç biçimde ayrı ayrı okunur, sıkıştırmada 0; ama çakışma kararı sorusunu dört biçimin dördü de yanıtlayamaz — 0/4.
  • Gerekçe işleme mesajına yazıldığında on beş işlemenin 1’i çözüm kaydını taşır ve altıncı soru yanıtlanır; yazılmadığında çözümün gerekçesi hiçbir yerde durmaz.

Sonraki Adım

Bu derste çakışma tek bir yerde, birleştirme işlemesinin yazıldığı anda çözüldü. Çözüm bir kez verildi ve bir kez kaydedildi. Tarihçeyi çatallanmasız, tek çizgi hâlinde tutmak isteyen bir biçim ise dalın işlemelerini ana dalın ucuna yeniden yazar — ve o zaman aynı çakışma her işleme için ayrı ayrı sorulabilir. Sonraki ders bu biçime bakar: on iki işleme on iki olarak kalır ama hangi üç soru yanıtlanır, hangi bilgi silinir, ve paylaşılan bir dalda uygulandığında neden geri alınamaz.

İ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