Git Filter-Repo adalah alat serbaguna untuk menulis ulang sejarah, yang mencakup kemampuan yang belum saya temukan di tempat lain. Secara kasar jatuh ke ruang alat yang sama seperti Git Filter-Branch tetapi tanpa kapitulasi yang memicu kinerja yang buruk, dengan kemampuan yang jauh lebih banyak, dan dengan desain yang menskalakan kegunaan bijaksana di luar kasus penulisan ulang sepele. Git Filter-Repo sekarang direkomendasikan oleh proyek GIT alih-alih Git Filter-Cabang.
Sementara sebagian besar pengguna mungkin hanya akan menggunakan filter-repo sebagai alat baris perintah sederhana (dan kemungkinan hanya menggunakan beberapa bendera), pada inti filter-repo berisi perpustakaan untuk membuat alat penulisan ulang sejarah. Dengan demikian, pengguna dengan kebutuhan khusus dapat memanfaatkannya untuk dengan cepat membuat alat penulisan ulang sejarah yang sama sekali baru.
Filter-repo membutuhkan:
git-filter-repo
adalah skrip python satu file, yang dilakukan untuk membuat instalasi untuk penggunaan dasar pada banyak sistem sepele: cukup masukkan file itu ke jalur $ Anda.
Lihat install.md untuk hal -hal di luar penggunaan dasar atau kasus khusus. Instruksi yang lebih terlibat hanya diperlukan jika salah satu dari yang berikut berlaku:
Untuk dokumentasi yang komprehensif:
Jika Anda lebih suka belajar dari contoh:
Ini dibahas secara lebih rinci dalam artikel Git Rev News tentang filter-repo, tetapi beberapa sorotan untuk pesaing utama:
Filter-Cabang sangat lambat (ganda pesanan besar lebih lambat dari yang seharusnya) untuk repositori non-sepele.
Filter-cabang penuh dengan gotcha yang dapat diam-diam merusak penulisan ulang Anda atau setidaknya menggagalkan upaya "pembersihan" Anda dengan memberi Anda sesuatu yang lebih bermasalah dan berantakan daripada apa yang Anda mulai.
Filter-cabang sangat berat untuk digunakan untuk penulisan ulang yang bahkan sedikit tidak sepele.
Proyek GIT telah menyatakan bahwa masalah di atas dengan cabang filter tidak dapat diperbaiki ke belakang; Mereka menyarankan Anda berhenti menggunakan filter-branch
Penggemar die-hard-branch filter mungkin tertarik pada filter-lamely (alias filter-branch-ish), reimplementasi-branch filter berdasarkan filter-repo yang lebih berkinerja (meskipun tidak secepat atau aman seperti filter- repo).
Lembar cheat tersedia yang menunjukkan cara mengonversi perintah contoh dari manual-branch filter menjadi perintah filter-repo.
Alat yang bagus untuk waktunya, tetapi sementara itu membuat beberapa hal sederhana, terbatas pada beberapa jenis penulisan ulang.
Arsitekturnya tidak setuju untuk menangani lebih banyak jenis penulisan ulang.
Arsitekturnya menyajikan beberapa kekurangan dan bug bahkan untuk usecase yang dimaksudkan.
Penggemar BFG mungkin tertarik pada BFG-ish, penerimaan ulang BFG berdasarkan filter-repo yang mencakup beberapa fitur baru dan perbaikan bug relatif terhadap BFG.
Lembar cheat tersedia yang menunjukkan cara mengubah perintah contoh dari manual pembersih repo BFG menjadi perintah filter-repo.
Katakanlah bahwa kami ingin mengekstrak sepotong repositori, dengan maksud untuk menggabungkan bagian itu menjadi repo yang lebih besar. Untuk ekstraksi, kami ingin:
Melakukan ini dengan filter-repo sesederhana perintah berikut:
git filter-repo --path src/ --to-subdirectory-filter my-module --tag-rename ' ' : ' my-module- '
(Kutipan tunggal tidak perlu, tetapi buat lebih jelas bagi manusia bahwa kita mengganti string kosong sebagai awalan dengan my-module-
)
BFG Repo Cleaner tidak mampu menulis ulang semacam ini; Faktanya, ketiga jenis perubahan yang diinginkan berada di luar kemampuannya.
Filter-cabang dilengkapi dengan tumpukan peringatan (lebih lanjut tentang itu di bawah) bahkan setelah Anda mengetahui doa yang diperlukan:
git filter-branch
--tree-filter ' mkdir -p my-module &&
git ls-files
| grep -v ^src/
| xargs git rm -f -q &&
ls -d *
| grep -v my-module
| xargs -I files mv files my-module/ '
--tag-name-filter ' echo "my-module-$(cat)" '
--prune-empty -- --all
git clone file:// $( pwd ) newcopy
cd newcopy
git for-each-ref --format= " delete %(refname) " refs/tags/
| grep -v refs/tags/my-module-
| git update-ref --stdin
git gc --prune=now
Beberapa mungkin memperhatikan bahwa doa cabang filter di atas akan sangat lambat karena menggunakan --Tree-filter; Anda dapat menggunakan opsi --Index-filter untuk cabang-cabang, mengubah perintah di atas menjadi:
git filter-branch
--index-filter ' git ls-files
| grep -v ^src/
| xargs git rm -q --cached;
git ls-files -s
| sed "s%$(printf \t)%&my-module/%"
| git update-index --index-info;
git ls-files
| grep -v ^my-module/
| xargs git rm -q --cached '
--tag-name-filter ' echo "my-module-$(cat)" '
--prune-empty -- --all
git clone file:// $( pwd ) newcopy
cd newcopy
git for-each-ref --format= " delete %(refname) " refs/tags/
| grep -v refs/tags/my-module-
| git update-ref --stdin
git gc --prune=now
Namun, untuk salah satu perintah filter-cabang ada tumpukan peringatan. Pertama, beberapa mungkin bertanya-tanya mengapa saya mencantumkan lima perintah di sini untuk filter-branch. Meskipun menggunakan-semua dan-tag-name-filter, dan manpage filter-branch mengklaim bahwa klon sudah cukup untuk menyingkirkan objek lama, langkah-langkah tambahan untuk menghapus tag lainnya dan melakukan GC lain masih diperlukan Bersihkan benda -benda lama dan hindari mencampur sejarah baru dan lama sebelum mendorong ke suatu tempat. Peringatan Lainnya:
Seseorang dapat meretas ini bersama dengan sesuatu seperti:
git fast-export --no-data --reencode=yes --mark-tags --fake-missing-tagger
--signed-tags=strip --tag-of-filtered-object=rewrite --all
| grep -vP ' ^M [0-9]+ [0-9a-f]+ (?!src/) '
| grep -vP ' ^D (?!src/) '
| perl -pe ' s%^(M [0-9]+ [0-9a-f]+ )(.*)$%1my-module/2% '
| perl -pe ' s%^(D )(.*)$%1my-module/2% '
| perl -pe s%refs/tags/%refs/tags/my-module-%
| git -c core.ignorecase=false fast-import --date-format=raw-permissive
--force --quiet
git for-each-ref --format= " delete %(refname) " refs/tags/
| grep -v refs/tags/my-module-
| git update-ref --stdin
git reset --hard
git reflog expire --expire=now --all
git gc --prune=now
Tapi ini datang dengan beberapa peringatan dan keterbatasan yang buruk:
Tak satu pun dari alat penyaringan repositori yang ada melakukan apa yang saya inginkan; Mereka semua kekurangan kebutuhan saya. Tidak ada alat yang menyediakan salah satu dari delapan sifat pertama di bawah ini yang saya inginkan, dan tidak ada alat yang menyediakan lebih dari dua dari empat sifat terakhir:
[Laporan Mulai] Berikan pengguna analisis repo mereka untuk membantu mereka memulai apa yang harus dipangkas atau diganti nama, alih -alih mengharapkan mereka menebak atau menemukan alat lain untuk mengetahuinya. (Dipicu, misalnya dengan menjalankan pertama kali dengan bendera khusus, seperti --Analyze.)
[Keep vs. Remove] Alih -alih hanya menyediakan cara bagi pengguna untuk dengan mudah menghapus jalur yang dipilih, juga menyediakan bendera bagi pengguna untuk hanya menyimpan jalur tertentu. Tentu, pengguna dapat menjelaskan hal ini dengan menentukan untuk menghapus semua jalur selain yang ingin mereka pertahankan, tetapi kebutuhan untuk menentukan semua jalur yang pernah ada dalam versi repositori apa pun kadang -kadang bisa sangat menyakitkan. Untuk cabang filter, menggunakan jaringan pipa seperti git ls-files | grep -v ... | xargs -r git rm
mungkin merupakan solusi yang masuk akal tetapi bisa menjadi sulit dan tidak mudah bagi pengguna; Ditambah perintah-perintah itu sering spesifik sistem operasi (dapatkah Anda melihat gnuisme di cuplikan yang saya berikan?).
[Mengganti nama] Seharusnya mudah untuk mengganti nama jalur. Misalnya, selain memungkinkan seseorang untuk memperlakukan beberapa subdirektori sebagai akar repositori, juga menyediakan opsi bagi pengguna untuk membuat akar repositori hanya menjadi subdirektori. Dan lebih umum mengizinkan file dan direktori untuk dengan mudah diganti namanya. Berikan cek kewarasan jika penggantian nama menyebabkan banyak file ada di jalur yang sama. (Dan tambahkan penanganan khusus sehingga jika komitmen hanya disalin OldName-> newname tanpa modifikasi, maka memfilter OldName-> newname tidak memicu cek kewarasan dan mati pada komit itu.)
[Keselamatan yang lebih cerdas] Menulis salinan referensi asli ke namespace khusus dalam repo tidak memberikan mekanisme pemulihan yang ramah pengguna. Banyak yang akan berjuang untuk pulih menggunakannya. Hampir semua orang yang pernah saya lihat melakukan operasi penyaringan repositori telah melakukannya dengan klon baru, karena memusnahkan klon jika terjadi kesalahan adalah mekanisme pemulihan yang jauh lebih mudah. Sangat mendorong alur kerja itu dengan mendeteksi dan menyelamatkan jika kita tidak berada di klon baru, kecuali pengguna mengesampingkan -force.
[Auto Shrink] secara otomatis menghapus cruft lama dan mengemas repositori untuk pengguna setelah penyaringan (kecuali ditimpa); Ini menyederhanakan hal-hal bagi pengguna, membantu menghindari pencampuran riwayat lama dan baru bersama-sama, dan menghindari masalah di mana proses multi-langkah untuk menyusut repo yang didokumentasikan dalam manpage tidak benar-benar berfungsi dalam beberapa kasus. (Saya melihat Anda, filter-cabang.)
[Pemisahan yang bersih] Hindari pengguna yang membingungkan (dan cegah mendorong kembali barang-barang lama) karena mencampur repo lama dan ditulis ulang repo bersama-sama. (Ini terutama masalah dengan cabang filter saat menggunakan opsi--tag-name-filter, dan kadang-kadang juga masalah ketika hanya memfilter subset cabang.)
[Fleksibilitas] Memberikan pengguna kemampuan untuk memperluas alat atau bahkan menulis alat baru yang memanfaatkan kemampuan yang ada, dan memberikan ekstensibilitas ini dengan cara yang (a) menghindari kebutuhan untuk membayar proses yang terpisah (yang akan menghancurkan kinerja), (b) Menghindari membuat pengguna menentukan perintah shell yang bergantung pada OS (yang akan mencegah pengguna berbagi perintah satu sama lain), (c) mengambil keuntungan dari struktur data yang kaya (karena hash, dikts, daftar, dan array sangat sulit di shell) dan ( D) memberikan kemampuan manipulasi string yang masuk akal (yang sangat kurang dalam shell).
[Referensi komit lama] Menyediakan cara bagi pengguna untuk menggunakan ID komit lama dengan repositori baru (khususnya melalui pemetaan dari hash lama ke hash dengan referensi/ ganti/ referensi).
[Konsistensi Pesan Komit] Jika Pesan Komit Rujuk ke komit lain oleh ID (mis. "Ini kembali berkomitmen 01234567890ABCDEF", "dalam komit 0013DeadBeef9a ..."), pesan komit itu harus ditulis ulang untuk merujuk ke ID komit baru.
[Menjadi pemangkasan yang kosong] komit yang menjadi kosong karena penyaringan harus dipangkas. Jika orang tua dari komitmen dipangkas, leluhur yang tidak dipangkas pertama perlu menjadi orang tua baru. Jika tidak ada leluhur yang tidak dipangkas dan komit itu bukan penggabungan, maka itu menjadi komit akar baru. Jika tidak ada leluhur yang tidak dipangkas dan komit itu adalah penggabungan, maka penggabungan akan memiliki satu orang tua yang lebih sedikit (dan dengan demikian membuatnya cenderung menjadi komit non-merge yang akan dengan sendirinya dipangkas jika tidak memiliki perubahan file sendiri) . Satu hal khusus yang perlu diperhatikan di sini adalah bahwa kami memangkas komit yang menjadi kosong, bukan komit yang mulai kosong. Beberapa proyek sengaja membuat komit kosong untuk versi versi atau penerbitan, dan ini tidak boleh dihapus. (Sebagai kasus khusus, komit yang mulai kosong tetapi orang tuanya dipangkas juga akan dianggap "menjadi kosong".)
[Menjadi pemangkasan degenerasi] pemangkasan komit yang menjadi kosong dapat menyebabkan perubahan topologi, dan ada banyak kasus khusus. Biasanya, gabungan komit tidak dihapus karena mereka diperlukan untuk melestarikan topologi grafik, tetapi pemangkasan orang tua dan leluhur lainnya pada akhirnya dapat mengakibatkan hilangnya satu atau lebih orang tua. Kasus sederhana sudah dicatat di atas: jika komit gabungan kehilangan cukup banyak orang tua untuk menjadi komit non-merge dan tidak memiliki perubahan file, maka itu juga dapat dipangkas. Gabungan komit juga dapat memiliki topologi yang menjadi degenerasi: itu bisa berakhir dengan merge_base yang berfungsi sebagai kedua orang tua (jika semua intervensi berkomitmen dari repo asli dipangkas), atau bisa berakhir dengan satu orang tua yang merupakan leluhur dari yang lain yang lain induk. Dalam kasus seperti itu, jika penggabungan tidak memiliki perubahan file sendiri, maka komit gabungan juga dapat dipangkas. Namun, sebanyak yang kita lakukan dengan pemangkasan kosong, kita tidak memangkas komitmen yang mulai merosot (yang menunjukkan itu mungkin disengaja, seperti dengan-no-ff penggabungan) tetapi hanya gabungan komit yang menjadi degenerasi dan tidak memiliki perubahan file dari mereka sendiri.
[Kecepatan] penyaringan harus cukup cepat
Lihat pedoman yang berkontribusi.
Peserta dalam komunitas filter-repo diharapkan untuk mematuhi standar yang sama seperti untuk proyek GIT, sehingga kode perilaku GIT berlaku.
Bekerja pada filter-repo dan pendahulunya juga telah mendorong banyak peningkatan untuk ekspor cepat dan cepat-impor (dan kadang-kadang perintah lain) di inti git, berdasarkan hal-hal yang perlu dilakukan oleh filter-repo untuk melakukan pekerjaannya: