Rimuovi i file sensibili e i relativi commit dalla cronologia di Git


353

Vorrei mettere un progetto Git su GitHub ma contiene alcuni file con dati sensibili (nomi utente e password, come /config/deploy.rb per capistrano).

So di poter aggiungere questi nomi di file a .gitignore , ma ciò non rimuoverà la loro cronologia all'interno di Git.

Inoltre, non voglio ricominciare da capo cancellando la directory /.git.

C'è un modo per rimuovere tutte le tracce di un determinato file nella tua cronologia di Git?



Risposte:


448

A tutti gli effetti pratici, la prima cosa di cui dovresti preoccuparti è CAMBIARE LE PASSWORD! Non è chiaro dalla tua domanda se il tuo repository git è interamente locale o se hai ancora un repository remoto altrove; se è remoto e non protetto da altri hai un problema. Se qualcuno ha clonato quel repository prima di risolvere il problema, avrà una copia delle password sul proprio computer locale e non è possibile forzarle ad aggiornare la versione "fissa" con la cronologia. L'unica cosa sicura che puoi fare è cambiare la tua password in qualcos'altro ovunque tu l'abbia utilizzata.


Detto questo, ecco come risolverlo. GitHub ha risposto esattamente a questa domanda come FAQ :

Nota per gli utenti Windows : utilizzare le virgolette doppie (") invece dei singoli in questo comando

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

Aggiornamento 2019:

Questo è il codice corrente dalle 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

Tieni presente che una volta trasferito questo codice in un repository remoto come GitHub e altri hanno clonato quel repository remoto, ora ti trovi in ​​una situazione in cui stai riscrivendo la cronologia. Quando altri tentano di eliminare le ultime modifiche dopo questo, riceveranno un messaggio che indica che le modifiche non possono essere applicate perché non è un avanzamento rapido.

Per risolvere questo problema, dovranno eliminare il loro repository esistente e ri-clonarlo, oppure seguire le istruzioni in "RECUPERARE DALLA REVISIONE DI UPSTREAM" nella manpage git-rebase .

Suggerimento : eseguiregit rebase --interactive


In futuro, se si commettono accidentalmente alcune modifiche con informazioni riservate ma si nota prima di passare a un repository remoto, ci sono alcune correzioni più facili. Se l'ultimo commit è quello per aggiungere le informazioni sensibili, puoi semplicemente rimuovere le informazioni sensibili, quindi eseguire:

git commit -a --amend

Ciò modificherà il commit precedente con eventuali nuove modifiche apportate, comprese le intere rimozioni di file eseguite con a git rm. Se le modifiche sono più indietro nella cronologia ma non vengono ancora trasferite in un repository remoto, è possibile effettuare un rebase interattivo:

git rebase -i origin/master

Questo apre un editor con i commit che hai fatto dall'ultimo tuo antenato comune con il repository remoto. Cambia "scegli" in "modifica" su tutte le righe che rappresentano un commit con informazioni riservate, quindi salva ed esci. Git esaminerà le modifiche e ti lascerà in un punto in cui puoi:

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

Per ogni modifica con informazioni sensibili. Alla fine, tornerai al tuo ramo e puoi spingere in sicurezza le nuove modifiche.


5
Amico perfetto, questa è un'ottima risposta. Mi salvi la giornata.
zzeroo,

18
Solo per aggiungere un bit - su Windows, dovresti usare le virgolette doppie (") invece dei singoli.
ripper234

4
Ho fatto funzionare tutto questo. Mi ero perso nelle traduzioni. Ho usato il link al posto del comando qui. Inoltre, il comando di Windows ha richiesto doppie virgolette come menziona ripper234, percorso completo come suggerisce MigDus e non includendo i caratteri "\" che il collegamento ha incollato come nuovi indicatori di avvolgimento di riga. Il comando finale sembrava simile a: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Progetto] [File]. [Ext]" --prune-empty --tag- name-filter cat - --all
Eric Swanson,

3
Sembra che ci siano alcune differenze sostanziali tra il tuo filter-branchcodice e quello nella pagina github a cui ti sei collegato. Ad esempio la loro terza linea --prune-empty --tag-name-filter cat -- --all. La soluzione è cambiata o mi sto perdendo qualcosa?
geoteca il

2
Questa soluzione sembra abbastanza buona, ma se ho introdotto il file da rimuovere nel commit iniziale <introduction-revision-sha1>..HEADnon funziona. Rimuove solo il file dal secondo commit in poi. (Come posso includere il commit iniziale nella gamma di commit?) Il modo di salvataggio è indicato qui: 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

Cambiare le password è una buona idea, ma per il processo di rimozione delle password dalla cronologia dei repository, consiglio BFG Repo-Cleaner , un'alternativa più rapida e semplice a quella git-filter-branchprogettata esplicitamente per la rimozione di dati privati ​​dai repository Git.

Creare un private.txtfile che elenca le password, ecc., Che si desidera rimuovere (una voce per riga) e quindi eseguire questo comando:

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

Tutti i file con una soglia inferiore (1 MB per impostazione predefinita) nella cronologia del tuo repository verranno scansionati e qualsiasi stringa corrispondente (che non è nell'ultimo commit) verrà sostituita con la stringa "*** RIMOSSO ***". È quindi possibile utilizzare git gcper pulire i dati morti:

$ git gc --prune=now --aggressive

Il GGG è in genere 10-50 volte più veloce della corsa git-filter-branche le opzioni sono semplificate e personalizzate su questi due casi d'uso comuni:

  • Rimozione di file di grandi dimensioni pazzi
  • Rimozione di password, credenziali e altri dati privati

Informativa completa: sono l'autore del Repo-Cleaner di BFG.


Questa è un'opzione, ma potrebbe interrompere l'applicazione quando vengono utilizzate le password, ad esempio per impostare una connessione al database. Preferirei la risposta attualmente accettata perché è ancora possibile conservare le password nella copia di lavoro e ignorare i file che le contengono con .gitignore.
Henridv,

6
Questa è una grande vittoria proprio qui. Dopo un paio di tentativi, sono stato in grado di utilizzare questo per eliminare in modo completo e completo i commit di repository contenenti informazioni sensibili da un repository privato con la cronologia rivista. Una nota a margine è che devi assicurarti che la punta del tuo repository (HEAD) sia di per sé pulita senza dati sensibili poiché questo commit è considerato "protetto" e non sarà rivisto da questo strumento. In caso contrario, basta pulire / sostituire manualmente e git commit. Altrimenti, +1 per il nuovo strumento nella casella degli strumenti dello sviluppatore :)
Matt Borja,

1
@Henridv Per il mio recente commento, non dovrebbe interrompere la tua candidatura come potresti anticipare, supponendo che la tua candidatura sia attualmente situata in punta o a capo della tua filiale (ovvero il commit più recente). Questo strumento riporterà esplicitamente il tuo ultimo commit These are your protected commits, and so their contents will NOT be alteredmentre attraversi e rivedi il resto della cronologia dei tuoi commit. Se è necessario eseguire il rollback, tuttavia, sì, è necessario eseguire una ricerca ***REMOVED***nel commit a cui si è appena eseguito il rollback.
Matt Borja,

1
+1 per BFG (se hai Java installato o non ti dispiace installarlo). Un problema è che BFG rifiuta di eliminare un file se è contenuto in HEAD. Quindi è meglio fare prima un commit in cui i file desiderati verranno eliminati e solo successivamente eseguire BFG. Dopodiché puoi ripristinare l'ultimo commit, ora non cambia nulla.
Fr0sT,

1
Questo dovrebbe effettivamente essere accettato come la risposta corretta. Fa quello che dice sulla scatola!
gjoris,

21

Se si è passati a GitHub, forzare la spinta non è sufficiente, eliminare il repository o contattare l'assistenza

Anche se si forza la spinta un secondo dopo, non è sufficiente come spiegato di seguito.

Le uniche azioni valide sono:

  • cosa è trapelato una credenziale modificabile come una password?

    • sì: modifica immediatamente le tue password e considera l'utilizzo di più chiavi OAuth e API!
    • no (foto di nudo):

      • ti importa se tutti i problemi nel repository vengono bombardati?

        • no: elimina il repository
        • sì:

          • contatta il supporto
          • se la perdita è molto critica per te, al punto che sei disposto a ottenere dei tempi di inattività del repository per renderla meno probabile, perdila in privato mentre aspetti il ​​supporto di GitHub per risponderti

La spinta forzata un secondo dopo non è sufficiente perché:

Se elimini il repository invece di forzare semplicemente la spinta, tuttavia, i commit scompaiono immediatamente dall'API e danno 404, ad esempio https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Funziona anche se si ricrea un altro repository con lo stesso nome.

Per provarlo, ho creato un repository: https://github.com/cirosantilli/test-dangling e ho fatto:

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

Vedi anche: Come rimuovere un commit penzolante da GitHub?


20

Consiglio questa sceneggiatura di David Underhill, che ha funzionato come un incantesimo per me.

Aggiunge questi comandi in aggiunta al filtro-ramo di natacado per ripulire il casino che lascia dietro:

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

Sceneggiatura completa (tutto merito a 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

Gli ultimi due comandi potrebbero funzionare meglio se modificati nel modo seguente:

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

1
Nota che il tuo uso di scadenza e prugna non è corretto, se non specifichi la data, per impostazione predefinita tutti i commit sono più vecchi di 2 settimane per la potatura. Ciò che vuoi è tutto ciò che devi fare così:git gc --aggressive --prune=now
Adam Parkin,

@Adam Parkin Lascerò il codice nella risposta lo stesso perché proviene dallo script sul sito di David Underhill, potresti commentare lì e se lo cambia cambierei questa risposta poiché non so davvero che bene. Il comando expire prima della potatura non influenza questo?
Jason Goemaat,

1
@MarkusUnterwaditzer: che non funzionerà per i commit push.
Max Beikirch,

Forse dovresti semplicemente inserire tutti i comandi nella tua risposta; sarebbe molto più coerente e non richiederebbe la combinazione mentale di post separati :)
Andrew Mao,

9

Per essere chiari: la risposta accettata è corretta. Provalo prima. Tuttavia, potrebbe essere inutilmente complesso per alcuni casi d'uso, in particolare se si verificano errori odiosi come "fatal: bad revision --prune-empty", o davvero non ti interessa la cronologia del tuo repository.

Un'alternativa sarebbe:

  1. cd al ramo base del progetto
  2. Rimuovi il codice / file sensibile
  3. rm -rf .git / # Rimuovi tutte le informazioni git dal tuo codice
  4. Vai su github ed elimina il tuo repository
  5. Segui questa guida per trasferire il tuo codice in un nuovo repository come faresti normalmente - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Ciò rimuoverà ovviamente tutti i rami della cronologia di commit e i problemi sia dal repository github che dal repository git locale. Se ciò è inaccettabile, dovrai utilizzare un approccio alternativo.

Chiamalo opzione nucleare.


9

È possibile utilizzare git forget-blob.

L'utilizzo è piuttosto semplice git forget-blob file-to-forget. Puoi ottenere maggiori informazioni qui

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

Scomparirà da tutti i commit nella tua cronologia, reflog, tag e così via

Ogni tanto incontro lo stesso problema e ogni volta che devo tornare a questo post e ad altri, ecco perché ho automatizzato il processo.

Crediti a collaboratori di Stack Overflow che mi hanno permesso di mettere insieme


8

Ecco la mia soluzione in Windows

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

git push --force

assicurarsi che il percorso sia corretto, altrimenti non funzionerà

spero possa essere d'aiuto


8

Usa filtro-ramo :

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

Ho dovuto farlo alcune volte fino ad oggi. Nota che funziona solo su 1 file alla volta.

  1. Ottieni un elenco di tutti i commit che hanno modificato un file. Quello in fondo sarà il primo commit:

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

  2. Per rimuovere il file dalla cronologia, utilizzare il primo commit sha1 e il percorso del file dal comando precedente e inserirli in questo comando:

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


3

Quindi, sembra qualcosa del genere:

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

Rimuovere la cache per il file tracciato da Git e aggiungere quel file .gitignoreall'elenco


2

Nel mio progetto Android avevo admob_keys.xml come file xml separato nella cartella app / src / main / res / valori / . Per rimuovere questo file sensibile ho usato di seguito lo script e ho funzionato perfettamente.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.