Cómo grep (buscar) código comprometido en el historial de Git

1435

He eliminado un archivo o algún código en un archivo en algún momento del pasado. ¿Puedo grep en el contenido (no en los mensajes de confirmación)?

Una solución muy pobre es grep el registro:

git log -p | grep <pattern>

Sin embargo, esto no devuelve el hash de confirmación de inmediato. He jugado un poco con git grepvano.

Ortwin Gentz
fuente
2
Estas publicaciones de blog de Junio ​​C Hamano (mantenedor de git) pueden ser interesantes para usted: * La última herramienta de seguimiento de contenido de Linus (sobre búsqueda de picos, es decir, git log -Sy culpa) * [Diversión con "git log --grep"] [2] (búsqueda de mensajes de confirmación ) * [Diversión con "git grep"] [3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html
Jakub Narębski
la respuesta del posible duplicado realmente funciona: stackoverflow.com/a/1340245/492
CAD bloke
problema con esto es que no da ningún contexto al cambio ... es decir, quién / cuándo
Sonic Soul

Respuestas:

1890

Para buscar contenido de confirmación (es decir, líneas de origen reales, en lugar de mensajes de confirmación y similares), debe hacer lo siguiente:

git grep <regexp> $(git rev-list --all)

git rev-list --all | xargs git grep <expression> funcionará si te encuentras con un error "Lista de argumentos demasiado larga".

Si desea limitar la búsqueda a algún subárbol (por ejemplo, "lib / util"), deberá pasar eso al rev-listsubcomando y greptambién:

git grep <regexp> $(git rev-list --all -- lib/util) -- lib/util

Esto buscará en todo su texto de confirmación regexp.

La razón para pasar la ruta en ambos comandos es porque rev-listdevolverá la lista de revisiones donde lib/utilocurrieron todos los cambios , pero también debe pasar para grepque solo busque lib/util.

Solo imagine el siguiente escenario: greppodría encontrar lo mismo <regexp>en otros archivos que están contenidos en la misma revisión devuelta por rev-list(incluso si no hubo cambios en ese archivo en esa revisión).

Aquí hay otras formas útiles de buscar su fuente:

Busque en el árbol de trabajo el texto que coincida con la expresión regular regexp:

git grep <regexp>

Busque en el árbol de trabajo líneas de texto que coincidan con la expresión regular regexp1 o regexp2:

git grep -e <regexp1> [--or] -e <regexp2>

Busque en el árbol de trabajo líneas de texto que coincidan con la expresión regular regexp1 y regexp2, informando solo las rutas de los archivos:

git grep -l -e <regexp1> --and -e <regexp2>

Busque en el árbol de trabajo archivos que tengan líneas de texto que coincidan con la expresión regular regexp1 y líneas de texto que coincidan con la expresión regular regexp2:

git grep -l --all-match -e <regexp1> -e <regexp2>

Busque en el árbol de trabajo líneas modificadas de patrones de coincidencia de texto

git diff --unified=0 | grep <pattern>

Busque en todas las revisiones el texto que coincida con la expresión regular regexp:

git grep <regexp> $(git rev-list --all)

Busque en todas las revisiones entre rev1 y rev2 el texto que coincida con la expresión regular regexp:

git grep <regexp> $(git rev-list <rev1>..<rev2>)
Jeet
fuente
61
Gracias, funciona muy bien! Sin embargo, es triste que se necesite "$ (git rev-list --all)" y que no haya un cambio conveniente para especificar la búsqueda en todo el historial de una sucursal.
Ortwin Gentz
3
Excelente. +1. El GitBook agrega algunos detalles ( book.git-scm.com/4_finding_with_git_grep.html ), y Junio ​​C Hamano ilustra algunos de sus puntos: gitster.livejournal.com/27674.html
VonC
18
Desafortunadamente, no puedo hacer que esto funcione con msysgit-1.7.4. Me dice sh.exe": /bin/git: Bad file number. La respuesta de VonC también funciona con msysgit.
eckes
44
Si obtiene un error "no se puede leer el árbol" cuando invoca el historial de git grep con rev-list, es posible que deba limpiar las cosas. Pruebe git gco eche un vistazo: stackoverflow.com/questions/1507463/…
Anthony Panozzo
8
Sí, esto parece fallar también en Windows, por desgracia.
mlissner
552

Debe usar la opción pickaxe ( -S) de git log.

Para buscar Foo:

git log -SFoo -- path_containing_change
git log -SFoo --since=2009.1.1 --until=2010.1.1 -- path_containing_change

Vea el historial de Git: encuentre la línea perdida por palabra clave para obtener más información.


Como Jakub Narębski comentó:

  • esto busca diferencias que introducen o eliminan una instancia de<string> . Por lo general, significa "revisiones donde agregó o eliminó la línea con 'Foo'".

  • la --pickaxe-regexopción le permite usar expresiones regulares POSIX extendidas en lugar de buscar una cadena. Ejemplo (de git log):git log -S"frotz\(nitfol" --pickaxe-regex


Como comentó Rob , esta búsqueda distingue entre mayúsculas y minúsculas: abrió una pregunta de seguimiento sobre cómo buscar entre mayúsculas y minúsculas.

VonC
fuente
3
Gracias, no estaba al tanto de esta opción. Parece que esta es la mejor solución si está interesado en los mensajes de confirmación y la solución de Jeet es la más adecuada si necesita el comportamiento grep tradicional de UNIX de coincidencia de línea pura.
Ortwin Gentz
@Ortwin: de acuerdo (y he votado a favor la solución elegida). la git logparte de tu pregunta me confundió;)
VonC
12
Combínalo con la -pbandera para generar también la diferencia.
Sander
¿Hay alguna forma de excluir todos los directorios que coincidan con patrones específicos usando git log -S?
BakaKuna
3
@Anentropic necesitaría las --branches --allopciones para buscar el repositorio completo.
VonC
249

Mi forma favorita de hacerlo es con git logla -Gopción (agregada en la versión 1.7.4).

-G<regex>
       Look for differences whose added or removed line matches the given <regex>.

Hay una sutil diferencia entre la forma en que las opciones -Gy -Sdeterminan si una confirmación coincide:

  • La -Sopción esencialmente cuenta el número de veces que su búsqueda coincide en un archivo antes y después de una confirmación. La confirmación se muestra en el registro si los conteos antes y después son diferentes. Esto, por ejemplo, no mostrará confirmaciones donde se movió una línea que coincide con su búsqueda.
  • Con el -G opción, la confirmación se muestra en el registro si su búsqueda coincide con cualquier línea que se agregó, eliminó o modificó.

Tome este compromiso como un ejemplo:

diff --git a/test b/test
index dddc242..60a8ba6 100644
--- a/test
+++ b/test
@@ -1 +1 @@
-hello hello
+hello goodbye hello

Debido a que el número de veces que aparece "hola" en el archivo es el mismo antes y después de esta confirmación, no coincidirá con el uso -Shello. Sin embargo, dado que hubo un cambio en una coincidencia de línea hello, la confirmación se mostrará usando -Ghello.

Tyler Holien
fuente
2
¿Hay alguna manera de mostrar el contexto de cambio coincidente en la salida del registro git?
Thilo-Alexander Ginkel
13
@ Thilo-AlexanderGinkel: por lo general, solo agrego la -popción de mostrar una diferencia para cada confirmación. Luego, cuando el registro se abre en mi buscapersonas, busco lo que sea que esté buscando. Si su buscapersonas es lessy usted git log -Ghello -p, puede escribir /hello, presionar Entery usar ny Npara encontrar las ocurrencias siguientes / anteriores de "hola".
Tyler Holien
Encontré un problema interesante con -Gy Regex: si la línea de comando usa UTF-8 y el archivo que está mirando usa algo de codificación ISO-Latina (8 bits), .*falla. Por ejemplo, tengo un cambio Vierter Entwurf-> Fünfter Entwurf, y aunque 'V.*ter Entwurf'produce una coincidencia, 'F.*ter Entwurf'no.
U. Windl
51

Si desea examinar los cambios en el código (vea lo que realmente se ha cambiado con la palabra dada en todo el historial) vaya al patchmodo: encontré una combinación muy útil de hacer:

git log -p
# Hit '/' for search mode.
# Type in the word you are searching.
# If the first search is not relevant, hit 'n' for next (like in Vim ;) )
Bartek Skwira
fuente
11
La solución aceptada no funciona para mí ni el git log -S. Este lo hizo!
rodvlopes
29

git log puede ser una forma más efectiva de buscar texto en todas las ramas, especialmente si hay muchas coincidencias y desea ver primero los cambios más recientes (relevantes).

git log -p --all -S 'search string'
git log -p --all -G 'match regular expression'

Estos comandos de registro enumeran confirmaciones que agregan o eliminan la cadena de búsqueda / expresión regular dada (generalmente) más reciente primero. los-p opción hace que la diferencia relevante se muestre donde se agregó o eliminó el patrón, por lo que puede verlo en contexto.

Después de encontrar una confirmación relevante que agregue el texto que estaba buscando (por ejemplo, 8beeff00d), busque las ramas que contienen la confirmación:

git branch -a --contains 8beeff00d
Edward Anderson
fuente
Hola, estas líneas no parecen funcionar en absoluto. Mi comando es> git log -p --todos -S 'public string DOB {get; conjunto; } = string.Empty; ' y cada vez que intento ejecutarlo obtengo> fatal: argumento ambiguo 'cadena': revisión desconocida o ruta no en el árbol de trabajo. > Use '-' para separar las rutas de las revisiones, de esta manera:> 'git <comando> [<revisión> ...] - [<archivo> ...]'
usuario216652
@ user216652 Por alguna razón, las 'comillas no agrupan su cadena de búsqueda como un solo argumento. En cambio, 'publices el argumento de -S, y trata el resto como argumentos separados. No estoy seguro de en qué entorno se está ejecutando, pero ese contexto sería necesario para ayudar a solucionar problemas. Sugeriría abrir una pregunta separada de StackOverflow si es necesario para ayudarlo a solucionar problemas, con todo el contexto de cómo se envía su comando git al shell. ¿Me parece que se está enviando a través de algún otro comando? Los comentarios aquí no son el lugar correcto para resolver esto.
Edward Anderson
26

Tomé la respuesta de Jeet y la adapté a Windows (gracias a esta respuesta ):

FOR /F %x IN ('"git rev-list --all"') DO @git grep <regex> %x > out.txt

Tenga en cuenta que para mí, por alguna razón, la confirmación real que eliminó esta expresión regular no apareció en la salida del comando, sino que una confirmación anterior.

ripper234
fuente
2
+1 - y si desea evitar presionar "q" después de cada búsqueda, agregue --no-pageral comando git al final
cgp
2
Además, me gustaría señalar que agregar un archivo de texto tiene la ventaja adicional de mostrar realmente el texto correspondiente. (añadir a un archivo de texto usando >>results.txtpara aquellos que no versado en Windows tuberías ...
CGP
1
Y pensé que la sintaxis de bash es fea :)
smido
23

Buscar en cualquier revisión, cualquier archivo :

git rev-list --all | xargs git grep <regexp>

Buscar solo en algunos archivos, por ejemplo, archivos XML:

git rev-list --all | xargs -I{} git grep <regexp> {} -- "*.xml"

Las líneas de resultado deberían verse así: 6988bec26b1503d45eb0b2e8a4364afb87dde7af: bla.xml: texto de la línea que encontró ...

Luego puede obtener más información como autor, fecha y diferencia usando git show:

git show 6988bec26b1503d45eb0b2e8a4364afb87dde7af
Christophe Roussy
fuente
11

Para simplificar, sugeriría usar GUI: gitk : el navegador de repositorios Git . Es bastante flexible

  1. Para buscar el código:

    Ingrese la descripción de la imagen aquí
  2. Para buscar archivos:

    Ingrese la descripción de la imagen aquí
  3. Por supuesto, también admite expresiones regulares:

    Ingrese la descripción de la imagen aquí

Y puede navegar por los resultados con las flechas arriba / abajo.

watashiSHUN
fuente
6

Para cualquier otra persona que intente hacer esto en Sourcetree , no hay un comando directo en la interfaz de usuario (a partir de la versión 1.6.21.0). Sin embargo, puede usar los comandos especificados en la respuesta aceptada abriendo Terminal ventana (botón disponible en la barra de herramientas principal) y copiándolos / pegándolos allí.

Nota: La vista de búsqueda de Sourcetree puede hacer búsquedas de texto en parte. Presione Ctrl+ 3para ir a la vista de Búsqueda (o haga clic en la pestaña Buscar disponible en la parte inferior). Desde el extremo derecho, configure el tipo de búsqueda en Cambios de archivo y luego escriba la cadena que desea buscar. Este método tiene las siguientes limitaciones en comparación con el comando anterior:

  1. Sourcetree solo muestra los commits que contienen la palabra de búsqueda en uno de los archivos modificados. Encontrar el archivo exacto que contiene el texto de búsqueda es nuevamente una tarea manual.
  2. RegEx no es compatible.
punto net
fuente
4

Cada vez que me encuentro en su lugar, uso la siguiente línea de comando:

git log -S "<words/phrases i am trying to find>" --all --oneline  --graph

Explicación:

  1. git log- Necesito escribir más aquí; Muestra los registros en orden cronológico.
  2. -S "<words/phrases i am trying to find>" - Muestra todas las confirmaciones de Git donde cualquier archivo (agregado / modificado / eliminado) tiene las palabras / frases que estoy tratando de encontrar sin los símbolos '<>'.
  3. --all - Hacer cumplir y buscar en todas las ramas.
  4. --oneline - Comprime el registro de Git en una línea.
  5. --graph - Crea el gráfico de confirmaciones ordenadas cronológicamente.
surajs1n
fuente
1
"¡Cada vez que me encuentro en tu casa, siento la necesidad de usar git!"
Sebi
1
¡Esta es una respuesta genial!
Alf Eaton
@AlfEaton un placer!
surajs1n
2

La respuesta de Jeet funciona en PowerShell.

git grep -n <regex> $(git rev-list --all)

A continuación se muestran todos los archivos, en cualquier confirmación, que contengan a password.

# Store intermediate result
$result = git grep -n "password" $(git rev-list --all)

# Display unique file names
$result | select -unique { $_ -replace "(^.*?:)|(:.*)", "" }
Shaun Luttin
fuente
1

Entonces, ¿estás tratando de buscar versiones anteriores del código para ver dónde existe algo por última vez?

Si estuviera haciendo esto, probablemente usaría git bisect . Con bisect, puede especificar una versión buena conocida, una versión mala conocida y un script simple que verifica si la versión es buena o mala (en este caso, un grep para ver si el código que está buscando está presente) ) Al ejecutar esto, se encontrará cuando se eliminó el código.

Rob Di Marco
fuente
2
Sí, pero su "prueba" puede ser un script que busca el código y devuelve "verdadero" si el código existe y "falso" si no existe.
Rob Di Marco
2
Bueno, ¿y si el código fuera malo en la revisión 10, se vuelva bueno en la revisión 11 y vuelva a ser malo en la revisión 15 ...
Paolo
2
Estoy de acuerdo con Paolo La búsqueda binaria solo es apropiada para valores "ordenados". En el caso de git bisect, esto significa que todas las revisiones "buenas" van antes que todas las revisiones "malas", comenzando desde el punto de referencia, pero esa suposición no se puede hacer cuando se busca código transitorio. Esta solución puede funcionar en algunos casos, pero no es una buena solución de propósito general.
Kent
Creo que esto es altamente ineficiente ya que todo el árbol se extrae varias veces para bisecar.
U. Windl
0

Escenario: realizó una gran limpieza de su código utilizando su IDE. Problema: el IDE limpió más de lo debido y ahora el código no se compila (faltan recursos, etc.)

Solución:

git grep --cached "text_to_find"

Encontrará el archivo donde se modificó "text_to_find".

Ahora puede deshacer este cambio y compilar su código.

Garytech
fuente
0
git rev-list --all | xargs -n 5 git grep EXPRESSION

es un ajuste a la solución de Jeet , por lo que muestra resultados mientras busca y no solo al final (lo que puede llevar mucho tiempo en un repositorio grande).

laktak
fuente
-1

En mi caso, necesitaba buscar un commit corto y, por desgracia, las soluciones enumeradas no funcionaban.

Logré hacerlo con (reemplazar el token REGEX ):

for commit in $(git rev-list --all --abbrev-commit)
do
    if [[ $commit =~ __REGEX__ ]]; then 
        git --no-pager show -s --format='%h %an - %s' $commit
    fi
done
usuario9869932
fuente