1 puan yazan GN⁺ 2 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Git 2.54 ve 2.55 boyunca kullanıma sunulan deneysel git history komutu, mevcut Git iş akışını korurken geçmiş commit’leri düzenlemeyi ve türetilmiş branch’leri yeniden yapılandırmayı otomatikleştirir
  • fixup, stage edilen değişiklikleri geçmişteki bir commit’e birleştirir; ardından o commit’ten türetilen tüm yerel branch’leri yeni commit hash’ine göre otomatik olarak rebase eder
  • reword geçmiş commit mesajını değiştirir; split ise bir commit’in değişikliklerini hunk düzeyinde iki commit’e ayırdıktan sonra sonraki commit’leri ve branch’leri yeniden kurar
  • Bu üç alt komut, çakışma olasılığı taşıyan işlemleri reddeden atomik işleme sayesinde çalışma ağacının yarım kalmış bozuk bir duruma düşmesini engeller; ancak merge commit içeren geçmişlerde çalışmaz
  • jj’nin birinci sınıf çakışma yönetimi, işlem günlüğü tabanlı geri alma ve çalışma kopyasını commit olarak modelleme yeteneklerini sunmasa da, ayrı kurulum gerektirmeden Git çekirdek dağıtımında çeşitli jj tarzı geçmiş düzenleme özellikleri kullanılabilir

Mevcut Git geçmiş düzenleme neden kullanışsız

  • Birden fazla değişikliği paralel ele alınca branch ve commit’ler arasında sürekli gidip gelmek gerekir; ayrıca rebase -i sırasında çalışma ağacı eksik veya bozuk bir duruma düşebilir
  • Git alternatifi jj, bu sorunlara çözüm olarak sık anılsa da, bir buçuk yıl boyunca her üç ayda bir birkaç gün denenmesine rağmen sonunda yine mevcut Git iş akışına dönülmüş
  • git history, Git çekirdek dağıtımına dahil olan deneysel bir komut olduğu için ayrı bir araç kurmadan denenebilir
    • Git 2.54, nisan ayında reword ve split komutlarını ekledi
    • Git 2.55, haziran ayında fixup komutunu ekledi
  • Alt komutlar üç parçadan oluşur: fixup, reword ve split

fixup: geçmiş commit’i ve türetilmiş branch’leri aynı anda düzeltme

  • git history fixup, geçmişteki bir commit’teki hatayı düzeltir ve o commit’ten türetilen branch’leri otomatik olarak rebase eder
  • Normal düzeltme akışındaki gibi değişiklikler git add ile stage edildikten sonra git history fixup <commit> çalıştırılırsa, stage edilen değişiklikler hedef commit ile birleştirilir
  • Bu, git commit --fixup ile autosquash rebase’in birleşimine benzer; ancak farkı, hedef commit’i içeren diğer branch’leri de güncellemesidir
  • git rebase --update-refs yalnızca mevcut rebase kapsamındaki referansları taşırken, git history hedef commit’ten türeyen tüm yerel branch’leri bulup yeniden yazar
    • Gerekirse güncelleme kapsamı yalnızca mevcut branch ile sınırlandırılabilir
    • Merge commit içeren geçmişlerde çalışmadığı için bazı Git kullanım biçimlerinde uygulanması zordur
  • Düzeltme B içine birleştirildiğinde yeni hash’e sahip B* oluşur; bunun üzerindeki C ve D de sırasıyla C* ve D* olarak yeniden oluşturulur
    • feat-1 ve feat-2 branch uçları da yeni commit’leri takip edecek şekilde taşınır

Atomik işleme ve çakışma kısıtları

  • Üç alt komutun tamamı atomik olarak işlenir; böylece çalışma ağacı yarım bozulmuş bir durumda bırakılmaz
  • Atomiklik, çakışma yaratabilecek işlemlerin baştan reddedilmesiyle sağlanır
  • Bu, çakışmaları birinci sınıf nesne olarak ele alan ve rebase sırasında çakışma durumunu koruyup daha sonra çözmeye izin veren jj’ye kıyasla işlevsel olarak daha sınırlıdır
  • git history kısıtları, geçmiş yeniden yazımının durum koruyan bir işleme dönüşmemesi için bilinçli olarak uygulanmıştır
    • Git gelecekte birinci sınıf çakışma desteği sunarsa bu kısıtlar kaldırılabilir

reword: çalışma ağacına dokunmadan commit mesajı değiştirme

  • git history reword, geçmiş bir commit’in mesajını değiştirir ve onun üzerindeki commit’leri ve branch’leri otomatik olarak yeniden kurar
  • git history reword <commit> çalıştırıldığında düzenleyicide mevcut mesaj açılır; değiştirip kaydedince sonraki commit yığını yeni hash’e göre yeniden oluşturulur
  • Yinelemeli geliştirme sırasında tasarım değiştiğinde, eski commit mesajlarını mevcut yapıya uygun hale getirmek için kullanışlıdır
  • Ağaç içeriği yerine yalnızca commit mesajını değiştirmesi dışında fixup ile aynı şekilde çalışır
  • reword, index’e ya da çalışma ağacına dokunmadan yalnızca commit grafiğini değiştirir
    • Checkout edilmemiş branch’lerdeki commit’ler de mevcut çalışmayı kesintiye uğratmadan düzenlenebilir
    • B’nin mesajı değiştiğinde hash de değişeceği için sonraki C, C* olarak yeniden oluşturulur ve feat-1 de yeni commit’i takip eder

split: tek bir commit’i ikiye ayırma

  • git history split, bir commit’in değişikliklerini etkileşimli olarak seçip iki commit’e böler
  • git history split <commit> çalıştırıldığında hedef commit’in değişikliklerini hunk düzeyinde seçmeye yönelik bir istem açılır
    • Seçilip korunan hunk’lar ilk commit’i oluşturur
    • Kalan hunk’lar ikinci commit’e girer
  • git add -p benzeri bir deneyim sunar, ancak buna git rebase ekleyen karmaşık bir sürece ihtiyaç duymaz
  • Birbiriyle ilgisiz iki değişiklik içeren B bölündüğünde B1 ve B2 oluşur; sonraki C ise bu iki commit’in üzerine C* olarak yeniden kurulur
  • Üç komut arasında kullanım alanı en özel olanıdır, ancak gerçekten commit bölmek gerektiğinde faydalıdır

jj ile işlev farkları

1 yorum

 
GN⁺ 2 시간 전
Hacker News yorumları
  • Git kullanmak bana zor geliyordu ama ücretsiz yayımlanan Pro Git kitabının ilk üç bölümünü okuyunca durum düzeldi. Git’in iç işleyişine dair çok iyi bir zihinsel model sunuyor; iyi ya da kötü, Git’in kullanıcı arayüzü iç yapısını doğrudan yansıttığı için bunu anlayınca her şey yerine oturdu

    • Kullanıcı arayüzünün iç yapıyı doğrudan yansıttığına katılmıyorum. Örneğin git checkout, dal değiştirirken henüz Git’e girmemiş değişiklikleri kaybetmiyor; ama bir dosyanın başka bir sürümünü geri yüklerken değişiklikleri geri alacak şekilde tasarlanmış. Bu iç yapıyı nasıl doğrudan yansıtıyor?
      Bundan bağımsız olarak, içeride yapmak istediğiniz işlemi ve komutu bilseniz bile doğru komut satırı söz dizimini bulmayı zorlaştıran CLI tutarsızlığı da Git’i kullanmayı zorlaştırıyor
    • Git söylendiği kadar kafa karıştırıcı değil; birkaç saat ayırıp anlamak yeterli. Yazılım mühendisleri için temel bir araç olmasına rağmen bunu öğrenmemek üzücü; bir profesyonel için git commit -am “Changes” yeterli değil
    • Git komutları bana da kafa karıştırıcı geliyordu ama Linus’un Git’in iç veri yapılarını anlattığı YouTube videosunu izleyince aslında çok basit olduğunu gördüm. Aracın işlevlerinin veri yapılarını nasıl manipüle ettiğini kolayca eşleştirebildim; asıl Git’in sunmaya çalıştığı daha üst düzey soyutlamalar durumu daha da karıştırıyor
    • Öyle değil. Git komutlarının yarısı düşük seviyeli, diğer yarısı yüksek seviyeli; bu yüzden iç yapıyla bire bir eşleşmiyor
    • Mercurial’ın kullanıcı deneyimi açık ara en iyisiydi; ayrıca belge okumadan öğrenilebiliyordu. Git kullanırken de onu Mercurial gibi kullanıp sorun çıkınca yerel depoyu silerek tüm kariyerim boyunca idare ettim
  • git rebase --abort var; ayrıca rebase’den önce bir etiket oluşturup git reset --hard $set_tag ile geri dönebilirsiniz. Önceki durum kaybolmadığı için etkileşimli rebase’den korkmaya gerek yok

    • Ben de tam bu amaçla temp ya da before-rebase adlı çok sayıda dal oluşturuyorum. Etiket gibi kullanıyorum ama dalları taşımak daha kolay olduğu için büyük bir rebase -i öncesinde genelde git branch before-rebase/some-feature çalıştırıyorum
      Gerçekte git reset before-rebase’e pek ihtiyaç duymadım; ama rebase sonrası dalla karşılaştırıp çatışma çözme sürecindeki istenmeyen değişiklikleri kontrol etmek için git log -p before-rebase kullandığım çok oldu
    • Ayrı bir güvenlik etiketi oluşturmaya bile gerek yok; reflog bunu otomatik olarak yapıyor
    • git reflog kullanıyor musunuz?
    • jj undodan daha basit olamaz
    • Doğru. git rebase yazdıktan sonra yaklaşık yarısında hemen --abort yazıyorum
  • Kendimi coder ya da programcı olarak görmüyorum ama Git öğrenmek, düşünme biçimime düzenleme süper gücü kazandırmış gibiydi. Web siteleri, tasarım projeleri, elektronik, beste yapma, kişisel bilgi deposu, uzaktan yönetim betikleri, yapılandırma yönetimi, kod parçaları gibi pek çok amaçla kullanıyorum; mükemmel olmasa da birlikte çalıştığım herkese öğrenmesini öneriyorum

    • Benim de düzenleme becerim büyük ölçüde gelişti; ama bu Git’in kendisinden çok genel olarak sürüm kontrol sistemlerinin etkisine daha yakın. Sürüm kontrol sistemleri, ürettiklerinizi düzenleyip korumanızı ve istediğiniz zaman istediğiniz kadar kopyasını çıkarabilmenizi sağlıyor; böylece kaygı duymadan çalışabiliyorsunuz
      David Allen’ın 『Getting Things Done』 kitabında, düşünceleri toplamak ve düzenlemek için güvenilir bir sisteme ihtiyaç olduğundan bahsedilir. Ben işler, projeler ve listeler için OmniFocus kullanıyorum; ancak bilgisayarda ürettiğim yazılımları ve bazı verileri düzenleyip koruma konusunda Git’e güveniyorum
    • Beste yapmak için Git’i nasıl kullanıyorsunuz?
  • Bu komutları teoride çok seviyorum ve jj’deki karşılıklarını da sürekli kullanıyorum; ancak git history’nin değiştirdiği commit’leri imzalamanın yolunu bulamadım. Kılavuza ve Git kaynak koduna baktığımda da mevcut GPG imzalı commit’leri git history reword HEAD~ ile değiştirince tüm imzaların kaybolduğunu görüyorum
    Commit imzalarını korumak istediğim için sonunda geleneksel git rebase -iye geri döndüm

    • Bu büyük bir eksiklik gibi görünüyor ve standart commit işleme sürecini de atlıyor gibi. Kılavuzda da git historynin deneysel özellik olduğu yazdığına göre bug olarak bildirmeye değer
  • Yeni bir komut olmadan da 10 yıldan uzun süredir yerel Git commit geçmişini büyük zorluk yaşamadan kapsamlı biçimde değiştirebildim. Git’in ve rebase’in zor gelmesinin büyük nedeninin, doğru komut satırı seçeneklerini ve davranış biçimini kullanıcının kendisinin çözmesini gerektiren bir kullanıcı arayüzü sorunu olduğundan eminim
    Windows’ta TortoiseGit’in rebase iletişim kutusu hem güçlü hem de kullanımı kolaydı. Commit günlüğünde temel alınacak commit’e sağ tıklayıp "Rebase onto this" seçmek yeterli; https://tortoisegit.org/docs/tortoisegit/tgit-dug-rebase.htm... adresinde görüldüğü gibi commit’lerin sırasını değiştirme, bölme, birleştirme ve düzenleme net biçimde yapılabiliyor; ortasında iptal ederseniz de başlangıçtaki duruma dönüyor
    Ancak kabuk uzantısı şeklinde çalıştığı için tıklama sayısı fazla; sekmeli bağımsız bir uygulama olsa daha iyi olurdu, ama bu TortoiseSVN’den devralınan bir tasarım ilkesi gibi görünüyor. Git Extensions’ı da bir süre denedim, fakat sık kullandığım özellikleri bulamadım ya da TortoiseGit’e zaten alışmış olduğum için ona geçemedim. Günlük ekranı https://tortoisegit.org/docs/tortoisegit/tgit-dug-showlog.ht... adresinde görülebilir

    • Windows’tan Linux’a geçtikten sonra özlediğim şeylerden biri, tüm programların dosya/klasör sağ tık menüsüne, TortoiseGit’in durum simgeleri gibi overlay’lere ve My Computer gibi özel nesnelere ekleme yapabildiği entegre kabuk oldu. Bunun XP’de zirveye çıktığını, Vista’dan itibaren Microsoft’un genişletmeyi zorlaştıran bir yöne değiştirdiğini düşünüyorum
      Yine de VC++6’daki bir menünün neden normal bir liste yerine klasör gibi görünen bir kabuk nesnesi oluşturup dosya açma iletişim kutusunda gösterdiğini hâlâ anlamıyorum. Projeye bileşen ekleyen bir sihirbazdı; iletişim kutusu açıkken o sahte klasörü My Computer içinde arardım
    • Mevcut iş akışının biraz daha zahmetli olduğu ve yeni komutun daha kullanışlı olduğu doğru. fixup zaten git commit --fixup ile ya da ondan önce git rebase -i ile mümkündü; reword de git rebase -i ile yapılabiliyordu
      Her commit için uygulanacak işlemi belirlemek üzere dosya düzenleme sürecinin rahatsız edici olduğuna dair yeterince geri bildirim birikmiş olmalı ki, mevcut işlevleri saran daha üst düzey bir komut eklenmiş gibi görünüyor
    • Git kullanıcı arayüzleri arasında Magit’e yaklaşan bir araç görmedim. Hem derin özellikler hem de sadeleştirilmiş, durumunu koruyan bir arayüz sunuyor
      Komut satırı araçlarında bir sonraki komutu çalıştırdığınızda önceki çıktıya erişmek zorlaşır. git log içinde gördüğünüz bir commit’i cherry-pick yapmak isteseniz bile hash terminalde kalmamışsa ezberden giremezsiniz; oysa durumunu koruyan bir arayüz birden fazla etkileşimi paralel tutar ve önceki bilgileri saklar
      Git’te mevcut branch, önceki branch, değiştirilmiş dosyalar, push edilmemiş commit’ler gibi hatırlanması gereken çok fazla durum bilgisi var. Kabuk eklentisinin branch göstergesi ve otomatik tamamlama, birden fazla sekme/pencere bölme, sekmeler arası kopyalama, otomatik tamamlama araması gibi çevre araçlara alışamazsanız Git kullanımı çok tatsız hâle gelebilir
  • jj’nin pratikteki tek zayıflığının Git entegrasyonu olduğunu düşünüyorum. Dosya kümeleri, revizyon kümeleri ve şablonlar için alana özgü dilleri biçimlendirip yapılandırma ve CLI genelinde tutarlı biçimde kullanıyor; iç yapıyı doğrudan açığa çıkarmadan güçlü kavramları arayüz olarak sunuyor
    Ancak fazla esnek olduğu için Git ile birlikte çalışabilirlik büyük ölçüde geleneklere ve disipline dayanıyor; neredeyse tüm jj durumları Git ile ifade edilebildiğinden uzak depoyu bozmak kolay. Bunu düzeltmek için Git’i derinlemesine anlamak gerekiyor
    Yeni ve tek başına yürütülen bir geliştirme projesiyse jj denenmeye değer. Uygun koşullarda bakınca, Subversion’a kıyasla ilk dönem Git’i andırıyor; önce jj’nin kendi yöntemine alışmak, sonradan gelecek zorlu Git entegrasyonuna da biraz daha iyi hazırlanmayı sağlayabilir. İlgili bağlam https://news.ycombinator.com/item?id=48903054 adresinde

    • İlke olarak jj içinde Git dışı backend’ler de var, ancak şu an üretim ortamına uygun olduğu açıkça belirtilen tek seçenek Git backend’i. Git entegrasyonu olmayan jj şimdilik gerçeklikten çok bir kavrama yakın
      jj’yi ve onun sürüm kontrolüne dair zihinsel modelini sevdiğim için kullanmaya devam edeceğim; ama bazen işi bitirmek için Git seviyesine inmem gerektiğinden, yeni bir sürüm kontrol sisteminden çok Git üzerinde kullanışlı bir sarmalayıcı gibi geliyor. Git, jj iş akışını destekleyen komutlar eklerse jj’yi ayrıca kurmak için neden bulmak zorlaşır
  • git history fixup’ın tüm alt branch’leri otomatik rebase etme biçimi benim iş akışıma uymuyor. Özellik branch’inin her sürümünü myfeature.1, myfeature.2 gibi saklayıp yeni sürümde davranış değiştiğinde eski sürümü buluyorum; git reflog bunun yerini tutamaz
    Örneğin sorun myfeature.58 ile myfeature.59 arasında ortaya çıktıysa ve her özellikte 15 commit varsa, temel commit farklı olsa bile iki sürüm arasındaki farkı commit bazında git range-diff myfeature.58~15..myfeature.58 myfeature.59~15..myfeature.59 ile görebilirim. Bu yüzden belirli bir commit’i içeren tüm branch’lerin otomatik rebase edilmesini istemiyorum; eski sürümler için tag kullanma yöntemi de bana pek uymadı

  • Güncel Git, sık kullanılan üç git rebase --interactive işlemini daha az sürtünmeli ayrı komutlar olarak uygulamış ve yalnızca çakışma olmadığında çalışıyor gibi görünüyor

    • git history’nin gerçekten yararlı olup olmadığı şüpheli. Ben commit’leri interaktif rebase ile görsel olarak taşımayı tercih ediyorum ve mevcut yerel branch’te artık bulunmayan yetim bir commit hash’ini yanlışlıkla git history hedefi olarak belirtmek istemem
  • Git geçmişini kusursuzca düzenlemek için neden bu kadar uğraşıldığını anlamıyorum. Nasıl olsa tek tek commit'leri tekrar okumuyoruz; birleştirmeden önce hepsini squash etmek yeterli

    • Kesinlikle katılmıyorum. Bir PR'ı incelerken değişikliğin akışını anlamak için en azından commit geçmişini okurum; "oops" ve "fix" ile doluysa hemen geri çeviririm
      Commit'ler yapılan işin hikâyesini aktarır; bu yüzden inceleyen kişinin niyeti anlamak için daha fazla uğraşmasına gerek kalmasın diye okunabilir bir commit geçmişi sunmak gerekir
    • Ben tek tek commit'leri sık sık okurum. Özenle yönetilen depolarda hata veya regresyonları izlemek ve yazarın değişiklik niyetini anlamak için çok faydalıdır
    • Kodun neden bu hale geldiğini merak ettiğimde, o kodu getiren commit'in bağlamına bakmak çok yardımcı olur. Birbirinden farklı değişiklikleri tek seferde içeren devasa bir commit ise bunun değeri çok daha düşer
    • Büyük bir ekipte çalışmadığını belli eden bir düşünce. Tatilden döndüğümde ilk iş Git geçmişini okuyup o arada nelerin değiştiğini kontrol ederim; CI bozulduğunda da faydalı olduğu için neredeyse her gün bir şekilde geçmişe bakarım
      Kod tabanı ve özellikler çok küçükse hepsini squash etmek sorun olmayabilir; ama ben gerçek değişiklik akışını görebildiğim, commit'leri seçici olarak cherry-pick yapabildiğim veya geri alabildiğim bir yöntemi tercih ederim
    • Mantıksal değişiklik başına bir commit olacak şekilde iyi düzenlenmiş bir geçmiş, regresyonları ikili aramayla bulurken son derece faydalıdır
  • Şu anda D checkout edilmiş durumdayken B'yi B' olarak değiştirdiğimizde, git rebase --update-refs'in rebase kapsamı dışındaki E'yi olduğu gibi bırakıp yalnızca B' → C' → D' oluşturduğunu ve mevcut B → E'yi koruduğunu anlamıştım
    Buna karşılık git history fixup, E'yi de E' olarak güncelliyor mu? Öyleyse onlarca yıldır elim alışmış olan git rebase ile tüm alt dalları güncellemenin bir yolu olup olmadığını merak ediyorum

    • Commit hash'i, üst commit'in hash'ini de yansıttığı için E olduğu gibi bırakılamaz. --update-refs'i bizzat denemedim ama dokümana göre sonuç üçüncü grafikteki gibi E' de oluşacak şekilde olmalı; yani istediğiniz davranış zaten git rebase ile mümkün
    • https://blog.hot-coffee.dev/en/blog/git_update_refs/ adresine bakabilirsiniz