Jak usunąć obiekty blob bez odwołań z mojego repozytorium git

124

Mam repozytorium GitHub, które miało dwie gałęzie - master i release.

Gałąź wydania zawierała binarne pliki dystrybucyjne, które przyczyniły się do bardzo dużego rozmiaru repozytorium (> 250 MB), więc zdecydowałem się uporządkować.

Najpierw usunąłem gałąź zdalnego wydania, za pośrednictwem git push origin :release

Następnie usunąłem lokalną gałąź wydania. Najpierw próbowałem git branch -d release, ale git powiedział „błąd: gałąź 'release' nie jest przodkiem twojego obecnego HEAD.” co jest prawdą, więc git branch -D releasezmusiłem go do usunięcia.

Ale rozmiar mojego repozytorium, zarówno lokalnie, jak i na GitHub, był nadal ogromny. Więc wtedy przejrzałem zwykłą listę poleceń git, na przykład git gc --prune=today --aggressivebez powodzenia.

Postępując zgodnie z instrukcjami Charlesa Baileya podanymi w SO 1029969 , udało mi się uzyskać listę SHA1 dla największych plamek. Następnie użyłem skryptu z SO 460331, aby znaleźć plamy ... a pięć największych nie istnieje, chociaż zostały znalezione mniejsze, więc wiem, że skrypt działa.

Myślę, że te blogi są plikami binarnymi z gałęzi wydania i jakoś zostały po usunięciu tej gałęzi. Jak się ich pozbyć?

kkrugler
źródło
Jakiej wersji Git używasz? Czy próbowałeś stackoverflow.com/questions/1106529/… ?
VonC
Wersja git 1.6.2.3 Próbowałem gc i prune z różnymi argumentami. Nie próbowałem przepakować -a -d -l, po prostu go uruchomiłem, bez zmian.
kkrugler
2
Nowe informacje - świeży klon z GitHub nie ma już niereferencyjnych obiektów blob i został zmniejszony do „tylko” 84 MB z 250 MB.
kkrugler

Odpowiedzi:

219

... i bez zbędnych ceregieli, przedstawię wam to przydatne polecenie, „git-gc-all”, gwarantujące usunięcie wszystkich śmieci z git, dopóki nie pojawią się dodatkowe zmienne konfiguracyjne:

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

Być może będziesz musiał najpierw uruchomić coś takiego, ojej, git jest skomplikowany !!

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

Być może będziesz musiał usunąć niektóre tagi, dzięki Zitrax:

git tag | xargs git tag -d

Wszystko to umieściłem w skrypcie: git-gc-all-ferocious .

Sam Watkins
źródło
1
Ciekawy. Dobra alternatywa dla mojej bardziej ogólnej odpowiedzi. +1
VonC,
10
To zasługuje na więcej głosów pozytywnych. W końcu pozbył się wielu obiektów git, które zachowałyby inne metody. Dzięki!
Jean-Philippe Pellet
1
Głosowano za. Wow, nie wiem, co właśnie zrobiłem, ale wygląda na to, że dużo sprzątam. Czy możesz wyjaśnić, co to robi? Mam wrażenie, że to wszystko wyczyściło objects. Co to jest i dlaczego są (najwyraźniej) nieistotne?
Redsandro
2
@Redsandro, jak rozumiem, te polecenia „git rm origin”, „rm” i „git update-ref -d” usuwają odniesienia do starych zatwierdzeń dla pilotów i tym podobnych, które mogą uniemożliwiać czyszczenie pamięci. Opcje "git gc" mówią mu, aby nie trzymał się różnych starych zatwierdzeń, w przeciwnym razie będzie je trzymał przez chwilę. Np. Gc.rerereresolved jest przeznaczony dla „rekordów konfliktu scalania, które rozwiązałeś wcześniej”, domyślnie przechowywanych przez 60 dni. Te opcje znajdują się na stronie podręcznika git-gc. Nie jestem ekspertem od gita i nie wiem dokładnie, co to wszystko robi. Znalazłem je na stronach podręcznika i wyszukałem plik .git w poszukiwaniu referencji do zmian.
Sam Watkins,
1
Obiekt git to skompresowany plik lub drzewo lub zatwierdzenie w repozytorium git, w tym stare rzeczy z historii. git gc czyści niepotrzebne obiekty. Przechowuje obiekty, które są nadal potrzebne dla twojego aktualnego repozytorium, i jego historię.
Sam Watkins,
81

Jak opisano tutaj , jeśli chcesz trwale usunąć wszystko, do czego odwołuje się tylko reflog , po prostu użyj

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

git reflog expire --expire-unreachable=now --allusuwa wszystkie odniesienia do nieosiągalnych zatwierdzeń w reflog.

git gc --prune=now usuwa same zatwierdzenia.

Uwaga : tylko użycie git gc --prune=nownie zadziała, ponieważ te zatwierdzenia są nadal przywoływane w reflogu. Dlatego wyczyszczenie reflogu jest obowiązkowe. Zauważ również, że jeśli rererego używasz , dodatkowe odniesienia nie są czyszczone przez te polecenia. Zobacz, git help rerereaby uzyskać więcej informacji. Ponadto wszelkie zatwierdzenia, do których odwołują się lokalne lub zdalne gałęzie lub tagi, nie zostaną usunięte, ponieważ są one uważane przez git za cenne dane.

jiasli
źródło
14
Udało się, ale jakoś zgubiłem moje zapisane skrytki (nic ważnego w moim przypadku, tylko ostrzeżenie dla innych)
Amro
1
dlaczego nie - agresywne?
JoelFan,
3
Myślę, że ta odpowiedź wymaga wyraźnego ostrzeżenia, najlepiej u góry. Moja propozycja edycji została odrzucona, bo chyba powinienem zasugerować ją autorowi w komentarzu? Zaakceptuj tę edycję stackoverflow.com/review/suggested-edits/26023988 lub dodaj ostrzeżenie na swój własny sposób. Poza tym upuszcza wszystkie twoje skrytki . To powinno być również zapisane w ostrzeżeniu!
Inigo
Testowałem z git w wersji 2.17 i ukryte zatwierdzenia nie zostaną usunięte przez powyższe polecenia. Czy na pewno nie uruchomiłeś żadnych dodatkowych poleceń?
Mikko Rantalainen
1
git fetch --prunejeszcze bardziej zmniejsz rozmiar, ponieważ usuwa lokalne obiekty blob.
hectorpal
33

Jak wspomniano w tej odpowiedzi SO , git gcmoże faktycznie zwiększyć rozmiar repozytorium!

Zobacz także ten wątek

Teraz git ma mechanizm bezpieczeństwa, aby nie usuwać obiektów bez odniesień od razu po uruchomieniu ' git gc'.
Domyślnie obiekty, do których nie ma odniesień, są przechowywane przez okres 2 tygodni. Ma to na celu ułatwienie odzyskania przypadkowo usuniętych gałęzi lub zatwierdzeń, lub uniknięcie wyścigu, w którym właśnie utworzony obiekt będący w trakcie, ale jeszcze nie przywoływany, mógłby zostać usunięty przez git gcrównoległy proces „ ”.

Tak więc, aby dać ten okres karencji spakowanym, ale bez odniesień obiektom, proces przepakowywania wypycha je z opakowania do ich luźnej formy, aby można je było postarzać i ostatecznie przyciąć.
Obiekty, do których nie ma odniesień, zwykle nie są jednak tak liczne. Posiadanie 404855 obiektów bez odwołań to całkiem sporo, a wysyłanie tych obiektów w pierwszej kolejności przez klon jest głupie i kompletnym marnotrawstwem przepustowości sieci.

W każdym razie ... Aby rozwiązać problem, wystarczy uruchomić ' git gc' z --prune=nowargumentem, aby wyłączyć ten okres karencji i od razu pozbyć się tych obiektów, do których nie ma odniesienia (bezpieczne tylko wtedy, gdy żadne inne działania git nie są wykonywane w tym samym czasie, co powinno być łatwe do zapewnienia na stacji roboczej).

A tak przy okazji, użycie „ git gc --aggressive” z późniejszą wersją git (lub „ git repack -a -f -d --window=250 --depth=250”)

Ten sam wątek wspomina :

 git config pack.deltaCacheSize 1

Ogranicza to rozmiar pamięci podręcznej delta do jednego bajtu (skutecznie go wyłączając) zamiast domyślnej wartości 0, co oznacza nieograniczoną. Dzięki temu jestem w stanie przepakować to repozytorium za pomocą powyższego git repackpolecenia na systemie x86-64 z 4 GB pamięci RAM i przy użyciu 4 wątków (jest to czterordzeniowy rdzeń). Zużycie pamięci rezydentnej rośnie jednak do prawie 3,3 GB.

Jeśli twoja maszyna jest SMP i nie masz wystarczającej ilości pamięci RAM, możesz zmniejszyć liczbę wątków tylko do jednego:

git config pack.threads 1

Dodatkowo możesz dodatkowo ograniczyć użycie pamięci za pomocą --window-memory argumentto ' git repack'.
Na przykład użycie --window-memory=128Mpowinno utrzymywać rozsądną górną granicę wykorzystania pamięci wyszukiwania różnicowego, chociaż może to skutkować mniej optymalnym dopasowaniem delta, jeśli repozytorium zawiera wiele dużych plików.


Na froncie filtru można rozważyć (ostrożnie) ten skrypt

#!/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
źródło
stackoverflow.com/questions/359424/… jest również dobrym początkiem filter-branchużycia poleceń.
VonC
Cześć VonC - Próbowałem git gc prune = teraz bez powodzenia. Naprawdę wygląda to na błąd gita, ponieważ po usunięciu gałęzi wylądowałem lokalnie z niereferencyjnymi obiektami blob, ale nie ma ich ze świeżym klonem repozytorium GitHub ... więc jest to tylko problem z lokalnym repozytorium. Ale mam dodatkowe pliki, które chcę wyczyścić, więc skrypt, o którym wspomniałeś powyżej, jest świetny - dzięki!
kkrugler
19

git gc --prune=nowlub niski poziom git prune --expire now.

Jakub Narębski
źródło
12

Za każdym razem, gdy porusza się HEAD, git śledzi to w pliku reflog. Jeśli usunąłeś zatwierdzenia, nadal masz „wiszące zatwierdzenia”, ponieważ nadal są one przywoływane przez reflog~ 30 dni. To jest siatka bezpieczeństwa podczas przypadkowego usuwania zatwierdzeń.

Możesz użyć git reflogpolecenia usuń określone zatwierdzenia, przepakuj itp. Lub po prostu polecenia wysokiego poziomu:

git gc --prune=now
vdboor
źródło
5

Możesz użyć git forget-blob.

Użycie jest dość proste git forget-blob file-to-forget. Więcej informacji znajdziesz tutaj

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

Zniknie ze wszystkich zatwierdzeń w Twojej historii, reflogu, tagach i tak dalej

Od czasu do czasu napotykam ten sam problem i za każdym razem, gdy muszę wracać do tego postu i innych, dlatego zautomatyzowałem ten proces.

Podziękowania dla współpracowników, takich jak Sam Watkins

nachoparker
źródło
2

Spróbuj użyć git-filter-branch - nie usuwa dużych obiektów blob, ale może usunąć duże pliki, które określisz z całego repozytorium. Dla mnie zmniejsza rozmiar repozytorium z setek MB do 12 MB.

W55tKQbuRu28Q4xv
źródło
6
Teraz to jest przerażające polecenia :) Muszę spróbować, gdy mój git-fu czuje się silniejszy.
kkrugler
możesz powtórzyć. Zawsze uważam na polecenia, które manipulują historią repozytorium. Sytuacje zwykle idą bardzo źle, gdy wiele osób wypycha i wyciąga z tego repozytorium i nagle nie ma wielu obiektów, których oczekuje git.
Jonathan Dumaine
1

Czasami powodem, dla którego „gc” nie daje wiele dobrego, jest to, że istnieje niedokończona rebase lub skrytka oparta na starym zatwierdzeniu.

StellarVortex
źródło
Lub do starego zatwierdzenia odwołuje się HEAD, ORIG_HEAD, FETCH_HEAD, reflog lub inna rzecz, którą git automatycznie utrzymuje, próbując upewnić się, że nigdy nie straci nic wartościowego. Jeśli naprawdę chcesz to wszystko stracić, musisz dołożyć wszelkich starań, aby to zrobić.
Mikko Rantalainen
1

Aby dodać kolejną wskazówkę, nie zapomnij użyć git remote prune, aby usunąć przestarzałe gałęzie swoich pilotów przed użyciem git gc

możesz je zobaczyć za pomocą git branch -a

Jest to często przydatne, gdy pobierasz z github i repozytoriów rozwidlonych ...

Tanguy
źródło
1

Zanim to zrobisz git filter-branchi git gc, powinieneś przejrzeć tagi obecne w repozytorium. Każdy prawdziwy system, który ma automatyczne tagowanie dla rzeczy takich jak ciągła integracja i wdrożenia, sprawi, że niechciane obiekty będą nadal przywoływane przez te tagi, dlatego gcnie można ich usunąć i nadal będziesz się zastanawiać, dlaczego rozmiar repozytorium jest nadal tak duży.

Najlepszym sposobem, aby pozbyć się wszystkich nie-chciał rzeczy jest do uruchomienia git-filter& git gca następnie wcisnąć mistrza do nowej gołej repo. Nowe nagie repozytorium będzie miało oczyszczone drzewo.

v_abhi_v
źródło