IT-Swarm.Net

Cum să eliminați / să ștergeți un fișier mare din istoricul de comitere din depozitul Git?

Ocazional am aruncat un DVD-rip într-un proiect de site-ul web, apoi git commit -a -m ... și, zap, repo-ul a fost umflat de 2,2 concerte. Data viitoare am făcut câteva modificări, am șters fișierul video și am angajat totul, dar fișierul comprimat este încă acolo în depozit, în istorie.

Știu că pot să pornesc sucursale de la aceste angajamente și să redobândesc o ramură pe alta. Dar ce ar trebui să fac pentru a îmbina cele 2 angajamente, astfel încât fișierul cel mare să nu apară în istoric și să fie curățate în procedura de colectare a gunoiului?

590
culebrón

Folosiți BFG Repo-Cleaner , o alternativă mai simplă și mai rapidă la git-filter-branch special conceput pentru eliminarea fișierelor nedorite din istoricul Git.

Urmați cu atenție instrucțiuni de utilizare , partea principală este tocmai aceasta:

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

Orice fișiere cu o dimensiune de peste 100 MB (care nu sunt cele mai recente cele mai recente angajate) vor fi eliminate din istoricul depozitului Git. Puteți utiliza apoi git gc pentru a curăța datele moarte:

$ git gc --Prune=now --aggressive

BFG este de obicei cel puțin 10-50x mai rapid decât rularea git-filter-branch și, în general, mai ușor de utilizat.

Dezvăluire completă: Sunt autorul BFG Repo-Cleaner.

505
Roberto Tyley

Ce vrei să faci este extrem de perturbator dacă ai publicat istoric altor dezvoltatori. Consultați „Recuperarea din rambursare în amonte” din documentația git rebase pentru pașii necesari după repararea istoricului.

Aveți cel puțin două opțiuni: git filter-branch și un rebase interactiv, ambele explicate mai jos.

Folosind git filter-branch

Am avut o problemă similară cu datele de testare binare voluminoase voluminoase dintr-un import Subversion și am scris despre eliminarea datelor dintr-un depozit git .

Spuneți că istoricul dvs. este:

$ 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

Rețineți că git lola este un alias non-standard, dar extrem de util. Cu comutatorul --name-status, putem vedea modificări de arbori asociate fiecărui angajament.

În angajamentul „Careless” (al cărui nume de obiect SHA1 este ce36c98) fișierul oops.iso este DVD-rip-ul adăugat din greșeală și eliminat în următorul angajament, cb14efd. Folosind tehnica descrisă în postarea de blog menționată mai sus, comanda de executat este:

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

Opțiuni:

  • --Prune-empty elimină comitele care devin goale ( adică , nu schimbați arborele) ca urmare a operației filtrului. În cazul obișnuit, această opțiune produce un istoric mai curat.
  • -d numește un director temporar care nu există încă pentru a fi folosit pentru construirea istoricului filtrat. Dacă executați o distribuție Linux modernă, specificați un arborele din /dev/shm va duce la o execuție mai rapidă .
  • --index-filter este evenimentul principal și se execută cu indexul la fiecare pas din istoric. Doriți să eliminați oops.iso oriunde s-ar găsi, dar nu este prezent în toate angajamentele. Comanda git rm --cached -f --ignore-unmatch oops.iso șterge DVD-rip-ul atunci când este prezent și nu reușește altfel.
  • --tag-name-filter descrie cum să rescrieți numele etichetelor. Un filtru de cat este operația de identitate. Depozitul dvs., cum ar fi exemplul de mai sus, este posibil să nu aibă etichete, dar am inclus această opțiune pentru o generalitate deplină.
  • -- specifică finalul opțiunilor pentru git filter-branch
  • --all urmează -- este shorthand pentru toate avizele. Depozitul dvs., ca și exemplul de mai sus, poate avea un singur ref (master), dar am inclus această opțiune pentru o generalitate deplină.

După câteva prăbușiri, istoria este acum:

$ 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

Observați că noul angajament „Careless” adaugă doar other.html și că angajamentul „Remove DVD-rip” nu mai este în filiera principală. Sucursala etichetată refs/original/refs/heads/master conține angajamentele originale în cazul în care ați făcut o greșeală. Pentru ao elimina, urmați pașii din „Lista de verificare pentru reducerea unui depozit.”

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

Pentru o alternativă mai simplă, clonați depozitul pentru a renunța la biți nedoriti.

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

Folosind o adresă URL a unei clone file:///... copiază obiectele, mai degrabă decât crearea de link-uri hard.

Acum istoria dvs. este:

$ 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

Numele obiectului SHA1 pentru primele două comiteri („Index” și „Pagina de administrare”) au rămas aceleași, deoarece operațiunea de filtrare nu a modificat acele comiteri. „Careless” a pierdut oops.iso și „Pagina de conectare” au primit un nou părinte, astfel SHA1-urile lor s-au modificat .

Redresare interactivă

Cu un istoric de:

$ 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

doriți să eliminați oops.iso din „Careless”, de parcă nu l-ați adăugat niciodată, iar „Remove DVD-rip” nu vă este inutil. Astfel, planul nostru de a intra într-o rambursare interactivă este să păstrăm „pagina de administrare”, să edităm „Careless” și să eliminăm „Remove DVD-rip”.

Rularea $ git rebase -i 5af4522 pornește un editor cu următorul conținut.

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.
#

Executând planul nostru, îl modificăm la

edit ce36c98 Careless
pick f772d66 Login page

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

Adică ștergem linia cu „Eliminați DVD-rip” și schimbăm operația pe „Careless” pentru a fi edit, mai degrabă decât pick.

Salvarea-renunțarea la redactor ne aruncă la o comandă Prompt cu următorul mesaj.

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

După cum ne spune mesajul, suntem pe angajamentul „Careless” pe care dorim să-l edităm, deci executăm două comenzi.

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

Primul elimină fișierul care comite infracțiuni. Al doilea modifică sau modifică „Careless” pentru a fi indexul actualizat și -C HEAD instruiește git să refolosească vechiul mesaj de angajare. În cele din urmă, git rebase --continue merge înainte cu restul operațiunii de rambursare.

Acest lucru oferă o istorie a:

$ 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

ceea ce vrei tu.

510
Greg Bacon

De ce să nu folosiți această comandă simplă, dar puternică?

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

Opțiunea --tree-filter rulează comanda specificată după fiecare finalizare a proiectului și apoi recomandă rezultatele. În acest caz, eliminați un fișier numit DVD-rip din fiecare instantaneu, indiferent dacă există sau nu.

A se vedea acest link .

148
Gary Gauh

(Cel mai bun răspuns pe care l-am văzut la această problemă este: https://stackoverflow.com/a/42544963/714112 , copiat aici, deoarece acest thread apare în topul căutărilor Google, dar celălalt nu face acest lucru. „t)

???? Un fulger rapid Shell one-liner ????

Acest script Shell afișează toate obiectele blob din depozit, sortate de la cel mai mic la cel mai mare.

Pentru repoziția mea de probă, a fost de aproximativ de 100 de ori mai rapid decât celelalte găsite aici.
Pe sistemul meu de încredere Athlon II X4, acesta gestionează depozitul Kernel Linux cu cele 5.622.155 obiecte din în doar un minut .

Scriptul de bază

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

Când executați deasupra codului, veți obține o ieșire ușoară care poate fi citită de om astfel:

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

???? Eliminarea rapidă a fișierelor ????

Să presupunem că apoi doriți să eliminați fișierele a și b din fiecare angajare accesibilă din HEAD, puteți utiliza această comandă:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD
53
Sridhar-Sarnobat

Aceste comenzi au funcționat în cazul meu:

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

Este puțin diferit de versiunile de mai sus.

Pentru cei care au nevoie să împingă asta pentru github/bitbucket (am testat acest lucru doar cu bitbucket):

# 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
33
Kostanos

După ce am încercat practic fiecare răspuns din SO, am găsit în cele din urmă această bijuterie care a eliminat și șters rapid fișierele mari din depozitul meu și mi-a permis să sincronizez din nou: http://www.zyxware.com/articles/4027/how -sa-șterge-files-permanent-la-remote-GIT-depozitele dvs.-locale-și

CD în folderul local de lucru și executați următoarea comandă:

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

înlocuiți FOLDERNAME cu fișierul sau folderul pe care doriți să îl eliminați din depozitul git dat.

După ce faceți acest lucru, executați următoarele comenzi pentru a curăța depozitul local:

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

Acum împingeți toate modificările din depozitul de la distanță:

git Push --all --force

Aceasta va curăța depozitul la distanță.

28
Justin

Rețineți că aceste comenzi pot fi foarte distructive. Dacă mai mulți oameni lucrează la repo, va trebui să tragă noul copac. Cele trei comenzi de mijloc nu sunt necesare dacă obiectivul dvs. NU este de a reduce dimensiunea. Deoarece ramura de filtru creează o copie de rezervă a fișierului eliminat și poate rămâne acolo mult timp.

$ 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 a funcționat destul de bine pentru mine, deși am întâmpinat aceeași problemă descrisă aici , pe care am rezolvat-o urmând această sugestie .

Cartea pro-git are un întreg capitol despre istoricul rescrierii - aruncați o privire la filter-branch/Eliminarea unui fișier din fiecare comitere .

9
Thorsten Lorenz

Dacă știți că angajamentul dvs. a fost recent în loc să parcurgeți întregul arbore, faceți următoarele: git filter-branch --tree-filter 'rm LARGE_FILE.Zip' HEAD~10..HEAD

8
Soheil

M-am confruntat cu un cont de bitbucket, unde am stocat accidental backup-uri ginormous * .jpa de pe site-ul meu.

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

Relacionați MY-BIG-DIRECTORY cu folderul în cauză pentru a rescrie complet istoricul (, inclusiv etichetele ).

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

5
lfender6445

Practic am făcut ceea ce a fost la acest răspuns: https://stackoverflow.com/a/11032521/128642

(pentru istorie, o voi copia-lipi aici)

$ 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

Nu a funcționat, pentru că îmi place să redenumesc și să mut lucrurile foarte mult. Deci, unele fișiere mari au fost în foldere care au fost redenumite și cred că gc nu a putut șterge referința la acele fișiere din cauza referinței în obiectele tree care indică acel fișier. Soluția mea finală pentru a o ucide cu adevărat a fost să:

# 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

Repoziția mea (.git) s-a schimbat de la 32MB la 388KB, pe care nici filiala de filtru nu le putea curăța.

3
Dolanor

Puteți face acest lucru folosind comanda branch filter:

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

3
John Foley

git filter-branch este o comandă puternică pe care o puteți utiliza pentru a șterge un fișier imens din istoricul de comiteri. Fișierul va rămâne un timp și Git îl va elimina în următoarea colectare a gunoiului. Mai jos este procesul complet de la ștergerea fișierelor din istoricul de angajare . Pentru siguranță, mai întâi rulați comanda pe o nouă ramură:

# 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
1
zhangyu12

Folosiți Git Extensions , este un instrument UI. Are un plugin numit „Găsiți fișiere mari”, care găsește fișierele eliberate în depozite și permite eliminarea lor în mod constant.

Nu folosiți „git filter-branch” înainte de a utiliza acest instrument, deoarece nu va putea găsi fișierele eliminate de „filter-branch” (Altough „filter-branch” nu elimină complet fișierele din fișierele pachetului de depozitare) .

1
Nir

Când vă confruntați cu această problemă, git rm nu va fi suficient, deoarece git își amintește că fișierul a existat o singură dată în istoria noastră și va păstra o referință la acesta.

Pentru a înrăutăți lucrurile, rambursarea nu este niciodată ușoară, deoarece orice trimitere la blob va împiedica colectorul de gunoi de la git să curețe spațiul. Aceasta include referințe la distanță și referințe reflog.

Am creat git forget-blob, un mic script care încearcă să înlăture toate aceste referințe și apoi folosește git filter-branch pentru a rescrie fiecare angajare din ramură.

Odată ce blob-ul dvs. este complet nereferențiat, git gc va scăpa de el

Utilizarea este destul de simplă git forget-blob file-to-forget. Puteți obține mai multe informații aici

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

Am pus acest lucru împreună datorită răspunsurilor Stack Overflow și a unor intrări pe blog. Credite pentru ei!

1
nachoparker