1 poin oleh GN⁺ 2 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Perintah eksperimental git history yang diperkenalkan di Git 2.54 dan 2.55 mengotomatiskan perbaikan commit lama dan rekonstruksi branch turunan sambil tetap mempertahankan alur kerja Git yang sudah ada
  • fixup menggabungkan perubahan yang sudah di-stage ke commit lama, lalu secara otomatis me-rebase semua branch lokal yang diturunkan dari commit tersebut agar sesuai dengan hash commit baru
  • reword mengubah pesan commit lama, sedangkan split membagi perubahan dalam sebuah commit berdasarkan hunk menjadi dua commit, lalu menyusun ulang commit dan branch berikutnya
  • Ketiga subperintah mencegah work tree berada dalam kondisi setengah rusak lewat pemrosesan atomik yang menolak operasi yang berpotensi menimbulkan konflik, tetapi tidak bekerja pada riwayat yang memiliki merge commit
  • Meski tidak menyediakan penanganan konflik kelas satu, undo berbasis operation log, maupun pemodelan working copy sebagai commit seperti jj, beberapa fitur pengeditan riwayat ala jj dapat digunakan dari distribusi inti Git tanpa instalasi terpisah

Mengapa pengeditan riwayat Git yang ada terasa merepotkan

  • Saat menangani banyak perubahan secara paralel, Anda harus terus berpindah antara branch dan commit, dan selama rebase -i work tree bisa terjebak dalam keadaan tidak lengkap
  • Alternatif Git, jj, sering disebut sebagai solusi untuk masalah ini, tetapi meski dicoba selama beberapa hari setiap tiga bulan selama satu setengah tahun, pada akhirnya alur kerja Git yang lama tetap digunakan kembali
  • git history adalah perintah eksperimental yang disertakan dalam distribusi inti Git, sehingga dapat dicoba tanpa memasang tool terpisah
    • Git 2.54 memperkenalkan reword dan split pada April
    • Git 2.55 menambahkan fixup pada Juni
  • Subperintahnya terdiri dari tiga jenis: fixup, reword, dan split

fixup: memperbaiki commit lama dan branch turunan sekaligus

  • git history fixup memperbaiki bagian yang salah dari commit lama dan secara otomatis me-rebase branch yang diturunkan dari commit tersebut
  • Seperti prosedur perbaikan biasa, setelah men-stage perubahan dengan git add, jalankan git history fixup <commit> agar perubahan yang sudah di-stage digabungkan ke commit target
  • Ini mirip dengan gabungan git commit --fixup dan rebase autosquash, tetapi berbeda karena juga memperbarui branch lain yang memuat commit target
  • Dibandingkan git rebase --update-refs yang hanya memindahkan referensi dalam rentang rebase saat ini, git history mencari dan menulis ulang semua branch lokal yang diturunkan dari commit target
    • Jika perlu, cakupan pembaruan dapat dibatasi hanya ke branch saat ini
    • Tidak bekerja pada riwayat yang memiliki merge commit, sehingga sulit diterapkan pada sebagian gaya penggunaan Git
  • Jika perbaikan digabungkan ke B, akan dibuat B* dengan hash baru, dan C serta D di atasnya juga dibuat ulang masing-masing sebagai C* dan D*
    • Ujung branch feat-1 dan feat-2 juga berpindah mengikuti commit baru

Pemrosesan atomik dan batasan konflik

  • Ketiga subperintah semuanya diproses secara atomik, sehingga tidak meninggalkan work tree dalam kondisi setengah rusak
  • Atomisitas dijamin dengan cara menolak operasi yang dapat menimbulkan konflik
  • Ini lebih terbatas secara fungsional dibandingkan jj, yang memperlakukan konflik sebagai objek kelas satu sehingga status konflik dapat dipertahankan selama rebase dan diselesaikan belakangan
  • Batasan git history diterapkan secara sengaja agar penulisan ulang riwayat tidak berubah menjadi operasi pelestarian status
    • Jika Git mendukung konflik kelas satu di masa mendatang, batasan ini dapat dicabut

reword: mengubah pesan commit tanpa work tree

  • git history reword mengubah pesan commit lama dan secara otomatis menyusun ulang commit serta branch di atasnya
  • Saat menjalankan git history reword <commit>, pesan lama akan terbuka di editor; setelah diedit dan disimpan, stack commit berikutnya dibangun ulang agar sesuai dengan hash baru
  • Berguna untuk memperbaiki pesan commit lama agar sesuai dengan struktur saat ini ketika desain berubah selama pengembangan berulang
  • Cara kerjanya sama seperti fixup, kecuali yang diubah hanya pesan commit, bukan isi tree
  • reword tidak menyentuh indeks atau work tree, dan hanya mengubah graf commit
    • Commit pada branch yang tidak di-checkout juga dapat diperbaiki tanpa mengganggu pekerjaan saat ini
    • Jika pesan B diubah, hashnya ikut berubah, sehingga C berikutnya dibuat ulang sebagai C* dan feat-1 juga mengikuti commit baru

split: memisahkan satu commit menjadi dua

  • git history split memilih perubahan dalam satu commit secara interaktif dan membaginya menjadi dua commit
  • Saat menjalankan git history split <commit>, prompt untuk memilih perubahan commit target berdasarkan hunk akan terbuka
    • Hunk yang dipilih untuk dipertahankan membentuk commit pertama
    • Hunk sisanya masuk ke commit kedua
  • Mirip dengan git add -p, tetapi tidak memerlukan prosedur rumit yang menggabungkan git rebase
  • Jika B yang berisi dua perubahan tidak terkait dipisah, B1 dan B2 akan dibuat, dan C berikutnya dibangun ulang sebagai C* di atas kedua commit tersebut
  • Di antara tiga perintah ini, cakupan penggunaannya paling spesifik, tetapi berguna saat commit memang perlu dipisahkan

Perbedaan fitur dengan jj

1 komentar

 
GN⁺ 2 jam lalu
Komentar Hacker News
  • Git dulu terasa tidak nyaman, tetapi setelah membaca tiga bab pertama dari buku Pro Git yang tersedia gratis, semuanya membaik. Buku itu memberikan model mental yang sangat baik tentang cara kerja internal Git, dan entah baik atau buruk, antarmuka pengguna Git mencerminkan struktur internalnya secara langsung, sehingga setelah memahaminya semuanya terasa masuk akal

    • Saya tidak setuju bahwa antarmuka pengguna mencerminkan struktur internal secara langsung. Misalnya, git checkout dirancang agar saat berpindah branch, perubahan yang belum masuk ke Git tidak hilang, tetapi saat memulihkan versi lain dari sebuah file, perubahan justru dikembalikan; bagaimana itu mencerminkan struktur internal secara langsung?
      Terlepas dari itu, inkonsistensi CLI yang membuat sulit menemukan sintaks baris perintah yang tepat meskipun sudah tahu operasi dan perintah yang diinginkan secara internal juga membuat Git sulit digunakan
    • Git tidak sekacau reputasinya; cukup luangkan beberapa jam untuk memahaminya. Sayang sekali ada yang tidak mempelajarinya padahal ini alat wajib bagi software engineer, dan bagi profesional, sekadar git commit -am “Changes” tidaklah cukup
    • Saya juga dulu bingung dengan perintah Git, tetapi setelah menonton video YouTube Linus yang menjelaskan struktur data internal Git, saya sadar bahwa Git itu sangat sederhana. Mudah untuk memetakan bagaimana fungsi-fungsi alatnya memanipulasi struktur data, dan justru abstraksi tingkat lebih tinggi yang coba disediakan Git membuat situasinya lebih membingungkan
    • Tidak begitu. Separuh perintah Git bersifat level rendah dan separuh lainnya level tinggi, jadi tidak ada pemetaan 1:1 dengan struktur internal
    • Pengalaman pengguna Mercurial jelas yang terbaik, dan bisa dipelajari tanpa perlu membaca dokumentasi terpisah. Saat memakai Git pun saya menggunakannya seperti Mercurial, lalu kalau ada masalah saya menghapus repositori lokal; cara itu cukup untuk bertahan sepanjang karier saya
  • Ada git rebase --abort, dan Anda juga bisa membuat tag sebelum rebase lalu kembali dengan git reset --hard $set_tag. Karena keadaan sebelumnya tidak hilang, tidak ada alasan untuk takut pada rebase interaktif

    • Saya banyak membuat branch bernama temp atau before-rebase justru untuk keperluan itu. Dipakai seperti tag, tetapi branch lebih mudah dipindahkan, jadi sebelum rebase -i besar biasanya saya menjalankan git branch before-rebase/some-feature
      Nyaris tidak pernah benar-benar perlu git reset before-rebase, tetapi saya sering membandingkan git log -p before-rebase dengan branch setelah rebase untuk memeriksa perubahan tak disengaja selama proses penyelesaian konflik
    • Bahkan tidak perlu membuat tag pengaman terpisah, karena reflog otomatis menjalankan peran itu
    • Apakah Anda memakai git reflog?
    • Tidak bisa lebih sederhana daripada jj undo
    • Benar. Setelah mengetik git rebase, sekitar separuh waktu saya langsung mengetik --abort
  • Saya tidak menganggap diri saya coder atau programmer, tetapi mempelajari Git terasa seperti memberi cara berpikir saya kekuatan super untuk menata sesuatu. Saya memakainya untuk banyak hal: situs web, proyek desain, elektronika, komposisi musik, repositori pengetahuan pribadi, skrip manajemen jarak jauh, manajemen konfigurasi, potongan kode, dan lain-lain. Meski tidak sempurna, saya menyarankan semua orang yang bekerja bersama saya untuk mempelajarinya

    • Saya juga merasa kemampuan menata saya meningkat besar, tetapi itu lebih karena efek sistem version control secara umum daripada Git itu sendiri. Sistem version control menata dan melestarikan hal yang dibuat, serta memungkinkan kita mengambil salinan sebanyak yang diinginkan kapan saja, sehingga kita bisa bekerja tanpa khawatir
      Dalam 『Getting Things Done』 karya David Allen, ada pembahasan tentang perlunya sistem tepercaya untuk mengumpulkan dan menata pikiran. Untuk tugas, proyek, dan daftar saya memakai OmniFocus, tetapi untuk menata dan melestarikan software serta sebagian data yang saya buat di komputer, saya memercayai Git
    • Bagaimana Anda menggunakan Git untuk komposisi musik?
  • Secara teori saya sangat menyukai perintah-perintah ini dan juga selalu memakai perintah padanannya di jj, tetapi saya tidak menemukan cara agar git history menandatangani commit yang dimodifikasi. Setelah melihat manual dan kode sumber Git, jika commit yang sudah ditandatangani GPG diubah dengan git history reword HEAD~, semua tanda tangannya hilang
    Karena ingin mempertahankan tanda tangan commit, akhirnya saya kembali ke git rebase -i tradisional

    • Ini tampak seperti kelalaian besar, dan sepertinya juga melewati proses penanganan commit standar. Manual pun menyebut git history sebagai fitur eksperimental, jadi ini layak dilaporkan sebagai bug
  • Tanpa perintah baru pun, selama lebih dari 10 tahun saya sudah banyak mengubah riwayat commit Git lokal tanpa kesulitan besar. Saya yakin alasan besar mengapa Git dan rebase terasa sulit adalah masalah antarmuka pengguna, karena pengguna harus mencari sendiri opsi baris perintah yang tepat dan cara kerjanya
    Di Windows, dialog rebase TortoiseGit kuat sekaligus mudah digunakan. Dari log commit, cukup klik kanan commit acuan lalu pilih "Rebase onto this"; seperti di https://tortoisegit.org/docs/tortoisegit/tgit-dug-rebase.htm..., pengubahan urutan, pemecahan, penggabungan, dan penyuntingan commit terlihat jelas, dan jika dibatalkan di tengah jalan akan kembali ke keadaan semula
    Namun karena berupa ekstensi shell, jumlah kliknya terlalu banyak; akan lebih baik kalau berupa aplikasi mandiri dengan tab. Meski begitu, ini tampaknya prinsip desain yang mewarisi TortoiseSVN. Saya juga sempat mencoba Git Extensions, tetapi entah tidak menemukan fitur yang sering saya pakai atau sudah terlalu terbiasa dengan TortoiseGit sehingga tidak menetap memakainya. Tampilan log bisa dilihat di https://tortoisegit.org/docs/tortoisegit/tgit-dug-showlog.ht...

    • Salah satu hal yang saya rindukan setelah pindah dari Windows ke Linux adalah shell terpadu yang memungkinkan semua program menambahkan menu klik kanan pada file/folder, overlay seperti ikon status TortoiseGit, dan objek khusus seperti My Computer. Menurut pemahaman saya, ini mencapai puncaknya di XP, lalu sejak Vista Microsoft mengubahnya ke arah yang lebih sulit diperluas
      Namun saya masih tidak mengerti mengapa suatu menu di VC++6 membuat objek shell yang tampak seperti folder alih-alih daftar biasa, lalu menampilkannya di dialog buka file. Itu adalah wizard untuk menambahkan komponen ke proyek, dan selama dialog terbuka saya sering menemukan folder palsu itu di My Computer
    • Alur kerja lama memang sedikit lebih merepotkan, dan perintah baru memang lebih nyaman. fixup sudah bisa dilakukan dengan git commit --fixup atau sebelumnya dengan git rebase -i, dan reword juga bisa dilakukan dengan git rebase -i
      Sepertinya sudah cukup banyak umpan balik bahwa proses mengedit file untuk menentukan tindakan yang akan dijalankan pada tiap commit itu tidak nyaman, sehingga ditambahkan perintah tingkat lebih tinggi yang membungkus fungsi yang sudah ada
    • Di antara antarmuka pengguna Git, saya belum pernah melihat alat yang mendekati Magit. Ia menyediakan fitur mendalam sekaligus antarmuka stateful yang disederhanakan
      Pada alat baris perintah, setelah menjalankan perintah berikutnya, keluaran sebelumnya menjadi sulit diakses. Bahkan jika ingin melakukan cherry-pick commit yang terlihat di git log, jika hash-nya tidak tersisa di terminal, Anda tidak bisa memasukkannya hanya dari ingatan; sedangkan antarmuka stateful mempertahankan beberapa interaksi secara paralel dan menyimpan informasi sebelumnya
      Git memiliki sangat banyak informasi status yang harus diingat, seperti branch saat ini, branch sebelumnya, file yang dimodifikasi, commit yang belum di-push, dan sebagainya. Jika tidak menguasai alat pendukung seperti tampilan branch dan autocomplete dari plugin shell, banyak tab/pemisahan jendela, penyalinan antartab, serta pencarian autocomplete, penggunaan Git bisa menjadi sangat tidak menyenangkan
  • Saya melihat kelemahan praktis jj hanyalah integrasi Git. Ia menstrukturkan bahasa spesifik domain untuk kumpulan file, kumpulan revisi, dan template, lalu menggunakannya secara konsisten di seluruh konfigurasi dan CLI; alih-alih mengekspos struktur internal secara langsung, ia menyediakan konsep yang kuat melalui antarmuka
    Namun karena terlalu fleksibel, interoperabilitas dengan Git sangat bergantung pada konvensi dan disiplin, dan karena hampir semua status jj bisa diekspresikan ke Git, mudah sekali merusak repository remote. Untuk memulihkannya, Anda harus memahami Git secara mendalam
    Untuk proyek pengembangan baru yang dikerjakan sendiri, jj layak dicoba. Dalam kondisi yang menguntungkan, rasanya seperti Git awal jika dibandingkan dengan Subversion; jika lebih dulu terbiasa dengan cara kerja jj itu sendiri, Anda juga bisa sedikit lebih siap menghadapi integrasi Git yang berat nantinya. Konteks terkait ada di https://news.ycombinator.com/item?id=48903054

    • Secara prinsip, jj juga memiliki backend non-Git, tetapi saat ini satu-satunya yang dinyatakan cocok untuk lingkungan operasional adalah backend Git. jj tanpa integrasi Git masih lebih dekat ke konsep daripada kenyataan
      Saya menyukai jj dan model mental version control-nya sehingga akan terus memakainya, tetapi karena kadang harus turun ke Git untuk menyelesaikan pekerjaan, ia terasa lebih seperti wrapper praktis di atas Git daripada sistem version control baru. Jika Git menambahkan perintah yang mendukung alur kerja jj, akan sulit menemukan alasan untuk memasang jj secara terpisah
  • Cara git history fixup otomatis me-rebase semua branch turunan tidak cocok dengan alur kerja saya. Saya menyimpan setiap versi branch fitur seperti myfeature.1, myfeature.2 untuk mencari versi lama saat perilaku berubah di versi baru, dan git reflog tidak bisa menggantikannya
    Misalnya jika masalah muncul antara myfeature.58 dan myfeature.59, dan tiap fitur memiliki 15 commit, dengan git range-diff myfeature.58~15..myfeature.58 myfeature.59~15..myfeature.59 saya bisa melihat perbedaan kedua versi per commit meski commit basisnya berbeda. Karena itu saya tidak menginginkan rebase otomatis semua branch yang menyertakan commit tertentu, dan cara memakai tag untuk versi lama juga tidak terlalu cocok bagi saya

  • Git terbaru mengimplementasikan tiga operasi git rebase --interactive yang sering dipakai sebagai perintah terpisah dengan friksi rendah, dan tampaknya hanya berfungsi saat tidak ada konflik

    • Saya ragu apakah git history benar-benar berguna. Saya lebih suka memindahkan commit secara visual dengan rebase interaktif, dan tidak ingin secara tidak sengaja menetapkan hash commit yatim yang sudah hilang dari branch lokal saat ini sebagai target git history
  • Saya tidak paham mengapa orang begitu bersusah payah merapikan riwayat Git hingga sempurna. Toh kita tidak akan membaca ulang commit satu per satu, jadi sebelum merge cukup squash semuanya

    • Saya sangat tidak setuju. Saat meninjau PR, setidaknya saya membaca riwayat commit untuk memahami alur perubahan, dan kalau penuh dengan "oops" dan "fix", saya langsung menolaknya
      Commit menyampaikan cerita pekerjaan, jadi kita harus menyediakan riwayat commit yang mudah dibaca agar reviewer tidak perlu bekerja lebih keras untuk memahami maksudnya
    • Saya sering membaca commit satu per satu. Di repositori yang dikelola dengan cermat, itu sangat berguna untuk melacak bug atau regresi serta memahami maksud perubahan dari penulisnya
    • Saat saya penasaran mengapa kode menjadi seperti ini, melihat konteks commit yang memperkenalkan kode tersebut sangat membantu. Jika itu commit raksasa yang memuat berbagai perubahan berbeda sekaligus, nilainya jauh berkurang
    • Ini menunjukkan cara berpikir orang yang tidak bekerja di tim besar. Saat kembali dari cuti, hal pertama yang saya lakukan adalah membaca riwayat Git untuk mengecek apa saja yang berubah selama itu, dan saat CI rusak pun riwayat itu berguna, jadi pada dasarnya saya melihat riwayat dalam suatu bentuk hampir setiap hari
      Kalau codebase dan fiturnya sangat kecil, mungkin semuanya bisa di-squash, tetapi saya lebih suka cara yang memungkinkan saya melihat alur perubahan sebenarnya dan melakukan cherry-pick atau revert commit secara selektif
    • Riwayat yang tertata rapi dengan satu commit untuk satu perubahan logis sangat berguna saat melakukan pencarian biner untuk regresi
  • Dalam kondisi saat ini checkout di D, ketika mengubah B menjadi B', saya memahami bahwa git rebase --update-refs membiarkan E yang berada di luar cakupan rebase tetap apa adanya, hanya membuat B' → C' → D', dan mempertahankan B → E yang lama
    Sebaliknya, apakah git history fixup juga memperbarui E menjadi E'? Jika ya, saya penasaran apakah dengan git rebase yang sudah melekat di tangan selama puluhan tahun pun ada cara untuk melakukan pembaruan semua branch turunan

    • Hash commit juga mencerminkan hash commit induknya, jadi E tidak bisa dibiarkan apa adanya. Saya belum pernah memakai --update-refs secara langsung, tetapi jika sesuai dokumentasi, hasilnya akan berupa grafik ketiga yang juga menghasilkan E', jadi perilaku yang diinginkan sudah bisa dilakukan dengan git rebase
    • Lihat https://blog.hot-coffee.dev/en/blog/git_update_refs/