Eliminar archivos confidenciales y sus confirmaciones del historial de Git

353

Me gustaría poner un proyecto Git en GitHub pero contiene ciertos archivos con datos confidenciales (nombres de usuario y contraseñas, como /config/deploy.rb para capistrano).

Sé que puedo agregar estos nombres de archivo a .gitignore , pero esto no eliminaría su historial dentro de Git.

Tampoco quiero comenzar de nuevo eliminando el directorio /.git.

¿Hay alguna manera de eliminar todos los rastros de un archivo en particular en su historial de Git?

Stefan
fuente

Respuestas:

448

¡Para todos los fines prácticos, lo primero que debe preocuparle es CAMBIAR SUS CONTRASEÑAS! No queda claro a partir de su pregunta si su repositorio git es completamente local o si todavía tiene un repositorio remoto en otro lugar; Si es remoto y no está protegido de otros, tiene un problema. Si alguien ha clonado ese repositorio antes de que corrija esto, tendrá una copia de sus contraseñas en su máquina local, y no hay forma de que pueda obligarlos a actualizar a su versión "fija" sin pasar del historial. Lo único seguro que puede hacer es cambiar su contraseña a otra en cualquier lugar donde la haya usado.


Con eso fuera del camino, aquí está cómo solucionarlo. GitHub respondió exactamente esa pregunta como una pregunta frecuente :

Nota para usuarios de Windows : use comillas dobles (") en lugar de simples en este 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

Actualización 2019:

Este es el código actual de las preguntas frecuentes:

  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

Tenga en cuenta que una vez que haya insertado este código en un repositorio remoto como GitHub y otros hayan clonado ese repositorio remoto, ahora se encuentra en una situación en la que está reescribiendo el historial. Cuando otros intentan desplegar sus últimos cambios después de esto, recibirán un mensaje que indica que los cambios no se pueden aplicar porque no es un avance rápido.

Para solucionar esto, tendrán que eliminar su repositorio existente y volver a clonarlo, o seguir las instrucciones en "RECUPERACIÓN DE LA REBASE DE UPSTREAM" en la página de manual de git-rebase .

Consejo : Ejecutargit rebase --interactive


En el futuro, si accidentalmente confirma algunos cambios con información confidencial pero se da cuenta antes de pasar a un repositorio remoto, hay algunas soluciones más fáciles. Si la última confirmación es la que agrega la información confidencial, simplemente puede eliminar la información confidencial y luego ejecutar:

git commit -a --amend

Eso enmendará la confirmación anterior con cualquier cambio nuevo que haya realizado, incluidas las eliminaciones de archivos completos realizadas con a git rm. Si los cambios están más atrás en el historial pero aún no se envían a un repositorio remoto, puede hacer un cambio de base interactivo:

git rebase -i origin/master

Eso abre un editor con las confirmaciones que ha realizado desde su último antepasado común con el repositorio remoto. Cambie "elegir" a "editar" en cualquier línea que represente una confirmación con información confidencial, y guarde y salga. Git analizará los cambios y te dejará en un lugar donde puedes:

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

Para cada cambio con información sensible. Eventualmente, terminará de nuevo en su sucursal, y puede impulsar con seguridad los nuevos cambios.

natacado
fuente
55
Perfecto amigo, esa es una gran respuesta. Me salvas el día.
zzeroo
18
Solo para agregar un bit: en Windows, debe usar comillas dobles (") en lugar de simples.
ripper234
44
Tengo esto para trabajar. Estaba perdido en las traducciones. Usé el enlace en lugar del comando aquí. Además, el comando de Windows terminó requiriendo comillas dobles como menciona ripper234, ruta completa como sugiere MigDus, y sin incluir los caracteres "\" que el enlace pegó como nuevos indicadores de ajuste de línea. El comando final se parecía a: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Proyecto] [Archivo]. [Ext]" --prune-empty --tag- gato con filtro de nombre - --todos
Eric Swanson
3
Parece que hay algunas diferencias sustanciales entre su filter-branchcódigo y el de la página de github a la que se vinculó. Por ejemplo, su tercera línea --prune-empty --tag-name-filter cat -- --all. ¿Ha cambiado la solución o me falta algo?
geotheory
2
Esta solución se ve bastante bien, pero si he introducido el archivo para eliminar en la confirmación inicial <introduction-revision-sha1>..HEADno funciona. Solo elimina el archivo del segundo commit en adelante. (¿Cómo incluyo el commit inicial en el rango de commits?) La forma de guardar se indica aquí: 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

Cambiar sus contraseñas es una buena idea, pero para el proceso de eliminar las contraseñas del historial de su repositorio, recomiendo el BFG Repo-Cleaner , una alternativa más rápida y sencilla que git-filter-branchla diseñada explícitamente para eliminar datos privados de repositorios de Git.

Crear un private.txt archivo que enumere las contraseñas, etc., que desea eliminar (una entrada por línea) y luego ejecute este comando:

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

Se escanearán todos los archivos con un tamaño de umbral (1 MB por defecto) en el historial de su repositorio, y cualquier cadena coincidente (que no esté en su último commit) será reemplazada por la cadena "*** ELIMINADO ***". Luego puede usar git gcpara limpiar los datos muertos:

$ git gc --prune=now --aggressive

El BFG suele ser 10-50x más rápido que la ejecución git-filter-branchy las opciones se simplifican y se adaptan a estos dos casos de uso comunes:

  • Quitando Crazy Big Files
  • Eliminar contraseñas, credenciales y otros datos privados

Divulgación completa: soy el autor del BFG Repo-Cleaner.

Roberto Tyley
fuente
Esta es una opción, pero podría romper su aplicación cuando se usan las contraseñas, por ejemplo, para configurar una conexión de base de datos. Prefiero la respuesta actualmente aceptada porque aún es posible mantener las contraseñas en su copia de trabajo e ignorar los archivos que las contienen con .gitignore.
Henridv
66
Esta es una gran victoria aquí mismo. Después de un par de intentos, pude usar esto para eliminar las confirmaciones que contienen información confidencial de un repositorio privado de manera muy exhaustiva y actualizar con fuerza el repositorio remoto con el historial revisado. Una nota al margen es que debe asegurarse de que la punta de su repositorio (HEAD) esté limpia, sin datos confidenciales, ya que esta confirmación se considera "protegida" y esta herramienta no la revisará. Si no es así, simplemente limpie / reemplace manualmente y git commit. De lo contrario, +1 para la nueva herramienta en la caja de herramientas del desarrollador :)
Matt Borja
1
@Henridv Según mi comentario reciente, no debería interrumpir su aplicación como podría anticipar, suponiendo que su aplicación se encuentre actualmente en la punta o cabeza de su sucursal (es decir, la última confirmación). Esta herramienta informará explícitamente sobre su última confirmación These are your protected commits, and so their contents will NOT be alteredmientras recorre y revisa el resto de su historial de confirmación. Sin embargo, si necesita revertir, entonces sí, tendría que hacer una búsqueda ***REMOVED***en la confirmación a la que acaba de retroceder.
Matt Borja
1
+1 para BFG (si tiene Java instalado o no le importa instalarlo). Un inconveniente es que BFG se niega a eliminar un archivo si está contenido en HEAD. Por lo tanto, es mejor hacer una confirmación donde se eliminarán los archivos deseados y solo luego ejecutar BFG. Después de eso puedes revertir ese último commit, ahora no cambia nada.
Viernes
1
Esto debería ser aceptado como la respuesta correcta. ¡Hace lo que dice en la caja!
gjoris
21

Si presionó a GitHub, forzar el empuje no es suficiente, elimine el repositorio o póngase en contacto con el soporte

Incluso si fuerza el empuje un segundo después, no es suficiente como se explica a continuación.

Los únicos cursos de acción válidos son:

  • ¿Qué se filtró una credencial modificable como una contraseña?

    • sí: modifique sus contraseñas de inmediato, ¡y considere usar más claves OAuth y API!
    • no (fotos desnudas):

      • ¿te importa si se resuelven todos los problemas del repositorio?

        • no: eliminar el repositorio
        • si:

          • soporte de contacto
          • Si la fuga es muy crítica para usted, hasta el punto de que está dispuesto a obtener un tiempo de inactividad del repositorio para que sea menos probable que se filtre, hágalo privado mientras espera que el soporte de GitHub le responda

Forzar un segundo más tarde no es suficiente porque:

Sin embargo, si elimina el repositorio en lugar de forzar el empuje, las confirmaciones desaparecen incluso de la API de inmediato y dan 404, por ejemplo, https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824 Esto funciona incluso si recrea otro repositorio con el mismo nombre.

Para probar esto, he creado un repositorio: https://github.com/cirosantilli/test-dangling e hice:

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

Consulte también: ¿Cómo eliminar una confirmación pendiente de GitHub?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
20

Recomiendo este guión de David Underhill, funcionó de maravilla para mí.

Agrega estos comandos además de la rama de filtro de natacado para limpiar el desorden que deja:

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

Guión completo (todo el crédito 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

Los dos últimos comandos pueden funcionar mejor si se cambian a los siguientes:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now
Jason Goemaat
fuente
1
Tenga en cuenta que su uso de caducar y podar es incorrecto, si no especifica la fecha, se establece de forma predeterminada todas las confirmaciones anteriores a 2 semanas para podar. Lo que queremos es todos los envíos también lo hacen:git gc --aggressive --prune=now
Adam Parkin
@ Adam Parkin, voy a dejar el código en la respuesta igual porque es del guión en el sitio de David Underhill, podrías comentar allí y si lo cambia, cambiaría esta respuesta ya que realmente no sé bien. ¿El comando de caducidad anterior a la poda no afecta eso?
Jason Goemaat
1
@ MarkusUnterwaditzer: Ese no funcionará para commits empujados.
Max Beikirch
Tal vez deberías poner todos los comandos en tu respuesta; sería mucho más consistente y no requeriría la combinación mental de publicaciones separadas :)
Andrew Mao
9

Para ser claros: la respuesta aceptada es correcta. Pruébalo primero. Sin embargo, puede ser innecesariamente complejo para algunos casos de uso, especialmente si encuentra errores desagradables como 'fatal: mala revisión --prune-empty', o realmente no le importa el historial de su repositorio.

Una alternativa sería:

  1. cd a la rama base del proyecto
  2. Eliminar el código / archivo sensible
  3. rm -rf .git / # Elimina toda la información de git de tu código
  4. Ve a github y elimina tu repositorio
  5. Siga esta guía para enviar su código a un nuevo repositorio como lo haría normalmente: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Por supuesto, esto eliminará todas las ramas del historial de confirmaciones y los problemas tanto de su repositorio de github como de su repositorio de git local. Si esto es inaceptable, deberá utilizar un enfoque alternativo.

Llame a esto la opción nuclear.

filósofo perdido
fuente
9

Puedes usar git forget-blob .

El uso es bastante simple. git forget-blob file-to-forget . Puedes obtener más información aquí

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

Desaparecerá de todos los commits en tu historial, reflog, etiquetas, etc.

Me encuentro con el mismo problema de vez en cuando, y cada vez que tengo que volver a esta publicación y a otras, es por eso que automaticé el proceso.

Créditos a contribuyentes de Stack Overflow que me permitieron armar esto

nachoparker
fuente
8

Aquí está mi solución en windows

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

git push --force

asegúrese de que la ruta sea correcta, de lo contrario no funcionará

Espero que ayude

vértigo71
fuente
8

Utilice filter-branch :

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
fuente
3

He tenido que hacer esto varias veces hasta la fecha. Tenga en cuenta que esto solo funciona en 1 archivo a la vez.

  1. Obtenga una lista de todas las confirmaciones que modificaron un archivo. El que está en la parte inferior será el primer commit:

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

  2. Para eliminar el archivo del historial, use el primer commit sha1 y la ruta al archivo del comando anterior, y complételos en este comando:

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

b01
fuente
3

Entonces, se parece a esto:

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

Elimine el caché para el archivo rastreado de git y agregue ese archivo a la .gitignorelista

przbadu
fuente
2

En mi proyecto de Android tenía admob_keys.xml como archivo xml separado en la carpeta app / src / main / res / values ​​/ . Para eliminar este archivo confidencial, utilicé el siguiente script y funcioné perfectamente.

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
fuente