Usuń folder i jego zawartość z historii git / GitHub

318

Pracowałem nad repozytorium na moim koncie GitHub i na ten problem natknąłem się.

  • Projekt Node.js z folderem z zainstalowanymi kilkoma pakietami npm
  • Paczki były w node_modulesfolderze
  • Dodałem ten folder do repozytorium git i przekazałem kod do github (nie myślałem wtedy o części npm)
  • Uświadomiłem sobie, że tak naprawdę nie potrzebujesz, aby ten folder był częścią kodu
  • Usunąłem ten folder, wypchnąłem go

W tym przypadku całkowity rozmiar repozytorium git wynosił około 6 MB, a rzeczywisty kod (wszystkie oprócz tego folderu) miał tylko około 300 KB .

Teraz w końcu szukam sposobu, aby pozbyć się szczegółów tego folderu pakietu z historii gita, więc jeśli ktoś go sklonuje, nie będzie musiał pobierać historii o wartości 6 MB, z której będą pobierać jedyne rzeczywiste pliki od ostatniego zatwierdzenia wynosiłby 300 KB.

Szukałem możliwych rozwiązań tego problemu i wypróbowałem te 2 metody

Wydawało się, że Gist działał, gdy po uruchomieniu skryptu pokazał, że pozbył się tego folderu, a następnie zmodyfikował 50 różnych zatwierdzeń. Ale nie pozwoliło mi to przepchnąć tego kodu. Kiedy próbowałem to przeforsować, napisałem, Branch up to dateale pokazałem, że 50 zatwierdzeń zostało zmodyfikowanych po git status. Pozostałe 2 metody też nie pomogły.

Teraz, mimo że pokazało, że pozbył się historii tego folderu, kiedy sprawdziłem rozmiar tego repozytorium na moim lokalnym hoście, nadal miał około 6 MB. (Ja również usunąłem refs/originalfolder, ale nie zauważyłem zmiany rozmiaru repozytorium).

Chciałbym wyjaśnić, czy istnieje sposób, aby pozbyć się nie tylko historii zatwierdzeń (co wydaje mi się jedyną rzeczą, która się wydarzyła), ale także tych plików, które git ciągle zakłada, że ​​chce się wycofać.

Powiedzmy, że rozwiązanie zostało przedstawione w tym celu i jest zastosowane na moim lokalnym hoście, ale nie można go odtworzyć w tym repozytorium GitHub, czy można sklonować to repo, przywrócić do pierwszego zatwierdzenia wykonać lewę i wcisnąć (lub czy to oznacza, że ​​git będzie nadal masz historię tych wszystkich zmian? (inaczej 6 MB).

Moim ostatecznym celem jest po prostu znalezienie najlepszego sposobu na pozbycie się zawartości folderu z git, aby użytkownik nie musiał pobierać rzeczy o wartości 6 MB i nadal mógł mieć inne zatwierdzenia, które nigdy nie dotknęły folderu modułów (to całkiem ładnie wiele z nich) w historii gita.

W jaki sposób mogę to zrobić?

Kartik
źródło
3
Jeśli którakolwiek z poniższych odpowiedzi rozwiązała problem, być może powinieneś rozważyć przyjęcie jednej z nich jako odpowiedzi na swoje pytanie. meta.stackexchange.com/questions/5234/…
starbeamrainbowlabs
Najlepsza odpowiedź to: stackoverflow.com/a/32886427/5973334
Kuzeko

Odpowiedzi:

556

Jeśli jesteś tutaj, aby skopiować i wkleić kod:

To przykład, który usuwa node_modulesz historii

git filter-branch --tree-filter "rm -rf node_modules" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Co właściwie robi git:

Pierwszy wiersz iteruje wszystkie odniesienia w tym samym drzewie ( --tree-filter) co HEAD (twoja aktualna gałąź), uruchamiając polecenie rm -rf node_modules. To polecenie usuwa folder node_modules ( -rbez -r, rmnie usuwa folderów), bez monitu dla użytkownika ( -f). Dodane --prune-emptyusuwa bezużyteczne (nic nie zmieniając) zatwierdza rekurencyjnie.

Drugi wiersz usuwa odniesienie do tej starej gałęzi.

Pozostałe polecenia są stosunkowo proste.

Mohsen
źródło
3
Uwaga dodatkowa: zwykłem git count-objects -vsprawdzać, czy pliki zostały faktycznie usunięte, ale rozmiar repozytorium pozostaje taki sam, dopóki nie sklonuję repozytorium. Myślę, że Git przechowuje kopię wszystkich oryginalnych plików.
Davide Icardi,
4
W przypadku nie-starożytnego dupka to prawdopodobnie powinno brzmieć --force-with-lease, nie --force.
Griwes,
4
Żadne z tych poleceń nie działa w systemie Windows. Lub przynajmniej nie Windows 10, proszę opublikuj system operacyjny, na którym działa „wycinanie i wklejanie”
David
3
W przypadku użytkowników systemu Windows 10 działa to dobrze pod Bash dla Windows (użyłem Ubuntu)
Andrej Kyselica,
3
Próbowałem z powłoką systemu Windows i git bash i nie działałem. Pierwsze polecenie przechodzi, drugie polecenie się nie udaje!
Mohy Eldeen,
240

Uważam, że --tree-filteropcja używana w innych odpowiedziach może być bardzo wolna, szczególnie w przypadku większych repozytoriów z dużą ilością zatwierdzeń.

Oto metoda, której używam do całkowitego usunięcia katalogu z historii git przy użyciu --index-filteropcji, która działa znacznie szybciej:

# Make a fresh clone of YOUR_REPO
git clone YOUR_REPO
cd YOUR_REPO

# Create tracking branches of all branches
for remote in `git branch -r | grep -v /HEAD`; do git checkout --track $remote ; done

# Remove DIRECTORY_NAME from all commits, then remove the refs to the old commits
# (repeat these two commands for as many directories that you want to remove)
git filter-branch --index-filter 'git rm -rf --cached --ignore-unmatch DIRECTORY_NAME/' --prune-empty --tag-name-filter cat -- --all
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d

# Ensure all old refs are fully removed
rm -Rf .git/logs .git/refs/original

# Perform a garbage collection to remove commits with no refs
git gc --prune=all --aggressive

# Force push all branches to overwrite their history
# (use with caution!)
git push origin --all --force
git push origin --tags --force

Możesz sprawdzić rozmiar repozytorium przed i po gc:

git count-objects -vH
Lee Netherton
źródło
3
czy możesz wyjaśnić, dlaczego jest to znacznie szybsze?
knocte
7
@knocte: z dokumentacji ( git-scm.com/docs/git-filter-branch ). „--index-filter: ... jest podobny do filtra drzewa, ale nie sprawdza drzewa, co czyni go znacznie szybszym”
Lee Netherton
23
Dlaczego nie jest to akceptowana odpowiedź? To takie dokładne.
Szalony fizyk,
2
Jeśli robisz to w Windows, potrzebujesz podwójnych cudzysłowów zamiast pojedynczych cudzysłowów.
Kris Morness,
12
Przejście --quietdo git rmpowyższego przyspieszyło moje przepisanie przynajmniej 4 razy
ctusch
46

Oprócz popularnej powyższej odpowiedzi chciałbym dodać kilka uwag dotyczących systemów Windows . Komenda

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
  • działa idealnie bez żadnych modyfikacji! Dlatego nie wolno używać Remove-Item, delczy cokolwiek innego zamiast rm -rf.

  • Jeśli musisz podać ścieżkę do pliku lub katalogu, użyj ukośników, takich jak./path/to/node_modules

uczestnik
źródło
To nie będzie działać w systemie Windows, jeśli katalog zawiera. (kropka) w nazwie.
Corneliu Serediuc
4
I znalazłem rozwiązanie. Użyj podwójnych przecinków dla polecenia rm w następujący sposób: „rm -rf node.modules”.
Corneliu Serediuc
23

Najlepszą i najdokładniejszą metodą, jaką znalazłem, było pobranie pliku bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

Następnie uruchom polecenia:

git clone --bare https://project/repository project-repository
cd project-repository
java -jar bfg.jar --delete-folders DIRECTORY_NAME  # i.e. 'node_modules' in other examples
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --mirror https://project/new-repository

Jeśli chcesz usunąć pliki, użyj zamiast tego opcji delete-files:

java -jar bfg.jar --delete-files *.pyc
Kim T.
źródło
1
bardzo łatwe :) jeśli chcesz mieć pewność, że tylko określony folder zostanie usunięty, to pomoże: stackoverflow.com/questions/21142986/...
emjay
9

Wydaje się, że odpowiedź się na bieżąco do tego celu jest nie używać filter-branchbezpośrednio (przynajmniej git sama nie poleca go już), oraz przesunięcie że narzędzie do pracy zewnętrznej. W szczególności obecnie zalecane jest git-filter-repo . Autor tego narzędzia podaje argumenty, dlaczego filter-branchbezpośrednie użycie może prowadzić do problemów.

Większość wyżej wymienionych wielowierszowych skryptów do usunięcia dirz historii można zapisać ponownie:

git filter-repo --path dir --invert-paths

Najwyraźniej narzędzie jest potężniejsze. Możesz zastosować filtry według autora, adresu e-mail, zmiany nazwy i innych ( pełna strona podręcznika tutaj ). Ponadto jest szybki . Instalacja jest łatwa - jest dystrybuowana w różnych formatach .

André Anjos
źródło
Ładne narzędzie! Działa dobrze na Ubuntu 20.04, możesz to zrobić, pip3 install git-filter-repoponieważ jest on tylko stdlib i nie instaluje żadnych zależności. Na Ubuntu 18 jest niekompatybilny z wersją git distro Error: need a version of git whose diff-tree command has the --combined-all-paths option, ale łatwo jest go uruchomić nadocker run -ti ubuntu:20.04
kubańczyk
7

Kompletny przepis kopiuj i wklej, po prostu dodając polecenia w komentarzach (dla rozwiązania kopiuj-wklej), po ich przetestowaniu:

git filter-branch --tree-filter 'rm -rf node_modules' --prune-empty HEAD
echo node_modules/ >> .gitignore
git add .gitignore
git commit -m 'Removing node_modules from git history'
git gc
git push origin master --force

Następnie możesz usunąć wiersz „node_modules /” z .gitignore

jgbarah
źródło
Dlaczego byś następnie usunąć node_modulesz .gitignore? Aby mogły zostać ponownie przypadkowo popełnione?
Adamski
1
Nie jest usuwany z gitignore, jest dodawany do gitignore. Wiadomość zatwierdzenia mówi „historia gita”, a nie „gitignore” :)
Danny Tuppeny
ale komentarz mówi, że można następnie usunąć node_modulesz .gitignore.
zavr
7

W przypadku użytkownika systemu Windows należy pamiętać, aby użyć "zamiast ' Dodano również, -faby wymusić polecenie, jeśli istnieje już inna kopia zapasowa.

git filter-branch -f --tree-filter "rm -rf FOLDERNAME" --prune-empty HEAD
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
echo FOLDERNAME/ >> .gitignore
git add .gitignore
git commit -m "Removing FOLDERNAME from git history"
git gc
git push origin master --force
kcode
źródło
3

Usunąłem foldery bin i obj ze starych projektów w języku C # za pomocą git na Windowsie. Uważaj z

git filter-branch --tree-filter "rm -rf bin" --prune-empty HEAD

Niszczy integralność instalacji git, usuwając folder usr / bin w folderze instalacyjnym git.

LordObi
źródło