Entfernen Sie vertrauliche Dateien und ihre Commits aus dem Git-Verlauf


353

Ich möchte ein Git-Projekt auf GitHub stellen, aber es enthält bestimmte Dateien mit vertraulichen Daten (Benutzernamen und Passwörter wie /config/deploy.rb für capistrano).

Ich weiß, dass ich diese Dateinamen zu .gitignore hinzufügen kann , aber dies würde ihren Verlauf in Git nicht entfernen.

Ich möchte auch nicht noch einmal von vorne anfangen, indem ich das Verzeichnis /.git lösche.

Gibt es eine Möglichkeit, alle Spuren einer bestimmten Datei in Ihrem Git-Verlauf zu entfernen ?



Antworten:


448

Für alle praktischen Zwecke ist das erste , worüber Sie sich Sorgen machen sollten, das Ändern Ihrer Passwörter! Aus Ihrer Frage geht nicht hervor, ob Ihr Git-Repository vollständig lokal ist oder ob Sie noch ein Remote-Repository an einem anderen Ort haben. Wenn es entfernt und nicht vor anderen geschützt ist, haben Sie ein Problem. Wenn jemand dieses Repository geklont hat, bevor Sie dies beheben, hat er eine Kopie Ihrer Kennwörter auf seinem lokalen Computer, und Sie können ihn auf keinen Fall zwingen, auf Ihre "feste" Version zu aktualisieren, wenn diese aus dem Verlauf verschwunden ist. Das einzig sichere, was Sie tun können, ist, Ihr Passwort überall dort, wo Sie es verwendet haben, in etwas anderes zu ändern.


Hier erfahren Sie, wie Sie das Problem beheben können. GitHub beantwortete genau diese Frage als FAQ :

Hinweis für Windows-Benutzer : Verwenden Sie in diesem Befehl doppelte Anführungszeichen (") anstelle von einfachen Anführungszeichen

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

Update 2019:

Dies ist der aktuelle Code aus den 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

Denken Sie daran, dass Sie sich jetzt in einer Situation befinden, in der Sie den Verlauf neu schreiben, sobald Sie diesen Code in ein Remote-Repository wie GitHub übertragen haben und andere dieses Remote-Repository geklont haben. Wenn andere versuchen, Ihre letzten Änderungen abzurufen, erhalten sie eine Meldung, dass die Änderungen nicht übernommen werden können, da es sich nicht um einen schnellen Vorlauf handelt.

Um dies zu beheben, müssen sie entweder ihr vorhandenes Repository löschen und es erneut klonen oder die Anweisungen unter "WIEDERHERSTELLEN VON UPSTREAM REBASE" in der git-rebase-Manpage befolgen .

Tipp : Ausführengit rebase --interactive


Wenn Sie in Zukunft versehentlich einige Änderungen mit vertraulichen Informationen festschreiben, diese jedoch vor dem Verschieben in ein Remote-Repository bemerken , gibt es einige einfachere Korrekturen. Wenn Sie beim letzten Commit die vertraulichen Informationen hinzugefügt haben, können Sie die vertraulichen Informationen einfach entfernen und dann Folgendes ausführen:

git commit -a --amend

Dadurch wird das vorherige Commit mit allen neuen Änderungen geändert, die Sie vorgenommen haben, einschließlich des vollständigen Entfernens von Dateien, die mit a vorgenommen wurden git rm. Wenn die Änderungen weiter zurück in der Historie liegen, aber immer noch nicht in ein Remote-Repository übertragen werden, können Sie eine interaktive Rebase durchführen:

git rebase -i origin/master

Dadurch wird ein Editor mit den Commits geöffnet, die Sie seit Ihrem letzten gemeinsamen Vorfahren mit dem Remote-Repository vorgenommen haben. Ändern Sie "pick" in "edit" in allen Zeilen, die ein Commit mit vertraulichen Informationen darstellen, und speichern und beenden Sie es. Git wird die Änderungen durchgehen und Sie an einem Ort zurücklassen, an dem Sie:

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

Für jede Änderung mit vertraulichen Informationen. Schließlich landen Sie wieder in Ihrer Filiale und können die neuen Änderungen sicher übertragen.


5
Perfekter Typ, das ist eine großartige Antwort. Du rettest meinen Tag.
Zzeroo

18
Nur um ein Bit hinzuzufügen - unter Windows sollten Sie doppelte Anführungszeichen (") anstelle von
einfachen verwenden

4
Habe das zum Laufen gebracht. Ich war in Übersetzungen verloren. Ich habe hier den Link anstelle des Befehls verwendet. Außerdem erforderte der Windows-Befehl doppelte Anführungszeichen als ripper234-Erwähnungen, einen vollständigen Pfad, wie MigDus vorschlägt, und ohne die Zeichen "\", die der Link als neue Zeilenumbruchindikatoren einfügte. Der letzte Befehl sah ungefähr so ​​aus: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Projekt] [Datei]. [Ext]" --prune-empty --tag- Namensfilter Katze - --all
Eric Swanson

3
Es scheint einige wesentliche Unterschiede zwischen Ihrem filter-branchCode und dem auf der Github-Seite zu geben, auf die Sie verlinkt haben. ZB ihre 3. Zeile --prune-empty --tag-name-filter cat -- --all. Hat sich die Lösung geändert oder fehlt mir etwas?
Geotheory

2
Diese Lösung sieht recht gut aus, aber wenn ich die zu entfernende Datei beim ersten Festschreiben eingeführt habe, <introduction-revision-sha1>..HEADfunktioniert sie nicht. Die Datei wird erst ab dem zweiten Commit entfernt. (Wie füge ich das anfängliche Commit in den Bereich der Commits ein?) Der sichere Weg wird hier beschrieben: 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

Das Ändern Ihrer Passwörter ist eine gute Idee, aber für das Entfernen von Passwörtern aus dem Verlauf Ihres Repos empfehle ich den BFG Repo-Cleaner , eine schnellere und einfachere Alternative zugit-filter-branch Repos expliziten Entwerfen privater Daten aus Git-Repos.

Erstellen Sie eine private.txtDatei mit den Kennwörtern usw., die Sie entfernen möchten (ein Eintrag pro Zeile), und führen Sie dann den folgenden Befehl aus:

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

Alle Dateien mit einer Schwellengröße (standardmäßig 1 MB) im Verlauf Ihres Repos werden gescannt, und alle übereinstimmenden Zeichenfolgen (die nicht in Ihrem letzten Commit enthalten sind) werden durch die Zeichenfolge "*** ENTFERNT ***" ersetzt. Sie können dann git gcdie toten Daten entfernen:

$ git gc --prune=now --aggressive

Die BFG ist in der Regel 10-50-mal schneller als die Ausführung, git-filter-branchund die Optionen werden vereinfacht und auf diese beiden gängigen Anwendungsfälle zugeschnitten:

  • Verrückte große Dateien entfernen
  • Entfernen von Passwörtern, Anmeldeinformationen und anderen privaten Daten

Vollständige Offenlegung: Ich bin der Autor des BFG Repo-Cleaner.


Dies ist eine Option, die jedoch Ihre Anwendung beschädigen kann, wenn die Kennwörter verwendet werden, z. B. zum Einrichten einer Datenbankverbindung. Ich würde die aktuell akzeptierte Antwort bevorzugen, da es möglich ist, die Passwörter in Ihrer Arbeitskopie beizubehalten und die Dateien, die sie enthalten, mit .gitignore zu ignorieren.
Henridv

6
Dies ist genau hier ein großer Gewinn. Nach einigen Versuchen konnte ich damit Commits mit vertraulichen Informationen aus einem privaten Repo sehr gründlich entfernen und das Remote-Repo mit dem überarbeiteten Verlauf zwangsweise aktualisieren. Eine Randnotiz ist, dass Sie sicherstellen müssen, dass die Spitze Ihres Repos (HEAD) selbst sauber ist und keine sensiblen Daten enthält, da dieses Commit als "geschützt" gilt und von diesem Tool nicht überarbeitet wird. Wenn dies nicht der Fall ist, reinigen / ersetzen Sie es einfach manuell und git commit. Ansonsten +1 für neues Tool in der Toolbox des Entwicklers :)
Matt Borja

1
@Henridv Laut meinem letzten Kommentar sollte Ihre Bewerbung nicht wie erwartet beschädigt werden, vorausgesetzt, Ihre Bewerbung befindet sich derzeit an der Spitze oder am Kopf Ihrer Niederlassung (dh beim letzten Commit). Dieses Tool meldet explizit Ihr letztes Commit, These are your protected commits, and so their contents will NOT be alteredwährend es den Rest Ihres Commit-Verlaufs durchläuft und überarbeitet. Wenn Sie jedoch ein Rollback durchführen müssen, müssen ***REMOVED***Sie in dem Commit, auf das Sie gerade zurückgesetzt haben, nur nach suchen .
Matt Borja

1
+1 für BFG (wenn Sie Java installiert haben oder nichts dagegen haben, es zu installieren). Ein Haken ist, dass BFG sich weigert, eine Datei zu löschen, wenn sie in HEAD enthalten ist. Es ist daher besser, zuerst ein Commit durchzuführen, bei dem die gewünschten Dateien gelöscht werden, und erst dann BFG auszuführen. Danach können Sie das letzte Commit zurücksetzen, jetzt ändert sich nichts mehr.
Fr0sT

1
Dies sollte eigentlich als die richtige Antwort akzeptiert werden. Tut was es auf der Box steht!
Gjoris

21

Wenn Sie auf GitHub pushen, reicht ein erzwungenes Pushen nicht aus. Löschen Sie das Repository oder wenden Sie sich an den Support

Selbst wenn Sie eine Sekunde später einen Druck erzwingen, reicht dies nicht aus, wie unten erläutert.

Die einzig gültigen Vorgehensweisen sind:

  • Was hat einen veränderbaren Berechtigungsnachweis wie ein Passwort verloren?

    • Ja: Ändern Sie Ihre Passwörter sofort und erwägen Sie die Verwendung weiterer OAuth- und API-Schlüssel!
    • nein (nackte Bilder):

      • Interessiert es Sie, wenn alle Probleme im Repository behoben werden?

        • nein: lösche das Repository
        • Ja:

          • Kontaktieren Sie Support
          • Wenn das Leck für Sie sehr kritisch ist und Sie bereit sind, Ausfallzeiten für das Repository zu vermeiden, um die Wahrscheinlichkeit eines Lecks zu verringern, machen Sie es privat, während Sie darauf warten, dass der GitHub-Support Ihnen antwortet

Eine Sekunde später zu drücken, reicht nicht aus, weil:

Wenn Sie das Repository löschen, anstatt nur das Drücken zu erzwingen, verschwinden Commits sogar sofort aus der API und geben 404 an, z. B. https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Dies funktioniert auch wenn Sie ein anderes Repository mit demselben Namen neu erstellen.

Um dies zu testen, habe ich ein Repo erstellt: https://github.com/cirosantilli/test-dangling und habe:

git init
git remote add origin git@github.com: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

Siehe auch: Wie entferne ich ein baumelndes Commit von GitHub?


20

Ich empfehle dieses Skript von David Underhill, das für mich wie ein Zauber funktioniert hat.

Es fügt diese Befehle zusätzlich zum Filterzweig von natacado hinzu, um das Chaos zu beseitigen, das es hinterlässt:

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

Vollständiges Skript (alle Ehre gebührt David Underhill)

#!/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

Die letzten beiden Befehle funktionieren möglicherweise besser, wenn sie wie folgt geändert werden:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now

1
Beachten Sie, dass Ihre Verwendung von Ablaufdatum und Bereinigung nicht korrekt ist. Wenn Sie das Datum nicht angeben, werden standardmäßig alle Commits für das Bereinigen verwendet, die älter als 2 Wochen sind. Was Sie wollen, ist alles, was Sie tun müssen:git gc --aggressive --prune=now
Adam Parkin,

@Adam Parkin Ich werde den Code in der Antwort gleich belassen, da er aus dem Skript auf David Underhills Website stammt. Sie können ihn dort kommentieren. Wenn er ihn ändert, würde ich diese Antwort ändern, da ich das wirklich nicht weiß Gut. Der Ablaufbefehl vor dem Bereinigen hat keinen Einfluss darauf, oder?
Jason Goemaat

1
@ MarkusUnterwaditzer: Das funktioniert nicht für Push-Commits.
Max Beikirch

Vielleicht sollten Sie einfach alle Befehle in Ihre Antwort einfügen. es wäre viel konsistenter und würde nicht die mentale Kombination einzelner Beiträge erfordern :)
Andrew Mao

9

Um es klar auszudrücken: Die akzeptierte Antwort ist richtig. Versuchen Sie es zuerst. In einigen Anwendungsfällen kann dies jedoch unnötig komplex sein, insbesondere wenn Sie auf unangenehme Fehler wie "fatal: bad revision --prune-empty" stoßen oder sich wirklich nicht um den Verlauf Ihres Repos kümmern.

Eine Alternative wäre:

  1. CD zum Basiszweig des Projekts
  2. Entfernen Sie den vertraulichen Code / die Datei
  3. rm -rf .git / # Entferne alle Git-Informationen aus deinem Code
  4. Gehen Sie zu Github und löschen Sie Ihr Repository
  5. Befolgen Sie diese Anleitung, um Ihren Code wie gewohnt in ein neues Repository zu verschieben - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Dadurch werden natürlich alle Commit-Verlaufszweige und Probleme sowohl aus Ihrem Github-Repo als auch aus Ihrem lokalen Git-Repo entfernt. Wenn dies nicht akzeptabel ist, müssen Sie einen alternativen Ansatz verwenden.

Nennen Sie dies die nukleare Option.


9

Sie können verwenden git forget-blob .

Die Verwendung ist ziemlich einfach git forget-blob file-to-forget. Weitere Informationen erhalten Sie hier

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

Es verschwindet aus allen Commits in Ihrem Verlauf, Reflog, Tags und so weiter

Hin und wieder stoße ich auf das gleiche Problem, und jedes Mal, wenn ich auf diesen und andere Beiträge zurückkommen muss, habe ich den Prozess automatisiert.

Dank an die Mitwirkenden von Stack Overflow, mit denen ich dies zusammenstellen konnte


8

Hier ist meine Lösung in Windows

Git Filter-Zweig - Baum-Filter "rm -f 'Filedir / Dateiname'" HEAD

Git Push - Force

Stellen Sie sicher, dass der Pfad korrekt ist, da er sonst nicht funktioniert

Ich hoffe, es hilft


8

Verwendung Filterzweig :

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

3

Ich musste das bis jetzt ein paar Mal machen. Beachten Sie, dass dies jeweils nur für eine Datei funktioniert.

  1. Rufen Sie eine Liste aller Commits ab, die eine Datei geändert haben. Der unten stehende wird den ersten Commit ausführen:

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

  2. Um die Datei aus dem Verlauf zu entfernen, verwenden Sie das erste Commit sha1 und den Pfad zur Datei aus dem vorherigen Befehl und füllen Sie sie in diesen Befehl:

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


3

Es sieht also ungefähr so ​​aus:

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

Entfernen Sie den Cache für die verfolgte Datei aus Git und fügen Sie diese Datei zur .gitignoreListe hinzu


2

In meinem Android-Projekt hatte ich admob_keys.xml als getrennte XML-Datei im Ordner app / src / main / res / values ​​/ . Um diese vertrauliche Datei zu entfernen, habe ich das folgende Skript verwendet und perfekt funktioniert.

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.