1 puan yazan GN⁺ 2 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Git, kaynak kod için dağıtık depo olarak başarılı oldu; ancak dağıtık iş akışını ele alma biçimi sonradan eklenmiş bir çözüm olmaya daha yakın ve sınırları ortaya çıkıyor
  • Git’in commit ve branch yapıları, sonraki commit’leri, amend geçmişini, rebase geçmişini ve terk edilmiş durumları kendi başına ifade edemiyor
  • Stacked PR içinde sonraki PR’ları bulmak ve yığını koruyarak rebase etmek gerekir; ancak Git bu ilişkiyi güvenilir biçimde saptamakta zorlanır
  • Git, staging, unstaged, dosya sistemi ve HEAD gibi değiştirilebilir durumları commit ve branch’lerin dışında tutarak öğrenmeyi ve kullanımı karmaşıklaştırır
  • Birleştirilmeden önce PR’ların birlikte kullanılmasını gerektiren asenkron geliştirme akışlarında Git’in geçmişe bakan değişmez geçmiş modeli tekrar eden sorunlar üretir

Git’in iki rolü

  • Git, hem kaynak kod için dağıtık depo hem de dağıtık iş akışı aracı olarak kullanılır
  • Kaynak kod deposu olarak çok başarılı oldu, ancak dağıtık iş akışını ele alma yöntemi çoğunlukla sonradan eklenmiş çözümlere daha yakındır
  • Asenkron geliştirme, East River Source Control’ün ifadesiyle neredeyse temel bir koşuldur; yalnızca farklı zaman dilimlerindeki iş birliğinde değil, kişinin kendisiyle zaman farkıyla çalışmasında da ortaya çıkar
  • jj, Git’in sınırlarını daha net görünür kılan bir araçtır; Git’in yeterli olduğunu düşünenler büyük olasılıkla jj’yi ciddi biçimde denemeyecektir

Git’in temel modelinin kaçırdığı ilişkiler

  • Git düşüncesinin merkezinde commit ve branch’ler vardır
    • Commit, kaynak kodu ve geçmişi içeren değişmez bir nesnedir
    • Branch, geçmişi olan değiştirilebilir bir işaretçidir
  • Tipik Git diyagramları commit’leri C1, C2, C3 gibi göstererek sıra ve ilişkiyi açıkmış gibi sunar; ancak gerçek bir depoda commit adları hash’lere ya da mesajlara daha yakındır, dolayısıyla böyle bir sıralama ilişkisi sistemin içinde yoktur
  • Rebase sonrasındaki C2 ve C2’ gibi gösterimler, insanların kolay anlaması için yapılmış anlatımlardır; Git, bu iki commit’in birbirine karşılık geldiğini bilmez
  • Belirli bir commit’in sonraki commit’lerini bulmak için tüm branch’leri tarayıp o commit’e uzanan yol üzerindeki commit’leri bulmak gerekir; bu yüzden iş basit değildir

Git’te “C” yok

  • Git commit’leri aşağıdaki bilgileri kendi başına bilemez
    • Sonraki commit’ler

      • amend sonrasında yeni commit’ten eski commit’e uzanan değişiklik geçmişi
    • rebase geçmişi

      • ilgili commit’in terk edilip edilmediği
      • Branch’lerin de sınırları vardır
      • Branch’lerde bir geçmiş kavramı olsa da, kod değişiklikleriyle bire bir eşleştiğine güvenmek zordur
      • Branch’ler kendi aralarında ilişki taşımaz; örneğin trunk üzerinden wp/bugfix güvenilir biçimde bulunamaz
      • trunktan wp/bugfixe giden ileri yönlü bir referans olmadığı için bu, erişilebilir bir ilişki de değildir
      • Git diyagramları insana sıra ve eşleşme ilişkisi varmış gibi görünse de, gerçek araçların sunduğu yetenekleri abartılı gösterebilir

Stacked PR neden zor

  • Farklı zaman dilimlerindeki insanlarla çalışırken ve inceleme bitmeden merge etmek istemiyorken, işleri CPU’daki gibi pipeline etmek gerekir
  • Tek bir PR açıp inceleme bitene kadar beklemek yerine, ilk PR’ın üstüne ikinciyi, onun üstüne de sonrakini koyarak birden fazla sıralı PR’ı aynı anda incelemeye açma yöntemi Stacked PR’dır
  • Git, Stacked PR yapısını güvenilir biçimde ele almayı zorlaştırır
    • Fix key entry race üzerine Refactor key entry code gibi sonraki bir PR oluşturup daha sonra trunku fetch ederek güncellediğinizde, yığını koruyarak rebase etmeniz gerekir
    • Git, sonraki commit’leri bilmediği için Fix key entry race üzerinden Refactor key entry codeu kolayca göremez
    • Commit terk edilmiş de olabilir; bu yüzden sonraki commit’leri görseniz bile güncel olup olmadıklarını anlamak zordur
    • Branch’ler PR’ın kendisi gibi kullanılır, ancak bu akışta yanlışlıkla üzerlerine yazmak kolaydır
  • Graphite gibi stacking araçları bu işi Git üzerinde yapabilir, ancak Git’in commit ya da branch yapısını güçlendiremez
    • Ayrı bir branch metadata deposu oluşturup bunu Git ile senkronize etmeleri gerekir
    • Kullanıcı Git’in kendisini doğrudan manipüle ederse bu depo Git durumu ile uyumsuz hale gelebilir

Değiştirilebilir durum commit’in dışında

  • Git’teki birçok sorun, değiştirilebilirliği (mutability) doğrudan modellemeyen yaklaşımdan kaynaklanır
  • Git’in düzenleme iş akışında commit ve branch’lerin dışında ayrı durumlar vardır
    • Staging ya da index, çalışma kopyasından üretilen kaynak kod anlık görüntüsüdür ve yeni commit buradan oluşturulur
    • Unstaged, index ile dosya sistemi arasındaki farkı gösteren ikinci bir diff’tir
    • Dosya sistemi checkout edilmiş içeriği taşır; staged ve unstaged değişiklikler de bunun üstüne eklenir
    • HEAD, yeni commit’in oluşturulduğu konumdur
  • stash, staging ve unstaged değişiklikleri saklayıp geri yükleyen ayrı bir depo gibi davranır
  • Checkout’ı başka bir commit ya da branch’e değiştirdiğinizde Git, dosya sistemini yeni konuma uydururken staging ya da unstaged diff’lerini korumaya da çalışır
  • Bu süreçte komutlar farklı olsa da, ok ilişkilerine bakıldığında staging’i yeni bir taban üzerine taşıyan rebase benzeri bir biçim ortaya çıkar

Her şeyi commit olarak modellemek neden zor

  • Staging ve çalışma kopyası da açık bir ataya sahiptir ve kaynak kod içerir; bu yüzden yalnızca statik duruma bakılırsa commit gibi ifade edilebilirler
  • Ancak commit kimliği içeriğin hash’i olduğu için, commit değiştirilebilir olsaydı kimliği sürekli değişirdi
  • Staging ve çalışma kopyasının “ne olduğunu” tutarlı biçimde göstermek için commit değil branch gibi ele alınmaları gerekir, fakat branch’lerde de yukarıda değinilen sınırlamalar vardır
  • Bu karmaşıklık gerçek sorunlara yol açar
    • Git’i öğrenmek ve kullanmak daha zor hale gelir; çünkü aynı kavram iki tarafta ayrı ayrı bulunur
    • Deponun tüm durumu, clone ile alınan durumdan büyük ölçüde farklı olduğu için dışa aktarma tuhaflaşır
    • Zaman içinde değişiklik setlerinin değiştiği asenkron akışlar iyi işlemez
    • Değiştirilebilir taraftaki sistem merge’ü ifade edemediği için, gerçek iş akışını gösteremediği durumlar oluşur

Git’in gerçek iş akışını ifade edemediği durumlar

  • Yeni bir özellik branch’inde henüz commit etmeden geliştirme yaparken, cihaz üzerinde geliştirmeyi engelleyen bir hatayla karşılaşabilirsiniz
  • Bu hata yeni özelliği doğrudan engellemese de geliştirmeyi can sıkıcı hale getiriyorsa, çalışmayı stash edip yeni bir branch’e geçerek yeniden üretme testi ve düzeltme hazırlayıp PR açabilirsiniz
  • Sonrasında yeniden yeni özellik branch’ine döndüğünüzde seçenekler sınırlıdır
    • Gerçekte bağımlılık olmayan new-feature branch’ini bugfix üzerine rebase edip incelemeye öyle gönderirsiniz
    • Geliştirme sırasında new-featurei bugfix üzerinde rebase ederek kullanır, branch’i göndermeden önce ise bu rebase’i geri alırsınız
  • Git ile “düzenleme çalışma alanında bugfix’in tüm kodu ile daha önce commit edilmiş new feature kodu birlikte bulunmalı” durumunu ifade etmek mümkün değildir
  • Bu ihtiyaç, merge edilmemiş PR’larla uyumluluk testi gibi daha zor problemlerde de aynı yapıyla ortaya çıkar
  • Jujutsu megamerges gibi uygun araçlar kullanıldığında, birden fazla PR’ı paralel biçimde korurken düzenleme alanında bunları birlikte kullanmak da mümkün olur

Git artık yeterli değil

  • 2000’lerin başındaki sürüm kontrol araçlarının kullanımı ve yönetimi zordu, kalite tutarsızdı ve Subversion’ın da acı verici olduğu algısı yaygındı
  • O dönemde yerelde tüm deponun bir kopyasına sahip olma isteği yaygın değildi; yerel branch oluşturma talebi de genel kabul görmüyordu
  • Dosya kilitlemeden rahatsız olan çok kişi vardı, ancak bazıları dosya kilitlemenin gerekli olduğunu düşünüyordu ve Git’te tek tek dosyaları ya da dizinleri kilitlemenin mümkün olup olmadığını soruyordu
  • Açık kaynakta olduğu gibi dağıtık iş akışını doğrudan yaşayan insanlar için DVCS, eski yaraları kapatan bir bandaj gibi karşılandı
  • Bugün anlamlı ölçüde dağıtık iş akışları kullananlar için Git’in geçmişe bakan değişmez geçmiş modeli, tekrar eden sorunların kaynağı haline geliyor
  • Meta gibi şirketler neredeyse 10 yıldır Git’i açıkça geride bırakan kurum içi sistemler kullanıyor
  • “Artık Git’i Claude yönetiyor” yönündeki akım, bu alternatifleri anlamsız hale getirmiyor
  • LLM kullanımıyla birlikte, mühendisler tek bir makine içinde bile eskisine kıyasla daha fazla asenkron geliştirme yapıyor gibi görünüyor

1 yorum

 
GN⁺ 2 시간 전
Lobste.rs görüşleri
  • Keşke yazıda jj’nin bunu nasıl çözdüğü de gösterilseymiş
    jj kullanıcıları için bu bariz olabilir, ama o kişilerin yazının ana hedef kitlesi olmama ihtimali yüksek

  • Yazıda Git’in iyi olmadığına kanıt olarak sunulan özelliklere şahsen hiç ihtiyaç duymadım
    Acaba sadece ben mi böyleyim diye düşündüm

    • Hiç de yalnız değilsin
      Araçlarda önemli noktalardan biri, aracın dinamik bir sistemin parçası olmasıdır. Bir aracın mümkün kıldığı şeyler, “yapabileceğime inandığım şeyleri” etkiler; bu inanç da araca dair algıyı ve aracın evrim yönünü yeniden değiştirir
      Bir araç mevcut durumu sarsarsa, nelerin yapılabileceğine dair inanç ve beklentiler de birlikte değişir
  • İlginç görünüyor ama diyagramı görünce başım dönüyor

  • 2000’lerin başındaki kadar vahim değiliz ve Git öncesi sürüm kontrol sistemlerinin sınırları oldukça açıktı denmesine karşılık, Darcs Git’ten önce çıkmıştı ve snapshot tabanlı sürüm kontrolünün bazı sorunlarını kökten düzeltmiş yönleri vardı
    Başlarda performansı kötü olduğu için geride kaldı, ama sonrasında performansı iyileşti ve insanlar dönüp tekrar bakmadı. İlginç işler yapan başka sürüm kontrol sistemleri de var; bu yüzden “Git değilse Jujutsu” tek seçenekmiş gibi ele alınmasa iyi olur. Bu tarz mantığı fazla sık görüyorum

    • Yazarın Git’in veri modelinin çok iyi olduğunu söyleyip, Git branch’lerinin yalnızca branch ucunu gösteren bir pointer olduğu için iş akışının iyi olmadığını geçiştirerek söylemesi biraz komik
      Bu da veri modeliyle ilgili bir sorun
  • jj bunu nasıl ele alıyor? https://www.billjings.com/posts/title/git-is-not-fine/RealityEx23.png

    • jj new A B kullanırsanız working copy commit birden fazla parent’a sahip olabilir, yani merge commit gibi davranır
      Böylece working copy’ye iki parent’ın değişiklikleri de gelir ve bu merge’ün üstünde çalışmaya devam edebilir ya da bunlardan birindeki commit’i amend ederek ilerleyebilirsiniz
  • Şimdilik hâlâ Git’i tercih ediyorum ve yazarın önyargılı olduğu izlenimini veriyor

    • Git’i tercih etmenin sebebinin, yazarın Git’te zor dediği durumlarla karşılaşmayıp zaten Git’e alışkın olmak mı, yoksa böyle durumlarla karşılaşsa bile Git’in Jujutsu’dan daha iyi bir iş akışı sunduğunu düşünmek mi olduğunu merak ediyorum
    • Sadece jj new çalıştırmanız gerektiğini hatırlarsanız, git ile jjyi birlikte kullanabilirsiniz
      Git her zaman parent commit’i gösterir, mevcut jj commit ise working tree’deki commit edilmemiş değişiklikler gibi görünür
      Ben jjyi böyle öğrendim. Rebase yönetimi veya tree taşıma gibi jjnin iyi yaptığı şeylerde jj kullandım; jj karşılığı olan komutu henüz bilmediğim ya da git blame gibi aklıma önce Git komutunun geldiği gündelik işlerde ise git komutlarını kullanmayı sürdürdüm
      Açıkçası her gün kullanmaya başlayana kadar jjnin neden daha iyi olduğunu pek hissedemedim; sadece okurken “Bu özelliğe gerçekten ihtiyaç var mı?” ya da “Bunu Git’te zaten yapabiliyorum” diye düşünüyordum
      Elbette jjnin de dezavantajları var. Güncel bir .gitignore yoksa binary dosyalar yanlışlıkla commit’e girebilir. Neyse ki çok büyük bir dosya eklemeye çalışırsanız jj uyarıyor, ama küçük dosyalar aradan kaçabiliyor
      Debugging sırasında mevcut dizinde izlenen dosyalar veya log dosyaları varsa onlar da girebilir; bu yüzden tree üzerinde çok oynadıktan sonra tüm diffstat çıktısını gözden geçirmek iyi olur
      Özellikle jj ile bisect yaparken .gitignoreyu güncelleyen commit’ten daha eski bir commit’i test ederseniz bu sorun olabilir. Belki bisect için bir read-only mode olmalı