¿Cómo reemplazar una cadena en todo el historial de Git?

7 minutos de lectura

Avatar de usuario de Karol Selak
karol selak

Tengo una de mis contraseñas confirmadas en probablemente algunos archivos en mi repositorio de Git. ¿Hay alguna forma de reemplazar esta contraseña con alguna otra cadena en todo el historial automáticamente para que no quede rastro de ella? Idealmente, si pudiera escribir un script bash simple que reciba cadenas para buscar y reemplazar y hacer todo el trabajo por sí mismo, algo como:

./replaceStringInWholeGitHistory.sh "my_password" "xxxxxxxx"

Editar: esta pregunta no es un duplicado de esa, porque estoy preguntando sobre el reemplazo de cadenas sin eliminar archivos completos.

  • Se puede hacer. ¿Ha publicado su repositorio en un servidor remoto (github, gitlab, otro…)? ¿Hay otra persona que trabaje con él?

    – Techniv

    26 de octubre de 2017 a las 9:42

  • Posible duplicado de Eliminar archivos confidenciales y sus confirmaciones del historial de Git

    – kowsky

    26 de octubre de 2017 a las 9:44

  • Para ser estrictos, esta es la cuenta de nuestra empresa, pocas personas tienen acceso a ella y usamos el repositorio interno de GitHub en nuestro propio servidor. Pero, en general, por ahora se confía en todas las personas que tienen acceso al repositorio.

    -Karol Selak

    26 de octubre de 2017 a las 9:46

Avatar de usuario de ElpieKay
elpiekay

Primero, busque todos los archivos que puedan contener la contraseña. Supongamos que la contraseña es abc123 y la rama es master. Es posible que deba excluir aquellos archivos que tienen abc123 sólo como una cadena normal.

git log -S "abc123" master --name-only --pretty=format: | sort -u

Luego reemplace “abc123” con “******”. Supongamos que uno de los archivos es foo/bar.txt.

git filter-branch --tree-filter "if [ -f foo/bar.txt ];then sed -i s/abc123/******/g foo/bar.txt;fi"

Finalmente, forzar el empuje master al repositorio remoto si existe.

git push origin -f master:master

Hice una prueba simple y funcionó, pero no estoy seguro si está bien con su caso. Debe manejar todos los archivos de todas las sucursales. En cuanto a las etiquetas, es posible que deba eliminar todas las antiguas y crear otras nuevas.

  • Hm, está bien, funciona para la rama real, pero al tener más, probablemente tendré que hacer eso para cada una de ellas.

    -Karol Selak

    26 oct 2017 a las 10:40

  • Tengo un problema con sucursales diferentes a master. cuando intento git log -S "abc123" test --name-only --pretty=format: | sort -u me sale error: fatal: ambiguous argument 'test': both revision and filename. ¿Puedo evitarlo de alguna manera?

    -Karol Selak

    6 de noviembre de 2017 a las 17:05

  • @KarolSelak el error dice que tienes un árbitro llamado test y también un archivo llamado test. Es un conflicto de nombres. Si esperas que Git interprete test como referencia, luego use git log -S "abc123" test --name-only --pretty=format: -- | sort -u. Si se interpreta como un archivo, utilice git log -S "abc123" --name-only --pretty=format: -- test | sort -u. Si necesitas ambos, entonces git log -S "abc123" test --name-only --pretty=format: -- test | sort -u. Hay espacios alrededor del --. Ver git-scm.com/docs/gitcli#_descripción para más.

    – Elpie Kay

    6 de noviembre de 2017 a las 22:57


  • Muchas gracias, finalmente he escrito lo que necesito, pero eso es principalmente tu mérito. Espero que la solución final sirva a otros durante mucho tiempo 🙂

    -Karol Selak

    7 de noviembre de 2017 a las 12:47


  • @KarolSelak Me alegro de que ayude =). No olvide eliminar y volver a crear las etiquetas que ha enviado. Todavía están apuntando a las confirmaciones antiguas que pueden contener su contraseña.

    – Elpie Kay

    7 de noviembre de 2017 a las 13:08


Ciro Santilli Avatar de usuario de OurBigBook.com
Ciro Santilli OurBigBook.com

git filter-repo --replace-text

Git 2.25 man git-filter-branch ya recomienda claramente usar git filter-repo en vez de git filter-treeasí que, aquí vamos.

Instalar https://superuser.com/questions/1563034/how-do-you-install-git-filter-repo/1589985#1589985

python3 -m pip install --user git-filter-repo

y luego usa:

echo 'my_password==>xxxxxxxx' > replace.txt
git filter-repo --replace-text replace.txt

o equivalente con Bash magic:

git filter-repo --replace-text <(echo 'my_password==>xxxxxxxx')

Probado con este repositorio de prueba simple: https://github.com/cirosantilli/test-git-filter-repositorio y cadenas de reemplazo:

d1==>asdf
d2==>qwer

Lo anterior actúa en todas las ramas de forma predeterminada (¡¡tan invasivo!), Para actuar solo en las ramas seleccionadas, use: git filter-repo: ¿se puede usar en una rama específica? p.ej:

--refs HEAD
--refs refs/heads/master

La opción --replace-text opción está documentada en: https://github.com/newren/git-filter-repo/blob/7b3e714b94a6e5b9f478cb981c7f560ef3f36506/Documentation/git-filter-repo.txt#L155

–replace-text ::

Un archivo con expresiones que, si se encuentran, serán reemplazadas. Por defecto, cada expresión se trata como texto literal, pero
regex: y glob: Se admiten prefijos. Puede terminar la línea con ==> y algún texto de reemplazo para elegir una opción de reemplazo que no sea la predeterminada de ***REMOVED***.

Por supuesto, una vez que haya enviado una contraseña públicamente, siempre es demasiado tarde y tendrá que cambiar la contraseña, por lo que ni siquiera me molestaría en reemplazar en este caso: Eliminar archivos confidenciales y sus confirmaciones del historial de Git

Relacionado: ¿Cómo sustituir el texto de los archivos en el historial de git?

Probado en git-filter-repo ac039ecc095d.

Avatar de usuario de Karol Selak
karol selak

Al principio, me gustaría agradecer a ElpieKay, quien publicó las funciones principales de mis soluciones, que solo he automatizado.

Entonces, finalmente tengo el guión que quería tener. Lo dividí en partes que dependen unas de otras y pueden servir como guiones independientes. Se parece a esto:

censorStringsInWholeGitHistory.sh:

#!/bin/bash
#arguments are strings to censore

for string in "$@"
do
  echo ""
  echo "================ Censoring string "$string": ================"
  ~/replaceStringInWholeGitHistory.sh "$string" "********"
done

uso:

~/censorStringsInWholeGitHistory.sh "my_password1" "my_password2" "some_f_word"

reemplazarStringInWholeGitHistory.sh:

#!/bin/bash
# $1 - string to find
# $2 - string to replace with

for branch in $(git branch | cut -c 3-); do
  echo ""
  echo ">>> Replacing strings in branch $branch:"
  echo ""
  ~/replaceStringInBranch.sh "$branch" "$1" "$2"
done

uso:

~/replaceStringInWholeGitHistory.sh "my_password" "********"

reemplazarStringInBranch.sh:

#!/bin/bash
# $1 - branch
# $2 - string to find
# $3 - string to replace with

git checkout $1
for file in $(~/findFilesContainingStringInBranch.sh "$2"); do
  echo "          Filtering file $file:"
  ~/changeStringsInFileInCurrentBranch.sh "$file" "$2" "$3"
done

uso:

~/replaceStringInBranch.sh master "my_password" "********"

findFilesContainingStringInBranch.sh:

#!/bin/bash

# $1 - string to find
# $2 - branch name or nothing (current branch in that case)

git log -S "$1" $2 --name-only --pretty=format: -- | sort -u

uso:

~/findFilesContainingStringInBranch.sh "my_password" master

changeStringsInFileInCurrentBranch.sh:

#!/bin/bash

# $1 - file name
# $2 - string to find
# $3 - string to replace

git filter-branch -f --tree-filter "if [ -f $1 ];then sed -i s/$2/$3/g $1;fi"

uso:

~/changeStringsInFileInCurrentBranch.sh "abc.txt" "my_password" "********"

Tengo todos esos scripts ubicados en mi carpeta de inicio, lo que es necesario para que funcione correctamente en esta versión. No estoy seguro de que sea la mejor opción, pero por ahora no puedo encontrar una mejor. Por supuesto, cada script tiene que ser ejecutable, lo que podemos lograr con chmod +x ~/myscript.sh.

Probablemente mi secuencia de comandos no sea óptima, para repositorios grandes se procesará por mucho tiempo, pero funciona 🙂

Y, al final, podemos enviar nuestro repositorio censurado a cualquier control remoto con:

git push <remote> -f --all

Editar: pista importante de ElpieKay:

No olvide eliminar y volver a crear las etiquetas que ha enviado. Todavía están apuntando a las confirmaciones antiguas que pueden contener su contraseña.

Tal vez mejore mi script en el futuro para hacer esto automáticamente.

  • ¿Estos scripts realmente funcionan? No pude hacer que funcionaran: sed: -e expression #1, char 7: falló el filtro del árbol de comandos `s’ sin terminar:

    – hora del Este

    29 de abril de 2020 a las 21:42

  • Sí, lo acabo de comprobar y ahora me funciona. Aunque uso Git v2.17.1, no estoy seguro de qué pasa con las versiones más nuevas. Y uso Ubuntu.

    -Karol Selak

    29 de abril de 2020 a las 22:02


  • ¿Es el problema por casualidad que la cadena sed realmente debería escaparse? No veo cómo podría funcionar esto si contiene espacios, barras o similar

    – hora del Este

    29 de abril de 2020 a las 23:14

  • No sé, mi respuesta se basa en la de ElpieKay (stackoverflow.com/a/46951323/3668967), así que tal vez él pueda ayudarte.

    -Karol Selak

    1 de mayo de 2020 a las 15:27

¿Ha sido útil esta solución?