Warum ist mein Git-Repository so groß?


141

145M = .git / Objekte / Pack /

Ich habe ein Skript geschrieben, um die Größen der Unterschiede zwischen jedem Commit und dem Commit zu addieren, bevor es von der Spitze jedes Zweigs rückwärts geht. Ich erhalte 129 MB, ohne Komprimierung und ohne Berücksichtigung der gleichen Dateien in verschiedenen Filialen und des gemeinsamen Verlaufs zwischen den Filialen.

Git berücksichtigt all diese Dinge, so dass ich ein viel viel kleineres Repository erwarten würde. Warum ist .git so groß?

Ich habe getan:

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

Um zu beantworten, wie viele Dateien / Commits ich habe, habe ich 19 Zweige mit jeweils etwa 40 Dateien. 287 Commits, gefunden mit:

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

Es sollte nicht 10 Megabyte dauern, um Informationen darüber zu speichern.


5
Linus empfiehlt Folgendes gegenüber aggressivem gc. Macht es einen signifikanten Unterschied? git repack -a -d --depth = 250 --window = 250
Greg Bacon

danke gbacon, aber kein unterschied.
Ian Kelling

Das liegt daran, dass Ihnen das -f fehlt. metalinguist.wordpress.com/2007/12/06/…
spuder

git repack -a -dschrumpfte mein 956MB Repo auf 250MB . Großer Erfolg! Vielen Dank!
Xanderiel

Antworten:


68

Ich habe kürzlich das falsche Remote-Repository in das lokale ( git remote add ...und git remote update) gezogen. Nach dem Löschen der unerwünschten Remote-Referenz, Zweige und Tags hatte ich immer noch 1,4 GB (!) Verschwendeten Speicherplatz in meinem Repository. Ich konnte dies nur durch Klonen loswerden git clone file:///path/to/repository. Beachten Sie, dass file://dies beim Klonen eines lokalen Repositorys einen großen Unterschied macht - nur die referenzierten Objekte werden kopiert, nicht die gesamte Verzeichnisstruktur.

Bearbeiten: Hier ist Ians einziger Liner zum Neuerstellen aller Zweige im neuen Repo:

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

1
Beeindruckend. DANKE. .git = 15M jetzt !! Nach dem Klonen finden Sie hier einen kleinen Liner, um Ihre vorherigen Zweige zu erhalten. d1 = # ursprüngliches Repo; d2 = # neues Repo; cd $ d1; für b in $ (git branch | cut -c 3-); git checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; Git Checkout -b $ b $ x; cd $ d1; fertig
Ian Kelling

Wenn Sie dies aktivieren, können Sie Ihrer Antwort den 1-Liner hinzufügen, damit er als Code formatiert ist.
Ian Kelling

1
Ich habe meinem Repo dummerweise eine Reihe von Videodateien hinzugefügt und musste --soft HEAD ^ zurücksetzen und erneut festlegen. Das .git / object-Verzeichnis war danach riesig, und dies war der einzige Weg, um es wieder herunter zu bringen. Es hat mir jedoch nicht gefallen, wie der eine Liner meine Filialnamen geändert hat (er zeigte Ursprung / Filialname statt nur Filialname). Also ging ich noch einen Schritt weiter und führte eine skizzenhafte Operation durch - ich löschte das Verzeichnis .git / properties aus dem Original und fügte das Verzeichnis aus dem Klon ein. Das hat den Trick gemacht und alle ursprünglichen Zweige, Refs usw. intakt gelassen, und alles scheint zu funktionieren (Daumen drücken).
Jack Senechal

1
danke für den
tipp

3
@vonbrand Wenn Sie eine feste Verknüpfung zu einer Datei herstellen und die Originaldatei löschen, geschieht nichts, außer dass ein Referenzzähler von 2 auf 1 dekrementiert wird. Nur wenn dieser Zähler auf 0 dekrementiert wird, wird der Speicherplatz für andere Dateien auf dem fs freigegeben. Also nein, selbst wenn die Dateien fest verknüpft wären, würde nichts passieren, wenn das Original gelöscht wird.
Stefreak

157

Einige Skripte, die ich benutze:

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

Wenn Sie mehr Zeilen wünschen, lesen Sie auch die Perl-Version in einer benachbarten Antwort: https://stackoverflow.com/a/45366030/266720

git-eradicate (für 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

Hinweis: Das zweite Skript dient zum vollständigen Entfernen von Informationen aus Git (einschließlich aller Informationen aus Reflogs). Mit Vorsicht verwenden.


2
Endlich ... Ironischerweise habe ich diese Antwort früher in meiner Suche gesehen, aber sie sah zu kompliziert aus ... nachdem ich andere Dinge ausprobiert hatte, begann diese sinnvoll und voila!
Msanteler

@msanteler, Das frühere ( git-fatfiles) Skript ist entstanden, als ich die Frage im IRC (Freenode / # git) gestellt habe. Ich habe die beste Version in einer Datei gespeichert und sie dann als Antwort hier veröffentlicht. (Ich kann den ursprünglichen Autor jedoch nicht in IRC-Protokollen finden).
Vi.

Dies funktioniert zunächst sehr gut. Aber wenn ich wieder von der Fernbedienung rufe oder ziehe, kopiert es einfach alle großen Dateien zurück in das Archiv. Wie verhindere ich das?
Pir

1
@felbo, dann liegt das Problem wahrscheinlich nicht nur in Ihrem lokalen Repository, sondern auch in anderen Repositorys. Möglicherweise müssen Sie das Verfahren überall ausführen oder alle dazu zwingen, die ursprünglichen Zweige aufzugeben und zu neu geschriebenen Zweigen zu wechseln. In einem großen Team ist das nicht einfach und erfordert die Zusammenarbeit zwischen Entwicklern und / oder Managern. Manchmal kann es besser sein, den Ladestein im Inneren zu lassen.
Vi.

1
Diese Funktion ist großartig, aber unvorstellbar langsam. Es kann nicht einmal auf meinem Computer beendet werden, wenn ich das 40-Zeilen-Limit entferne. Zu Ihrer Information, ich habe gerade eine Antwort mit einer effizienteren Version dieser Funktion hinzugefügt. Probieren Sie es aus, wenn Sie diese Logik in einem großen Repository verwenden möchten oder wenn Sie die pro Datei oder Ordner summierten Größen anzeigen möchten.
Piojo

66

git gcgit repackWenn Sie dies bereits tun, macht es keinen Sinn, manuell neu zu verpacken, es sei denn, Sie übergeben ihm einige spezielle Optionen.

Der erste Schritt besteht darin, festzustellen, ob der größte Teil des Speicherplatzes (wie normalerweise der Fall ist) Ihre Objektdatenbank ist.

git count-objects -v

Dies sollte einen Bericht darüber geben, wie viele entpackte Objekte sich in Ihrem Repository befinden, wie viel Speicherplatz sie beanspruchen, wie viele Packdateien Sie haben und wie viel Speicherplatz sie belegen.

Idealerweise hätten Sie nach einem Umpacken keine entpackten Objekte und eine Packdatei, aber es ist völlig normal, dass einige Objekte, auf die die aktuellen Zweige nicht direkt verweisen, noch vorhanden und entpackt sind.

Wenn Sie eine einzelne große Packung haben und wissen möchten, was den Speicherplatz einnimmt, können Sie die Objekte, aus denen die Packung besteht, zusammen mit ihrer Speicherung auflisten.

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

Beachten Sie, dass verify-pack eine Indexdatei und nicht die Packdatei selbst verwendet wird. Dies gibt einen Bericht über jedes Objekt in der Packung, seine wahre Größe und seine Packungsgröße sowie Informationen darüber, ob es "deltifiziert" wurde und wenn ja, woher die Delta-Kette stammt.

Um festzustellen, ob sich ungewöhnlich große Objekte in Ihrem Repository befinden, können Sie die Ausgabe numerisch in der dritten der vierten Spalte sortieren (z | sort -k3n . ) .

Über diese Ausgabe können Sie den Inhalt eines Objekts mit dem git showBefehl anzeigen, obwohl nicht genau erkennbar ist, wo im Festschreibungsverlauf des Repositorys auf das Objekt verwiesen wird. Wenn Sie dies tun müssen, versuchen Sie etwas aus dieser Frage .


1
Dies fand die großen Objekte großartig. Die akzeptierte Antwort wurde sie los.
Ian Kelling

2
Der Unterschied zwischen git gc und git repack nach linus torvalds. metalinguist.wordpress.com/2007/12/06/…
spuder

30

Nur zu Ihrer Information, der Hauptgrund, warum Sie möglicherweise unerwünschte Objekte in der Nähe haben, ist, dass Git ein Reflog aufrechterhält.

Das Reflog dient dazu, Ihren Hintern zu retten, wenn Sie versehentlich Ihren Hauptzweig löschen oder Ihr Repository auf andere Weise katastrophal beschädigen.

Der einfachste Weg, dies zu beheben, besteht darin, Ihre Reflogs vor dem Komprimieren abzuschneiden (stellen Sie nur sicher, dass Sie niemals zu einem der Commits im Reflog zurückkehren möchten).

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

Dies unterscheidet sich davon, git gc --prune=todaydass das gesamte Reflog sofort abläuft.


1
Dieser hat es für mich getan! Ich ging von etwa 5 GB auf 32 MB.
Hawkee

Diese Antwort schien einfacher zu sein, funktionierte aber leider nicht für mich. In meinem Fall habe ich an einem gerade geklonten Repository gearbeitet. Ist das der Grund?
Mert

13

Wenn Sie herausfinden möchten, welche Dateien Speicherplatz in Ihrem Git-Repository belegen, führen Sie aus

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

Extrahieren Sie dann die Blob-Referenz, die am meisten Platz beansprucht (die letzte Zeile), und überprüfen Sie den Dateinamen, der so viel Platz beansprucht

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

Dies kann sogar eine Datei sein, mit der Sie entfernt haben git rm , aber git merkt sich das, weil es immer noch Verweise darauf gibt, wie Tags, Fernbedienungen und Reflog.

Sobald Sie wissen, welche Datei Sie entfernen möchten, empfehle ich die Verwendung git forget-blob

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

Es ist einfach zu bedienen, tun Sie es einfach

git forget-blob file-to-forget

Dadurch wird jeder Verweis aus git entfernt, der Blob aus jedem Commit im Verlauf entfernt und die Garbage Collection ausgeführt, um den Speicherplatz freizugeben.


7

Das Git-Fatfiles-Skript aus Vis Antwort ist sehr schön, wenn Sie die Größe all Ihrer Blobs sehen möchten, aber es ist so langsam, dass es unbrauchbar wird. Ich habe die 40-Zeilen-Ausgabegrenze entfernt und versucht, den gesamten RAM meines Computers zu verwenden, anstatt fertig zu werden. Also habe ich es umgeschrieben: Dies ist tausende Male schneller, hat Funktionen hinzugefügt (optional) und ein seltsamer Fehler wurde behoben - die alte Version würde ungenaue Zählungen ergeben, wenn Sie die Ausgabe summieren, um den gesamten von einer Datei verwendeten Speicherplatz zu sehen.

#!/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];
}

Nennen Sie diese git-fatfiles.pl und führen Sie sie aus. Verwenden Sie die --sumOption , um den von allen Revisionen einer Datei verwendeten Speicherplatz anzuzeigen. Verwenden Sie die --directoriesOption , um dasselbe zu sehen, jedoch für Dateien in jedem Verzeichnis . Wenn Sie das Modul Number :: Bytes :: Human cpan installieren (führen Sie "cpan Number :: Bytes :: Human" aus), werden die Größen wie folgt formatiert: "21M /path/to/file.mp4".


4

Sind Sie sicher, dass Sie nur die .pack-Dateien und nicht die .idx-Dateien zählen? Sie befinden sich im selben Verzeichnis wie die .pack-Dateien, haben jedoch keine Repository-Daten (wie die Erweiterung angibt, handelt es sich lediglich um Indizes für das entsprechende Pack. Wenn Sie den richtigen Befehl kennen, können Sie dies sogar tun Erstellen Sie sie einfach aus der Pack-Datei neu, und Git selbst erledigt dies beim Klonen, da nur eine Pack-Datei mit dem nativen Git-Protokoll übertragen wird.

Als repräsentatives Beispiel habe ich mir meinen lokalen Klon des Linux-2.6-Repositorys angesehen:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Was darauf hinweist, dass eine Expansion von rund 7% üblich sein sollte.

Es gibt auch die Dateien draußen objects/; in meiner persönlichen Erfahrung, von ihnen indexund gitk.cacheist in der Regel die größten (insgesamt 11M in meinem Klon der Linux-2.6 - Repository) sein.


3

Andere in gespeicherte Git-Objekte .gitsind Bäume, Commits und Tags. Commits und Tags sind klein, aber Bäume können groß werden, insbesondere wenn Sie eine sehr große Anzahl kleiner Dateien in Ihrem Repository haben. Wie viele Dateien und wie viele Commits haben Sie?


Gute Frage. 19 Zweige mit jeweils ca. 40 Dateien. Git Count-Objekte -v sagt "In-Pack: 1570". Ich weiß nicht genau, was das bedeutet oder wie ich zählen soll, wie viele Commits ich habe. Ein paar hundert würde ich vermuten.
Ian Kelling

Ok, das hört sich dann nicht so an. Einige hundert sind im Vergleich zu 145 MB unbedeutend.
Greg Hewgill


2

Bevor Sie git filter-branch & git gc ausführen, sollten Sie die Tags überprüfen, die in Ihrem Repo vorhanden sind. Jedes echte System, das über ein automatisches Tagging für Dinge wie kontinuierliche Integration und Bereitstellung verfügt, führt dazu, dass unerwünschte Objekte immer noch durch diese Tags aktualisiert werden. Daher kann gc sie nicht entfernen, und Sie werden sich immer wieder fragen, warum das Repo immer noch so groß ist.

Der beste Weg, um alle unerwünschten Dinge loszuwerden, besteht darin, git-filter & git gc auszuführen und den Master dann auf ein neues Bare-Repo zu schieben. Das neue nackte Repo wird den aufgeräumten Baum haben.


1

Dies kann passieren, wenn Sie versehentlich einen großen Teil der Dateien hinzugefügt und diese bereitgestellt haben und nicht unbedingt festschreiben. Dies kann in einer railsApp passieren, wenn Sie sie ausführen, bundle install --deploymentund dann werden versehentlich git add .alle unter vendor/bundleIhnen hinzugefügten Dateien entfernt, aber sie sind bereits in den Git-Verlauf eingegangen. Sie müssen also die Antwort video/parasite-intro.avi von Vi anwenden und ändern, bis Sie vendor/bundleden zweiten von ihm bereitgestellten Befehl ausführen.

Sie können den Unterschied sehen, mit git count-objects -vdem in meinem Fall vor dem Anwenden des Skripts ein Größenpaket von 52 KB und nach dem Anwenden 3,8 KB vorhanden war.


1

Es lohnt sich, die Datei stacktrace.log zu überprüfen. Grundsätzlich handelt es sich um ein Fehlerprotokoll zum Verfolgen von fehlgeschlagenen Commits. Ich habe kürzlich herausgefunden, dass mein stacktrace.log 65,5 GB und meine App 66,7 GB hat.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.