Lepaskan (pindahkan) subdirektori ke repositori Git yang terpisah

1758

Saya memiliki repositori Git yang berisi sejumlah subdirektori. Sekarang saya telah menemukan bahwa salah satu subdirektori tidak berhubungan dengan yang lain dan harus dilepaskan ke repositori yang terpisah.

Bagaimana saya bisa melakukan ini sambil menjaga sejarah file dalam subdirektori?

Saya kira saya bisa membuat klon dan menghapus bagian yang tidak diinginkan dari masing-masing klon, tetapi saya kira ini akan memberi saya pohon lengkap ketika memeriksa revisi yang lebih tua dll. Ini mungkin dapat diterima, tetapi saya lebih suka untuk dapat berpura-pura bahwa dua repositori tidak memiliki riwayat bersama.

Untuk memperjelasnya, saya memiliki struktur berikut:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Tapi saya ingin ini sebagai gantinya:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/
matli
sumber
7
Ini sepele sekarang dengan git filter-branchmelihat jawaban saya di bawah ini.
jeremyjjbrown
8
@ jeremyjjbrown benar. Ini tidak lagi sulit dilakukan tetapi sulit untuk menemukan jawaban yang tepat di Google karena semua jawaban lama mendominasi hasil.
Agnel Kurian

Jawaban:

1228

Pembaruan : Proses ini sangat umum, sehingga tim git membuatnya lebih sederhana dengan alat baru git subtree,. Lihat di sini: Lepaskan (pindah) subdirektori ke repositori Git yang terpisah


Anda ingin mengkloning repositori Anda dan kemudian gunakan git filter-branchuntuk menandai semuanya tetapi subdirektori yang Anda inginkan dalam repo baru Anda dikumpulkan dari sampah.

  1. Untuk mengkloning repositori lokal Anda:

    git clone /XYZ /ABC
    

    (Catatan: repositori akan dikloning menggunakan tautan keras, tetapi itu tidak menjadi masalah karena file yang ditautkan tidak akan dimodifikasi sendiri - yang baru akan dibuat.)

  2. Sekarang, mari kita lestarikan cabang-cabang menarik yang ingin kita tulis ulang juga, dan kemudian hapus asal untuk menghindari mendorong di sana dan untuk memastikan bahwa komit lama tidak akan dirujuk oleh asal:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    atau untuk semua cabang jarak jauh:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Sekarang Anda mungkin ingin juga menghapus tag yang tidak ada hubungannya dengan proyek; Anda juga dapat melakukannya nanti, tetapi Anda mungkin harus memangkas repo lagi. Saya tidak melakukannya dan mendapat WARNING: Ref 'refs/tags/v0.1' is unchangedsemua tag (karena semuanya tidak terkait dengan sub proyek); selain itu, setelah menghapus tag semacam itu, lebih banyak ruang akan direklamasi. Tampaknya git filter-branchharus dapat menulis ulang tag lain, tetapi saya tidak dapat memverifikasi ini. Jika Anda ingin menghapus semua tag, gunakan git tag -l | xargs git tag -d.

  4. Kemudian gunakan filter-branch dan reset untuk mengecualikan file lain, sehingga mereka dapat dipangkas. Mari kita juga menambahkan --tag-name-filter cat --prune-emptyuntuk menghapus komit kosong dan menulis ulang tag (perhatikan bahwa ini harus menghapus tanda tangan mereka):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    atau sebagai alternatif, untuk hanya menulis ulang cabang HEAD dan mengabaikan tag dan cabang lainnya:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Kemudian hapus reflog cadangan sehingga ruang dapat benar-benar direklamasi (meskipun sekarang operasinya destruktif)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    dan sekarang Anda memiliki repositori git lokal dari sub-direktori ABC dengan semua riwayatnya dipertahankan.

Catatan: Untuk sebagian besar penggunaan, git filter-branchmemang harus memiliki parameter yang ditambahkan -- --all. Ya itu benar --space-- all. Ini perlu menjadi parameter terakhir untuk perintah. Seperti Matli ditemukan, ini membuat cabang proyek dan tag termasuk dalam repo baru.

Sunting: berbagai saran dari komentar di bawah ini dimasukkan untuk memastikan, misalnya, bahwa repositori benar-benar menyusut (yang tidak selalu terjadi sebelumnya).

Paul
sumber
29
Jawaban yang sangat bagus Terima kasih! Dan untuk benar-benar mendapatkan apa yang saya inginkan, saya menambahkan "- --all" ke perintah filter-branch.
matli
12
Mengapa Anda perlu --no-hardlinks? Menghapus satu hardlink tidak akan memengaruhi file lainnya. Objek Git juga tidak berubah. Hanya jika Anda ingin mengubah izin pemilik / file yang Anda butuhkan --no-hardlinks.
vdboor
67
Langkah tambahan yang saya sarankan adalah "git remote rm origin". Ini akan menahan dorongan untuk tidak kembali ke repositori asli, jika saya tidak salah.
Tom
13
Perintah lain untuk ditambahkan filter-branchadalah --prune-empty, untuk menghapus commit yang sekarang kosong.
Seth Johnson
8
Seperti Paul, saya tidak ingin tag proyek di repo baru saya, jadi saya tidak menggunakannya -- --all. Saya juga berlari git remote rm origin, dan git tag -l | xargs git tag -dsebelum git filter-branchkomando. Ini menyusutkan .gitdirektori saya dari 60M ke ~ 300K. Perhatikan bahwa saya perlu menjalankan kedua perintah ini untuk mendapatkan pengurangan ukuran.
saltycrane
1321

The Easy Way ™

Ternyata ini adalah praktik yang umum dan bermanfaat sehingga tuan Git membuatnya sangat mudah, tetapi Anda harus memiliki versi Git yang lebih baru (> = 1.7.11 Mei 2012). Lihat lampiran untuk cara menginstal Git terbaru. Juga, ada contoh dunia nyata dalam penelusuran di bawah ini.

  1. Siapkan repo lama

    cd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    

    Catatan: <name-of-folder> harus TIDAK mengandung karakter yang memimpin atau mengikuti. Misalnya, folder bernama subprojectHARUS dilewatkan sebagai subproject, BUKAN./subproject/

    Catatan untuk pengguna Windows: Ketika kedalaman folder Anda> 1, <name-of-folder>harus memiliki pemisah folder gaya * nix (/). Misalnya, folder bernama path1\path2\subprojectHARUS dilewatkan sebagaipath1/path2/subproject

  2. Buat repo baru

    mkdir ~/<new-repo> && cd ~/<new-repo>
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Tautkan repo baru ke GitHub atau di mana pun

    git remote add origin <[email protected]:user/new-repo.git>
    git push -u origin master
    
  4. Bersihkan di dalam <big-repo>, jika diinginkan

    git rm -rf <name-of-folder>
    

    Catatan : Ini meninggalkan semua referensi historis dalam repositori. Lihat Lampiran di bawah ini jika Anda benar-benar khawatir tentang melakukan kata sandi atau Anda perlu mengurangi ukuran file .gitfolder Anda .

...

Panduan

Ini adalah langkah yang sama seperti di atas , tetapi mengikuti langkah-langkah tepat saya untuk repositori saya alih-alih menggunakan <meta-named-things>.

Ini adalah proyek yang saya miliki untuk mengimplementasikan modul browser JavaScript di node:

tree ~/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Saya ingin membagi satu folder btoa,, ke dalam repositori Git yang terpisah

cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only

Saya sekarang memiliki cabang baru btoa-only,, yang hanya memiliki komit btoadan saya ingin membuat repositori baru.

mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only

Selanjutnya saya membuat repo baru di GitHub atau Bitbucket, atau apa pun dan menambahkannya sebagai origin

git remote add origin [email protected]:node-browser-compat/btoa.git
git push -u origin master

Hari bahagia!

Catatan: Jika Anda membuat repo dengan README.md, .gitignoredan LICENSE, Anda harus menarik lebih dulu:

git pull origin master
git push origin master

Terakhir, saya ingin menghapus folder dari repo yang lebih besar

git rm -rf btoa

...

Lampiran

Git terbaru di macOS

Untuk mendapatkan versi terbaru dari Git menggunakan Homebrew :

brew install git

Git terbaru di Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

Jika itu tidak berhasil (Anda memiliki versi Ubuntu yang sangat lama), coba

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Jika itu masih tidak berhasil, coba

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Terima kasih kepada rui.araujo dari komentarnya.

Menghapus riwayat Anda

Secara default menghapus file dari Git tidak benar-benar menghapusnya, itu hanya menyatakan bahwa mereka tidak ada lagi. Jika Anda ingin benar-benar menghapus referensi historis (yaitu Anda memiliki kata sandi yang dikomit), Anda perlu melakukan ini:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

Setelah itu Anda dapat memeriksa apakah file atau folder Anda tidak lagi muncul di riwayat Git sama sekali

git log -- <name-of-folder> # should show nothing

Namun, Anda tidak dapat "mendorong" menghapus ke GitHub dan sejenisnya. Jika Anda mencoba, Anda akan mendapatkan kesalahan dan Anda harus melakukannya git pullsebelum Anda bisa git push- dan kemudian Anda kembali memiliki segalanya dalam riwayat Anda.

Jadi, jika Anda ingin menghapus riwayat dari "asal" - artinya menghapusnya dari GitHub, Bitbucket, dll - Anda harus menghapus repo dan mendorong kembali salinan repo yang sudah dipangkas. Tapi tunggu - masih ada lagi ! - Jika Anda benar-benar khawatir tentang menghilangkan kata sandi atau sesuatu seperti itu, Anda harus memangkas cadangan (lihat di bawah).

Membuat .gitlebih kecil

Perintah hapus riwayat yang disebutkan di atas masih menyisakan banyak file cadangan - karena Git terlalu baik dalam membantu Anda untuk tidak merusak repo Anda secara tidak sengaja. Pada akhirnya akan menghapus file yatim selama berhari-hari dan berbulan-bulan, tetapi meninggalkannya di sana untuk sementara waktu jika Anda menyadari bahwa Anda secara tidak sengaja menghapus sesuatu yang tidak Anda inginkan.

Jadi jika Anda benar-benar ingin mengosongkan sampah untuk mengurangi ukuran klon repo segera Anda harus melakukan semua hal yang sangat aneh ini:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Yang mengatakan, saya sarankan tidak melakukan langkah-langkah ini kecuali Anda tahu bahwa Anda perlu - kalau-kalau Anda memangkas subdirektori yang salah, Anda tahu? File cadangan tidak boleh dikloning ketika Anda mendorong repo, mereka hanya akan ada di salinan lokal Anda.

Kredit

CoolAJ86
sumber
16
git subtreemasih merupakan bagian dari folder 'contrib' dan tidak diinstal secara default di semua distro. github.com/git/git/blob/master/contrib/subtree
onionjake
11
@krlmlr sudo chmod + x / usr/share/doc/git/contrib/subtree/git-subtree.sh sudo ln -s /usr/share/doc/git/contrib/subtree/git-subtree.sh / usr / lib / git-core / git-subtree Untuk mengaktifkan di Ubuntu 13.04
rui.araujo
41
Jika Anda telah memasukkan kata sandi ke repositori publik, Anda harus mengubah kata sandi, jangan mencoba untuk menghapusnya dari repo publik dan berharap tidak ada yang melihatnya.
Rute Mil
8
Solusi ini tidak melindungi sejarah.
Cœur
18
The popddan pushdperintah make ini agak implisit dan lebih sulit untuk grok apa yang hendak melakukan ...
jones77
133

Jawaban Paul menciptakan repositori baru yang berisi / ABC, tetapi tidak menghapus / ABC dari dalam / XYZ. Perintah berikut akan menghapus / ABC dari dalam / XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Tentu saja, uji terlebih dahulu dalam repositori 'clone --no-hardlinks', dan ikuti dengan perintah reset, gc, dan prune yang didaftar Paul.

pgs
sumber
53
buat itu git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEADdan itu akan jauh lebih cepat. index-filter bekerja pada indeks sementara tree-filter harus checkout dan mem-stage semua untuk setiap commit .
fmarc
51
dalam beberapa kasus mengacaukan sejarah repositori XYZ adalah berlebihan ... hanya "rm -rf ABC; git rm -r ABC; git commit -m'extracted ABC ke dalam repo sendiri '" akan bekerja lebih baik bagi kebanyakan orang.
Evgeny
2
Anda mungkin ingin menggunakan -f (memaksa) pada perintah ini jika Anda melakukannya lebih dari satu kali, misalnya, untuk menghapus dua direktori setelah dipisahkan. Kalau tidak, Anda akan mendapatkan "Tidak dapat membuat cadangan baru."
Brian Carlton
4
Jika Anda melakukan --index-filtermetode ini, Anda mungkin juga ingin membuatnya git rm -q -r -f, sehingga setiap doa tidak akan mencetak baris untuk setiap file yang dihapus.
Eric Naeseth
1
Saya menyarankan untuk mengedit jawaban Paul, hanya karena jawaban Paul sangat teliti.
Erik Aronesty
96

Saya telah menemukan bahwa untuk menghapus dengan benar riwayat lama dari repositori baru, Anda harus melakukan lebih banyak pekerjaan setelah filter-branchlangkah.

  1. Lakukan klon dan filter:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Hapus setiap referensi ke riwayat lama. "Asal" melacak klon Anda, dan "asli" adalah tempat cabang-filter menyimpan hal-hal lama:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Bahkan sekarang, riwayat Anda mungkin terjebak dalam paket yang fsck tidak akan menyentuh. Sobek-sobek, buat file pack baru dan hapus objek yang tidak terpakai:

    git repack -ad
    

Ada penjelasan tentang ini dalam manual untuk cabang-filter .

Josh Lee
sumber
3
Saya pikir sesuatu seperti git gc --aggressive --prune=nowmasih hilang, bukan?
Albert
1
@Albert Perintah repack menangani hal itu, dan tidak akan ada objek yang longgar.
Josh Lee
ya, git gc --aggressive --prune=nowmengurangi banyak repo baru
Tomek Wyderka
Sederhana dan elegan. Terima kasih!
Marco Pelegrini
40

Edit: Skrip Bash ditambahkan.

Jawaban yang diberikan di sini hanya berfungsi sebagian untuk saya; Banyak file besar tetap ada di cache. Apa yang akhirnya berhasil (setelah berjam-jam di #git di freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

Dengan solusi sebelumnya, ukuran repositori sekitar 100 MB. Yang ini membawanya ke 1,7 MB. Mungkin itu membantu seseorang :)


Skrip bash berikut mengotomatiskan tugas:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now
Simon A. Eugster
sumber
26

Ini tidak lagi begitu rumit, Anda hanya dapat menggunakan perintah git filter-branch pada klon repo Anda untuk menyisihkan subdirektori yang tidak Anda inginkan dan kemudian mendorong ke remote baru.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .
jeremyjjbrown
sumber
3
Ini bekerja seperti pesona. YOUR_SUBDIR dalam contoh di atas adalah subdirektori yang ingin Anda TETAP, segala sesuatu yang lain akan dihapus
JT Taylor
1
Pembaruan berdasarkan komentar Anda.
jeremyjjbrown
2
Ini tidak menjawab pertanyaan. Dari dokumen itu dikatakan The result will contain that directory (and only that) as its project root.dan memang inilah yang akan Anda dapatkan, yaitu struktur proyek asli tidak dipertahankan.
NicBright
2
@NicBright Bisakah Anda mengilustrasikan masalah Anda dengan XYZ dan ABC seperti dalam pertanyaan, untuk menunjukkan apa yang salah?
Adam
@jeremyjjbrown apakah mungkin untuk menggunakan kembali repo yang dikloning dan tidak menggunakan repo baru, yaitu pertanyaan saya di sini stackoverflow.com/questions/49269602/…
Qiulang
19

Pembaruan : Modul git-subtree sangat berguna sehingga tim git menariknya ke inti dan membuatnya git subtree. Lihat di sini: Lepaskan (pindah) subdirektori ke repositori Git yang terpisah

git-subtree mungkin berguna untuk ini

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (usang)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

DW
sumber
1
git-subtree sekarang menjadi bagian dari Git, walaupun ia ada di pohon contrib, jadi tidak selalu diinstal secara default. Saya tahu itu diinstal oleh rumus Homebrew git, tetapi tanpa halaman manualnya. apenwarr dengan demikian menyebut versinya usang.
echristopherson
19

Berikut ini adalah modifikasi kecil untuk CoolAJ86 's 'The Easy Way ™' jawaban untuk membagi beberapa sub folder (katakanlah sub1dan sub2) ke repositori git baru.

The Easy Way ™ (beberapa sub folder)

  1. Siapkan repo lama

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Catatan: <name-of-folder> harus TIDAK mengandung karakter yang memimpin atau mengikuti. Misalnya, folder bernama subprojectHARUS dilewatkan sebagai subproject, BUKAN./subproject/

    Catatan untuk pengguna windows: ketika kedalaman folder Anda> 1, <name-of-folder>harus memiliki * nix style folder separator (/). Misalnya, folder bernama path1\path2\subprojectHARUS dilewatkan sebagai path1/path2/subproject. Apalagi tidak menggunakan mvperintah tetapi move.

    Catatan akhir: perbedaan unik dan besar dengan jawaban dasar adalah baris kedua dari skrip " git filter-branch..."

  2. Buat repo baru

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Tautkan repo baru ke Github atau di mana pun

    git remote add origin <[email protected]:my-user/new-repo.git>
    git push origin -u master
    
  4. Bersihkan, jika diinginkan

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Catatan : Ini meninggalkan semua referensi historis dalam repositori. Lihat Lampiran dalam jawaban asli jika Anda benar-benar khawatir tentang melakukan kata sandi atau Anda perlu mengurangi ukuran file .gitfolder Anda .

Anthony O.
sumber
1
Ini bekerja untuk saya dengan sedikit modifikasi. Karena saya sub1dan sub2folder tidak ada dengan versi awal, saya harus memodifikasi saya --tree-filterskrip sebagai berikut: "mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi". Untuk filter-branchperintah kedua saya mengganti <sub1> dengan <sub2>, menghapus kreasi dari <name-of-folder>, dan menyertakan -fsetelah filter-branchmengganti peringatan cadangan yang ada.
pglezen
Ini tidak berfungsi jika salah satu dari subdir telah berubah selama sejarah di git. Bagaimana ini bisa diselesaikan?
nietras
@nietras lihat jawaban rogerdpack. Butuh waktu beberapa saat untuk menemukannya setelah membaca dan menyerap semua info di jawaban lain ini.
Adam
12

Pertanyaan aslinya ingin XYZ / ABC / (* file) menjadi ABC / ABC / (* file). Setelah menerapkan jawaban yang diterima untuk kode saya sendiri, saya perhatikan bahwa itu benar-benar mengubah XYZ / ABC / (* file) menjadi ABC / (* file). Halaman manual filter-branch bahkan mengatakan,

Hasilnya akan berisi direktori itu (dan hanya itu) sebagai root proyeknya . "

Dengan kata lain, ini mempromosikan folder tingkat atas "naik" satu tingkat. Itu perbedaan penting karena, misalnya, dalam sejarah saya, saya telah mengganti nama folder tingkat atas. Dengan mempromosikan folder "naik" satu tingkat, git kehilangan kontinuitas di komit di mana saya mengganti nama.

Saya kehilangan kontak setelah filter-cabang

Jawaban saya atas pertanyaan kemudian adalah membuat 2 salinan repositori dan secara manual menghapus folder yang ingin Anda simpan di masing-masing. Halaman manual mendukung saya dengan ini:

[...] hindari menggunakan [perintah ini] jika komit tunggal sederhana sudah cukup untuk memperbaiki masalah Anda

MM.
sumber
1
Saya suka gaya grafik itu. Bolehkah saya bertanya alat apa yang Anda gunakan?
Slipp D. Thompson
3
Menara untuk Mac. Aku benar-benar menyukainya. Hampir layak untuk beralih ke Mac untuk dirinya sendiri.
MM.
2
Yap, meskipun dalam kasus saya, subfolder saya targetdirtelah diganti nama pada suatu titik dan git filter-branchhanya memanggilnya sehari, menghapus semua komitmen yang dilakukan sebelum mengganti nama! Mengejutkan, mengingat betapa mahirnya Git dalam melacak hal-hal seperti itu dan bahkan migrasi potongan konten individual!
Jay Allen
1
Oh, juga, jika ada orang yang berada di kapal yang sama, inilah perintah yang saya gunakan. Jangan lupa bahwa git rmdibutuhkan beberapa argumen, jadi tidak ada alasan untuk menjalankannya untuk setiap file / folder: BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Jay Allen
7

Untuk menambah jawaban Paul , saya menemukan bahwa untuk akhirnya memulihkan ruang, saya harus mendorong HEAD ke repositori bersih dan yang memangkas ukuran direktori .git / objek / paket.

yaitu

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

Setelah pemangkasan gc, lakukan juga:

$ git push ... ABC.git HEAD

Maka Anda bisa melakukannya

$ git clone ... ABC.git

dan ukuran ABC / .git berkurang

Sebenarnya, beberapa langkah yang memakan waktu (mis. Git gc) tidak diperlukan dengan push to clean repository, yaitu:

$ git clone --no-hardlinks / XYZ / ABC
$ git filter-branch --subdirectory-filter ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD
Case Larsen
sumber
6

Cara yang tepat sekarang adalah sebagai berikut:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub sekarang bahkan memiliki artikel kecil tentang kasus-kasus seperti itu.

Tetapi pastikan untuk mengkloning repo asli Anda ke direktori yang terpisah terlebih dahulu (karena itu akan menghapus semua file dan direktori lain dan Anda mungkin perlu bekerja dengannya).

Jadi algoritma Anda harus:

  1. klon repo jarak jauh Anda ke direktori lain
  2. menggunakan git filter-branchhanya file yang tersisa di bawah beberapa subdirektori, dorong ke remote baru
  3. buat komit untuk menghapus subdirektori ini dari repo jarak jauh asli Anda
Olexandr Shapovalov
sumber
6

Tampaknya sebagian besar (semua?) Jawaban di sini bergantung pada beberapa bentuk git filter-branch --subdirectory-filterdan sejenisnya. Namun ini dapat berfungsi "paling sering" untuk beberapa kasus, misalnya kasus ketika Anda mengganti nama folder, mis:

 ABC/
    /move_this_dir # did some work here, then renamed it to

ABC/
    /move_this_dir_renamed

Jika Anda melakukan gaya git filter normal untuk mengekstrak "move_me_renamed", Anda akan kehilangan riwayat perubahan file yang terjadi dari belakang ketika awalnya move_this_dir ( ref ).

Dengan demikian nampak bahwa satu-satunya cara untuk benar-benar menyimpan semua perubahan sejarah (jika milik Anda adalah kasus seperti ini), adalah, pada dasarnya, untuk menyalin repositori (membuat repo baru, atur yang menjadi asal), lalu nuke semua yang lain dan ganti nama subdirektori ke induk seperti ini:

  1. Mengkloning proyek multi-modul secara lokal
  2. Cabang - periksa apa yang ada di sana: git branch -a
  3. Lakukan checkout ke setiap cabang untuk dimasukkan dalam split untuk mendapatkan salinan lokal di workstation Anda: git checkout --track origin/branchABC
  4. Buat salinan di direktori baru: cp -r oldmultimod simple
  5. Buka salinan proyek baru: cd simple
  6. Singkirkan modul lain yang tidak diperlukan dalam proyek ini:
  7. git rm otherModule1 other2 other3
  8. Sekarang hanya subdir dari modul target yang tersisa
  9. Singkirkan subdir modul sehingga root modul menjadi root proyek baru
  10. git mv moduleSubdir1/* .
  11. Hapus subdirektori relik: rmdir moduleSubdir1
  12. Periksa perubahan kapan saja: git status
  13. Buat git repo baru dan salin URL-nya untuk mengarahkan proyek ini ke dalamnya:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. Verifikasi ini bagus: git remote -v
  16. Dorong perubahan ke repo jarak jauh: git push
  17. Pergi ke repo jarak jauh dan periksa semuanya ada di sana
  18. Ulangi untuk cabang lain yang dibutuhkan: git checkout branch2

Ini mengikuti dokumen github "Memisahkan subfolder menjadi repositori baru" langkah 6-11 untuk mendorong modul ke repo baru.

Ini tidak akan menghemat ruang apa pun di folder .git Anda, tetapi itu akan mempertahankan semua riwayat perubahan Anda untuk file-file itu bahkan di seluruh nama. Dan ini mungkin tidak layak jika tidak ada "banyak" sejarah yang hilang, dll. Tapi setidaknya Anda dijamin tidak akan kehilangan komitmen yang lebih tua!

rogerdpack
sumber
1
Menemukan jarum di tumpukan jerami git! Sekarang saya bisa menyimpan SEMUA riwayat komit saya.
Adam
5

Saya merekomendasikan panduan GitHub untuk memisahkan subfolder menjadi repositori baru . Langkah-langkahnya mirip dengan jawaban Paul , tetapi saya menemukan instruksi mereka lebih mudah dimengerti.

Saya telah memodifikasi instruksi sehingga mereka menerapkan repositori lokal, bukan satu yang di-host di GitHub.


Membagi subfolder menjadi repositori baru

  1. Buka Git Bash.

  2. Ubah direktori kerja saat ini ke lokasi di mana Anda ingin membuat repositori baru Anda.

  3. Kloning repositori yang berisi subfolder.

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. Ubah direktori kerja saat ini ke repositori hasil kloning Anda.

cd REPOSITORY-NAME
  1. Untuk memfilter subfolder dari sisa file di repositori, jalankan git filter-branch, berikan informasi ini:
    • FOLDER-NAME: Folder dalam proyek Anda yang ingin Anda buat repositori terpisah.
      • Kiat: Pengguna Windows harus menggunakan / untuk membatasi folder.
    • BRANCH-NAME: Cabang default untuk proyek Anda saat ini, misalnya, masteratau gh-pages.

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME 
# Filter the specified branch in your directory and remove empty commits
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)
Ref 'refs/heads/BRANCH-NAME' was rewritten
Stevoisiak
sumber
Posting yang bagus, tapi saya perhatikan paragraf pertama dari dokumen yang Anda tautkan mengatakan If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.Namun menurut komentar pada semua jawaban di sini baik filter-branchdan subtreescript mengakibatkan hilangnya sejarah di mana subdirektori telah diubah namanya. Adakah yang bisa dilakukan untuk mengatasi hal ini?
Adam
Menemukan solusi untuk menjaga semua komit, termasuk yang mengubah nama direktori sebelumnya / bergerak - ini jawaban rogerdpack untuk pertanyaan ini.
Adam
Satu-satunya masalah adalah saya tidak bisa lagi menggunakan repo hasil kloning
Qiulang
5

Saat menjalankan git filter-branchmenggunakan versi yang lebih baru dari git( 2.22+mungkin?), Dikatakan untuk menggunakan alat baru ini git-filter-repo . Alat ini tentu menyederhanakan hal bagi saya.

Penyaringan dengan filter-repo

Perintah untuk membuat XYZrepo dari pertanyaan awal:

# create local clone of original repo in directory XYZ
tmp $ git clone [email protected]:user/original.git XYZ

# switch to working in XYZ
tmp $ cd XYZ

# keep subdirectories XY1 and XY2 (dropping ABC)
XYZ $ git filter-repo --path XY1 --path XY2

# note: original remote origin was dropped
# (protecting against accidental pushes overwriting original repo data)

# XYZ $ ls -1
# XY1
# XY2

# XYZ $ git log --oneline
# last commit modifying ./XY1 or ./XY2
# first commit modifying ./XY1 or ./XY2

# point at new hosted, dedicated repo
XYZ $ git remote add origin [email protected]:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

asumsi: * repo XYZ jauh baru dan kosong sebelum push

Memfilter dan bergerak

Dalam kasus saya, saya juga ingin memindahkan beberapa direktori untuk struktur yang lebih konsisten. Awalnya, saya menjalankan filter-repoperintah sederhana yang diikuti git mv dir-to-rename, tetapi saya menemukan saya bisa mendapatkan sedikit sejarah "lebih baik" menggunakan --path-renameopsi. Alih-alih melihat modifikasi terakhir 5 hours agopada file yang dipindahkan dalam repo baru sekarang saya melihat last year(di GitHub UI), yang cocok dengan waktu yang dimodifikasi dalam repo asli.

Dari pada...

git filter-repo --path XY1 --path XY2 --path inconsistent
git mv inconsistent XY3  # which updates last modification time

Saya akhirnya berlari ...

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Catatan:
  • Saya pikir posting blog Git Rev News menjelaskan dengan baik alasan di balik membuat alat repo-filtering lain.
  • Saya awalnya mencoba jalur membuat sub-direktori yang cocok dengan nama repo target dalam repositori asli dan kemudian menyaring (menggunakan git filter-repo --subdirectory-filter dir-matching-new-repo-name). Perintah itu dengan benar mengkonversi subdirektori tersebut ke root dari repo lokal yang disalin, tetapi itu juga menghasilkan riwayat hanya tiga komit yang diperlukan untuk membuat subdirektori. (Saya tidak menyadari bahwa --pathdapat ditentukan beberapa kali; dengan demikian, menghilangkan kebutuhan untuk membuat subdirektori dalam repo sumber.) Karena seseorang telah berkomitmen untuk repo sumber pada saat saya perhatikan bahwa saya gagal meneruskan sejarah, saya baru saja menggunakan git reset commit-before-subdir-move --hardsetelah cloneperintah, dan ditambahkan --forceke filter-repoperintah untuk membuatnya beroperasi pada klon lokal yang sedikit dimodifikasi.
git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected
  • Saya bingung pada instalasi karena saya tidak mengetahui pola ekstensi dengan git, tetapi akhirnya saya kloning git-filter-repo dan menghubungkannya dengan $(git --exec-path):
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)
lpearson
sumber
1
Terpilih untuk merekomendasikan filter-repoalat baru (yang saya presentasikan bulan lalu di stackoverflow.com/a/58251653/6309 )
VonC
Menggunakan git-filter-repotentunya harus menjadi pendekatan yang disukai pada saat ini. Ini jauh, jauh lebih cepat dan lebih aman daripada git-filter-branch, dan perlindungan terhadap banyak kesulitan yang dapat ditemui ketika menulis ulang sejarah git seseorang. Semoga jawaban ini mendapat perhatian lebih, karena itu yang harus diatasi git-filter-repo.
Jeremy Caney
4

Saya memiliki masalah ini, tetapi semua solusi standar berdasarkan git filter-branch sangat lambat. Jika Anda memiliki repositori kecil maka ini mungkin bukan masalah, itu untuk saya. Saya menulis program penyaringan git lain berdasarkan libgit2 yang sebagai langkah pertama membuat cabang untuk setiap penyaringan repositori primer dan kemudian mendorongnya untuk membersihkan repositori sebagai langkah selanjutnya. Pada repositori saya (500Mb 100000 komit) metode cabang-standar git filter membutuhkan waktu berhari-hari. Program saya membutuhkan beberapa menit untuk melakukan penyaringan yang sama.

Ia memiliki nama luar biasa git_filter dan tinggal di sini:

https://github.com/slobobaby/git_filter

di GitHub.

Semoga bermanfaat bagi seseorang.

slobobaby
sumber
4

Gunakan perintah filter ini untuk menghapus subdirektori, sambil mempertahankan tag dan cabang Anda:

git filter-branch --index-filter \
"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \
--tag-name-filter cat -- --all
cmcginty
sumber
apa yang kucing disini?
rogerdpack
4

Untuk apa nilainya, inilah cara menggunakan GitHub pada mesin Windows. Katakanlah Anda memiliki repo kloning yang berada di C:\dir1. Struktur direktori terlihat seperti ini: C:\dir1\dir2\dir3. The dir3direktori adalah yang saya ingin menjadi repo terpisah baru.

Github:

  1. Buat repositori baru Anda: MyTeam/mynewrepo

Bash Prompt:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    Dikembalikan: Ref 'refs/heads/master' was rewritten(fyi: dir2 / dir3 peka huruf besar-kecil.)

  3. $ git remote add some_name [email protected]:MyTeam/mynewrepo.git
    git remote add origin etc. tidak bekerja, dikembalikan " remote origin already exists"

  4. $ git push --progress some_name master

James Lawruk
sumber
3

Seperti yang saya sebutkan di atas , saya harus menggunakan solusi terbalik (menghapus semua komit yang tidak menyentuh saya dir/subdir/targetdir) yang tampaknya bekerja dengan sangat baik menghilangkan sekitar 95% dari komit (seperti yang diinginkan). Namun, ada dua masalah kecil yang tersisa.

PERTAMA , filter-branchmelakukan pekerjaan mematikan menghapus komit yang memperkenalkan atau memodifikasi kode tetapi tampaknya, menggabungkan komit berada di bawah stasiunnya di Gitiverse.

Ini adalah masalah kosmetik yang mungkin bisa saya jalani (katanya ... mundur perlahan dengan mata dihindari) .

KEDUA , beberapa komit yang tersisa hampir SEMUA diduplikasi! Saya sepertinya telah memperoleh timeline kedua yang mubazir yang mencakup hampir seluruh sejarah proyek. Hal yang menarik (yang dapat Anda lihat dari gambar di bawah), adalah bahwa tiga cabang lokal saya tidak semuanya berada di timeline yang sama (yang tentu saja mengapa ada dan bukan hanya sampah yang dikumpulkan).

Satu-satunya hal yang dapat saya bayangkan adalah bahwa salah satu dari komit yang dihapus adalah, mungkin, komit gabungan tunggal yang filter-branch benar-benar menghapus , dan yang menciptakan garis waktu paralel ketika setiap untai yang sekarang tidak tergabung mengambil salinan komitnya sendiri. ( mengangkat bahu Di mana TARDi saya?) Saya cukup yakin saya dapat memperbaiki masalah ini, meskipun saya benar - benar ingin memahami bagaimana hal itu terjadi.

Dalam kasus gila mergefest-O-RAMA, saya mungkin akan meninggalkan yang itu sendirian karena sudah begitu melekat dalam sejarah komit saya — mengancam saya setiap kali saya mendekat—, sepertinya itu tidak benar-benar menyebabkan masalah non-kosmetik dan karena itu cukup cantik di Tower.app.

Jay Allen
sumber
3

Cara Lebih Mudah

  1. instal git splits. Saya membuatnya sebagai ekstensi git, berdasarkan solusi jkeating .
  2. Membagi 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 XY1 XY2

  3. Buat repo kosong di suatu tempat. Kami akan menganggap kami telah membuat repo kosong bernama xyzGitHub yang memiliki path:[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. Klon 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
Keuntungan dari metode ini dibandingkan dengan "Cara Mudah" adalah bahwa kendali jarak jauh telah diatur untuk repo baru, sehingga Anda dapat segera melakukan penambahan subtree. Sebenarnya cara ini sepertinya lebih mudah bagi saya (bahkan tanpa git splits)
MM
Props to AndrewD untuk memposting solusi ini. Saya telah forked repo-nya untuk membuatnya bekerja di OSX ( github.com/ricardoespsanto/git-splits ) jika itu berguna untuk orang lain
ricardoespsanto
2

Anda mungkin memerlukan sesuatu seperti "git reflog kedaluwarsa - expire = now --all" sebelum pengumpulan sampah untuk benar-benar membersihkan file. git filter-branch hanya menghilangkan referensi dalam sejarah, tetapi tidak menghapus entri reflog yang menyimpan data. Tentu saja, ujilah ini dulu.

Penggunaan disk saya turun drastis dalam melakukan ini, meskipun kondisi awal saya agak berbeda. Mungkin --subdirectory-filter meniadakan kebutuhan ini, tapi saya ragu.


sumber
2

Lihat proyek git_split di https://github.com/vangorra/git_split

Ubah direktori git menjadi repositori mereka sendiri di lokasi mereka sendiri. Tidak ada bisnis lucu subtree. Script ini akan mengambil direktori yang ada di repositori git Anda dan mengubah direktori itu menjadi repositori independennya sendiri. Sepanjang jalan, itu akan menyalin seluruh perubahan sejarah untuk direktori yang Anda berikan.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.
vangorra
sumber
1

Masukkan ini ke gitconfig Anda:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'
lebih kotor
sumber
1

Saya yakin subtree git baik-baik saja dan luar biasa, tetapi subdirektori dari kode yang dikelola git yang ingin saya pindahkan semuanya dalam gerhana. Jadi jika Anda menggunakan egit, itu sangat mudah. Ambil proyek yang ingin Anda pindahkan dan tim-> putuskan sambungannya, lalu tim-> bagikan ke lokasi baru. Ini akan menjadi standar untuk mencoba menggunakan lokasi repo yang lama, tetapi Anda dapat menghapus centang pada pilihan yang sudah ada yang digunakan dan memilih tempat baru untuk memindahkannya. Semua salam egit.

stu
sumber
3
Bagian subtree yang "bagus dan indah" adalah bahwa sejarah subdirektori Anda ikut dalam perjalanan. Jika Anda tidak membutuhkan sejarah, maka cara mudah Anda yang menyakitkan adalah cara untuk melakukannya.
pglezen
0

Anda dapat dengan mudah mencoba https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

Ini berhasil untuk saya. Masalah yang saya hadapi dalam langkah-langkah yang diberikan di atas adalah

  1. dalam perintah ini git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME The BRANCH-NAMEadalah Master

  2. jika langkah terakhir gagal ketika melakukan karena masalah perlindungan ikuti - https://docs.gitlab.com/ee/user/project/protected_branches.html

Barath Ravichander
sumber
0

Saya telah menemukan solusi yang cukup mudah, idenya adalah menyalin repositori dan kemudian menghapus bagian yang tidak perlu. Begini Cara kerjanya:

1) Mengkloning repositori yang ingin Anda bagi

git clone [email protected]:testrepo/test.git

2) Pindah ke folder git

cd test/

2) Hapus folder yang tidak perlu dan komit

rm -r ABC/
git add .
enter code here
git commit -m 'Remove ABC'

3) Hapus riwayat formulir folder yang tidak perlu dengan BFG

cd ..
java -jar bfg.jar --delete-folders "{ABC}" test
cd test/
git reflog expire --expire=now --all && git gc --prune=now --aggressive

untuk folder banyak, Anda dapat menggunakan koma

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4) Periksa apakah riwayat tidak mengandung file / folder yang baru saja Anda hapus

git log --diff-filter=D --summary | grep delete

5) Sekarang Anda memiliki repositori bersih tanpa ABC, jadi cukup dorong ke asal baru

remote add origin [email protected]:username/new_repo
git push -u origin master

Itu dia. Anda dapat mengulangi langkah-langkah untuk mendapatkan repositori lain,

hapus saja XY1, XY2 dan ganti nama XYZ -> ABC pada langkah 3

Vladislav Troyan
sumber
Hampir sempurna ... tetapi Anda lupa "git filter-branch --prune-empty" untuk menghapus semua commit lama yang sekarang kosong. Yang harus dilakukan sebelum mendorong ke master asal!
ZettaCircl
Jika Anda melakukan kesalahan dan masih ingin "menolak" setelah menghapus komit kosong lama, lakukan: "git push -u master asal - force-with-leasing"
ZettaCircl