Wie finde / identifiziere ich große Commits in der Git-Geschichte?

366

Ich habe ein 300 MB Git Repo. Die Gesamtgröße meiner aktuell ausgecheckten Dateien beträgt 2 MB, und die Gesamtgröße des restlichen Git-Repos beträgt 298 MB. Dies ist im Grunde ein reines Code-Repo, das nicht mehr als ein paar MB groß sein sollte.

Ich vermute, jemand hat versehentlich einige große Dateien (Videos, Bilder usw.) festgeschrieben und sie dann entfernt ... aber nicht von git, sodass der Verlauf immer noch nutzlose große Dateien enthält. Wie kann man die großen Dateien im Git-Verlauf finden? Es gibt mehr als 400 Commits, daher ist es nicht praktikabel, einzeln zu arbeiten.

HINWEIS : meine Frage geht es nicht darum , wie die Datei zu entfernen , aber wie man findet es in erster Linie.

Hose
quelle

Antworten:

143

Ich fand dieses Skript in der Vergangenheit sehr nützlich, um große (und nicht offensichtliche) Objekte in einem Git-Repository zu finden:


#!/bin/bash
#set -x 

# Shows you the largest objects in your repo's pack file.
# Written for osx.
#
# @see https://stubbisms.wordpress.com/2009/07/10/git-script-to-show-largest-pack-objects-and-trim-your-waist-line/
# @author Antony Stubbs

# set the internal field separator to line break, so that we can iterate easily over the verify-pack output
IFS=$'\n';

# list all objects including their size, sort by size, take top 10
objects=`git verify-pack -v .git/objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head`

echo "All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file."

output="size,pack,SHA,location"
allObjects=`git rev-list --all --objects`
for y in $objects
do
    # extract the size in bytes
    size=$((`echo $y | cut -f 5 -d ' '`/1024))
    # extract the compressed size in bytes
    compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024))
    # extract the SHA
    sha=`echo $y | cut -f 1 -d ' '`
    # find the objects location in the repository tree
    other=`echo "${allObjects}" | grep $sha`
    #lineBreak=`echo -e "\n"`
    output="${output}\n${size},${compressedSize},${other}"
done

echo -e $output | column -t -s ', '

Das gibt Ihnen den Objektnamen (SHA1sum) des Blobs, und dann können Sie ein Skript wie dieses verwenden:

... um das Commit zu finden, das auf jeden dieser Blobs verweist.

Mark Longair
quelle
31
Diese Antwort war wirklich hilfreich, weil sie mich zu dem obigen Beitrag geschickt hat. Während das Skript des Beitrags funktionierte, fand ich es schmerzhaft langsam. Also habe ich es umgeschrieben und es ist jetzt in großen Repositories deutlich schneller. Schauen Sie mal rein
Nick K9
7
Bitte fügen Sie Ihren Antworten vollständige Anweisungen bei und nicht nur Links außerhalb des Unternehmens. Was machen wir, wenn stubbisms.wordpress.com unweigerlich ausfällt?
ThorSummoner
@ NickK9 Interessanterweise bekomme ich unterschiedliche Ausgaben von deinem Skript und dem anderen. Es gibt eine Reihe größerer Objekte, die Ihnen zu fehlen scheinen. Fehlt mir etwas?
UpAndAdam
Oh cool! Vielen Dank, dass Sie mein Skript schneller gemacht haben @nick \ k9: D @UpAndAdam. Wollen Sie damit sagen, dass mein Skript eine falsche Ausgabe erzeugt hat?
Antony Stubbs
1
Diese Kommentare lassen es so klingen, als würden wir die Größe in Bytes angeben, aber ich bekomme Kilobyte.
Kat
682

🚀 Ein blitzschneller Shell-Einzeiler 🚀

Dieses Shell-Skript zeigt alle Blob-Objekte im Repository an, sortiert vom kleinsten zum größten.

Für mein Beispiel-Repo lief es ungefähr 100-mal schneller als die anderen hier gefundenen.
Auf meinem vertrauenswürdigen Athlon II X4-System verwaltet es das Linux-Kernel-Repository mit seinen 5,6 Millionen Objekten in etwas mehr als einer Minute .

Das Basisskript

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| sed -n 's/^blob //p' \
| sort --numeric-sort --key=2 \
| cut -c 1-12,41- \
| $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Wenn Sie den obigen Code ausführen, erhalten Sie eine schöne, für Menschen lesbare Ausgabe wie folgt :

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

macOS-Benutzer : Da numfmtes unter macOS nicht verfügbar ist, können Sie entweder die letzte Zeile weglassen und sich mit Rohbyte-Größen befassen oder brew install coreutils.

Filtern

Fügen Sie vor der sortZeile eine der folgenden Zeilen ein, um eine weitere Filterung zu erzielen .

Zum Ausschließen von Dateien , die in vorhanden sindHEAD , fügen Sie die folgende Zeile ein :

| grep -vF --file=<(git ls-tree -r HEAD | awk '{print $3}') \

Zum zeigen nur Dateien gegebene Größe übersteigt (zB 1 MiB = 2 20  B), fügen Sie die folgende Zeile ein :

| awk '$2 >= 2^20' \

Ausgabe für Computer

Lassen Sie die letzten beiden Zeilen des Basisskripts weg, um eine Ausgabe zu generieren, die für die weitere Verarbeitung durch Computer besser geeignet ist . Sie übernehmen die gesamte Formatierung. Damit haben Sie ungefähr Folgendes:

...
0d99bb93129939b72069df14af0d0dbda7eb6dba 542455 path/to/some-image.jpg
2ba44098e28f8f66bac5e21210c2774085d2319b 12446815 path/to/hires-image.png
bd1741ddce0d07b72ccf69ed281e09bf8a2d0b2f 65183843 path/to/some-video-1080p.mp4

Datei entfernen

Informationen zum eigentlichen Entfernen von Dateien finden Sie in dieser SO-Frage zum Thema .

raphinesse
quelle
14
Das verdient mehr als nur meine Gegenstimme! Besonderer Dank für die Bereitstellung von computer- und lesbaren Ausgaben.
Michel Jung
2
Dies ist extrem schnell und einfach zu bedienen!
Chin
32
So verwenden Sie diese auf Mac müssen Sie brew install coreutilsund ersetzen Sie dann cutmit gcutund numfmtmit gnumfmt.
Nick Sweeting
2
Lassen Sie mich noch einmal betonen - dies ist viel schneller als alle anderen Angebote, die ich gesehen habe.
Sridhar Sarnobat
4
das macht einen tollen git alias :) git largejemand?
Anarcat
160

Ich habe eine einzeilige Lösung auf der Wiki-Seite des Fachbereichs Physik der ETH Zürich gefunden (am Ende dieser Seite). Machen Sie einfach einen git gc, um abgestandenen Müll zu entfernen, und dann

git rev-list --objects --all \
  | grep "$(git verify-pack -v .git/objects/pack/*.idx \
           | sort -k 3 -n \
           | tail -10 \
           | awk '{print$1}')"

gibt Ihnen die 10 größten Dateien im Repository.

Es gibt auch eine faule Lösung jetzt verfügbar, GitExtensions jetzt ein Plugin hat , die diese in UI funktioniert (und Griffe Geschichte neu geschrieben als auch).

GitExtensions-Dialogfeld "Große Dateien suchen"

Skolima
quelle
8
Dieser Einzeiler funktioniert nur, wenn Sie die größte Einzeldatei erhalten möchten (dh Schwanz -1 verwenden). Zeilenumbrüche stören alles Größere. Sie können sed verwenden, um die Zeilenumbrüche zu konvertieren, damit grep gut spielt:git rev-list --objects --all | grep -E `git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk '{print$1}' | sed ':a;N;$!ba;s/\n/|/g'`
Throctukes
10
grep: a70783fca9bfbec1ade1519a41b6cc4ee36faea0: Keine solche Datei oder Verzeichnis
Jonathan Allard
1
Die Wiki - Link bewegt: readme.phys.ethz.ch/documentation/git_advanced_hints
outsmartin
11
Das Finden von GitExtensions ist wie das Finden des Goldschatzes und des Endes des Regenbogens - danke!
Ckapilla
3
Gibt es auch eine Erweiterung, die die Größe der Dateien druckt?
Michael
27

Schritt 1 Schreiben Sie alle Datei-SHA1s in eine Textdatei:

git rev-list --objects --all | sort -k 2 > allfileshas.txt

Schritt 2 Sortieren Sie die Blobs vom größten zum kleinsten und schreiben Sie die Ergebnisse in die Textdatei:

git gc && git verify-pack -v .git/objects/pack/pack-*.idx | egrep "^\w+ blob\W+[0-9]+ [0-9]+ [0-9]+$" | sort -k 3 -n -r > bigobjects.txt

Schritt 3a Kombinieren Sie beide Textdateien, um Informationen zum Dateinamen / sha1 / Größe zu erhalten:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | awk '{print $1,$3,$7}' >> bigtosmall.txt
done;

Schritt 3b Wenn Sie Dateinamen oder Pfadnamen haben, die Leerzeichen enthalten , versuchen Sie diese Variante von Schritt 3a. Es verwendetcut anstelle von verwendet awk, um die gewünschten Spalten inkl. Leerzeichen von Spalte 7 bis Zeilenende:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | cut -d ' ' -f'1,3,7-' >> bigtosmall.txt
done;

Jetzt können Sie sich die Datei bigtosmall.txt ansehen, um zu entscheiden, welche Dateien Sie aus Ihrem Git-Verlauf entfernen möchten.

Schritt 4 So führen Sie das Entfernen durch (beachten Sie, dass dieser Teil langsam ist, da jedes Commit in Ihrem Verlauf auf Daten zu der von Ihnen identifizierten Datei überprüft wird):

git filter-branch --tree-filter 'rm -f myLargeFile.log' HEAD

Quelle

Die Schritte 1 bis 3a wurden aus Suchen und Löschen großer Dateien aus dem Git-Verlauf kopiert

BEARBEITEN

Der Artikel wurde irgendwann in der zweiten Jahreshälfte 2017 gelöscht, aber auf eine archivierte Kopie davon kann weiterhin mit der Wayback-Maschine zugegriffen werden .

friederbluemle
quelle
6
Ein Liner, um dasselbe zu tun:git gc && join -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 ) | sort -k2gr
Iwan Aucamp
1
@Iwan, danke für den Einzeiler! Es werden keine Dateinamen mit Leerzeichen behandelt. Dies scheint : join -t' ' -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sed 's/[[:space:]]/\t/' | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 | sed 's/[[:space:]]\+/\t/g' ) | sort -k2gr | less. Beachten Sie, dass Sie das tatsächliche TAB-Zeichen nachher join -t'mit STRG + V <TAB> per geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html eingeben müssen
Nickolay
2
@ Nickolay mit Bash $'\t'sollte Ihnen einen Tab geben. echo -n $'\t' | xxd -ps->09
Iwan Aucamp
1
@IwanAucamp: noch besser, danke für den Tipp! (Schade, dass ich den vorherigen Kommentar nicht bearbeiten kann.
Nickolay
1
@ Sridhar-Sarnobat Der Artikel wurde von der Wayback Machine gespeichert! :) web.archive.org/web/20170621125743/http://www.naleid.com/blog/…
friederbluemle
18

Sie sollten BFG Repo-Cleaner verwenden .

Laut der Website:

Die BFG ist eine einfachere und schnellere Alternative zum Git-Filter-Zweig, um fehlerhafte Daten aus Ihrem Git-Repository-Verlauf zu entfernen:

  • Verrückte große Dateien entfernen
  • Entfernen von Passwörtern, Anmeldeinformationen und anderen privaten Daten

Das klassische Verfahren zum Reduzieren der Größe eines Repositorys wäre:

git clone --mirror git://example.com/some-big-repo.git
java -jar bfg.jar --strip-biggest-blobs 500 some-big-repo.git
cd some-big-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push
Warren Seine
quelle
4
BFG Repo-Cleaner ist sehr gut. Es blitzt schnell und arbeitet sehr zuverlässig.
fschmitt
30
Dies sagt Ihnen jedoch nicht, wie Sie die größten Dateien auflisten sollen.
Andi Jay
5
Das Problem dabei ist, dass Sie nicht einfach SEHEN können, was die großen Dateien sind, ohne sie tatsächlich zu entfernen. Ich fühle mich nicht wohl, wenn ich dies nicht ohne einen Probelauf mache, der einfach die großen Dateien auflistet.
Sridhar Sarnobat
Was macht --strip-biggest-blobs 500das
2540625
git lehnt Änderungen ab, die dieses Tool vornimmt.
Christopher
9

Wenn Sie nur eine Liste großer Dateien haben möchten, möchte ich Ihnen den folgenden Einzeiler zur Verfügung stellen:

join -o "1.1 1.2 2.3" <(git rev-list --objects --all | sort) <(git verify-pack -v objects/pack/*.idx | sort -k3 -n | tail -5 | sort) | sort -k3 -n

Wessen Ausgabe wird sein:

commit       file name                                  size in bytes

72e1e6d20... db/players.sql 818314
ea20b964a... app/assets/images/background_final2.png 6739212
f8344b9b5... data_test/pg_xlog/000000010000000000000001 1625545
1ecc2395c... data_development/pg_xlog/000000010000000000000001 16777216
bc83d216d... app/assets/images/background_1forfinal.psd 95533848

Der letzte Eintrag in der Liste zeigt auf die größte Datei in Ihrem Git-Verlauf.

Mit dieser Ausgabe können Sie sicherstellen, dass Sie keine Inhalte mit BFG löschen, die Sie in Ihrem Verlauf benötigt hätten.

schmijos
quelle
2
Genial!! Beachten Sie jedoch, dass Sie das Repo mit den Optionen --mirror klonen müssen, bevor Sie diesen Befehl ausführen.
Andi Jay
Ich bin neugierig, wofür sind die 1.1, 1.2, 2.3Zahlen?
ympostor
Die Zahlen sind eine Liste, in der <filenumber>.<field>die Reihenfolge der Kombination angegeben ist. Weitere Informationen finden Sie unter man.cx/join .
schmijos
6

Wenn Sie unter Windows arbeiten, finden Sie hier ein PowerShell-Skript, mit dem die 10 größten Dateien in Ihrem Repository gedruckt werden:

$revision_objects = git rev-list --objects --all;
$files = $revision_objects.Split() | Where-Object {$_.Length -gt 0 -and $(Test-Path -Path $_ -PathType Leaf) };
$files | Get-Item -Force | select fullname, length | sort -Descending -Property Length | select -First 10
Julia Schwarz
quelle
1
Dies führt zu einer anderen Antwort als @raphinesse, bei der einige der größten Dateien in meinem Repository fehlen. Auch wenn eine große Datei viele Änderungen aufweist, wird nur die größte Größe gemeldet.
Kristianp
Dieses Skript ist für mich mit dem Fehler fehlgeschlagen : You cannot call a method on a null-valued expression. At line: 2 char: 1. Diese Antwort funktionierte jedoch: stackoverflow.com/a/57793716/2441655 (es ist auch kürzer)
Venryx
4

Versuchen Sie es git ls-files | xargs du -hs --threshold=1M.

Wir verwenden den folgenden Befehl in unserer CI-Pipeline. Er wird angehalten, wenn große Dateien im Git-Repo gefunden werden:

test $(git ls-files | xargs du -hs --threshold=1M 2>/dev/null | tee /dev/stderr | wc -l) -gt 0 && { echo; echo "Aborting due to big files in the git repository."; exit 1; } || true
Vojtech Vitek
quelle
2

Ich konnte die beliebteste Antwort nicht verwenden, da der --batch-checkBefehlszeilenwechsel zu Git 1.8.3 (den ich verwenden muss) keine Argumente akzeptiert. Die folgenden Schritte wurden unter CentOS 6.5 mit Bash 4.1.2 ausprobiert

Schlüssel Konzepte

In Git der Begriff Blob impliziert den Inhalt einer Datei. Beachten Sie, dass ein Commit möglicherweise den Inhalt einer Datei oder eines Pfadnamens ändert. Daher kann dieselbe Datei je nach Festschreiben auf einen anderen Blob verweisen. Eine bestimmte Datei kann in einem Commit die größte in der Verzeichnishierarchie sein, in einem anderen jedoch nicht. Die Frage, große Commits anstelle großer Dateien zu finden, bringt die Sache daher in die richtige Perspektive.

Für den Ungeduldigen

Der Befehl zum Drucken der Liste der Blobs in absteigender Reihenfolge der Größe lautet:

git cat-file --batch-check < <(git rev-list --all --objects  | \
awk '{print $1}')  | grep blob  | sort -n -r -k 3

Beispielausgabe:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

Verwenden Sie zum Entfernen solcher Blobs den BFG Repo Cleaner , wie in anderen Antworten erwähnt. Bei einer Datei blobs.txt, die nur die Blob-Hashes enthält, zum Beispiel:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Tun:

java -jar bfg.jar -bi blobs.txt <repo_dir>

Die Frage ist, wie man die Commits findet, was mehr Arbeit ist als das Finden von Blobs. Um es zu wissen, lesen Sie bitte weiter.

Weitere Arbeit

Bei einem Commit-Hash lautet ein Befehl, der Hashes aller damit verbundenen Objekte, einschließlich Blobs, druckt:

git ls-tree -r --full-tree <commit_hash>

Wenn also solche Ausgaben für alle Commits im Repo verfügbar sind und ein Blob-Hash vorliegt, stimmen die Commits mit den Ausgaben überein. Diese Idee ist im folgenden Skript codiert:

#!/bin/bash
DB_DIR='trees-db'

find_commit() {
    cd ${DB_DIR}
    for f in *; do
        if grep -q $1 ${f}; then
            echo ${f}
        fi
    done
    cd - > /dev/null
}

create_db() {
    local tfile='/tmp/commits.txt'
    mkdir -p ${DB_DIR} && cd ${DB_DIR}
    git rev-list --all > ${tfile}

    while read commit_hash; do
        if [[ ! -e ${commit_hash} ]]; then
            git ls-tree -r --full-tree ${commit_hash} > ${commit_hash}
        fi
    done < ${tfile}
    cd - > /dev/null
    rm -f ${tfile}
}

create_db

while read id; do
    find_commit ${id};
done

Wenn der Inhalt in einer Datei mit dem Namen gespeichert wird, lautet find-commits.shein typischer Aufruf wie folgt:

cat blobs.txt | find-commits.sh

Wie zuvor blobs.txtlistet die Datei Blob-Hashes auf, einen pro Zeile. Die create_db()Funktion speichert einen Cache aller Festschreibungslisten in einem Unterverzeichnis im aktuellen Verzeichnis.

Einige Statistiken aus meinen Experimenten mit einem System mit zwei Intel (R) Xeon (R) CPU E5-2620 2,00-GHz-Prozessoren, die vom Betriebssystem als 24 virtuelle Kerne dargestellt werden:

  • Gesamtzahl der Commits im Repo = fast 11.000
  • Dateierstellungsgeschwindigkeit = 126 Dateien / s. Das Skript erstellt pro Commit eine einzelne Datei. Dies tritt nur auf, wenn der Cache zum ersten Mal erstellt wird.
  • Aufwand für die Cache-Erstellung = 87 s.
  • Durchschnittliche Suchgeschwindigkeit = 522 Commits / s. Die Cache-Optimierung führte zu einer Reduzierung der Laufzeit um 80%.

Beachten Sie, dass das Skript Single-Threaded ist. Daher wird immer nur ein Kern verwendet.

pdp
quelle
2

Powershell-Lösung für Windows Git, finden Sie die größten Dateien:

git ls-tree -r -t -l --full-name HEAD | Where-Object {
 $_ -match '(.+)\s+(.+)\s+(.+)\s+(\d+)\s+(.*)'
 } | ForEach-Object {
 New-Object -Type PSObject -Property @{
     'col1'        = $matches[1]
     'col2'      = $matches[2]
     'col3' = $matches[3]
     'Size'      = [int]$matches[4]
     'path'     = $matches[5]
 }
 } | sort -Property Size -Top 10 -Descending
Aaron
quelle
0

Wie kann ich die großen Dateien im Git-Verlauf aufspüren?

Beginnen Sie mit der Analyse, Validierung und Auswahl der Grundursache. Verwenden Sie git-repo-analysis, um zu helfen.

Möglicherweise finden Sie auch einen gewissen Wert in den detaillierten Berichten, die von BFG Repo-Cleaner erstellt wurden und die sehr schnell ausgeführt werden können, indem Sie mit ihrem Netzwerkdurchsatz von 10 MB / s auf ein Digital Ocean-Tröpfchen klonen.

Josh Habdas
quelle
Ich denke, Sie haben eine nette allgemeine Antwort im BFG-Vorschlag, aber Sie verderben sie, indem Sie keine Details angeben und dann vorschlagen, einen anderen Drittanbieter-Service zu verwenden (auch ohne Erklärung). Können Sie dies bereinigen, um ein Befehlszeilenbeispiel für diese BFG-Verwendung bereitzustellen?
Phord
0

Ich bin aus dem gleichen Grund wie jeder andere darüber gestolpert. Aber die zitierten Skripte haben bei mir nicht ganz funktioniert. Ich habe eine gemacht, die eher eine Mischung aus denen ist, die ich gesehen habe, und sie lebt jetzt hier - https://gitlab.com/inorton/git-size-calc

IanNorton
quelle