Dlaczego moje repozytorium git jest tak duże?

141

145 mln = .git / objects / pack /

Napisałem skrypt, aby zsumować rozmiary różnic każdego zatwierdzenia i zatwierdzenia przed przejściem wstecz od końcówki każdej gałęzi. Otrzymuję 129 MB, czyli bez kompresji i bez uwzględniania tych samych plików we wszystkich oddziałach i wspólnej historii między oddziałami.

Git bierze to wszystko pod uwagę, więc spodziewałbym się dużo mniejszego repozytorium. Dlaczego więc .git jest taki duży?

Zrobiłem:

git fsck --full
git gc --prune=today --aggressive
git repack

Aby odpowiedzieć na pytanie, ile plików / zatwierdzeń, mam 19 gałęzi po około 40 plików w każdym. 287 zatwierdzeń, znalezionych przy użyciu:

git log --oneline --all|wc -l

Przechowywanie informacji o tym nie powinno zajmować 10 megabajtów.

Ian Kelling
źródło
5
Linus zaleca następujące zamiast agresywnego gc. Czy to ma znaczenie? git repack -a -d --depth = 250 --window = 250
Greg Bacon
dzięki gbacon, ale bez różnicy.
Ian Kelling
To dlatego, że brakuje opcji -f. metalinguist.wordpress.com/2007/12/06/...
spuder
git repack -a -dzmniejszyłem moje repozytorium 956 MB do 250 MB . Wielkim sukcesem! Dzięki!
xanderiel

Odpowiedzi:

68

Niedawno ściągnąłem złe repozytorium zdalne do lokalnego ( git remote add ...i git remote update). Po usunięciu niechcianego zdalnego odniesienia, gałęzi i tagów nadal miałem 1,4 GB (!) Zmarnowanego miejsca w moim repozytorium. Mogłem się tego pozbyć tylko klonując go za pomocą git clone file:///path/to/repository. Zwróć uwagę, file://że podczas klonowania lokalnego repozytorium ma to ogromne znaczenie - kopiowane są tylko obiekty, do których istnieją odwołania, a nie cała struktura katalogów.

Edycja: Oto jedna linijka Iana do odtworzenia wszystkich gałęzi w nowym repozytorium:

d1=#original repo
d2=#new repo (must already exist)
cd $d1
for b in $(git branch | cut -c 3-)
do
    git checkout $b
    x=$(git rev-parse HEAD)
    cd $d2
    git checkout -b $b $x
    cd $d1
done
str
źródło
1
łał. DZIĘKUJĘ CI. .git = 15 mln teraz !! po sklonowaniu, oto mała 1 wkładka do zachowania poprzednich gałęzi. d1 = # oryginalne repozytorium; d2 = # nowe repozytorium; cd $ d1; for b in $ (git branch | cut -c 3-); do git checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; git checkout -b $ b $ x; cd $ d1; gotowe
Ian Kelling
jeśli to zaznaczysz, możesz dodać 1 linijkę do swojej odpowiedzi, aby była sformatowana jako kod.
Ian Kelling,
1
Głupio dodałem kilka plików wideo do mojego repozytorium i musiałem zresetować - soft HEAD ^ i ponownie to zrobić. Katalog .git / objects był po tym ogromny i tylko w ten sposób udało się go przywrócić. Jednak nie podobał mi się sposób, w jaki jedna linijka zmieniała nazwy moich gałęzi (pokazywała pochodzenie / nazwę gałęzi zamiast tylko nazwy gałęzi). Poszedłem więc o krok dalej i wykonałem szkicową operację - usunąłem katalog .git / objects z oryginału i włożyłem ten z klonu. To załatwiło sprawę, pozostawiając wszystkie oryginalne gałęzie, referencje itp. Nietknięte i wszystko wydaje się działać (trzymanie palców).
Jack Senechal
1
dzięki za wskazówkę dotyczącą pliku: // klon, który
załatwił sprawę
3
@vonbrand jeśli podłączysz twardy link do pliku i usuniesz oryginalny plik, nic się nie dzieje poza tym, że licznik odniesienia zostanie zmniejszony z 2 do 1. Tylko jeśli ten licznik zostanie zmniejszony do 0, miejsce na inne pliki w systemie fs zostanie zwolnione. Więc nie, nawet jeśli pliki byłyby trwale połączone, nic by się nie stało, gdyby oryginał został usunięty.
passa
157

Niektóre skrypty, których używam:

git-fatfiles

git rev-list --all --objects | \
    sed -n $(git rev-list --objects --all | \
    cut -f1 -d' ' | \
    git cat-file --batch-check | \
    grep blob | \
    sort -n -k 3 | \
    tail -n40 | \
    while read hash type size; do 
         echo -n "-e s/$hash/$size/p ";
    done) | \
    sort -n -k1
...
89076 images/screenshots/properties.png
103472 images/screenshots/signals.png
9434202 video/parasite-intro.avi

Jeśli chcesz więcej wierszy, zobacz także wersję Perla w sąsiedniej odpowiedzi: https://stackoverflow.com/a/45366030/266720

git-eradicate (for video/parasite.avi):

git filter-branch -f  --index-filter \
    'git rm --force --cached --ignore-unmatch video/parasite-intro.avi' \
     -- --all
rm -Rf .git/refs/original && \
    git reflog expire --expire=now --all && \
    git gc --aggressive && \
    git prune

Uwaga: drugi skrypt jest przeznaczony do całkowitego usunięcia informacji z Git (w tym wszystkich informacji z reflogów). Używaj ostrożnie.

Vi.
źródło
2
Wreszcie ... Jak na ironię, widziałem tę odpowiedź wcześniej w moich poszukiwaniach, ale wyglądała na zbyt skomplikowaną ... po wypróbowaniu innych rzeczy, ta zaczęła mieć sens i voila!
msanteler
@msanteler, Poprzedni git-fatfilesskrypt ( ) pojawił się, gdy zadałem pytanie na IRC (Freenode / # git). Zapisałem najlepszą wersję do pliku, a następnie opublikowałem jako odpowiedź tutaj. (Nie mogę jednak oryginalnego autora w dziennikach IRC).
Vi.
Na początku działa to bardzo dobrze. Ale kiedy ponownie pobieram lub pobieram z pilota, po prostu kopiuje wszystkie duże pliki z powrotem do archiwum. Jak temu zapobiec?
pir
1
@felbo, W takim razie problem prawdopodobnie nie dotyczy tylko twojego lokalnego repozytorium, ale także innych repozytoriów. Może musisz wykonać tę procedurę wszędzie lub zmusić wszystkich do porzucenia oryginalnych gałęzi i przejścia na gałęzie przepisane. W dużym zespole nie jest to łatwe i wymaga współpracy programistów i / lub interwencji menedżera. Czasami po prostu pozostawienie kamienia ładunkowego w środku może być lepszą opcją.
Vi.
1
Ta funkcja jest świetna, ale jest niewyobrażalnie powolna. Nie można go nawet zakończyć na moim komputerze, jeśli usunę limit 40 linii. FYI, właśnie dodałem odpowiedź z bardziej wydajną wersją tej funkcji. Sprawdź to, jeśli chcesz użyć tej logiki w dużym repozytorium, lub jeśli chcesz zobaczyć zsumowane rozmiary dla pliku lub folderu.
piojo
66

git gcjuż to robi, git repackwięc nie ma sensu ręcznie przepakowywać, chyba że masz zamiar przekazać mu jakieś specjalne opcje.

Pierwszym krokiem jest sprawdzenie, czy większość miejsca zajmuje (jak w normalnym przypadku) baza danych obiektów.

git count-objects -v

Powinno to dać raport o tym, ile rozpakowanych obiektów jest w twoim repozytorium, ile zajmują miejsca, ile masz plików paczek i ile zajmują miejsca.

Idealnie byłoby, gdyby po przepakowaniu nie było żadnych rozpakowanych obiektów i jednego pliku pakietu, ale jest całkowicie normalne, że niektóre obiekty, do których nie odwołują się bezpośrednio bieżące gałęzie, są nadal obecne i rozpakowane.

Jeśli masz jedną dużą paczkę i chcesz wiedzieć, co zajmuje miejsce, możesz wyświetlić listę obiektów, które tworzą paczkę, wraz ze sposobem ich przechowywania.

git verify-pack -v .git/objects/pack/pack-*.idx

Zauważ, że verify-packpobiera plik indeksu, a nie sam plik pakietu. Daje to raport o każdym obiekcie w paczce, jego prawdziwym rozmiarze i rozmiarze po spakowaniu, a także informacje o tym, czy został on „usunięty”, a jeśli tak, to pochodzenie łańcucha delta.

Aby sprawdzić, czy w repozytorium znajdują się jakieś niezwykle duże obiekty, możesz posortować dane wyjściowe według trzeciej z czwartej kolumny (np | sort -k3n.).

Z tego wyniku będziesz mógł zobaczyć zawartość dowolnego obiektu za pomocą git showpolecenia, chociaż nie jest możliwe dokładne zobaczenie, gdzie w historii zatwierdzania repozytorium odwołuje się do obiektu. Jeśli musisz to zrobić, spróbuj czegoś z tego pytania .

CB Bailey
źródło
1
To okazało się, że duże obiekty są świetne. Przyjęta odpowiedź pozbawiła ich.
Ian Kelling,
2
Różnica między git gc i git repack według Linusa Torvaldsa. metalinguist.wordpress.com/2007/12/06/...
spuder
31

Do Twojej wiadomości, największym powodem, dla którego możesz skończyć z niechcianymi obiektami, jest to, że git utrzymuje reflog.

Reflog jest po to, aby uratować twój tyłek, gdy przypadkowo usuniesz gałąź główną lub w inny sposób katastrofalnie uszkodzisz repozytorium.

Najłatwiejszym sposobem rozwiązania tego problemu jest obcięcie plików reflog przed kompresją (po prostu upewnij się, że nigdy nie chcesz wracać do żadnego z zatwierdzeń w reflogu).

git gc --prune=now --aggressive
git repack

Różni się to od git gc --prune=todaytego, że natychmiast wygasa cały reflog.

John Gietzen
źródło
1
Ten zrobił to dla mnie! Przeszedłem z około 5 GB do 32 MB.
Hawkee
Ta odpowiedź wydawała się łatwiejsza do wykonania, ale niestety nie działała dla mnie. W moim przypadku pracowałem na sklonowanym repozytorium. Czy to jest powód?
Mert
13

Jeśli chcesz dowiedzieć się, które pliki zajmują miejsce w repozytorium git, uruchom

git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5

Następnie wyodrębnij odwołanie do obiektu blob, które zajmuje najwięcej miejsca (ostatnia linia) i sprawdź nazwę pliku, która zajmuje tak dużo miejsca

git rev-list --objects --all | grep <reference>

Może to być nawet plik, który został przez Ciebie usunięty git rm, ale git pamięta go, ponieważ nadal istnieją do niego odniesienia, takie jak tagi, piloty i reflog.

Gdy już wiesz, jakiego pliku chcesz się pozbyć, polecam skorzystanie z git forget-blob

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

Jest łatwy w użyciu, po prostu zrób

git forget-blob file-to-forget

Spowoduje to usunięcie każdego odwołania z gita, usunięcie obiektu blob z każdego zatwierdzenia w historii i uruchomienie czyszczenia pamięci, aby zwolnić miejsce.

nachoparker
źródło
7

Skrypt git-fatfiles z odpowiedzi Vi jest cudowny, jeśli chcesz zobaczyć rozmiar wszystkich twoich plamek, ale jest tak powolny, że nie nadaje się do użytku. Usunąłem limit 40 linii wyjściowych i zamiast kończyć próbę, próbowałem wykorzystać całą pamięć RAM mojego komputera. Więc przepisałem to: jest tysiące razy szybsze, ma dodane funkcje (opcjonalne) i jakiś dziwny błąd został usunięty - stara wersja dawałaby niedokładne liczby, jeśli zsumujesz dane wyjściowe, aby zobaczyć całkowitą przestrzeń używaną przez plik.

#!/usr/bin/perl
use warnings;
use strict;
use IPC::Open2;
use v5.14;

# Try to get the "format_bytes" function:
my $canFormat = eval {
    require Number::Bytes::Human;
    Number::Bytes::Human->import('format_bytes');
    1;
};
my $format_bytes;
if ($canFormat) {
    $format_bytes = \&format_bytes;
}
else {
    $format_bytes = sub { return shift; };
}

# parse arguments:
my ($directories, $sum);
{
    my $arg = $ARGV[0] // "";
    if ($arg eq "--sum" || $arg eq "-s") {
        $sum = 1;
    }
    elsif ($arg eq "--directories" || $arg eq "-d") {
        $directories = 1;
        $sum = 1;
    }
    elsif ($arg) {
        print "Usage: $0 [ --sum, -s | --directories, -d ]\n";
        exit 1;
    } 
}

# the format is [hash, file]
my %revList = map { (split(' ', $_))[0 => 1]; } qx(git rev-list --all --objects);
my $pid = open2(my $childOut, my $childIn, "git cat-file --batch-check");

# The format is (hash => size)
my %hashSizes = map {
    print $childIn $_ . "\n";
    my @blobData = split(' ', <$childOut>);
    if ($blobData[1] eq 'blob') {
        # [hash, size]
        $blobData[0] => $blobData[2];
    }
    else {
        ();
    }
} keys %revList;
close($childIn);
waitpid($pid, 0);

# Need to filter because some aren't files--there are useless directories in this list.
# Format is name => size.
my %fileSizes =
    map { exists($hashSizes{$_}) ? ($revList{$_} => $hashSizes{$_}) : () } keys %revList;


my @sortedSizes;
if ($sum) {
    my %fileSizeSums;
    if ($directories) {
        while (my ($name, $size) = each %fileSizes) {
            # strip off the trailing part of the filename:
            $fileSizeSums{$name =~ s|/[^/]*$||r} += $size;
        }
    }
    else {
        while (my ($name, $size) = each %fileSizes) {
            $fileSizeSums{$name} += $size;
        }
    }

    @sortedSizes = map { [$_, $fileSizeSums{$_}] }
        sort { $fileSizeSums{$a} <=> $fileSizeSums{$b} } keys %fileSizeSums;
}
else {
    # Print the space taken by each file/blob, sorted by size
    @sortedSizes = map { [$_, $fileSizes{$_}] }
        sort { $fileSizes{$a} <=> $fileSizes{$b} } keys %fileSizes;

}

for my $fileSize (@sortedSizes) {
    printf "%s\t%s\n", $format_bytes->($fileSize->[1]), $fileSize->[0];
}

Nazwij ten git-fatfiles.pl i uruchom go. Aby zobaczyć miejsce na dysku używane przez wszystkie wersje pliku, użyj --sumopcji. Aby zobaczyć to samo, ale dla plików w każdym katalogu, użyj --directoriesopcji. Jeśli zainstalujesz moduł Number :: Bytes :: Human cpan (uruchom „cpan Number :: Bytes :: Human”), rozmiary zostaną sformatowane: „21M / ścieżka/do/pliku.mp4”.

piojo
źródło
4

Czy na pewno liczysz tylko pliki .pack, a nie pliki .idx? Znajdują się w tym samym katalogu co pliki .pack, ale nie mają żadnych danych repozytorium (jak wskazuje rozszerzenie, są one niczym innym jak indeksami dla odpowiedniego pakietu - w rzeczywistości, jeśli znasz poprawną komendę, możesz łatwo odtworzyć je z pliku pakietu, a sam git robi to podczas klonowania, ponieważ tylko plik pakietu jest przesyłany przy użyciu natywnego protokołu git).

Jako reprezentatywny przykład przyjrzałem się mojemu lokalnemu klonowi repozytorium linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Co wskazuje, że ekspansja o około 7% powinna być powszechna.

Są też pliki na zewnątrz objects/; z mojego osobistego doświadczenia, z nich indexi gitk.cachezwykle są największymi (łącznie 11M w moim klonie repozytorium linux-2.6).

CesarB
źródło
3

Inne obiekty git przechowywane w .gitto drzewa, zatwierdzenia i tagi. Zatwierdzenia i znaczniki są małe, ale drzewa mogą być duże, szczególnie jeśli masz bardzo dużą liczbę małych plików w swoim repozytorium. Ile masz plików i ile masz zatwierdzeń?

Greg Hewgill
źródło
Dobre pytanie. 19 oddziałów po około 40 plików w każdym. git count-objects -v mówi „in-pack: 1570”. Nie jestem pewien, co to dokładnie oznacza i jak policzyć, ile mam zatwierdzeń. Chyba kilkaset.
Ian Kelling
Ok, to nie brzmi tak, jakby to była odpowiedź. Kilkaset będzie nieistotnych w porównaniu do 145 MB.
Greg Hewgill
2

Czy próbowałeś użyć repackowania git ?

baudtack
źródło
Dobre pytanie. Miałem, też odniosłem wrażenie, że git gc to też robi?
Ian Kelling
Działa z git gc --auto Nie jestem pewien, czego użyłeś.
baudtack
2

przed wykonaniem git filter-branch i 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 nadal będą odnosić się do tych tagów, dlatego gc nie może ich usunąć i nadal będziesz się zastanawiać, dlaczego rozmiar repozytorium jest nadal tak duży.

Najlepszym sposobem na pozbycie się wszystkich niechcianych rzeczy jest uruchomienie git-filter i git gc, a następnie wypchnięcie mastera do nowego czystego repozytorium. Nowe nagie repozytorium będzie miało oczyszczone drzewo.

v_abhi_v
źródło
1

Może się to zdarzyć, jeśli przypadkowo dodałeś dużą porcję plików i umieściłeś je w poczekalni, niekoniecznie zatwierdzając je. Może się to zdarzyć w railsaplikacji, gdy uruchomisz, bundle install --deploymenta następnie przypadkowo git add .zobaczysz wszystkie pliki dodane pod vendor/bundletobą, aby je usunąć, ale już weszły do ​​historii git, więc musisz zastosować odpowiedź Vi i zmienić video/parasite-intro.avi, vendor/bundlea następnie uruchomić drugie polecenie, które zapewnia.

Widać różnicę, z git count-objects -vjaką w moim przypadku skrypt miał przed zastosowaniem rozmiar paczki: 52K a po zastosowaniu 3,8K.

juliangonzalez
źródło
1

Warto sprawdzić stacktrace.log. Jest to w zasadzie dziennik błędów do śledzenia zatwierdzeń, które się nie powiodły. Niedawno dowiedziałem się, że mój stacktrace.log ma 65,5 GB, a moja aplikacja 66,7 GB.

Nes
źródło