Lepaskan banyak subdirektori ke dalam repositori Git baru yang terpisah

137

Pertanyaan ini didasarkan pada subdirektori Lepaskan ke dalam repositori Git terpisah

Alih-alih melepaskan satu subdirektori, saya ingin melepaskan pasangan. Misalnya, pohon direktori saya saat ini terlihat seperti ini:

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ

Dan saya ingin ini sebagai gantinya:

/apps
  /AAA
/libs
  /XXX

The --subdirectory-filterargumen untuk git filter-branchtidak akan bekerja karena mendapat menyingkirkan segala sesuatu kecuali untuk direktori yang diberikan pertama kali run. Saya pikir menggunakan --index-filterargumen untuk semua file yang tidak diinginkan akan berhasil (meskipun membosankan), tetapi jika saya mencoba menjalankannya lebih dari sekali, saya mendapatkan pesan berikut:

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

Ada ide? TIA

penjaraerjohn
sumber

Jawaban:

161

Daripada harus berurusan dengan subkulit dan menggunakan ext glob (seperti yang disarankan kynan ), coba pendekatan yang jauh lebih sederhana ini:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

Seperti yang disebutkan oleh komentar void.pointer , ini akan menghapus semuanya kecuali apps/AAAdan libs/XXXdari repositori saat ini.

Pangkas komit gabungan yang kosong

Ini meninggalkan banyak penggabungan kosong. Ini dapat dihapus dengan cara lain seperti yang dijelaskan oleh raphinesse dalam jawabannya :

git filter-branch --prune-empty --parent-filter \
'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

⚠️ Peringatan : Di atas harus menggunakan versi GNU seddan xargsjika tidak maka akan menghapus semua komit karena xargsgagal. brew install gnu-sed findutilslalu gunakan gseddan gxargs:

git filter-branch --prune-empty --parent-filter \
'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"' 
David Smiley
sumber
4
Selain itu, flag --ignore-unmatch harus diteruskan ke git rm, itu gagal untuk komit pertama untuk saya sebaliknya (repositori dibuat dengan git svn clone dalam kasus saya)
Pontomedon
8
Dengan asumsi Anda memiliki tag dalam campuran, Anda mungkin harus menambahkan --tag-name-filter catparameter Anda
Yonatan
16
Bisakah Anda menambahkan beberapa informasi lagi yang menjelaskan apa yang dilakukan oleh perintah yang panjang ini?
Burhan Ali
4
Saya sangat terkejut bahwa ini berfungsi dengan sempurna di Windows menggunakan git bash, Fiuh!
Dai
4
@BurhanAli Untuk setiap komit dalam sejarah, itu menghapus semua file kecuali yang ingin Anda simpan. Ketika semuanya selesai, Anda hanya memiliki bagian dari pohon yang Anda tentukan, bersama dengan hanya sejarah itu.
void.pointer
39

Langkah manual dengan perintah git sederhana

Rencananya adalah untuk membagi direktori individu menjadi repositori sendiri, lalu menggabungkannya bersama. Langkah-langkah manual berikut tidak menggunakan skrip geek-to-use tetapi perintah yang mudah dipahami dan dapat membantu menggabungkan N sub-folder tambahan ke dalam satu repositori.

Membagi

Anggaplah repo asli Anda adalah: original_repo

1 - Aplikasi terpisah:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2 - Pisahkan libs

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

Lanjutkan jika Anda memiliki lebih dari 2 folder. Sekarang Anda akan memiliki dua repositori git baru dan sementara.

Taklukkan dengan Menggabungkan aplikasi dan libs

3 - Siapkan repo baru:

mkdir my-desired-repo
cd my-desired-repo
git init

Dan Anda harus membuat setidaknya satu komitmen. Jika tiga baris berikut harus dilewati, repo pertama Anda akan langsung muncul di bawah root repo Anda:

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

Dengan file temp yang dikomit, mergeperintah di bagian selanjutnya akan berhenti seperti yang diharapkan.

Mengambil dari umpan balik pengguna, daripada menambahkan file acak seperti a_file_and_make_a_commit, Anda dapat memilih untuk menambahkan .gitignore, atau README.mddll.

4 - Gabungkan repo aplikasi terlebih dahulu:

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

Sekarang Anda akan melihat direktori aplikasi di dalam repositori baru Anda. git logharus menunjukkan semua pesan komit historis yang relevan.

Catatan: seperti yang Chris catat di bawah dalam komentar, untuk versi terbaru (> = 2.9) dari git, Anda perlu menentukan --allow-unrelated-historiesdengangit merge

5 - Gabungkan repo libs berikutnya dengan cara yang sama:

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

Lanjutkan jika Anda memiliki lebih dari 2 repo untuk digabungkan.

Referensi: Gabungkan subdirektori dari repositori lain dengan git

chfw
sumber
4
Sejak git 2.9 Anda perlu menggunakan --allow-unrelated-histories pada perintah merge. Jika tidak, ini tampaknya bekerja dengan baik untuk saya.
Chris
1
Jenius! Terima kasih banyak untuk ini. Jawaban awal yang saya lihat, menggunakan filter pohon pada repositori yang sangat besar, memiliki prediksi git yang membutuhkan waktu lebih dari 26 jam untuk menyelesaikan penulisan ulang git. Jauh lebih bahagia dengan pendekatan sederhana namun berulang ini dan telah berhasil memindahkan 4 sub folder ke dalam repo baru dengan semua riwayat komit yang diharapkan.
shuttsy
1
Anda dapat menggunakan komit pertama untuk "Komitmen awal" yang menambahkan .gitignoredan README.mdfile.
Jack Miller
2
Sayangnya, pendekatan ini tampaknya merusak riwayat pelacakan untuk file yang ditambahkan pada git merge .. git read-treelangkah tersebut, karena pendekatan ini mencatatnya sebagai file yang baru ditambahkan dan semua git guis saya tidak membuat sambungan ke komit sebelumnya.
Dai
1
@ksadjad, Tidak tahu, jujur ​​saja. Titik sentral dari penggabungan manual adalah memilih direktori untuk membentuk repo baru dan menyimpan riwayat komit mereka. Saya tidak yakin bagaimana menangani situasi seperti itu di mana sebuah komit meletakkan file ke dirA, dirB, dirDrop dan hanya dirA dan dirB yang dipilih untuk repo baru, bagaimana seharusnya riwayat komit berhubungan dengan yang asli.
chfw
28

Mengapa Anda ingin berlari filter-branchlebih dari sekali? Anda dapat melakukan semuanya dalam satu sapuan, jadi tidak perlu memaksanya (perhatikan bahwa Anda perlu extglobmengaktifkannya di shell Anda agar ini berfungsi):

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

Ini harus menyingkirkan semua perubahan di subdirektori yang tidak diinginkan dan menyimpan semua cabang dan komit Anda (kecuali mereka hanya memengaruhi file di subdirektori yang dipangkas, berdasarkan --prune-empty) - tidak ada masalah dengan komit duplikat, dll.

Setelah operasi ini, direktori yang tidak diinginkan akan terdaftar sebagai tidak dilacak oleh git status.

The $(ls ...)perlu st extglobdievaluasi oleh shell Anda bukan filter indeks, yang menggunakan shbuiltin eval(di mana extglobtidak tersedia). Lihat Bagaimana cara mengaktifkan opsi shell di git? untuk detail lebih lanjut tentang itu.

kynan
sumber
1
Ide yang menarik. Saya memiliki masalah serupa tetapi tidak dapat membuatnya berfungsi, lihat stackoverflow.com/questions/8050687/…
manol
Ini cukup banyak yang saya butuhkan, meskipun saya telah menaburkan file dan folder di repo saya ... Terima kasih :)
notlesh
1
hm. bahkan dengan extglob dihidupkan, saya mendapatkan kesalahan di dekat tanda kurung saya: kesalahan sintaks dekat token tak terduga `('perintah saya terlihat seperti: git filter-branch -f --index-filter" git rm -r -f --cached - -ignore-unmatch src / css / themes /! (some_theme *) "--prune-empty - --semua ls dengan src / css / themes /! (some_theme *) mengembalikan semua tema lain sehingga extglob tampaknya bekerja ...
robdodson
2
@MikeGraf Saya tidak berpikir itu akan memberikan hasil yang diinginkan: melarikan diri akan cocok dengan "!" Literal dll di jalan Anda.
kynan
1
Jawaban @ david-smiley (yang lebih baru) menggunakan pendekatan yang sangat mirip, tetapi memiliki keuntungan dengan mengandalkan gitperintah secara eksklusif , dan karenanya tidak rentan terhadap perbedaan dalam cara lsinterpretasi di seluruh sistem operasi, seperti yang ditemukan @Bae.
Jeremy Caney
20

Menjawab pertanyaan saya sendiri di sini ... setelah banyak trial and error.

Saya berhasil melakukan ini menggunakan kombinasi git subtreedan git-stitch-repo. Instruksi ini didasarkan pada:

Pertama, saya mengeluarkan direktori yang ingin saya simpan ke dalam repositori terpisah:

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx

cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD

cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

Saya kemudian membuat repositori kosong baru, dan mengimpor / menjahit dua yang terakhir ke dalamnya:

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

Ini menciptakan dua cabang, master-Adan master-B, masing-masing memegang konten dari salah satu repo yang dijahit. Untuk menggabungkannya dan membersihkan:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B

Sekarang saya tidak begitu yakin bagaimana / kapan ini terjadi, tetapi setelah yang pertama checkoutdan yang pull, kode secara ajaib bergabung ke dalam cabang master (wawasan apa pun tentang apa yang terjadi di sini dihargai!)

Semuanya tampaknya telah bekerja seperti yang diharapkan, kecuali jika saya melihat melalui newReporiwayat komit, ada duplikat ketika set perubahan mempengaruhi keduanya apps/AAAdan libs/XXX. Jika ada cara untuk menghapus duplikat, maka itu akan sempurna.

penjaraerjohn
sumber
Alat rapi yang Anda temukan di sini. Wawasan tentang "checkout": "git pull" sama dengan "git fetch && git merge". Bagian "ambil" tidak berbahaya karena Anda "mengambil secara lokal". Jadi menurut saya perintah checkout ini sama dengan "git merge master-B", yang sedikit lebih jelas. Lihat kernel.org/pub/software/scm/git/docs/git-pull.html
phord
1
Sayangnya alat git-stitch-repo rusak karena ketergantungan yang buruk saat ini.
Henrik
@Henrik Masalah apa yang Anda alami sebenarnya? Ini berfungsi untuk saya, meskipun saya harus menambahkan export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/"ke konfigurasi bash saya agar dapat menemukan Git.pm. Kemudian saya menginstalnya dengan cpan.
Itu mungkin digunakan git subtree adduntuk melakukan tugas ini. Lihat stackoverflow.com/a/58253979/1894803
laconbass
10

Solusi mudah: git-filter-repo

Saya memiliki masalah serupa dan, setelah meninjau berbagai pendekatan yang tercantum di sini, saya menemukan git-filter-repo . Direkomendasikan sebagai alternatif dari git-filter-branch dalam dokumentasi resmi git di sini .

Untuk membuat repositori baru dari subset direktori dalam repositori yang sudah ada, Anda dapat menggunakan perintah:

git filter-repo --path <file_to_remove>

Filter banyak file / folder dengan merangkainya:

git filter-repo --path keepthisfile --path keepthisfolder/

Jadi, untuk menjawab pertanyaan awal , dengan git-filter-repo Anda hanya memerlukan perintah berikut:

git filter-repo --path apps/AAA/ --path libs/XXX/
elmo
sumber
Ini pasti jawaban yang bagus. Masalah dengan semua solusi lain adalah saya tidak dapat mengekstrak konten SEMUA cabang direktori. Namun, git filter-repo mengambil folder dari semua cabang dan menulis ulang sejarah dengan sempurna, seperti membersihkan seluruh pohon dari semua yang tidak saya butuhkan.
Teodoro
Saya menggunakan jawaban Anda. Ini adalah solusi yang bagus. Lalu entah bagaimana itu hilang dalam banyak tab yang terbuka. Saya harus benar-benar menelusuri dari riwayat browser saya untuk menemukan Anda dan mengucapkan terima kasih.
blueray
7

Saya telah menulis filter git untuk menyelesaikan masalah ini dengan tepat. Ini memiliki nama fantastis git_filter dan terletak di github di sini:

https://github.com/slobobaby/git_filter

Ini didasarkan pada libgit2 yang sangat baik.

Saya perlu membagi repositori besar dengan banyak komit (~ 100000) dan solusi berdasarkan cabang filter git membutuhkan beberapa hari untuk dijalankan. git_filter membutuhkan waktu satu menit untuk melakukan hal yang sama.

jorok
sumber
7

Gunakan ekstensi git 'git split'

git splitsadalah skrip bash yang merupakan pembungkus git branch-filteryang saya buat sebagai ekstensi git, berdasarkan solusi jkeating .

Itu dibuat tepat untuk situasi ini. Untuk kesalahan Anda, coba gunakan git splits -fopsi untuk memaksa penghapusan cadangan. Karena git splitsberoperasi pada cabang baru, itu tidak akan menulis ulang cabang Anda saat ini, jadi cadangannya tidak relevan. Lihat readme untuk detail lebih lanjut dan pastikan untuk menggunakannya pada salinan / klon repo Anda (untuk berjaga-jaga!) .

  1. instal git splits.
  2. Pisahkan direktori menjadi cabang lokal #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ apps/AAA libs/ZZZ

  3. Buat repo kosong di suatu tempat. Kami akan menganggap kami telah membuat repo kosong yang disebut xyzdi GitHub yang memiliki jalur:[email protected]:simpliwp/xyz.git

  4. Dorong ke repo baru. #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz [email protected]:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. Gandakan repo jarak jauh yang baru dibuat ke direktori lokal baru
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone [email protected]:simpliwp/xyz.git

AndrewD
sumber
Tampaknya tidak mungkin untuk menambahkan file ke pemisahan dan memperbaruinya nanti, bukan?
Alex
Sepertinya ini berjalan lambat di repo saya dengan banyak komitmen
Shinta Smith
git-split tampaknya menggunakan filter git --index yang sangat lambat dibandingkan dengan --subdirectory-filter. Untuk beberapa repo, ini mungkin masih menjadi opsi yang layak, tetapi untuk repo besar (beberapa gigabyte, 6-digit komit) --index-filter secara efektif membutuhkan waktu berminggu-minggu untuk dijalankan, bahkan pada perangkat keras cloud khusus.
Jostein Kjønigsen
6
git clone [email protected]:thing.git
cd thing
git fetch
for originBranch in `git branch -r | grep -v master`; do
    branch=${originBranch:7:${#originBranch}}
    git checkout $branch
done
git checkout master

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- dir1 dir2 .gitignore' --prune-empty -- --all

git remote set-url origin [email protected]:newthing.git
git push --all
Richard Barraclough
sumber
Membaca semua komentar lainnya membuat saya berada di jalur yang benar. Namun, solusi Anda berhasil. Itu mengimpor semua cabang, dan bekerja dengan banyak direktori! Bagus!
jschober
1
The forLoop bernilai mengakui, sejak jawaban serupa lainnya tidak memasukkannya. Jika Anda tidak memiliki salinan lokal dari setiap cabang di klon Anda, maka filter-branchtidak akan memperhitungkannya sebagai bagian dari penulisan ulangnya, yang berpotensi mengecualikan file yang diperkenalkan di cabang lain, tetapi belum digabungkan dengan cabang Anda saat ini. (Meskipun itu juga layak dilakukan git fetchdi cabang mana pun yang telah Anda periksa sebelumnya untuk memastikan bahwa mereka tetap terkini.)
Jeremy Caney
3

Ya. Paksa menimpa cadangan dengan menggunakan-f tanda pada panggilan berikutnya filter-branchuntuk mengganti peringatan itu. :) Jika tidak, saya pikir Anda memiliki solusi (yaitu, hapus direktori yang tidak diinginkan sekaligus filter-branch).

Jakob Borg
sumber
-5

Hapus cadangan yang ada di bawah direktori .git di refs / original seperti yang disarankan pesan. Direktori disembunyikan.

pengguna5200576
sumber