it-swarm.com.de

Wie entferne / lösche ich eine große Datei aus dem Commit-Verlauf im Git-Repository?

Gelegentlich habe ich einen DVD-Rip in ein Website-Projekt gesteckt, dann achtlos git commit -a -m ..., und zap, der Repo war um 2,2 Gigs aufgebläht. Das nächste Mal habe ich einige Änderungen vorgenommen, die Videodatei gelöscht und alles festgeschrieben, aber die komprimierte Datei befindet sich im Verlauf noch im Repository.

Ich weiß, dass ich von diesen Commits Zweige starten und einen Zweig auf einen anderen verschieben kann. Aber was soll ich tun, um die beiden Commits zusammenzuführen, damit die große Datei nicht im Verlauf angezeigt wird und beim Garbage Collection-Verfahren bereinigt wird?

619
culebrón

Verwenden Sie BFG Repo-Cleaner , eine einfachere und schnellere Alternative zu git-filter-branch, die speziell zum Entfernen unerwünschter Dateien aus dem Git-Verlauf entwickelt wurde.

Befolgen Sie sorgfältig die Gebrauchsanweisung , der Kernteil ist genau dies:

$ Java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

Alle Dateien mit einer Größe von über 100 MB (die nicht in Ihrem letzten Commit enthalten sind) werden aus dem Verlauf Ihres Git-Repositorys entfernt. Sie können dann git gc verwenden, um die toten Daten zu entfernen:

$ git gc --Prune=now --aggressive

Die BFG ist in der Regel mindestens 10-50x schneller als die Ausführung von git-filter-branch und im Allgemeinen einfacher zu verwenden.

Vollständige Offenlegung: Ich bin der Autor des BFG Repo-Cleaners.

529
Roberto Tyley

Was Sie tun möchten, ist äußerst störend, wenn Sie den Verlauf für andere Entwickler veröffentlicht haben. Informationen zu den erforderlichen Schritten nach dem Reparieren Ihres Verlaufs finden Sie unter „Wiederherstellen nach einem Upstream-Neustart“ in der _git rebase_-Dokumentation .

Sie haben mindestens zwei Möglichkeiten: _git filter-branch_ und eine interaktive Rebase, die beide im Folgenden erläutert werden.

Verwenden von _git filter-branch_

Ich hatte ein ähnliches Problem mit umfangreichen binären Testdaten aus einem Subversion-Import und schrieb über Entfernen von Daten aus einem Git-Repository .

Sagen Sie, Ihre Git-Geschichte ist:

_$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html
_

Beachten Sie, dass git lola ein nicht standardmäßiger, aber sehr nützlicher Alias ​​ist. Mit dem Schalter _--name-status_ können wir Baumänderungen anzeigen, die mit jedem Commit verknüpft sind.

In der Festschreibung "Unvorsichtig" (deren SHA1-Objektname ce36c98 ist) ist die Datei _oops.iso_ die DVD-RIP, die versehentlich hinzugefügt und bei der nächsten Festschreibung entfernt wurde, cb14efd. Unter Verwendung der im vorgenannten Blog-Beitrag beschriebenen Technik lautet der auszuführende Befehl:

_git filter-branch --Prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all
_

Optionen:

  • _--Prune-empty_ entfernt Commits, die infolge der Filteroperation leer werden (, d. H. , den Baum nicht ändern). Im typischen Fall wird mit dieser Option ein übersichtlicherer Verlauf erstellt.
  • _-d_ bezeichnet ein temporäres Verzeichnis, das noch nicht zum Erstellen des gefilterten Verlaufs verwendet werden kann. Wenn Sie eine moderne Linux-Distribution verwenden, führt die Angabe eines Baum in _/dev/shm_ zu einer schnelleren Ausführung .
  • _--index-filter_ ist das Hauptereignis und wird bei jedem Schritt im Verlauf gegen den Index ausgeführt. Sie möchten _oops.iso_ überall dort entfernen, wo es gefunden wird, aber es ist nicht in allen Commits vorhanden. Der Befehl _git rm --cached -f --ignore-unmatch oops.iso_ löscht den DVD-Rip, wenn er vorhanden ist, und schlägt andernfalls nicht fehl.
  • _--tag-name-filter_ beschreibt, wie Tag-Namen neu geschrieben werden. Ein Filter von cat ist die Identitätsoperation. Ihr Repository hat, wie im obigen Beispiel, möglicherweise keine Tags, aber ich habe diese Option aus Gründen der Allgemeinheit hinzugefügt.
  • _--_ gibt das Ende der Optionen für _git filter-branch_ an.
  • _--all_ gefolgt von _--_ ist eine Abkürzung für alle Verweise. Ihr Repository hat, wie im obigen Beispiel, möglicherweise nur eine Referenz (Master), aber ich habe diese Option aus Gründen der Allgemeinheit eingeschlossen.

Nach einigem Hin und Her ist die Geschichte nun:

_$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/
|   A   oops.iso
|   A   other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html
_

Beachten Sie, dass das neue Commit "Unvorsichtig" nur _other.html_ hinzufügt und dass sich das Commit "DVD-Rip entfernen" nicht mehr im Master-Zweig befindet. Der Zweig mit der Bezeichnung _refs/original/refs/heads/master_ enthält Ihre ursprünglichen Commits, falls Sie einen Fehler gemacht haben. Befolgen Sie zum Entfernen die Schritte in „Checkliste zum Verkleinern eines Repositorys“

_$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --Prune=now
_

Für eine einfachere Alternative klonen Sie das Repository, um die unerwünschten Bits zu verwerfen.

_$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo
_

Wenn Sie eine _file:///..._-Klon-URL verwenden, werden Objekte kopiert, anstatt nur Hardlinks zu erstellen.

Jetzt ist deine Geschichte:

_$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html
_

Die SHA1-Objektnamen für die ersten beiden Festschreibungen („Index“ und „Admin-Seite“) blieben unverändert, da diese Festschreibungen durch den Filtervorgang nicht geändert wurden. "Careless" lost _oops.iso_ und "Login page" haben ein neues Elternteil, daher haben sich ihre SHA1s geändert .

Interaktive Basis

Mit einer Geschichte von:

_$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html
_

sie möchten _oops.iso_ aus "Careless" entfernen, als hätten Sie es nie hinzugefügt, und dann ist "Remove DVD-rip" für Sie nutzlos. Aus diesem Grund planen wir, in einer interaktiven Datenbank die Seite "Admin" zu behalten, "Unvorsichtig" zu bearbeiten und "DVD-Rip entfernen" zu verwerfen.

Wenn Sie _$ git rebase -i 5af4522_ ausführen, wird ein Editor mit den folgenden Inhalten gestartet.

_pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using Shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
_

Wir führen unseren Plan aus und ändern ihn auf

_edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...
_

Das heißt, wir löschen die Zeile mit „DVD-Rip entfernen“ und ändern den Vorgang für „Unvorsichtig“ so, dass er edit und nicht pick ist.

Durch das Beenden des Editors mit Speichern werden wir an einer Eingabeaufforderung mit der folgenden Meldung abgesetzt.

_Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue
_

Wie die Meldung besagt, befinden wir uns in dem zu bearbeitenden Commit "Unvorsichtig". Daher führen wir zwei Befehle aus.

_$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue
_

Die erste entfernt die fehlerhafte Datei aus dem Index. Der zweite modifiziert oder ändert "Careless", um der aktualisierte Index zu sein, und _-C HEAD_ weist git an, die alte Festschreibungsnachricht erneut zu verwenden. Schließlich setzt _git rebase --continue_ den Rest der Rebase-Operation fort.

Dies gibt eine Geschichte von:

_$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html
_

welches ist was du willst.

521
Greg Bacon

Warum nicht diesen einfachen, aber leistungsstarken Befehl verwenden?

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

Die Option --tree-filter führt den angegebenen Befehl nach jedem Auschecken des Projekts aus und schreibt die Ergebnisse erneut fest. In diesem Fall entfernen Sie eine Datei namens DVD-Rip aus jedem Schnappschuss, unabhängig davon, ob er vorhanden ist oder nicht.

Siehe dieser Link .

158
Gary Gauh

(Die beste Antwort, die ich auf dieses Problem erhalten habe, ist: https://stackoverflow.com/a/42544963/714112 , hier kopiert, da dieser Thread in den Google-Suchrankings hoch erscheint, der andere jedoch nicht 't)

???? Ein superschneller Shell-Einzeiler ????

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

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

Das Basisskript

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Wenn Sie den obigen Code ausführen, erhalten Sie Nice 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

???? Schnelle Dateientfernung ????

Angenommen, Sie möchten die Dateien a und b von jedem Commit entfernen, das von HEAD aus erreichbar ist. Sie können diesen Befehl verwenden:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD
64

Diese Befehle haben in meinem Fall funktioniert:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --Prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --Prune=now
git gc --aggressive --Prune=now

Es unterscheidet sich kaum von den obigen Versionen.

Für diejenigen, die dies auf Github/Bitbucket pushen müssen (ich habe dies nur mit Bitbucket getestet):

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git Push --all --Prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work
36
Kostanos

Nachdem ich praktisch jede Antwort in SO ausprobiert hatte, fand ich endlich dieses Juwel, das die großen Dateien in meinem Repository schnell entfernte und löschte und es mir ermöglichte, erneut zu synchronisieren: http://www.zyxware.com/articles/4027/how -zu-löschen-Dateien-permanent-von-Ihrem-lokalen-und-Remote-Git-Repository

CD in Ihren lokalen Arbeitsordner und führen Sie den folgenden Befehl aus:

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

ersetzen Sie FOLDERNAME durch die Datei oder den Ordner, die bzw. den Sie aus dem angegebenen Git-Repository entfernen möchten.

Führen Sie anschließend die folgenden Befehle aus, um das lokale Repository zu bereinigen:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --Prune=now
git gc --aggressive --Prune=now

Übertragen Sie nun alle Änderungen in das Remote-Repository:

git Push --all --force

Dadurch wird das Remote-Repository bereinigt.

35
Justin

Beachten Sie nur, dass diese Befehle sehr zerstörerisch sein können. Wenn mehr Leute am Repo arbeiten, müssen sie alle den neuen Baum ziehen. Die drei mittleren Befehle sind nicht erforderlich, wenn Sie NICHT die Größe reduzieren möchten. Denn der Filterzweig erstellt ein Backup der entfernten Datei und kann dort für lange Zeit bleiben.

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --Prune
$ git Push Origin master --force
9
mkljun

git filter-branch --tree-filter 'rm -f path/to/file' HEAD funktionierte ziemlich gut für mich, obwohl ich auf dasselbe Problem stieß wie beschrieben hier , das ich löste, indem ich diesem Vorschlag folgte.

Das Pro-Git-Buch enthält ein ganzes Kapitel über mschreiben des Verlaufs - Sehen Sie sich den Abschnitt filter-branch/Entfernen einer Datei aus jedem Commit an.

9
Thorsten Lorenz

Wenn Sie wissen, dass Ihr Commit vor Kurzem ausgeführt wurde, anstatt den gesamten Baum zu durchsuchen, gehen Sie wie folgt vor: git filter-branch --tree-filter 'rm LARGE_FILE.Zip' HEAD~10..HEAD

8
Soheil

Ich bin mit einem Bitbucket-Konto darauf gestoßen, in dem ich versehentlich gigantische * .jpa-Sicherungen meiner Site gespeichert hatte.

git filter-branch --Prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

Verschieben Sie MY-BIG-DIRECTORY in den betreffenden Ordner, um den Verlauf ( einschließlich Tags ) vollständig neu zu schreiben.

quelle: http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

6
lfender6445

Ich habe im Grunde genommen das gemacht, was auf dieser Antwort stand: https://stackoverflow.com/a/11032521/128642

(für die Geschichte, ich werde es hier kopieren und einfügen)

$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --Prune
$ git Push Origin master --force

Es hat nicht funktioniert, weil ich Dinge sehr gerne umbenenne und verschiebe. Es befanden sich also einige große Dateien in Ordnern, die umbenannt wurden, und ich glaube, der GC konnte den Verweis auf diese Dateien nicht löschen, da in tree Objekten Verweise auf diese Datei vorhanden waren. Meine ultimative Lösung, um es wirklich zu töten, war:

# First, apply what's in the answer linked in the front
# and before doing the gc --Prune --aggressive, do:

# Go back at the Origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --Prune --aggressive

Mein Repo (der .git) hat sich von 32MB auf 388KB geändert, was selbst der Filterzweig nicht sauber machen konnte.

3
Dolanor

Sie können dies mit dem Befehl branch filter tun:

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD

3
John Foley

git filter-branch ist ein leistungsstarker Befehl, mit dem Sie eine große Datei aus dem Commit-Verlauf löschen können. Die Datei bleibt eine Weile bestehen und Git entfernt sie in der nächsten Garbage Collection. Unten finden Sie den vollständigen Vorgang von Löschen von Dateien aus dem Festschreibungsverlauf . Aus Sicherheitsgründen wird der Befehl zuerst in einem neuen Zweig ausgeführt:

# Do it in a new testing branch
$ git checkout -b test

# Remove file-name from every commit on the new branch
# --index-filter, rewrite index without checking out
# --cached, remove it from index but not include working tree
# --ignore-unmatch, ignore if files to be removed are absent in a commit
# HEAD, execute the specified command for each commit reached from HEAD by parent link

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch file-name' HEAD

# The output is OK, reset it to the prior branch master
$ git checkout master
$ git reset --soft test

# Remove test branch
$ git branch -rm test

# Push it with force
$ git Push --force Origin master
2
zhangyu12

Verwenden Sie Git Extensions , es ist ein UI-Tool. Es hat ein Plugin mit dem Namen "Find large files", das große Dateien in Repositories findet und deren permanente Entfernung ermöglicht.

Verwenden Sie 'Git Filter-Branch' nicht, bevor Sie dieses Tool verwenden, da es nicht in der Lage ist, Dateien zu finden, die mit 'Filter-Branch' entfernt wurden (obwohl 'Filter-Branch' Dateien nicht vollständig aus den Repository-Pack-Dateien entfernt). .

1
Nir

Wenn Sie auf dieses Problem stoßen, wird git rm nicht ausreichen, da sich git daran erinnert, dass die Datei einmal in unserem Verlauf existiert hat, und daher einen Verweis darauf behält.

Um es noch schlimmer zu machen, ist das Umbasieren auch nicht einfach, da Verweise auf den Blob verhindern, dass der GIT-Müllsammler den Raum aufräumt. Dies umfasst Remote-Referenzen und Reflog-Referenzen.

Ich habe git forget-blob zusammengestellt, ein kleines Skript, das versucht, alle diese Verweise zu entfernen, und dann den Git-Filter-Zweig verwendet, um jedes Commit im Zweig neu zu schreiben.

Sobald Ihr Blob nicht mehr referenziert ist, wird git gc ihn entfernen

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/

Das habe ich dank der Antworten von Stack Overflow und einiger Blog-Einträge zusammengestellt. Kredite an sie!

1
nachoparker