Wprowadzenie: masz 5 dostępnych rozwiązań
Oryginalny plakat stwierdza:
Przypadkowo wysłałem niechciany plik ... do mojego repozytorium kilka zmian temu ... Chcę całkowicie usunąć plik z historii repozytorium.
Czy możliwe jest przepisanie historii zmian tak, aby filename.orig
nigdy nie była dodawana do repozytorium?
Istnieje wiele różnych sposobów całkowitego usunięcia historii pliku z git:
- Zmienia zobowiązania.
- Twarde resety (ewentualnie plus rebase).
- Nieinteraktywny rebase.
- Interaktywne bazy danych.
- Filtrowanie oddziałów.
W przypadku oryginalnego plakatu, zmiana zatwierdzenia nie jest tak naprawdę opcją samą w sobie, ponieważ później dokonał kilku dodatkowych zatwierdzeń, ale dla kompletności wyjaśnię również, jak to zrobić, dla każdego, kto tylko chce zmienić poprzednie zatwierdzenie.
Zauważ, że wszystkie te rozwiązania wymagają zmiany / ponownego zapisywania historii / zatwierdzeń w inny sposób, więc każdy, kto ma stare kopie zatwierdzeń, będzie musiał wykonać dodatkową pracę, aby ponownie zsynchronizować swoją historię z nową historią.
Rozwiązanie 1: Zobowiązania korygujące
Jeśli przypadkowo dokonałeś zmiany (np. Dodając plik) w poprzednim zatwierdzeniu i nie chcesz, aby historia tej zmiany już istniała, możesz po prostu zmienić poprzednie zatwierdzenie, aby usunąć z niego plik:
git rm <file>
git commit --amend --no-edit
Rozwiązanie 2: Twardy reset (prawdopodobnie plus rease)
Podobnie jak w rozwiązaniu nr 1, jeśli chcesz po prostu pozbyć się poprzedniego zatwierdzenia, masz również opcję twardego resetu do jego rodzica:
git reset --hard HEAD^
Że komenda będzie trudno zresetować oddziału do poprzedniego 1 st rodzica popełnił.
Jeśli jednak , podobnie jak oryginalny plakat, dokonałeś kilku zatwierdzeń po zatwierdzeniu, w którym chcesz cofnąć zmianę, nadal możesz użyć twardego resetu, aby ją zmodyfikować, ale to również wymaga użycia bazy. Oto kroki, których możesz użyć, aby zmienić zatwierdzenie dalej w historii:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Rozwiązanie 3: Nieinteraktywne Rebase
Działa to, jeśli chcesz całkowicie usunąć zatwierdzenie z historii:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Rozwiązanie 4: Interaktywne zmiany
To rozwiązanie pozwoli ci osiągnąć te same rzeczy, co rozwiązania 2 i 3, tj. Zmodyfikować lub usunąć zatwierdzenia z powrotem w historii niż poprzednie wcześniejsze zatwierdzenie, więc to, które rozwiązanie wybierzesz, zależy od ciebie. Interaktywne rebazy nie nadają się do rebasingu setek commits ze względu na wydajność, dlatego w takich sytuacjach użyłbym nieinteraktywnych rebase'ów lub rozwiązania gałęzi filtrowania (patrz poniżej).
Aby rozpocząć interaktywną bazę, użyj następujących poleceń:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Spowoduje to, że git przewinie historię zatwierdzeń z powrotem do elementu nadrzędnego zatwierdzenia, które chcesz zmodyfikować lub usunąć. Następnie wyświetli listę zatwierdzonych przewinięć w odwrotnej kolejności w dowolnym ustawionym git edytorze (domyślnie jest to Vim):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
Zatwierdzenie, które chcesz zmodyfikować lub usunąć, będzie na górze tej listy. Aby go usunąć, po prostu usuń jego linię z listy. W przeciwnym wypadku, należy wymienić „pick” z „edit” na 1 st linii, tak jak poniżej:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Następnie wprowadź git rebase --continue
. Jeśli zdecydujesz się całkowicie usunąć zatwierdzenie, to wszystko, co musisz zrobić (oprócz weryfikacji, zobacz ostatni krok tego rozwiązania). Jeśli z drugiej strony chcesz zmodyfikować zatwierdzenie, git ponownie zastosuje zatwierdzenie, a następnie zatrzyma zmianę bazy.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
W tym momencie możesz usunąć plik i zmienić zatwierdzenie, a następnie kontynuować rebase:
git rm <file>
git commit --amend --no-edit
git rebase --continue
Otóż to. Na koniec, niezależnie od tego, czy zmodyfikowałeś zatwierdzenie, czy całkowicie go usunąłeś, zawsze dobrym pomysłem jest sprawdzenie, czy nie dokonano żadnych innych nieoczekiwanych zmian w twojej gałęzi, różnicując go swoim stanem przed zmianą bazy:
git diff master@{1}
Rozwiązanie 5: Filtrowanie oddziałów
Wreszcie, to rozwiązanie jest najlepsze, jeśli chcesz całkowicie usunąć wszystkie ślady istnienia pliku z historii, a żadne z pozostałych rozwiązań nie spełnia tego zadania.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
To usunie <file>
wszystkie zatwierdzenia, poczynając od głównego zatwierdzenia. Jeśli zamiast tego chcesz po prostu przepisać zakres zatwierdzenia HEAD~5..HEAD
, możesz przekazać to jako dodatkowy argument filter-branch
, jak wskazano w
tej odpowiedzi :
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Ponownie, po filter-branch
zakończeniu, zwykle dobrym pomysłem jest sprawdzenie, czy nie ma innych nieoczekiwanych zmian, poprzez różnicowanie gałęzi do poprzedniego stanu przed operacją filtrowania:
git diff master@{1}
Alternatywa dla rozgałęzień: BFG Repo Cleaner
Słyszałem, że narzędzie BFG Repo Cleaner działa szybciej niż git filter-branch
, więc możesz również sprawdzić to jako opcję. Jest nawet oficjalnie wspomniany w dokumentacji gałęzi filtrów jako realna alternatywa:
git-filter-branch pozwala tworzyć złożone zapisy historii Gita w skryptach powłoki, ale prawdopodobnie nie potrzebujesz tej elastyczności, jeśli po prostu usuwasz niechciane dane, takie jak duże pliki lub hasła. W przypadku tych operacji warto rozważyć użycie BFG Repo-Cleaner , opartej na maszynie JVM alternatywy dla gałęzi git-filter, zwykle co najmniej 10-50 razy szybszej w tych przypadkach użycia i o całkiem innych właściwościach:
Każda konkretna wersja pliku jest czyszczona dokładnie raz . BFG, w przeciwieństwie do git-filter-branch, nie daje ci możliwości obsługi pliku inaczej w zależności od tego, gdzie i kiedy został popełniony w twojej historii. To ograniczenie daje podstawową korzyść BFG w zakresie wydajności i jest dobrze dostosowane do zadania czyszczenia złych danych - nie obchodzi Cię, gdzie są złe dane, po prostu chcesz, aby zniknęły .
Domyślnie BFG w pełni wykorzystuje maszyny wielordzeniowe, równolegle czyszcząc drzewa plików zatwierdzeń. czyszczenia np git-filter-branch commity kolejno (czyli w jednowątkowy sposób), choć jest
możliwe napisanie filtry, które zawierają własne parallellism w skrypty wykonywane przed każdym popełnić.
Te opcje polecenia są znacznie bardziej restrykcyjne niż oddział git-filtrującej, a dedykowany tylko do zadań usuwania niechcianych teleinformatyczny np --strip-blobs-bigger-than 1M
.
Dodatkowe zasoby
- Pro Git § 6.4 Git Tools - Przepisywanie historii .
- git-filter-branch (1) Podręcznik strony .
- git-commit (1) Podręcznik strony .
- git-reset (1) Podręcznik strony .
- git-rebase (1) Podręcznik strony .
- BFG Repo Cleaner (zobacz także odpowiedź samego twórcy ).