- Git berhasil sebagai repositori terdistribusi untuk source code, tetapi penanganan workflow terdistribusi lebih mirip solusi yang ditempel belakangan sehingga batasannya mulai terlihat
- Commit dan branch di Git tidak bisa dengan sendirinya merepresentasikan commit penerus, riwayat amend, riwayat rebase, atau status yang sudah dibuang
- Dalam Stacked PR, kita perlu menemukan PR lanjutan dan melakukan rebase sambil mempertahankan stack, tetapi Git sulit memahami relasi ini secara andal
- Git menempatkan status yang dapat berubah seperti staging, unstaged, file system, dan HEAD di luar commit dan branch, sehingga pembelajaran dan penggunaannya menjadi lebih rumit
- Dalam alur pengembangan asinkron yang mengharuskan beberapa PR ditulis bersama sebelum digabung, model riwayat immutable Git yang menghadap ke belakang menimbulkan masalah berulang
Dua peran Git
- Git digunakan sekaligus sebagai repositori terdistribusi untuk source code dan alat workflow terdistribusi
- Sebagai repositori source code, Git sangat sukses, tetapi cara Git menangani workflow terdistribusi pada dasarnya lebih mirip solusi tambahan yang ditempel belakangan
- Pengembangan asinkron, seperti ungkapan dari East River Source Control, nyaris merupakan kondisi dasar; ini terjadi bukan hanya saat berkolaborasi lintas zona waktu, tetapi juga saat bekerja dengan diri sendiri pada waktu yang berbeda
- jj adalah alat yang membuat keterbatasan Git terlihat lebih jelas, dan orang yang merasa Git sudah cukup besar kemungkinan tidak akan mencoba jj dengan serius
Relasi yang terlewat oleh model dasar Git
- Inti cara berpikir Git adalah commit dan branch
- Commit adalah objek immutable yang memuat source code dan riwayatnya
- Branch adalah pointer yang bisa berubah dengan log yang melekat padanya
- Diagram Git yang umum biasanya menggambar commit sebagai
C1, C2, C3 sehingga urutan dan relasinya tampak jelas, tetapi di repositori nyata nama commit lebih mendekati hash atau pesan, sehingga relasi urutan seperti itu tidak benar-benar ada di dalam sistem
- Notasi seperti
C2 dan C2’ setelah rebase hanyalah penjelasan yang mudah dipahami manusia; Git sendiri tidak tahu bahwa kedua commit itu saling berkorespondensi
- Untuk menemukan commit penerus dari commit tertentu, kita harus menelusuri semua branch dan mencari commit pada jalur yang terhubung ke commit tersebut, jadi ini bukan hal yang sederhana
Git tidak punya “C”
- Commit Git tidak bisa mengetahui sendiri informasi berikut
-
Commit penerus
- Riwayat perubahan yang menghubungkan commit lama ke commit baru setelah amend
-
Riwayat rebase
- Apakah commit tersebut sudah dibuang atau belum
- Branch juga punya keterbatasan
- Branch memang punya konsep riwayat, tetapi sulit dipercaya sebagai padanan 1:1 dengan perubahan kode
- Branch tidak memiliki relasi antarsesamanya; misalnya, dari
trunk kita tidak bisa menemukan wp/bugfix secara andal
- Karena tidak ada referensi maju dari
trunk ke wp/bugfix, relasi itu juga bukan relasi yang dapat dijangkau
- Diagram Git bagi manusia tampak memiliki urutan dan relasi korespondensi, tetapi bisa melebih-lebihkan kemampuan yang benar-benar disediakan alatnya
Mengapa Stacked PR sulit
- Jika ingin berkolaborasi dengan orang di zona waktu berbeda tanpa melakukan merge sebelum review selesai, pekerjaan harus di-pipeline seperti CPU
- Alih-alih membuat satu PR lalu menunggu review selesai, Stacked PR adalah pendekatan membuat PR kedua di atas PR pertama, lalu PR berikutnya di atas itu, sehingga beberapa PR berurutan bisa direview secara bersamaan
- Git membuat struktur Stacked PR sulit ditangani secara andal
- Kita bisa membuat PR lanjutan seperti
Refactor key entry code di atas Fix key entry race, lalu setelah mengambil pembaruan trunk, kita harus melakukan rebase sambil mempertahankan stack
- Karena Git tidak mengetahui commit penerus, dari
Fix key entry race kita tidak bisa dengan mudah melihat Refactor key entry code
- Commit itu juga mungkin sudah dibuang, sehingga meskipun commit lanjutan bisa ditemukan, sulit mengetahui apakah itu masih versi terbaru
- Branch dipakai seperti PR itu sendiri, tetapi dalam alur ini sangat mudah tertimpa secara tidak sengaja
- Alat stacking seperti Graphite bisa melakukan pekerjaan ini di atas Git, tetapi tidak dapat memperkuat commit atau branch Git itu sendiri
- Ia harus membuat repositori metadata branch terpisah dan menyinkronkannya dengan Git
- Jika pengguna memanipulasi Git secara langsung, repositori itu bisa menjadi tidak sinkron dengan status Git
Status yang dapat berubah berada di luar commit
- Banyak masalah Git berawal dari cara Git tidak memodelkan mutability secara langsung
- Dalam workflow pengeditan Git, ada status terpisah di luar commit dan branch
- Staging atau index adalah snapshot source code yang dibentuk dari working copy, dan commit baru dibuat dari sini
- Unstaged adalah diff kedua yang merepresentasikan perbedaan antara index dan file system
- File system menyimpan isi yang sedang di-checkout, dan perubahan staged maupun unstaged ditambahkan di atasnya
- HEAD adalah posisi tempat commit baru akan dibuat
stash bekerja seperti repositori terpisah untuk menyimpan dan memulihkan perubahan staged dan unstaged
- Saat checkout dipindah ke commit atau branch lain, Git mencoba menyesuaikan file system ke lokasi baru sambil tetap mempertahankan diff staging atau unstaged
- Proses ini memang memakai perintah berbeda, tetapi jika hanya melihat relasi panahnya, bentuknya mirip rebase yang memindahkan staging ke atas basis baru
Mengapa sulit memodelkan semuanya sebagai commit
- Staging dan working copy juga punya leluhur yang jelas dan berisi source code, jadi bila hanya dilihat sebagai status statis, keduanya tampak bisa direpresentasikan sebagai commit
- Namun ID commit adalah hash dari isinya, jadi jika commit bisa berubah, ID-nya akan terus berubah
- Agar staging dan working copy bisa secara konsisten menunjuk pada “apa” mereka, keduanya harus diperlakukan seperti branch, bukan commit, tetapi branch memiliki keterbatasan yang sudah dibahas sebelumnya
- Kompleksitas ini menimbulkan masalah nyata
- Git menjadi lebih sulit dipelajari dan digunakan, karena konsep yang sama hadir terpisah di dua sisi
- Status keseluruhan repositori sangat berbeda dari status yang dibawa lewat clone, sehingga ekspor terasa canggung
- Alur asinkron, di mana change set berubah seiring waktu, tidak berjalan dengan baik
- Sistem di sisi yang mutable tidak bisa merepresentasikan merge, sehingga kadang tidak mampu menggambarkan workflow nyata
Saat Git tidak bisa merepresentasikan workflow yang sebenarnya
- Saat sedang mengembangkan di feature branch baru tanpa commit, kita bisa menemukan bug pada perangkat yang mengganggu pengembangan
- Jika bug itu tidak menghalangi fitur baru, tetapi membuat pengembangan merepotkan, kita bisa melakukan
stash, pindah ke branch baru, membuat tes reproduksi dan perbaikannya, lalu mengajukan PR
- Setelah itu, ketika kembali ke feature branch baru, pilihan kita menjadi terbatas
- Melakukan rebase
new-feature ke atas bugfix, meskipun sebenarnya tidak ada dependensi nyata, lalu melanjutkan review
- Selama pengembangan, memakai
new-feature di atas bugfix melalui rebase, lalu membatalkan rebase itu sebelum branch diajukan
- Dengan Git, kita tidak bisa merepresentasikan keadaan bahwa “di ruang kerja pengeditan harus ada seluruh kode dari bugfix dan kode new feature yang sudah di-commit secara bersamaan”
- Kebutuhan seperti ini muncul dengan struktur yang sama pada masalah yang lebih sulit, misalnya pengujian kompatibilitas dengan PR yang belum di-merge
- Dengan alat yang tepat seperti Jujutsu megamerges, beberapa PR bisa dipertahankan secara paralel sambil tetap digunakan bersama di ruang pengeditan
Git tidak lagi cukup
- Alat version control pada awal 2000-an sulit digunakan dan dikelola, kualitasnya tidak konsisten, dan ada persepsi luas bahwa Subversion pun menyakitkan untuk dipakai
- Pada masa itu, kebutuhan untuk memiliki salinan penuh repositori secara lokal belum umum, dan keinginan untuk membuat local branch juga belum menjadi hal yang lazim
- Banyak orang merasa tidak nyaman dengan file locking, tetapi sebagian lain menganggapnya perlu, dan bahkan bertanya apakah Git bisa mengunci file atau direktori individual
- Bagi orang-orang yang mengalami workflow terdistribusi secara langsung seperti di open source, DVCS diterima seperti perban yang menutup luka lama
- Saat ini, bagi orang yang benar-benar menggunakan workflow terdistribusi secara bermakna, model riwayat immutable Git yang menghadap ke belakang menjadi sumber masalah yang berulang
- Perusahaan seperti Meta telah menggunakan sistem internal yang jauh melampaui Git selama hampir 10 tahun
- Arus pemikiran bahwa “sekarang Git disentuh oleh Claude sebagai gantinya” tidak membuat alternatif seperti ini menjadi tidak relevan
- Dengan penggunaan LLM, tampaknya para engineer bahkan dalam satu mesin tunggal pun melakukan jauh lebih banyak pengembangan asinkron dibanding sebelumnya
1 komentar
Opini di Lobste.rs
Akan lebih bagus kalau tulisan ini menunjukkan bagaimana jj menyelesaikan masalah yang diangkat
Bagi pengguna jj mungkin ini sudah jelas, tetapi kemungkinan besar mereka bukan target utama tulisan ini
Secara pribadi, saya belum pernah benar-benar membutuhkan fitur-fitur yang disebut di tulisan itu sebagai bukti bahwa Git tidak baik-baik saja
Jadi saya bertanya-tanya apakah cuma saya yang seperti itu
Salah satu hal penting tentang alat adalah bahwa alat merupakan bagian dari sistem yang dinamis. Hal yang dimungkinkan oleh suatu alat memengaruhi “hal yang saya yakini bisa saya lakukan”, dan keyakinan itu pada gilirannya mengubah persepsi terhadap alat tersebut serta arah evolusinya
Ketika suatu alat mengguncang keadaan yang sudah ada, keyakinan dan ekspektasi tentang apa yang bisa dilakukan ikut berubah
Terlihat menarik, tetapi melihat diagramnya bikin pusing
Menanggapi pernyataan bahwa situasinya tidak separah awal 2000-an sekarang, dan bahwa keterbatasan sistem kontrol versi sebelum Git cukup jelas, Darcs hadir lebih dulu daripada Git dan dalam beberapa hal secara mendasar memperbaiki sebagian masalah version control berbasis snapshot
Di awal ia kalah karena performanya buruk, tetapi performanya kemudian membaik dan orang-orang tidak kembali untuk memeriksanya lagi. Ada sistem kontrol versi lain yang juga mengerjakan hal-hal menarik, jadi saya harap ini tidak dibingkai seolah satu-satunya pilihan selain “Git adalah Jujutsu”. Saya terlalu sering melihat logika seperti itu
Itu juga masalah model data
Bagaimana jj menangani ini? https://www.billjings.com/posts/title/git-is-not-fine/RealityEx23.png
jj new A B, commit working copy bisa memiliki beberapa parent, sehingga bertindak seperti merge commitJadi working copy akan berisi perubahan dari kedua parent itu, lalu Anda bisa melanjutkan pekerjaan di atas hasil merge tersebut atau melakukan amend ke salah satu commit
Saya masih lebih menyukai Git, dan penulis tampaknya punya bias
jj new, Anda bisa mencampur penggunaangitdanjjGit selalu menunjuk ke parent commit, dan
jj commitsaat ini menjadi seperti perubahan yang belum di-commit di working treeSaya belajar
jjdengan cara itu. Saya memakaijjuntuk hal-hal yang memang dikerjakannya dengan baik, seperti menangani rebase atau memindahkan tree, dan tetap memakai perintahgituntuk pekerjaan sehari-hari ketika saya belum tahu padanannya dijjatau ketika perintah Git sepertigit blamelebih dulu terlintas di kepalaSaya benar-benar tidak terlalu merasakan kenapa
jjlebih baik sampai memakainya setiap hari; saat hanya membacanya saya berpikir, “apa fitur ini benar-benar perlu?” atau “bukankah ini sudah bisa dilakukan di Git?”Tentu saja
jjjuga punya kekurangan. Jika tidak ada.gitignoreterbaru, file biner bisa ikut masuk ke commit tanpa sengaja. Untungnyajjmemberi peringatan saat Anda menambahkan file yang sangat besar, tetapi file kecil bisa lolosKalau saat debugging ada file pelacakan atau file log di direktori saat ini, itu juga bisa ikut masuk, jadi setelah banyak memanipulasi tree sebaiknya meninjau seluruh diffstat
Ini terutama bisa jadi masalah jika Anda melakukan bisect dengan
jjlalu menguji commit yang lebih lama daripada commit yang memperbarui.gitignore. Mungkin mode read-only diperlukan untuk bisect