Cara menghapus gumpalan yang tidak direferensikan dari repo git saya

124

Saya memiliki repo GitHub yang memiliki dua cabang - master & rilis.

Cabang rilis berisi file distribusi biner yang berkontribusi pada ukuran repo yang sangat besar (> 250MB), jadi saya memutuskan untuk membersihkannya.

Pertama saya menghapus cabang rilis jarak jauh, melalui git push origin :release

Kemudian saya menghapus cabang rilis lokal. Pertama saya mencoba git branch -d release, tetapi git berkata "kesalahan: 'Rilis' cabang bukan nenek moyang dari HEAD Anda saat ini." yang mana benar, jadi saya lakukan git branch -D releaseuntuk memaksanya dihapus.

Tetapi ukuran repositori saya, baik secara lokal maupun di GitHub, masih sangat besar. Jadi saya menjalankan melalui daftar perintah git yang biasa, seperti git gc --prune=today --aggressive, tidak berhasil.

Dengan mengikuti instruksi Charles Bailey di SO 1029969 saya bisa mendapatkan daftar SHA1 untuk gumpalan terbesar. Saya kemudian menggunakan skrip dari SO 460331 untuk menemukan gumpalan ... dan lima gumpalan terbesar tidak ada, meskipun gumpalan yang lebih kecil ditemukan, jadi saya tahu skrip tersebut berfungsi.

Saya pikir blog ini adalah binari dari cabang rilis, dan entah bagaimana mereka ditinggalkan setelah cabang itu dihapus. Apa cara yang benar untuk menyingkirkannya?

kkrugler.dll
sumber
Versi Git apa yang Anda gunakan? Dan apakah Anda mencoba stackoverflow.com/questions/1106529/… ?
VonC
git versi 1.6.2.3 Saya telah mencoba gc dan memangkas dengan berbagai argumen. Saya belum mencoba membungkus ulang -a -d -l, hanya menjalankannya, tidak ada perubahan.
kkrugler
2
Info baru - klon baru dari GitHub tidak lagi memiliki blob yang tidak direferensikan, dan turun ke "hanya" 84MB dari 250MB.
kkrugler

Jawaban:

219

... dan tanpa basa-basi lagi, izinkan saya menyajikan kepada Anda perintah yang berguna ini, "git-gc-all", dijamin akan menghapus semua sampah git Anda hingga muncul variabel konfigurasi tambahan:

git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc

Anda mungkin juga perlu menjalankan sesuatu seperti ini terlebih dahulu, ya ampun, git itu rumit !!

git remote rm origin
rm -rf .git/refs/original/ .git/refs/remotes/ .git/*_HEAD .git/logs/
git for-each-ref --format="%(refname)" refs/original/ | xargs -n1 --no-run-if-empty git update-ref -d

Anda mungkin juga perlu menghapus beberapa tag, terima kasih Zitrax:

git tag | xargs git tag -d

Saya memasukkan semua ini ke dalam skrip: git-gc-all-ferocious .

Sam Watkins
sumber
1
Menarik. Alternatif yang bagus untuk jawaban saya yang lebih umum. +1
VonC
10
Ini membutuhkan lebih banyak suara. Itu akhirnya menyingkirkan banyak objek git yang akan disimpan metode lain. Terima kasih!
Jean-Philippe Pellet
1
Suara positif. Wow, saya tidak tahu apa yang baru saja saya lakukan tetapi sepertinya banyak bersih-bersih. Bisakah Anda menjelaskan apa fungsinya? Saya merasa itu membersihkan semua milik saya objects. Apa itu dan mengapa (tampaknya) tidak relevan?
Redsandro
2
@Redsandro, seperti yang saya mengerti, perintah "git rm origin", "rm" dan "git update-ref -d" menghapus referensi ke commit lama untuk remote dan semacamnya, yang mungkin mencegah pengumpulan sampah. Opsi untuk "git gc" memberitahukannya untuk tidak mempertahankan berbagai commit lama, jika tidak, ia akan menahannya untuk sementara. Misalnya gc.rerereresolved adalah untuk "catatan gabungan konflik yang Anda selesaikan sebelumnya", secara default disimpan selama 60 hari. Opsi tersebut ada di halaman manual git-gc. Saya bukan ahli git dan tidak tahu persis apa yang dilakukan semua hal ini. Saya menemukannya dari manpages, dan grepping .git untuk commit ref.
Sam Watkins
1
Objek git adalah file atau pohon terkompresi atau komit di repo git Anda, termasuk hal-hal lama dari riwayat. git gc membersihkan objek yang tidak dibutuhkan. Itu menyimpan objek yang masih dibutuhkan untuk repo Anda saat ini, dan sejarahnya.
Sam Watkins
81

Seperti yang dijelaskan di sini , jika Anda ingin menghapus secara permanen semua yang direferensikan hanya melalui reflog , cukup gunakan

git reflog expire --expire-unreachable=now --all
git gc --prune=now

git reflog expire --expire-unreachable=now --allmenghapus semua referensi tentang komitmen yang tidak dapat dijangkau di reflog.

git gc --prune=now menghapus komit itu sendiri.

Perhatian : Hanya menggunakan git gc --prune=nowtidak akan berfungsi karena komit tersebut masih direferensikan di reflog. Oleh karena itu, membersihkan reflog adalah wajib. Perhatikan juga bahwa jika Anda menggunakannya rererememiliki referensi tambahan yang tidak dihapus oleh perintah ini. Lihatgit help rerere untuk lebih jelasnya. Selain itu, setiap commit yang direferensikan oleh cabang atau tag lokal atau jarak jauh tidak akan dihapus karena dianggap sebagai data yang berharga oleh git.

jiasli
sumber
14
Ini berhasil, tetapi entah bagaimana saya kehilangan simpanan yang saya simpan dalam proses (tidak ada yang penting dalam kasus saya, hanya hati-hati untuk orang lain)
Amro
1
mengapa tidak - agresif?
JoelFan
3
Saya pikir jawaban ini membutuhkan peringatan yang jelas, lebih disukai di atas. Saran edit saya ditolak, karena saya kira saya harus menyarankannya kepada penulis dalam komentar? Harap terima hasil edit ini stackoverflow.com/review/suggested-edits/26023988 atau tambahkan peringatan dengan cara Anda sendiri. Juga, ini menjatuhkan semua simpanan Anda . Itu juga harus diingatkan dalam peringatan!
Inigo
Saya menguji dengan git versi 2.17 dan komit yang disimpan tidak akan dihapus oleh perintah di atas. Apakah Anda yakin tidak menjalankan perintah tambahan apa pun?
Mikko Rantalainen
1
git fetch --prunemengurangi ukuran karena menghapus blob lokal.
hectorpal
33

Seperti yang disebutkan dalam jawaban SO ini ,git gc sebenarnya dapat meningkatkan ukuran repo!

Lihat juga utas ini

Sekarang git memiliki mekanisme keamanan untuk tidak langsung menghapus objek yang tidak direferensikan saat menjalankan 'git gc '.
Secara default, objek yang tidak direferensikan disimpan selama jangka waktu 2 minggu. Ini untuk memudahkan Anda memulihkan cabang atau komitmen yang terhapus secara tidak sengaja, atau untuk menghindari perlombaan di mana objek yang baru saja dibuat dalam proses menjadi tetapi belum direferensikan dapat dihapus dengan proses ' git gc' yang berjalan secara paralel.

Jadi untuk memberikan masa tenggang itu ke objek yang dikemas tetapi tidak direferensikan, proses pengemasan ulang mendorong objek yang tidak direferensikan tersebut keluar dari paket ke dalam bentuk longgar sehingga mereka dapat berumur dan akhirnya dipangkas.
Objek yang menjadi tidak direferensikan biasanya tidak banyak. Memiliki 404855 objek yang tidak direferensikan cukup banyak, dan mengirim objek tersebut pada awalnya melalui klon adalah tindakan bodoh dan pemborosan bandwidth jaringan.

Pokoknya ... Untuk mengatasi masalah Anda, Anda hanya perlu menjalankan ' git gc' dengan--prune=now argumen untuk menonaktifkan masa tenggang itu dan segera menyingkirkan objek yang tidak direferensikan itu (aman hanya jika tidak ada aktivitas git lain yang berlangsung pada saat yang sama yang seharusnya mudah dipastikan di workstation).

Dan BTW, menggunakan ' git gc --aggressive' dengan versi git yang lebih baru (atau ' git repack -a -f -d --window=250 --depth=250')

The thread yang sama menyebutkan :

 git config pack.deltaCacheSize 1

Itu membatasi ukuran cache delta menjadi satu byte (secara efektif menonaktifkannya) daripada default 0 yang berarti tidak terbatas. Dengan itu saya dapat mengemas ulang repositori itu menggunakan git repackperintah di atas pada sistem x86-64 dengan RAM 4GB dan menggunakan 4 utas (ini adalah quad core). Penggunaan memori residen tumbuh hampir 3,3 GB.

Jika mesin Anda SMP dan Anda tidak memiliki RAM yang cukup, Anda dapat mengurangi jumlah utas menjadi hanya satu:

git config pack.threads 1

Selain itu, Anda selanjutnya dapat membatasi penggunaan memori dengan --window-memory argumentto ' git repack'.
Misalnya, penggunaan --window-memory=128Mharus menjaga batas atas yang wajar pada penggunaan memori pencarian delta meskipun ini dapat mengakibatkan kecocokan delta yang kurang optimal jika repo berisi banyak file besar.


Di depan cabang filter, Anda dapat mempertimbangkan (dengan hati-hati) skrip ini

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch otherwise leaves behind for a long time
rm -rf .git/refs/original/ && git reflog expire --all &&  git gc --aggressive --prune
VonC
sumber
stackoverflow.com/questions/359424/… juga merupakan awal yang baik untuk filter-branchpenggunaan perintah.
VonC
Hai VonC - Saya sudah mencoba git gc prune = sekarang tidak berhasil. Ini benar-benar terlihat seperti bug git, di mana saya berakhir dengan gumpalan yang tidak direferensikan secara lokal setelah penghapusan cabang, tetapi ini tidak ada di sana dengan klon baru dari repo GitHub ... jadi ini hanya masalah repo lokal. Tetapi saya memiliki file tambahan yang ingin saya hapus, jadi skrip yang Anda rujuk di atas sangat bagus - terima kasih!
kkrugler
19

git gc --prune=now, atau level rendah git prune --expire now.

Jakub Narębski
sumber
12

Setiap kali HEAD Anda bergerak, git melacaknya di file reflog. Jika Anda menghapus komit, Anda masih memiliki "komit menggantung" karena masih dirujuk olehreflog selama ~ 30 hari. Ini adalah jaring pengaman ketika Anda menghapus komit secara tidak sengaja.

Anda dapat menggunakan git reflogperintah menghapus komit tertentu, mengemas ulang, dll .., atau hanya perintah tingkat tinggi:

git gc --prune=now
vdboor.dll
sumber
5

Anda bisa menggunakan git forget-blob.

Penggunaannya cukup sederhana git forget-blob file-to-forget. Anda bisa mendapatkan info lebih lanjut di sini

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Ini akan hilang dari semua komit di riwayat, reflog, tag, dan sebagainya

Saya mengalami masalah yang sama sesekali, dan setiap kali saya harus kembali ke posting ini dan lainnya, itulah mengapa saya mengotomatiskan prosesnya.

Penghargaan untuk kontributor seperti Sam Watkins

nachoparker
sumber
2

Coba gunakan git-filter-branch - ini tidak menghapus gumpalan besar, tetapi dapat menghapus file besar yang Anda tentukan dari seluruh repo. Bagi saya ini mengurangi ukuran repo dari ratusan MB menjadi 12 MB.

W55tKQbuRu28Q4xv
sumber
6
Nah, itu perintah yang menakutkan :) Saya harus mencobanya ketika git-fu saya terasa lebih kuat.
kkrugler
kamu bisa mengatakannya lagi. Saya selalu waspada terhadap perintah apa pun yang memanipulasi riwayat repositori. Hal-hal cenderung menjadi sangat salah ketika banyak orang mendorong dan menarik dari repositori itu dan tiba-tiba sekelompok objek yang diharapkan tidak ada.
Jonathan Dumaine
1

Terkadang, alasan mengapa "gc" tidak banyak membantu adalah karena ada rebase atau simpanan yang belum selesai berdasarkan commit lama.

StellarVortex
sumber
Atau komit lama direferensikan oleh HEAD, ORIG_HEAD, FETCH_HEAD, reflog, atau hal lain yang terus dilakukan git untuk memastikan tidak pernah kehilangan sesuatu yang berharga. Jika Anda benar-benar ingin kehilangan semua itu, Anda harus bekerja ekstra untuk melakukannya.
Mikko Rantalainen
1

Untuk menambahkan tip lain, jangan lupa untuk menggunakan git remote prune untuk menghapus cabang-cabang remote Anda yang sudah usang sebelum digunakan git gc

Anda dapat melihatnya dengan git branch -a

Ini sering kali berguna ketika Anda mengambil dari github dan repositori bercabang ...

Tanguy
sumber
1

Sebelum melakukan git filter-branchdan git gc, Anda harus meninjau tag yang ada di repo Anda. Setiap sistem nyata yang memiliki penandaan otomatis untuk hal-hal seperti integrasi dan penerapan berkelanjutan akan membuat objek yang tidak diinginkan masih direferensikan oleh tag ini, karenanyagc tidak dapat menghapusnya dan Anda masih akan terus bertanya-tanya mengapa ukuran repo masih begitu besar.

Cara terbaik untuk menyingkirkan semua hal yang tidak diinginkan adalah dengan menjalankan git-filter& git gclalu mendorong master ke repo kosong yang baru. Repo telanjang baru akan memiliki pohon yang dibersihkan.

v_abhi_v
sumber