Usuń poufne pliki i ich zatwierdzenia z historii Git

353

Chciałbym umieścić projekt Git na GitHub, ale zawiera on pewne pliki z wrażliwymi danymi (nazwy użytkowników i hasła, takie jak /config/deploy.rb dla capistrano).

Wiem, że mogę dodać te nazwy plików do .gitignore , ale nie usunęłoby to ich historii w Git.

Nie chcę też zaczynać od nowa, usuwając katalog /.git.

Czy istnieje sposób na usunięcie wszystkich śladów określonego pliku z historii Git?

Stefan
źródło

Odpowiedzi:

448

Ze względów praktycznych pierwszą rzeczą, o którą powinieneś się martwić, jest ZMIANA HASŁA! Z twojego pytania nie wynika jasno, czy twoje repozytorium git jest całkowicie lokalne, czy też masz zdalne repozytorium gdzie indziej; jeśli jest zdalny i nie jest zabezpieczony przed innymi, masz problem. Jeśli ktokolwiek sklonował to repozytorium, zanim to naprawisz, będzie miał kopię twoich haseł na swoim komputerze lokalnym, i nie ma sposobu, aby zmusić ich do aktualizacji do „ustalonej” wersji, gdy zniknie z historii. Jedyną bezpieczną rzeczą, jaką możesz zrobić, jest zmiana hasła na coś innego, gdziekolwiek go użyjesz.


Aby to rozwiązać, oto jak to naprawić. GitHub odpowiedział dokładnie na to pytanie jako FAQ :

Uwaga dla użytkowników systemu Windows : w tym poleceniu używaj podwójnych cudzysłowów („) zamiast pojedynczych znaków

git filter-branch --index-filter \
'git update-index --remove PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

Aktualizacja 2019:

Oto aktualny kod z FAQ:

  git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \
  --prune-empty --tag-name-filter cat -- --all
  git push --force --verbose --dry-run
  git push --force

Pamiętaj, że po przekazaniu tego kodu do zdalnego repozytorium, takiego jak GitHub, a inni sklonowali to zdalne repozytorium, znajdujesz się w sytuacji, w której przepisujesz historię. Gdy inni spróbują później usunąć najnowsze zmiany, otrzymają komunikat informujący, że zmian nie można zastosować, ponieważ nie jest to przewijanie do przodu.

Aby to naprawić, będą musieli usunąć swoje istniejące repozytorium i ponownie je sklonować lub postępować zgodnie z instrukcjami w części „ODZYSKIWANIE Z REBASEU UPSTREAM” na stronie man git-rebase .

Wskazówka : Wykonajgit rebase --interactive


W przyszłości, jeśli przypadkowo wprowadzisz pewne zmiany za pomocą poufnych informacji, ale zauważysz to przed wypchnięciem do zdalnego repozytorium, będzie kilka łatwiejszych poprawek. Jeśli ostatnim zatwierdzeniem jest dodanie poufnych informacji, możesz po prostu usunąć wrażliwe informacje, a następnie uruchomić:

git commit -a --amend

Spowoduje to zmianę poprzedniego zatwierdzenia wszelkimi nowymi zmianami, które wprowadziłeś, w tym usunięcie całego pliku za pomocą git rm. Jeśli zmiany są dalej w historii, ale nadal nie są wypychane do zdalnego repozytorium, możesz wykonać interaktywną zmianę bazy:

git rebase -i origin/master

Spowoduje to otwarcie edytora z zatwierdzeniami dokonanymi od czasu ostatniego wspólnego przodka ze zdalnym repozytorium. Zmień „wybierz” na „edytuj” w dowolnym wierszu reprezentującym zatwierdzenie z poufnymi informacjami i zapisz i wyjdź. Git przejdzie przez zmiany i pozostawi cię w miejscu, w którym możesz:

$EDITOR file-to-fix
git commit -a --amend
git rebase --continue

Dla każdej zmiany z poufnymi informacjami. W końcu wrócisz do swojego oddziału i możesz bezpiecznie wprowadzać nowe zmiany.

natacado
źródło
5
Idealny koleś, to świetna odpowiedź. Ratujesz mój dzień.
zzeroo
18
Wystarczy dodać jeden bit - Windows, należy użyć cudzysłowów ( ") zamiast singli.
ripper234
4
Mam to do pracy. Zagubiłem się w tłumaczeniach. Użyłem tutaj linku zamiast polecenia. Ponadto polecenie systemu Windows wymagało podwójnego cudzysłowu, jak wspomina ripper234, pełnej ścieżki, jak sugeruje MigDus, i nie zawiera znaków „\” wklejonych przez łącze jako wskaźników zawijania nowego wiersza. Ostateczne polecenie wyglądało mniej więcej tak: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Projekt] [Plik]. [Ext]" --prune-empty --tag- name-filter cat - --all
Eric Swanson
3
Wygląda na to, że istnieją pewne istotne różnice między twoim filter-branchkodem a tym na stronie github, do której prowadzisz link. Np. Ich trzecia linia --prune-empty --tag-name-filter cat -- --all. Czy rozwiązanie się zmieniło, czy coś brakuje?
geotheory
2
To rozwiązanie wygląda całkiem dobrze, ale jeśli wprowadziłem plik do usunięcia w początkowym zatwierdzeniu <introduction-revision-sha1>..HEAD, nie działa. Usuwa tylko plik od drugiego zatwierdzenia. (Jak włączyć początkowe zatwierdzenie do zakresu zatwierdzeń?) Sposób zapisywania jest wskazany tutaj: help.github.com/articles/… git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \ --prune-empty --tag-name-filter cat -- --all
white_gecko
91

Zmiana haseł jest dobrym pomysłem, ale do procesu usuwania haseł z historii repozytorium polecam BFG Repo-Cleaner , szybszą, prostszą alternatywę dla git-filter-branchjawnie zaprojektowanej do usuwania prywatnych danych z repozytoriów Git.

Utwórz private.txtplik zawierający listę haseł itp., Które chcesz usunąć (jeden wpis w wierszu), a następnie uruchom następujące polecenie:

$ java -jar bfg.jar  --replace-text private.txt  my-repo.git

Wszystkie pliki poniżej wielkości progowej (domyślnie 1 MB) w historii Twojego repozytorium zostaną przeskanowane, a każdy pasujący ciąg (który nie jest w twoim ostatnim zatwierdzeniu) zostanie zastąpiony ciągiem „*** REMOVED ***”. Następnie możesz użyć git gcdo usunięcia martwych danych:

$ git gc --prune=now --aggressive

BFG jest zwykle 10-50 razy szybszy niż uruchamianie, git-filter-brancha opcje są uproszczone i dostosowane do tych dwóch typowych przypadków użycia:

  • Usuwanie zwariowanych dużych plików
  • Usuwanie haseł, poświadczeń i innych danych prywatnych

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

Roberto Tyley
źródło
Jest to opcja, ale może zepsuć aplikację, gdy używane są hasła, np. Do skonfigurowania połączenia z bazą danych. Wolę aktualnie akceptowaną odpowiedź, ponieważ nadal można przechowywać hasła w kopii roboczej i zignorować pliki zawierające je za pomocą .gitignore.
Henridv
6
To duża wygrana tutaj. Po kilku próbach udało mi się użyć tego do bardzo dokładnego usunięcia zleceń zawierających poufne informacje z prywatnego repozytorium i silną aktualizację repozytorium zdalnego ze zmienioną historią. Z jednej strony należy upewnić się, że końcówka repozytorium (HEAD) jest czysta bez żadnych wrażliwych danych, ponieważ zatwierdzenie to jest uważane za „chronione” i nie zostanie zmienione przez to narzędzie. Jeśli nie, po prostu wyczyść / wymień ręcznie i git commit. W przeciwnym razie +1 za nowe narzędzie w przyborniku programisty :)
Matt Borja
1
@Henridv Według mojego ostatniego komentarza nie powinien on przerywać działania aplikacji, jak można się spodziewać, zakładając, że twoja aplikacja znajduje się obecnie na końcu lub na początku oddziału (tj. Ostatnie zatwierdzenie). To narzędzie wyraźnie zgłasza ostatnie zatwierdzenie These are your protected commits, and so their contents will NOT be alteredpodczas przeglądania i przeglądania pozostałej historii zmian. Jeśli jednak chcesz cofnąć, to tak, musisz po prostu wyszukać ***REMOVED***w zatwierdzeniu, do którego właśnie wycofałeś.
Matt Borja
1
+1 dla BFG (jeśli masz zainstalowaną Javę lub nie masz nic przeciwko instalacji). Jednym z haczyków jest to, że BFG odmawia usunięcia pliku, jeśli jest on zawarty w HEAD. Dlatego lepiej najpierw wykonać zatwierdzenie, w którym wybrane pliki zostaną usunięte, a dopiero potem uruchomić BFG. Następnie możesz cofnąć ostatnie zatwierdzenie, teraz nic to nie zmienia.
Fr0sT
1
To właściwie powinno zostać zaakceptowane jako poprawna odpowiedź. Robi to, co jest napisane na pudełku!
gjoris,
21

Jeśli przekazałeś do GitHub, wymuszone wypychanie nie wystarczy, usuń repozytorium lub skontaktuj się z obsługą

Nawet jeśli siłą naciskasz sekundę później, to nie wystarczy, jak wyjaśniono poniżej.

Jedyne ważne kierunki działania to:

  • co wyciekło zmienne dane uwierzytelniające, takie jak hasło?

    • tak: natychmiast zmodyfikuj swoje hasła i rozważ użycie większej liczby kluczy OAuth i API!
    • nie (nagie zdjęcia):

      • obchodzi Cię, czy wszystkie problemy w repozytorium zostaną usunięte?

        • nie: usuń repozytorium
        • tak:

          • skontaktuj się z pomocą techniczną
          • jeśli wyciek jest dla Ciebie bardzo krytyczny, do tego stopnia, że ​​chcesz uzyskać przestój repozytorium, aby zmniejszyć prawdopodobieństwo wycieku, ustaw go jako prywatny , czekając na odpowiedź GitHub

Siła pchania sekundę później nie wystarczy, ponieważ:

Jeśli usuniesz repozytorium zamiast tylko wymuszać wypychanie, zatwierdzenia znikają natychmiast nawet z interfejsu API i dają 404, np. Https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Ten działa nawet jeśli odtworzysz inne repozytorium o tej samej nazwie.

Aby to przetestować, utworzyłem repozytorium: https://github.com/cirosantilli/test-dangling i zrobiłem:

git init
git remote add origin [email protected]:cirosantilli/test-dangling.git

touch a
git add .
git commit -m 0
git push

touch b
git add .
git commit -m 1
git push

touch c
git rm b
git add .
git commit --amend --no-edit
git push -f

Zobacz także: Jak usunąć wiszące zatwierdzenie z GitHub?

Ciro Santilli
źródło
20

Polecam ten skrypt Davida Underhilla, który działał dla mnie jak urok.

Dodaje te polecenia dodatkowo do gałęzi filtrów natacado, aby oczyścić bałagan, który pozostawia:

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

Pełny scenariusz (podziękowania dla Davida Underhilla)

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter \
"git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch
# otherwise leaves behind for a long time
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune

Ostatnie dwa polecenia mogą działać lepiej, jeśli zostaną zmienione na następujące:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now
Jason Goemaat
źródło
1
Pamiętaj, że użycie terminu ważności i przycinania jest nieprawidłowe, jeśli nie określisz daty, domyślnie przyjmowane są wszystkie zatwierdzenia starsze niż 2 tygodnie dla suszonych śliwek. To, czego chcesz, to wszystkie zobowiązania, więc rób:git gc --aggressive --prune=now
Adam Parkin,
@Adam Parkin Mam zamiar zostawić kod w odpowiedzi taki sam, ponieważ pochodzi on ze skryptu na stronie Davida Underhilla, możesz tam skomentować, a jeśli on to zmieni, zmieniłbym tę odpowiedź, ponieważ tak naprawdę nie wiem dobrze. Polecenie wygasania przed przycinaniem nie ma na to wpływu, prawda?
Jason Goemaat,
1
@MarkusUnterwaditzer: Ten nie będzie działał dla wypychanych zatwierdzeń.
Max Beikirch
Może powinieneś po prostu umieścić wszystkie polecenia w swojej odpowiedzi; byłoby znacznie bardziej spójne i nie wymagałoby mentalnego łączenia osobnych postów :)
Andrew Mao,
9

Żeby było jasne: zaakceptowana odpowiedź jest poprawna. Wypróbuj najpierw. Jednak w niektórych przypadkach użycia może być niepotrzebnie skomplikowany, szczególnie jeśli napotkasz nieprzyjemne błędy, takie jak „fatal: bad revision - prune-empty” lub naprawdę nie zależy ci na historii twojego repozytorium.

Alternatywą byłoby:

  1. cd do podstawowej gałęzi projektu
  2. Usuń wrażliwy kod / plik
  3. rm -rf .git / # Usuń wszystkie informacje git z kodu
  4. Przejdź do github i usuń swoje repozytorium
  5. Postępuj zgodnie z tym przewodnikiem, aby przenieść kod do nowego repozytorium w normalny sposób - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Spowoduje to oczywiście usunięcie wszystkich gałęzi historii zatwierdzeń i problemów zarówno z repozytorium github, jak i lokalnego repozytorium git. Jeśli jest to nie do przyjęcia, będziesz musiał zastosować alternatywne podejście.

Nazwij to opcją nuklearną.

Lostphilosopher
źródło
9

Możesz użyć git forget-blob.

Użycie jest dość proste git forget-blob file-to-forget. Możesz uzyskać więcej informacji tutaj

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

Zniknie ze wszystkich zmian w historii, przelogowaniu, tagach i tak dalej

Od czasu do czasu napotykam ten sam problem i za każdym razem, gdy muszę wracać do tego postu i innych, dlatego zautomatyzowałem ten proces.

Kredyty dla autorów z Stack Overflow, które pozwoliły mi to połączyć

nachoparker
źródło
8

Oto moje rozwiązanie w systemie Windows

git filter-branch --tree-filter "rm -f 'filedir / filename'" HEAD

git push --force

upewnij się, że ścieżka jest poprawna, w przeciwnym razie nie zadziała

Mam nadzieję, że to pomoże

zawroty głowy71
źródło
8

Użyj gałęzi filter :

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch *file_path_relative_to_git_repo*' --prune-empty --tag-name-filter cat -- --all

git push origin *branch_name* -f
Shiv Krishna Jaiswal
źródło
3

Do tej pory musiałem to zrobić kilka razy. Pamiętaj, że działa to tylko na 1 pliku na raz.

  1. Uzyskaj listę wszystkich zatwierdzeń, które zmodyfikowały plik. Ten na dole będzie pierwszym zatwierdzeniem:

    git log --pretty=oneline --branches -- pathToFile

  2. Aby usunąć plik z historii, użyj pierwszego zatwierdzenia sha1 i ścieżki do pliku z poprzedniego polecenia i wypełnij je tym poleceniem:

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch <path-to-file>' -- <sha1-where-the-file-was-first-added>..

b01
źródło
3

Wygląda to mniej więcej tak:

git rm --cached /config/deploy.rb
echo /config/deploy.rb >> .gitignore

Usuń pamięć podręczną dla śledzonego pliku z git i dodaj ten plik do .gitignorelisty

przbadu
źródło
2

W moim projekcie na Androida miałem admob_keys.xml jako oddzielny plik xml w folderze app / src / main / res / values ​​/ . Aby usunąć ten wrażliwy plik, użyłem poniżej skryptu i działałem idealnie.

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch  app/src/main/res/values/admob_keys.xml' \
--prune-empty --tag-name-filter cat -- --all
Ercan
źródło