Jak usunąć / usunąć duży plik z historii zatwierdzeń w repozytorium Git?

Czasami wrzuciłem DVD-rip do projektu strony internetowej, potem niedbale git commit -a -m ..., i, zap, repo było rozdęte przez 2,2 GIGA. Następnym razem zrobiłem kilka zmian, usunąłem plik wideo i zatwierdziłem wszystko, ale skompresowany plik nadal jest w repozytorium, w historii.

Wiem, że mogę uruchamiać gałęzie z tych commitów i zamieniać jedną gałąź na inną. Ale co mam zrobic aby scalic razem 2 commity tak aby duzy plik nie pojawil sie w historii i zostal wyczyszczony w procedura zbierania śmieci?

Author: Whymarrh, 2010-01-20

14 answers

Użyj BFG Repo-Cleaner, prostszej i szybszej alternatywy dla git-filter-branch, zaprojektowanej specjalnie do usuwania niechcianych plików z historii Gita.

Uważnie postępuj zgodnie z instrukcjami użycia , podstawowa część jest taka:

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

Wszystkie pliki o rozmiarze powyżej 100MB (które nie znajdują się w Twoim najnowszym commicie) zostaną usunięte z historii Twojego repozytorium Git. Następnie możesz użyć git gc, aby wyczyścić martwe DANE:

$ git gc --prune=now --aggressive

BFG jest zazwyczaj co najmniej 10-50x szybszy niż bieganie git-filter-branch i ogólnie łatwiejszy w użyciu.

pełne ujawnienie: jestem autorem BFG Repo-Cleaner.

 435
Author: Roberto Tyley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 21:44:20

To, co chcesz zrobić, jest bardzo uciążliwe, jeśli opublikowałeś historię innym programistom. Zobacz "odzyskiwanie z Upstream Rebase" w git rebase dokumentacja dla niezbędnych kroków po naprawie historii.

Masz co najmniej dwie opcje: git filter-branch i interaktywną rebase, obie opisane poniżej.

Za pomocą git filter-branch

Miałem podobny problem z nieporęcznymi binarnymi danymi testowymi z importu Subversion i napisałem o usunięciu danych z Gita repozytorium .

Powiedz, że Twoja historia Gita to:

$ 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

Zauważ, że git lola jest niestandardowym, ale bardzo przydatnym aliasem. Za pomocą przełącznika --name-status możemy zobaczyć modyfikacje drzewa powiązane z każdym zatwierdzeniem.

W commicie "Careless" (którego obiekt SHA1 nazywa się ce36c98) plik oops.iso jest plikiem DVD-rip dodanym przypadkowo i usuniętym w następnym commicie, cb14efd. Stosując technikę opisaną we wspomnianym poście na blogu, polecenie do wykonania jest:

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

Opcje:

  • --prune-empty usuwa commity, które stają się puste (tzn., nie zmieniają drzewa) w wyniku działania filtra. W typowym przypadku ta opcja tworzy czystszą historię.
  • -d nazywa tymczasowy katalog, który jeszcze nie istnieje, aby użyć go do budowania filtrowanej historii. Jeśli używasz nowoczesnej dystrybucji Linuksa, podanie drzewa w /dev/shm spowoduje szybsze wykonanie.
  • --index-filter jest głównym wydarzeniem i biegnie przed indeksem na każdym kroku w historii. Chcesz usunąć oops.iso gdziekolwiek zostanie znaleziony, ale nie jest obecny we wszystkich commitach. Polecenie git rm --cached -f --ignore-unmatch oops.iso usuwa plik DVD-rip, gdy jest obecny i nie zawodzi w przeciwnym razie.
  • --tag-name-filter opisuje sposób przepisywania nazw znaczników. Filtr cat jest operacją tożsamości. Twoje repozytorium, podobnie jak przykład powyżej, może nie mieć żadnych tagów, ale włączyłem tę opcję dla pełnej ogólności.
  • -- określa koniec opcje do git filter-branch
  • --all po -- jest skrótem dla wszystkich refów. Twoje repozytorium, podobnie jak przykład powyżej, może mieć tylko jeden ref (master), ale włączyłem tę opcję dla pełnej ogólności.

Po pewnym ubijaniu, historia jest teraz:

$ 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

Zauważ, że nowy commit "Careless" dodaje tylko other.html i że commit "Remove DVD-rip" nie znajduje się już w gałęzi master. Gałąź oznaczona refs/original/refs/heads/master zawiera oryginalne commity na wypadek, gdybyś popełnił błąd. Na usuń go, wykonaj kroki z "listy kontrolnej zmniejszania repozytorium."

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

Aby uzyskać prostszą alternatywę, Sklonuj repozytorium, aby usunąć niechciane bity.

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

Użycie adresu URL file:///... do klonowania kopiuje obiekty, a nie tylko tworzy hardlinki.

Teraz twoja historia jest:

$ 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

Nazwy obiektów SHA1 dla pierwszych dwóch commitów ("Index "i" admin page") pozostały takie same, ponieważ operacja filtrowania nie zmodyfikowała tych commitów. "Careless" lost oops.iso i "Strona logowania" ma nowego rodzica, więc ich SHA1s zmieniły się.

Interactive rebase

Z historią:

$ 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

Chcesz usunąć oops.iso z "nieostrożnego", jakbyś nigdy go nie dodał, a następnie "usuń DVD-rip" jest bezużyteczny dla Ciebie. Tak więc nasz plan przechodzenia do interaktywnej rebazy polega na utrzymaniu "strony administratora", edycji " nieostrożnej "i odrzuceniu" Usuń DVD-rip."

Uruchamianie $ git rebase -i 5af4522 uruchamia edytor z następującym zawartość.

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

Realizując nasz plan, modyfikujemy go do

edit ce36c98 Careless
pick f772d66 Login page

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

Oznacza to, że usuwamy linię z "Remove DVD-rip " i zmieniamy operację na" Careless " na edit zamiast pick.

Save-zamknięcie edytora powoduje wyświetlenie wiersza poleceń z następującym komunikatem.

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

Jak mówi nam wiadomość, jesteśmy na" nieostrożnym " commicie, który chcemy edytować, więc uruchamiamy dwa polecenia.

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

Pierwszy usuwa plik z indeksu. Na druga modyfikuje lub zmienia "Careless" na zaktualizowany indeks i -C HEAD instruuje git, aby ponownie użył starego komunikatu commit. W końcu git rebase --continue kontynuuje resztę operacji rebase.

To daje historię:

$ 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
Tego właśnie chcesz.
 479
Author: Greg Bacon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-20 10:04:24

Dlaczego nie użyć tego prostego, ale potężnego polecenia?

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

Opcja --tree-filter uruchamia podaną komendę po każdej kasie projektu, a następnie wznawia wyniki. W takim przypadku usuwasz plik o nazwie DVD-rip z każdej migawki, niezależnie od tego, czy istnieje, czy nie.

Zobacz ten link .

 119
Author: Gary Gauh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 02:39:52

Te komendy zadziałały w moim przypadku:

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

Niewiele różni się od powyższych wersji.

Dla tych, którzy muszą wcisnąć to na github / bitbucket (testowałem to tylko z 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
 27
Author: Kostanos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-23 15:54:06

(najlepsza odpowiedź jaką widziałem na ten problem to: https://stackoverflow.com/a/42544963/714112 , skopiowany tutaj, ponieważ ten wątek pojawia się wysoko w rankingach wyszukiwarek Google, ale ten drugi nie)

Blazngly fast shell one-liner

Ten skrypt powłoki wyświetla wszystkie obiekty blob w repozytorium, posortowane od najmniejszego do największego.

Dla mojej próbki repo, to działało około 100 razy szybciej niż inne znalezione tutaj.
Na moim zaufanym Athlonie II X4 system obsługuje repozytorium jądra Linuksa z 5 622 155 obiektami w nieco ponad minutę.

Skrypt Bazowy

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

Gdy uruchomisz powyższy kod, otrzymasz ładne czytelne dla człowieka wyjście w następujący sposób:

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

Szybkie Usuwanie Plików

Załóżmy, że chcesz usunąć Pliki a i b z każdego commita osiągalnego z HEAD, możesz użyć tego polecenia:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD
 25
Author: Sridhar-Sarnobat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-07 00:37:00

Po wypróbowaniu praktycznie każdej odpowiedzi w SO, w końcu znalazłem ten klejnot, który szybko usunął i usunął duże pliki w moim repozytorium i pozwolił mi zsynchronizować ponownie: http://www.zyxware.com/articles/4027/how-to-delete-files-permanently-from-your-local-and-remote-git-repositories

CD do lokalnego folderu roboczego i uruchom następujące polecenie:

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

Zastąp FOLDERNAME plikiem lub folderem, który chcesz usunąć z podanego repozytorium git.

Once w tym celu uruchom następujące polecenia, aby wyczyścić lokalne repozytorium:

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

Teraz wypchnij wszystkie zmiany do zdalnego repozytorium:

git push --all --force

To wyczyści zdalne repozytorium.

 18
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-26 17:35:48

git filter-branch --tree-filter 'rm -f path/to/file' HEAD dla mnie zadziałało całkiem dobrze, chociaż napotkałem ten sam problem, co opisany TUTAJ , który rozwiązałem podążając za tą sugestią.

Książka pro-git zawiera cały rozdział o przepisywaniu historii - zajrzyj do filter-branch/Usuwanie pliku z każdej sekcji Commit .

 9
Author: Thorsten Lorenz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:57

Zauważ, że te polecenia mogą być bardzo destrukcyjne. Jeśli więcej ludzi pracuje nad repo, wszyscy będą musieli wyciągnąć nowe drzewo. Trzy środkowe polecenia nie są konieczne, jeśli twoim celem nie jest zmniejszenie rozmiaru. Ponieważ gałąź filtra tworzy kopię zapasową usuniętego pliku i może tam pozostać przez długi czas.

$ 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
 8
Author: mkljun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-16 15:41:05

Jeśli wiesz, że Twój commit został niedawno, zamiast przechodzić przez całe drzewo, wykonaj następujące czynności: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD

 7
Author: Soheil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-01 06:21:33

Natknąłem się na to z kontem bitbucket, na którym przypadkowo zapisałem gigantyczne*.kopie zapasowe jpa mojej strony.

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

Relpace MY-BIG-DIRECTORY z danym folderem, aby całkowicie przepisać historię ( wraz z tagami ).

Źródło: http://naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

 5
Author: lfender6445,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-03 12:54:26

Możesz to zrobić używając branch filter komendy:

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

 3
Author: John Foley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-05 23:28:30

Użyj Git Extensions , jest to narzędzie interfejsu użytkownika. Posiada wtyczkę o nazwie "Find large files", która znajduje pliki lage w repozytoriach i umożliwia ich trwałe usuwanie.

Nie używaj 'git filter-branch' przed użyciem tego narzędzia, ponieważ nie będzie ono w stanie znaleźć plików usuniętych przez 'filter-branch' (oprócz 'filter-branch' nie usuwa całkowicie plików z repozytorium pack files).

 1
Author: Nir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-31 13:22:11

Gdy napotkasz ten problem, git rm nie wystarczy, ponieważ git pamięta, że plik istniał raz w naszej historii i dlatego zachowa do niego odniesienie.

Co gorsza, rebasing również nie jest łatwy, ponieważ wszelkie odniesienia do obiektu blob zapobiegną czyszczeniu przestrzeni przez git garbage collector. Obejmuje to odwołania zdalne i odwołania reflog.

Stworzyłem git forget-blob mały skrypt, który próbuje usunąć wszystkie odnośniki, a następnie używa git filter-branch aby przepisać każdy commit w gałęzi.

Gdy twój blob będzie całkowicie niezrealizowany, git gc pozbędzie się go

Użycie jest dość proste git forget-blob file-to-forget. Więcej informacji znajdziesz tutaj

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

Poskładałem to do kupy dzięki odpowiedziom ze Stack Overflow i kilku wpisom na blogu. Podziękowania dla nich!

 1
Author: nachoparker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-23 12:21:46

W zasadzie zrobiłem to, co było na tej odpowiedzi: https://stackoverflow.com/a/11032521/1286423

(dla historii, skopiuję-wkleję tutaj)

$ 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

Nie zadziałało, ponieważ lubię zmieniać nazwy i przenosić rzeczy. Więc jakiś duży plik był w folderach, które zostały przemianowane i myślę, że gc nie mógł usunąć odniesienia do tych plików z powodu odniesienia w obiektach tree wskazujących na te pliki. Moim ostatecznym rozwiązaniem do naprawdę zabicia było:

# 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

My repo (the .git) zmieniono z 32MB na 388KB, że nawet filter-branch nie mógł wyczyścić.

 1
Author: Dolanor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-31 14:22:12