Memahami git rev-list
Saat mencari contoh git hook, saya menemukan posting berikut: https://github.com/Movidone/git-hooks/blob/master/pre-receive dan saya ingin memahami perintah berikut:
git rev-list $new_list --not --all
di mana new_list diperoleh dari:
NULL_SHA1="0000000000000000000000000000000000000000" # 40 0's
new_list=
any_deleted=false
while read oldsha newsha refname; do
case $oldsha,$newsha in *,$NULL_SHA1) # it's a delete
any_deleted=true;;
$NULL_SHA1,*) # it's a create new_list="$new_list $newsha";; *,*) # it's an update new_list="$new_list $newsha";;
esac
done
Saya membayangkan bahwa rev-list menunjukkan komit dalam urutan kronologis terbalik.
Tapi, dapatkah seseorang berbagi lebih banyak wawasan tentang apa -notdan -allopsi dimaksudkan untuk?
Sesuai dokumentasi:
--not
Reverses the meaning of the ^ prefix (or lack thereof) for all following revision specifiers, up to the next --not.
--all
Pretend as if all the refs in refs/ are listed on the command line as <commit>.
Saya tidak dapat sepenuhnya memahami opsi ini.
[Perbarui] Setelah melakukan beberapa uji komitmen, saya pikir jika saya tidak menggunakan --notdan --allopsi kemudian, git rev-listdaftar semua komitmen di cabang dan bukan yang saat saya berniat untuk mendorong.
Namun, ingin memahami mengapa tidak mencetak nilai sha pada terminal ketika --allopsi dilewatkan?
Jawaban
The git rev-listperintah adalah sangat rumit, sangat sentral perintah dalam Git, seperti apa yang dilakukannya adalah berjalan grafik . Grafik kata di sini merujuk pada grafik komit itu sendiri, dan dalam beberapa kasus, level berikutnya ke bawah (objek Git dapat dijangkau dari komit).
Saya membayangkan bahwa rev-list menunjukkan komit dalam urutan kronologis terbalik.
Tidak persis, tapi hampir:
- Urutannya bisa diubah. The standar adalah reverse-kronologis.
- Defaultnya adalah menjalankan beberapa commit, tetapi Anda bisa
rev-listmelangkah lebih dalam untuk menyertakan objek tree dan blob dan bahkan objek tag. Ini untuk program sepertigit fetchdangit push(yang memanggilgit pack-objects) dangit pack-objects. Saya berencana untuk mengabaikan kemungkinan ini sepenuhnya di sini, tetapi saya merasa setidaknya saya harus menyebutkannya. 😀
Jadi defaultnya adalah membuat daftar beberapa komit dalam urutan kronologis terbalik. Ini penting, dan sedikit rumit, untuk menentukan dengan tepat bagian mana dari grafik yang akan kita git rev-listjalani: beberapa di beberapa komit .
Tapi, dapatkah seseorang berbagi lebih banyak wawasan tentang apa
--notdan--allopsi dimaksudkan untuk?
Sebagai catatan VonC , efeknya di sini adalah membuat daftar komit yang baru untuk repositori penerima. Ini tergantung pada fakta bahwa git rev-listperintah ini berjalan di hook pra-terima . Biasanya tidak melakukan sesuatu yang berguna di luar pengait khusus ini. Jadi, seperti yang Anda lihat, lingkungan run-time hook, di Git, sering kali paling tidak spesial. (Ini benar untuk lebih dari sekedar hook pra-terima: seseorang harus memikirkan tentang konteks aktivasi setiap hook.)
Lebih tentang --not --all
The --allpilihan ini hanya apa yang Anda dikutip dari dokumentasi:
Berpura-pura seolah-olah semua referensi
refs/terdaftar di baris perintah ...
Jadi ini sama dengan a git for-each-ref refs: loop di atas setiap referensi. Itu termasuk nama-nama cabang ( masteratau main, develop, feature/tall, dan sebagainya, yang semuanya benar-benar dalam refs/heads/), nama tag ( v1.2yang benar-benar refs/tags/v1.2), remote-pelacakan nama ( origin/developyang benar-benar refs/remotes/origin/develop) pengganti ref, (di refs/replace/), simpanan ( refs/stash), bisection refs, Gerrit refs jika Anda menggunakan Gerrit, dan sebagainya. Perhatikan bahwa itu tidak mengulang entri reflog.
The --notprefix adalah operasi boolean sederhana. Dalam sintaksis gitrevision — lihat dokumentasi gitrevision —kita dapat menulis hal-hal seperti develop, artinya saya memberi tahu Anda untuk memulai dari developdan bekerja mundur dan menyertakan komit ini , tetapi juga hal-hal seperti ^develop, artinya saya memberi tahu Anda untuk memulai dari developdan bekerja mundur dan mengecualikan komit ini . Jadi jika saya menulis:
git rev-list feature1 feature2 ^main
Saya meminta Git untuk menjalankan komitmen yang dapat dijangkau dari komitmen yang diidentifikasi oleh nama feature1dan feature2, tetapi untuk mengecualikan komitmen yang dapat dijangkau dari komitmen yang diidentifikasi oleh main. Untuk (banyak) lebih lanjut tentang gagasan umum tentang jangkauan dan grafik-berjalan, lihat Think Like (a) Git .
The --notOperator efektif membalik ^pada setiap ref:
git rev-list --not feature1 feature2 ^main
adalah singkatan, seolah-olah, untuk:
git rev-list ^feature1 ^feature2 main
Ini menelusuri daftar komitmen yang dapat dijangkau dari main, tetapi mengecualikan yang dapat dijangkau dari salah satu feature1atau feature2.
Biasanya semua komitmen dapat ditemukan dengan--all
Jika Anda menggunakan Git dengan cara biasa sehari-hari, dan tidak memiliki "HEAD yang terpisah" saat ini — mode HEAD yang terlepas sebenarnya tidak normal tetapi ini bukan cara kerja yang biasa — --allopsi untuk git rev-listmemberitahukannya agar menyertakan semua commit , karena semua komitmen dapat dijangkau dari semua referensi. 1 Jadi --not --allsecara efektif mengecualikan semua commit. Jadi, menambahkan --not --allke salah satu git rev-listyang seharusnya mencantumkan beberapa komitmen memiliki efek menghambat daftar. Outputnya kosong: kenapa kita repot-repot?
Jika Anda berada dalam mode HEAD terpisah dan telah membuat beberapa komit baru — ini dapat terjadi saat Anda berada di tengah-tengah rebase interaktif atau konflik, misalnya — maka git rev-list HEAD --not --allakan mencantumkan komit yang dapat dijangkau dari HEADtetapi bukan dari nama cabang mana pun. Dalam rebase itu, misalnya, itu hanya komitmen yang telah Anda salin sejauh ini.
Jadi mode "HEAD terpisah" akan menjadi tempat yang git rev-list --not --alldapat berguna dari baris perintah. Tetapi untuk situasi yang Anda periksa — pengait pra-terima — kami tidak benar-benar berada pada baris perintah.
Kait pra-terima
Ketika seseorang menggunakan git pushuntuk mengirim komit ke Git Anda sendiri, Git Anda:
- menyiapkan area karantina untuk menampung objek baru (komit dan blob baru dan seterusnya); 1
- bernegosiasi dengan pengirim untuk memutuskan apa yang harus dikirim pengirim;
- menerima benda-benda ini; dan
- mengambil daftar permintaan pembaruan ref . Permintaan pembaruan ini pada dasarnya katakan saja buat nama ini memegang ID hash ini . 2
Sebelum benar - benar melakukan pembaruan apa pun yang diminta, Git Anda:
- Memberi makan seluruh daftar ke hook pra-terima. Pengait itu bisa berkata "tidak"; jika demikian, seluruh dorongan, secara keseluruhan, ditolak.
- Jika itu mengatakan "ok", masukkan daftar, satu permintaan pada satu waktu, ke hook pembaruan. Ketika pengait itu mengatakan "ok", lakukan pembaruan. Jika pengait mengatakan "tidak", Git Anda menolak satu pembaruan, tetapi melanjutkan untuk memeriksa yang lain.
- Setelah semua pembaruan diterima atau ditolak pada langkah 2, masukkan daftar yang diterima ke hook pasca-terima.
Objek yang diperlukan, yang telah ditambahkan ke beberapa ref di langkah 2, dipindahkan dari karantina ke database objek Git. Mereka yang ditolak tidak.
Sekarang, pikirkan tipikal git push. Kami mendapatkan beberapa komit baru dan permintaan: buat nama cabang barufeature/short , atau kami mendapatkan beberapa komit baru dan permintaan: perbarui nama cabang yang ada developuntuk menyertakan komit baru ini, bersama dengan yang lama .
Pada langkah 1 di atas, kami memiliki satu ID hash baru. Kami menjalankan loop untuk membaca semua nama ref, dan ID hash baru dan yang saat ini diusulkan, dan loop hanya berjalan sekali, karena hanya satu nama yang diberi namagit push . Itu ID hash mengacu pada baru melakukan atau komit, yang akan baik ditambahkan ke cabang yang ada ini, atau menjadi ujung dan komit lain yang eksklusif untuk cabang baru.
Kami sekarang ingin memeriksa komit ini, dan bukan komit yang ada yang dapat dijangkau dari cabang yang ada. Untuk kesederhanaan, daripada $new_listdi jawaban saya yang lain, anggap saja kita hanya satu ID hash baru $new,, dan ID hash lama untuk nama cabang,: $oldsemua-nol jika cabang itu semuanya baru, atau beberapa komit yang ada yang valid jika itu nama cabang yang sudah ada.
Jika komit baru berada di cabang yang benar-benar baru, maka:
git rev-list $new ^master ^develop ^feature/short ^feature/tall
akan menutupi mereka, misalnya, jika kita tahu bahwa satu-satunya cabang yang ada adalah empat cabang ini (dan tidak ada tag dll yang perlu dikhawatirkan). Tetapi bagaimana jika mereka ditambahkan ke, katakanlah develop,? Kemudian kita ingin mengecualikan komit yang saat ini di develop. Kita dapat menggunakan $oldID hash untuk melakukan itu:
git rev-list $new ^master ^$old ^feature/short ^feature/tall
Itu lagi-lagi akan mencantumkan hanya komitmen baru yang git push origin developingin ditambahkan oleh siapa pun yang menjalankannya ke kami develop.
Tapi pikirkanlah $old. Ini adalah ID hash. Di mana Git mendapatkannya? Git mendapatkan ID hash ini dari namanya develop . Ini adalah hook pra-terima ; yang nama develop belum diperbarui belum . Jadi nama develop adalah nama untuk ID hash lama $old. Itu berarti:
git rev-list $new ^master ^develop ^feature/short ^feature/tall
juga akan melakukan pekerjaan itu.
Jika git rev-list $newdiikuti oleh "dan tidak semua yang ada" akan melakukan pekerjaan itu, maka:
git rev-list $new --not --branches
akan melakukan pekerjaan itu. Hampir seperti itulah yang kita miliki di sini.
Bug dengan hanya menggunakan --branchesadalah tidak mendapatkan tag, atau referensi lainnya. Kita bisa menggunakan --not --branches --tagstapi --not --alllebih pendek dan juga mendapat semua referensi lainnya.
Jadi dari sinilah --not --allasalnya: itu tergantung pada kasus khusus dari hook pra-terima. Kami membuat daftar ID hash baru, seperti yang diusulkan oleh siapa pun yang menjalankan a git push, yang telah diberikan Git kepada kami sebagai daftar baris. Kami telah git rev-listmenjalankan grafik komit yang diusulkan untuk diperbarui, melihat komit baru di area karantina, tetapi mengecualikan semua komit yang sudah ada di repositori kami. Perintah rev-list menghasilkan ID hash ini, satu ID per baris, yang kemudian kita baca dalam loop shell, dan melakukan apa pun yang kita suka untuk memeriksa setiap komit.
1 Area karantina baru di Git 2.11. Sebelumnya, objek baru bisa tetap berada di repositori untuk sementara waktu, meskipun dorongan ditolak. Area karantina sebenarnya bukan masalah besar bagi kebanyakan orang, tetapi untuk server besar seperti GitHub, ini dapat menghemat banyak ruang disk.
2 Permintaan bisa dipaksakan atau tidak, dan jika dipaksakan, bisa paksa dengan sewa, atau tidak. Informasi ini tidak tersedia di hook pra-terima (atau di hook pembaruan), yang, um, anggap saja tidak terlalu bagus , tetapi ada masalah kompatibilitas dengan menambahkannya. Namun, semuanya layak huni. Hook dapat mengetahui apakah itu membuat ref baru atau menghapus permintaan ref yang ada karena jika demikian, salah satu dari dua ID hash — lama atau baru — akan menjadi semua-nol "null hash" (yang dicadangkan; tidak ada ID hash yang diizinkan menjadi nol semua).
Itu berarti:
- Buat daftar komit yang dapat dijangkau dengan mengikuti tautan induk dari komit yang diberikan, di sini
$new_list, komit baru, yang diubah, atau dihapus - tetapi kecualikan komitmen yang dapat dijangkau dari yang diberikan dengan a
^di depannya, di sini " semua ", yaitu, semua komitmen HEADS, atau komitmen yang diberi tag.
Itu membatasi rev-list hanya pada komit baru yang diterima, dan tidak semua komit (diterima dan sudah ada di repositori penerima)