Wie entferne ich nicht referenzierte Blobs von meinem Git Repo?

124

Ich habe ein GitHub-Repo mit zwei Zweigen - Master & Release.

Der Release-Zweig enthielt binäre Distributionsdateien, die zu einer sehr großen Repo-Größe (> 250 MB) beitrugen. Deshalb habe ich beschlossen, die Dinge zu bereinigen.

Zuerst habe ich den Remote Release-Zweig über gelöscht git push origin :release

Dann habe ich den lokalen Release-Zweig gelöscht. Zuerst habe ich es versucht git branch -d release, aber git sagte "Fehler: Der Zweig 'release' ist kein Vorfahr Ihres aktuellen HEAD." Das ist wahr, also habe ich es erzwungen git branch -D release, um es zu löschen.

Aber meine Repository-Größe, sowohl lokal als auch auf GitHub, war immer noch riesig. Also ging ich die übliche Liste der Git-Befehle durch, git gc --prune=today --aggressiveohne Glück.

Durch Befolgen der Anweisungen von Charles Bailey bei SO 1029969 konnte ich eine Liste der SHA1s für die größten Blobs erhalten. Ich habe dann das Skript von SO 460331 verwendet , um die Blobs zu finden ... und die fünf größten existieren nicht, obwohl kleinere Blobs gefunden werden, sodass ich weiß, dass das Skript funktioniert.

Ich denke, diese Blogs sind die Binärdateien aus dem Release-Zweig, und sie sind nach dem Löschen dieses Zweigs irgendwie herumgekommen. Was ist der richtige Weg, um sie loszuwerden?

kkrugler
quelle
Welche Version von Git verwenden Sie? Und haben Sie stackoverflow.com/questions/1106529/… ausprobiert ?
VonC
git version 1.6.2.3 Ich hatte gc ausprobiert und mit verschiedenen Argumenten beschnitten. Ich hatte nicht versucht, -a -d -l neu zu packen, sondern nur ausgeführt, keine Änderung.
kkrugler
2
Neue Informationen - Ein neuer Klon von GitHub hat nicht mehr die nicht referenzierten Blobs und ist von 250 MB auf "nur" 84 MB beschränkt.
kkrugler

Antworten:

217

... und ohne weiteres darf ich Ihnen diesen nützlichen Befehl "git-gc-all" vorstellen, der garantiert Ihren gesamten Git-Müll entfernt, bis zusätzliche Konfigurationsvariablen auftauchen:

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

Möglicherweise müssen Sie auch zuerst so etwas ausführen, oh je, git ist kompliziert !!

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

Möglicherweise müssen Sie auch einige Tags entfernen, danke Zitrax:

git tag | xargs git tag -d

Ich habe das alles in ein Skript geschrieben: git-gc-all-ferocious .

Sam Watkins
quelle
1
Interessant. Eine gute Alternative zu meiner allgemeineren Antwort. +1
VonC
10
Dies verdient mehr Stimmen. Es wurden schließlich viele Git-Objekte entfernt, die andere Methoden behalten würden. Vielen Dank!
Jean-Philippe Pellet
1
Upvoted. Wow, ich weiß nicht, was ich gerade getan habe, aber es scheint viel aufzuräumen. Können Sie näher erläutern, was es tut? Ich habe das Gefühl, es hat alle meine ausgeräumt objects. Was sind das und warum sind sie (anscheinend) irrelevant?
Redsandro
1
@Redsandro, wie ich verstehe, entfernen diese Befehle "git rm origin", "rm" und "git update-ref -d" Verweise auf alte Commits für Fernbedienungen und dergleichen, was möglicherweise die Speicherbereinigung verhindert. Die Optionen für "git gc" weisen an, dass verschiedene alte Commits nicht beibehalten werden sollen, da sie sonst eine Weile beibehalten werden. Zum Beispiel ist gc.rerereresolved für "Aufzeichnungen von Konflikten, die Sie zuvor gelöst haben", standardmäßig 60 Tage lang aufbewahrt. Diese Optionen finden Sie in der git-gc-Manpage. Ich bin kein Experte für Git und weiß nicht genau, was all diese Dinge bewirken. Ich fand sie auf Manpages und suchte nach .git für Commit-Refs.
Sam Watkins
1
Ein Git-Objekt ist eine komprimierte Datei oder ein komprimierter Baum oder ein Commit in Ihrem Git-Repo, einschließlich alter Inhalte aus dem Verlauf. git gc löscht nicht benötigte Objekte. Es speichert Objekte, die noch für Ihr aktuelles Repo benötigt werden, und dessen Verlauf.
Sam Watkins
81

Wie hier beschrieben , verwenden Sie einfach , wenn Sie alles, auf das nur per Reflog verwiesen wird , dauerhaft entfernen möchten

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

git reflog expire --expire-unreachable=now --allEntfernt alle Verweise auf nicht erreichbare Commits in reflog.

git gc --prune=now entfernt die Commits selbst.

Achtung : Nur die Verwendung git gc --prune=nowfunktioniert nicht, da auf diese Commits im Reflog noch verwiesen wird. Daher ist das Löschen des Reflogs obligatorisch. Beachten Sie auch, dass bei Verwendung rererezusätzliche Referenzen vorhanden sind, die von diesen Befehlen nicht gelöscht werden. Siehe git help rererefür weitere Details. Darüber hinaus werden Commits, auf die von lokalen oder Remote-Zweigen oder -Tags verwiesen wird, nicht entfernt, da diese von git als wertvolle Daten betrachtet werden.

jiasli
quelle
14
Es hat funktioniert, aber irgendwie habe ich meine gespeicherten Vorräte verloren (nichts Wichtiges in meinem Fall, nur eine Warnung für andere)
Amro
1
warum nicht - aggressiv?
JoelFan
2
Ich denke, diese Antwort braucht eine klare Warnung, vorzugsweise ganz oben. Mein Bearbeitungsvorschlag wurde abgelehnt, weil ich ihn dem Autor in einem Kommentar vorschlagen sollte. Bitte akzeptieren Sie diese Bearbeitung entweder stackoverflow.com/review/suggested-edits/26023988 oder fügen Sie eine Warnung auf Ihre eigene Weise hinzu. Dies lässt auch alle Ihre Verstecke fallen . Das sollte auch in der Warnung erwähnt werden!
Inigo
Ich habe mit Git Version 2.17 getestet und versteckte Commits werden durch die obigen Befehle nicht entfernt. Sind Sie sicher, dass Sie keine zusätzlichen Befehle ausgeführt haben?
Mikko Rantalainen
1
git fetch --pruneReduzieren Sie die Größe weiter, da lokale Blobs gelöscht werden.
Hectorpal
33

Wie in dieser SO-Antwort erwähnt , git gckann das Repo tatsächlich vergrößert werden!

Siehe auch diesen Thread

Jetzt hat git einen Sicherheitsmechanismus, um nicht referenzierte Objekte beim Ausführen von ' git gc' nicht sofort zu löschen .
Standardmäßig werden nicht referenzierte Objekte 2 Wochen lang aufbewahrt. Dies soll Ihnen das Wiederherstellen versehentlich gelöschter Zweige oder Commits erleichtern oder ein Rennen vermeiden, bei dem ein gerade erstelltes Objekt, das gerade erstellt, aber noch nicht referenziert wurde, von einem 'gelöscht werden könnte.git gc parallel laufenden Prozess .

Um gepackten, aber nicht referenzierten Objekten diese Kulanzfrist zu geben, werden diese nicht referenzierten Objekte durch das Umpacken aus der Packung in ihre lose Form gebracht, damit sie gealtert und schließlich beschnitten werden können.
Objekte, die nicht mehr referenziert werden, sind normalerweise nicht so viele. 404855 nicht referenzierte Objekte zu haben, ist ziemlich viel, und diese Objekte überhaupt über einen Klon zu senden, ist dumm und eine völlige Verschwendung von Netzwerkbandbreite.

Wie auch immer ... Um Ihr Problem zu lösen, müssen Sie einfach ' git gc' mit dem --prune=nowArgument ausführen, um diese Kulanzfrist zu deaktivieren und diese nicht referenzierten Objekte sofort zu entfernen (nur dann sicher, wenn keine anderen Git-Aktivitäten gleichzeitig stattfinden, die dies sollten auf einer Workstation einfach sicherzustellen sein).

Und übrigens mit ' git gc --aggressive' mit einer späteren Git-Version (oder ' git repack -a -f -d --window=250 --depth=250')

Der gleiche Thread erwähnt :

 git config pack.deltaCacheSize 1

Dies begrenzt die Delta-Cache-Größe auf ein Byte (wodurch sie effektiv deaktiviert wird) anstelle des Standardwerts 0, was unbegrenzt bedeutet. Damit kann ich das Repository mit dem obigen git repackBefehl auf einem x86-64-System mit 4 GB RAM und 4 Threads neu packen (dies ist ein Quad-Core). Die Auslastung des residenten Speichers steigt jedoch auf fast 3,3 GB.

Wenn Ihr Computer SMP ist und Sie nicht über genügend RAM verfügen, können Sie die Anzahl der Threads auf nur einen reduzieren:

git config pack.threads 1

Darüber hinaus können Sie die Speichernutzung mit --window-memory argumentto ' git repack' weiter einschränken .
Beispielsweise --window-memory=128Msollte die Verwendung eine angemessene Obergrenze für die Verwendung des Delta-Suchspeichers beibehalten, obwohl dies zu einer weniger optimalen Delta-Übereinstimmung führen kann, wenn das Repo viele große Dateien enthält.


Auf der Filter-Zweig-Front können Sie dieses Skript (mit Vorsicht) betrachten

#!/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
quelle
stackoverflow.com/questions/359424/… ist auch ein guter Anfang für die filter-branchBefehlsverwendung.
VonC
Hallo VonC - NI hatte git gc prune = jetzt ohne Glück ausprobiert. Es sieht wirklich wie ein Git-Bug aus, da ich nach dem Löschen eines Zweigs lokal mit nicht referenzierten Blobs endete, aber diese sind nicht mit einem neuen Klon des GitHub-Repos vorhanden ... es handelt sich also nur um ein lokales Repo-Problem. Aber ich habe zusätzliche Dateien, die ich löschen möchte, daher ist das Skript, auf das Sie oben verwiesen haben, großartig - danke!
kkrugler
19

git gc --prune=nowoder niedriges Niveau git prune --expire now.

Jakub Narębski
quelle
12

Jedes Mal, wenn sich Ihr KOPF bewegt, verfolgt Git dies in der reflog. Wenn Sie Commits entfernt haben, haben Sie immer noch "baumelnde Commits", da diese noch reflogca. 30 Tage lang von den referenziert werden . Dies ist das Sicherheitsnetz, wenn Sie Commits versehentlich löschen.

Sie können den git reflogBefehl zum Entfernen bestimmter Commits, zum Umpacken usw. oder nur den Befehl auf hoher Ebene verwenden:

git gc --prune=now
vdboor
quelle
5

Sie können verwenden git forget-blob.

Die Verwendung ist ziemlich einfach git forget-blob file-to-forget. Weitere Informationen erhalten Sie hier

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

Es verschwindet aus allen Commits in Ihrem Verlauf, Reflog, Tags und so weiter

Hin und wieder stoße ich auf das gleiche Problem, und jedes Mal, wenn ich auf diesen und andere Beiträge zurückkommen muss, habe ich den Prozess automatisiert.

Credits für Mitwirkende wie Sam Watkins

Nachoparker
quelle
2

Versuchen Sie, git-filter-branch zu verwenden - es werden keine großen Blobs entfernt, aber es können große Dateien, die Sie angeben, aus dem gesamten Repo entfernt werden. Für mich reduziert es die Repo-Größe von Hunderten MB auf 12 MB.

W55tKQbuRu28Q4xv
quelle
6
Nun , dass ist ein unheimlich Befehl :) Ich werde es geben , haben einen Versuch , wenn meine git-fu fühlt sich stärker.
kkrugler
können Sie laut sagen. Ich bin immer vorsichtig bei Befehlen, die den Verlauf eines Repositorys manipulieren. Dinge neigen dazu, sehr schief zu gehen, wenn mehrere Personen aus diesem Repository schieben und ziehen und plötzlich eine Reihe von Objekten, die Git erwartet, nicht da sind.
Jonathan Dumaine
1

Manchmal ist der Grund, warum "gc" nicht viel bringt, dass es eine unvollendete Rebase oder einen unvollendeten Stash gibt, der auf einem alten Commit basiert.

StellarVortex
quelle
Oder das alte Commit wird von HEAD, ORIG_HEAD, FETCH_HEAD, Reflog oder einer anderen Sache referenziert, die git automatisch weiter versucht, um sicherzustellen, dass es nie etwas Wertvolles verliert. Wenn Sie wirklich alle diese verlieren wollen, müssen Sie die Extrameile gehen, um dies zu tun.
Mikko Rantalainen
1

Um eine weitere Spitze hinzuzufügen, vergessen Sie nicht verwenden git remote Prune die veralteten Zweige Ihrer Fernbedienungen zu löschen , bevor Sie git gc

Sie können sie mit Git Branch -a sehen

Es ist oft nützlich, wenn Sie von Github- und Forked-Repositories abrufen ...

Tanguy
quelle
1

Bevor Sie git filter-branchund tun git gc, sollten Sie Tags überprüfen, die in Ihrem Repo vorhanden sind. Jedes echte System, das über automatische Tagging-Funktionen für die kontinuierliche Integration und Bereitstellung verfügt, führt dazu, dass unerwünschte Objekte immer noch von diesen Tags referenziert werden. Daher gckönnen sie nicht entfernt werden, und Sie werden sich immer wieder fragen, warum das Repo immer noch so groß ist.

Der beste Weg, um alle unerwünschten Dinge loszuwerden, ist zu rennen git-filter und git gcdann schiebt Master auf einen neuen nackten Repo. Das neue nackte Repo wird den aufgeräumten Baum haben.

v_abhi_v
quelle