Bagaimana cara mengekstrak subdirektori git dan membuat submodul darinya?

120

Saya memulai sebuah proyek beberapa bulan yang lalu dan menyimpan semuanya di dalam direktori utama. Di direktori utama saya "Project", ada beberapa subdirektori yang berisi hal-hal berbeda: Project / paper berisi dokumen yang ditulis dalam LaTeX Project / sourcecode / RailsApp berisi aplikasi rel saya.

"Project" adalah GITified dan ada banyak komit di direktori "paper" dan "RailsApp". Sekarang, karena saya ingin menggunakan cruisecontrol.rb untuk "RailsApp" saya, saya ingin tahu apakah ada cara untuk membuat submodul dari "RailsApp" tanpa kehilangan histori.

Cœur
sumber
2
Juga jawaban yang sangat bagus: stackoverflow.com/questions/359424/…
Rehno Lindeque
Kemungkinan duplikat subdirektori Lepaskan (pindahkan) ke repositori Git terpisah
Kolonel Tiga Puluh Dua

Jawaban:

123

Saat ini ada cara yang jauh lebih mudah untuk melakukannya daripada secara manual menggunakan git filter-branch: git subtree

Instalasi

CATATAN git-subtree sekarang menjadi bagian dari git(jika Anda menginstal contrib) mulai 1.7.11, jadi Anda mungkin sudah menginstalnya. Anda dapat memeriksanya dengan menjalankan git subtree.


Untuk menginstal git-subtree dari sumber (untuk versi git yang lebih lama):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

Atau jika Anda menginginkan halaman manual dan semuanya

make doc
make install

Pemakaian

Bagi yang lebih besar menjadi potongan yang lebih kecil:

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add origin [email protected]:my-user/new-project.git
git pull ../ foo-only
git push origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add [email protected]:my-user/new-project.git foo

Untuk dokumentasi rinci (halaman manual), silakan baca git-subtree.txt.

apenwarr
sumber
10
git subpohon batu!
Simon Woodside
3
Tapi bukankah gunanya git-subtree untuk menghindari penggunaan submodul? Maksud saya, Anda memang pembuat git-subtree (kecuali jika ada benturan nama panggilan), tetapi sepertinya git-subtree berubah, meskipun perintah yang Anda tampilkan tampaknya masih valid. Apakah saya melakukan ini dengan benar?
Blaisorblade
18
git-subtree sekarang menjadi bagian dari git (jika Anda menginstal contrib) mulai 1.7.11
Jeremy
8
Yah git rm -rf ./foomenghapus foodari HEADtapi tidak memfilter my-projectriwayat lengkap. Kemudian, git submodule add [email protected]:my-user/new-project.git foohanya membuat foosubmodul mulai dari HEAD. Dalam hal ini, scripting filter-branchlebih unggul karena memungkinkan untuk mencapai "lakukan seolah-olah subdir adalah submodul sejak awal"
Gregory Pakosz
thx for this - git subtree docs hanya sedikit membingungkan, dan ini (bagi saya) hal yang paling jelas berguna yang ingin saya lakukan dengannya ...
hwjp
38

Lihat git filter-branch .

The Examplesbagian dari halaman manual menunjukkan cara mengekstrak sub-direktori dalam proyek itu sendiri sekaligus menjaga semua sejarah itu dan membuang sejarah file lain / direktori (hanya apa yang Anda cari).

Untuk menulis ulang repositori agar terlihat seolah-olah foodir/telah menjadi root proyeknya, dan buang semua histori lainnya:

   git filter-branch --subdirectory-filter foodir -- --all

Dengan demikian Anda dapat, misalnya, mengubah subdirektori perpustakaan menjadi repositori miliknya sendiri.
Perhatikan --bahwa filter-branchopsi memisahkan dari opsi revisi, dan --alluntuk menulis ulang semua cabang dan tag.

Pat Notz
sumber
1
Ini bekerja dengan baik untuk saya. Satunya downside yang saya perhatikan adalah hasilnya adalah satu cabang master dengan semua komit.
aceofspades
@aceofspades: mengapa hal itu merugikan?
n nothing101
2
Bagi saya, inti dari mengekstraksi komit dari git repo adalah saya ingin menyimpan riwayatnya.
aceofspades
13

Salah satu cara untuk melakukannya adalah kebalikannya - hapus semuanya kecuali file yang ingin Anda simpan.

Pada dasarnya, buat salinan repositori, lalu gunakan git filter-branchuntuk menghapus semuanya kecuali file / folder yang ingin Anda simpan.

Misalnya, saya memiliki proyek tempat saya ingin mengekstrak file tvnamer.pyke repositori baru:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

Itu menggunakan git filter-branch --tree-filteruntuk melewati setiap komit, menjalankan perintah dan komit ulang konten direktori yang dihasilkan. Ini sangat merusak (jadi Anda seharusnya hanya melakukan ini pada salinan repositori Anda!), Dan dapat memakan waktu cukup lama (sekitar 1 menit pada repositori dengan 300 komit dan sekitar 20 file)

Perintah di atas hanya menjalankan skrip-shell berikut pada setiap revisi, yang tentunya harus Anda modifikasi (untuk membuatnya mengecualikan sub-direktori Anda, bukan tvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

Masalah terbesar yang jelas adalah meninggalkan semua pesan komit, bahkan jika pesan itu tidak terkait dengan file yang tersisa. Skrip git-remove-empty-commits , perbaiki ini ..

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

Anda perlu menggunakan -fargumen kekuatan, jalankan filter-branchlagi dengan apa pun di refs/original/(yang pada dasarnya adalah cadangan)

Tentu saja ini tidak akan pernah sempurna, misalnya jika pesan komit Anda menyebutkan file lain, tetapi ini sedekat yang diizinkan oleh git saat ini (sejauh yang saya ketahui).

Sekali lagi, jalankan ini hanya pada salinan repositori Anda! - tetapi secara ringkas, untuk menghapus semua file kecuali "thisismyfilename.txt":

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'
dbr
sumber
4
git filter-branchmemiliki (sekarang?) opsi bawaan untuk menghapus komit kosong, yaitu --prune-empty. Panduan yang lebih baik git filter-branchada dalam jawaban atas pertanyaan ini: stackoverflow.com/questions/359424/…
Blaisorblade
4

Baik CoolAJ86 dan jawaban apenwarr sangat mirip. Saya bolak-balik di antara keduanya mencoba memahami bagian yang hilang dari salah satunya. Di bawah ini adalah kombinasi keduanya.

Pertama navigasikan Git Bash ke root repo git yang akan dipecah. Dalam contoh saya inilah~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin [email protected]:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add [email protected]:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Di bawah ini adalah salinan di atas dengan nama yang dapat disesuaikan diganti dan menggunakan https sebagai gantinya. Folder root sekarang~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package
ShawnFeatherly
sumber
3

Jika Anda ingin mentransfer beberapa subset file ke repositori baru tetapi menyimpan riwayatnya, pada dasarnya Anda akan mendapatkan riwayat yang benar-benar baru. Cara kerjanya pada dasarnya adalah sebagai berikut:

  1. Buat repositori baru.
  2. Untuk setiap revisi repositori lama Anda, gabungkan perubahan pada modul Anda ke dalam repositori baru. Ini akan membuat "salinan" dari riwayat proyek Anda yang sudah ada.

Seharusnya agak mudah untuk mengotomatiskan ini jika Anda tidak keberatan menulis skrip kecil tapi berbulu. Terus terang, ya, tapi juga menyakitkan. Orang-orang telah melakukan penulisan ulang riwayat di Git sebelumnya, Anda dapat melakukan penelusuran untuk itu.

Alternatifnya: klon repositori, dan hapus kertas di klon, hapus aplikasi dalam aslinya. Ini akan memakan waktu satu menit, dijamin akan berhasil, dan Anda dapat kembali ke hal-hal yang lebih penting daripada mencoba memurnikan riwayat git Anda. Dan jangan khawatir tentang ruang hard drive yang digunakan oleh salinan riwayat yang berlebihan.

Dietrich Epp
sumber