07

Atama motoru

Commit'lerin yeniden oynatılması, parmak izleri, atama durumu ve güven düzeyi

5 dk okuma9 bölüm

Atama motoru her ihlali kimin eklediğine karar verir. src/attribution/ içinde yer alır ve CleanLens'i blame tabanlı araçlardan ayıran şeyin özüdür.

Tek paragrafta fikir

CleanLens analiz edilen her commit için değişen her dosyayı commit'ten önce (ebeveyn sürümü) ve sonra analiz eder, her ihlale bir parmak izi verir ve iki kümeyi karşılaştırır. Yeni parmak izleri commit tarafından eklenmiştir; kaybolanlar onun tarafından düzeltilmiştir; ortak olanlar zaten vardı. Yürüyüş bittikten sonra mevcut koddaki her ihlal, onu ekleyen commit'i (ve geliştiriciyi) bulmak için parmak iziyle aranır.

1. adım — commit'lerde yürümek (commitAttribution.ts)

attributeCommits(root, config, opts):

  1. Yeniden oynatılacak commit'leri listeler (birleştirme olmayan en yeni N, en eskiden başlayarak; bkz. 04 — Git katmanı).
  2. 12 commit'e kadar paralel işler (COMMIT_CONCURRENCY). İşin çoğu git'i beklemektir; bu yüzden paralellik çok yardımcı olur.
  3. Her commit için önbellekte sonuç varsa onu kullanır; yoksa commit'in katkısını hesaplar (2. adım) ve önbelleğe yazar.
  4. Katkıları kesinlikle en eskiden başlayarak uygular; böylece köken kaydı her zaman en son durumu yansıtır.
  5. Çalıştırma iptal edilirse ilk eksik katkıda durur ve sonucu aborted olarak işaretler.

2. adım — tek bir commit (computeCommit)

text
parent (or empty tree) ──git diff──▶ changed files + added/removed line ranges
                                          │ filter
                                          ▼
                 analyzable, not excluded, not binary, not whitespace-only
                                          │ git cat-file --batch
                                          ▼
                            before blob          after blob
                                │                     │
                         analyzeRevision()     analyzeRevision()
                                │                     │
                         fingerprints (B)      fingerprints (A)
                                  \                   /
                                   compare the two sets

Dosyalar şu durumlarda atlanır: ikiliyse, analizörü yoksa, hariç tutma listesiyle eşleşiyorsa, yalnızca boşluk değişikliğiyse (-w altında satır aralığı yok) veya üretilmiş başlığı varsa (excludeGenerated açıkken).

Kalan her dosya için:

  • Analiz edilen satırlar += commit'in added aralıklarındaki satır sayısı. Puanların bölündüğü "geliştiricinin yazdığı kod" budur.
  • Önceki ve sonraki blob'lar analiz edilir. Sonuçlar tüm çalıştırma boyunca blob kimliğine göre önbelleğe alınır; böylece ardışık commit'lerin paylaştığı bir dosya sürümü yalnızca bir kez analiz edilir.
  • Ardından kümeler karşılaştırılır:
Parmak izi nerede…SınıflandırmaEtkisi
yalnızca sonrasındaintroduced (eklendi)Yazara yazılır (güven high/medium ise)
önce ve sonraexisting (mevcut)Sayılır, asla kimseye yazılmaz
yalnızca öncesindefixed (düzeltildi)Yazarın hanesine artı yazılır (fixedWeighted)

Sonraki sürüm analiz edilemezse (ayrıştırma hatası, eksik blob), önceki sürümdeki her ihlal o commit için atanamayan (unattributed) olarak sayılır.

3. adım — atama güveni

Eklenen her ihlal classifyConfidence() ile bir güven düzeyi alır:

GüvenNe zamanPuana girer mi?
highDosya yenidir (eklenmiş, kopyalanmış, kök commit), veya ihlalin başlangıç satırı değişen bir aralık içindedir, veya satır kapsamı değişen bir aralıkla çakışır✅
mediumDeğişen satırlarda değildir, ama adlandırılmış bir sembole (fonksiyon/sınıf) aittir ve commit dosyada satır değiştirmiştir✅
lowDüzenlemenin buna neden olduğuna dair kanıt yok (örn. var olan bir dosyanın ebeveyn sürümü okunamadı)❌ yalnızca gösterilir

Yalnızca high ve medium sayılır (scoringConfig.ts içinde SCORING_CONFIDENCES).

Bunun neden önemli olduğu, örneklerle:

  • Mehmet var olan bir fonksiyona 60 satır ekler → fonksiyon artık maxFunctionLines kuralını ihlal eder. Fonksiyonun kapsamı Mehmet'in eklediği satırlarla çakışır → high, Mehmet'e yazılır.
  • Zeynep bir fonksiyonun derinlerine tek bir if ekler ve fonksiyon karmaşıklık sınırını aşar. Bulgu fonksiyon başlığında başlar (değişmemiş), ama kapsamı Zeynep'in satırıyla çakışır → high, Zeynep'e yazılır.
  • Can bir dosyayı yeniden biçimlendirir. -w altında hiçbir şey değişmemiştir → dosya atlanır. Kimse bir şey devralmaz.
  • Elif utils.js dosyasını düzenlemeden helpers.js olarak yeniden adlandırır. -M yeniden adlandırmayı tespit eder, ama parmak izleri yolu içerir; bu yüzden her ihlal yeni yol altında yeni görünür. Yeniden adlandırma hiçbir satırı değiştirmemiştir (added aralığı yok); bu yüzden her biri low güven alır ve Elif'e yazılmaz. Mevcut uygulamanın yan etkileri: eski yolun parmak izleri Elif'in gösterilen toplamlarında fixed olarak sayılır (düzeltilen sayısı, net kalite etkisi; puan değil) ve raporda bu ihlaller Elif'in commit'iyle introduced / low olarak görünür. Asıl yazarın onlarla bağlantısı kopar.

4. adım — parmak izleri (fingerprint.ts)

Bir parmak izi, satır numaralarını (üstünde biri düzenleme yaptığında değişirler) kullanmadan iki sürümde "aynı ihlali" tanımlar:

text
fingerprint = FNV-1a( ruleId + normalizedPath + symbolName + normalizedContext )
  • normalizedPath: / ayıraçları, başta ./ yok.
  • symbolName: biliniyorsa kapsayan fonksiyon/sınıf.
  • normalizedContext: startLine ile min(endLine, startLine + 2) arasındaki kaynak satırları (en çok 3 satır); // ve # yorumları kaldırılmış ve boşluklar daraltılmış.

Sonuçları:

  • Bir ihlalin üstüne satır eklemek → aynı parmak izi (hâlâ existing).
  • O satırlardaki boşlukları veya yorumları düzenlemek → aynı parmak izi.
  • İhlalin ilk satırlarındaki kodu düzenlemek → yeni bir parmak izi: eskisi aynı commit tarafından fixed, yenisi introduced olur. Yazara hem eksi hem artı yazılır; net etki kabaca nötrdür.
  • Kapsayan fonksiyonu yeniden adlandırmak → yeni parmak izi (sembol değişti).

5. adım — mevcut kodla birleştirme (attributeReport.ts)

Yürüyüşten sonra HEAD taramasındaki her ihlal son durumunu alır:

KoşulDurumGüvendeveloperId
Yol hariç tutma listesiyle eşleşiyorexcluded—yok
Dosya diskten okunamıyorunattributedlowyok
Parmak izinin son kökeni introducedintroducedkökeninkihigh/medium ise yazar
Aksi halde (pencereden eski veya yürüyüşte görülmemiş)existinglowyok

introducedBy, introducedCommit ve introducedAt, köken biliniyorsa düşük güvende bile doldurulur; böylece commit'i yine de görebilirsiniz.

6. adım — geliştirici başına toplamlar

Her geliştirici için (küçük harfli e-posta → geliştirici kimliği ile eşleştirilir) motor commit'leri üzerinden toplar:

AlanAnlamı
analyzedLinesAnaliz edilebilir dosyalarda eklediği/değiştirdiği satırlar
introducedWeighted[category]Kategori başına, high/medium güvenle eklediği ihlallerin ağırlık toplamı
fixedWeightedCommit'lerinin kaldırdığı ihlallerin ağırlık toplamı
counts.introducedEklenenlerin tümü (her güven düzeyi)
counts.introducedScoredhigh/medium güvenle eklenenler
counts.fixed, counts.existing, counts.unattributedHer commit'te sınıflandırıldığı gibi

Bunların tarihsel sayılar olduğuna dikkat edin: eklenip sonra düzeltilen ihlalleri de içerir. Bu yüzden bir geliştiricinin "Yeni ihlaller" sayısı, kodda hâlâ duran ihlallerinin sayısından büyük olabilir.

Bu toplamlar puanı besler: bkz. 08 — Puanlama.

Garantiler ve uç durumlar

DurumDavranış
Kök commit (ebeveyn yok)Boş ağaçla karşılaştırılır; her ihlal high güvenle introduced
Birleştirme commit'leriAtlanır (--no-merges)
Yalnızca boşluk değişikliğiYok sayılır
Yeniden adlandırma-M ile tespit edilir; yeniden adlandırana yazılmaz (low güven), ama yeniden adlandırma asıl yazarla bağlantıyı koparır ve yeniden adlandırana "fixed" kredisi ekler (yalnızca gösterim)
Kopyalama-C ile tespit edilir; yeni dosya olarak ele alınır, bu yüzden kopyadaki ihlaller kopyalayana yazılır (high güven)
Silinen dosyaİhlalleri silen kişi tarafından fixed olur
Geliştirici listesinde olmayan yazarProje toplamlarında sayılır, hiçbir geliştiricide sayılmaz
Pencereden eski ihlalexisting; asla kimseye yazılmaz
İptal edilen çalıştırmaBoşluktan sonraki katkılar yok sayılır

Bu davranışı sabitleyen testler

test/integration.test.ts gerçek geçici Git depoları oluşturur ve şunları doğrular: önceden var olan bir ihlal dosyayı sonradan düzenleyene yazılmaz; değiştirilen satırlardaki bir ihlal yazılır; kaldırılan bir ihlal düzeltildi olarak kaydedilir; boşluk yeniden biçimlendirmesi hiçbir şey aktarmaz; üretilmiş ve hariç tutulmuş dosyalar yok sayılır; kök commit güvenlidir; ve küçük ama temiz bir katkıcı 100 almaz ve sıralamaya girmez.