Git: ¿cómo veo el historial de cambios de un método / función?

93

Entonces encontré la pregunta sobre cómo ver el historial de cambios de un archivo, pero el historial de cambios de este archivo en particular es enorme y realmente solo estoy interesado en los cambios de un método en particular. Entonces, ¿sería posible ver el historial de cambios solo para ese método en particular?

Sé que esto requeriría que git analizara el código y que el análisis sería diferente para diferentes lenguajes, pero las declaraciones de método / función se ven muy similares en la mayoría de los lenguajes, así que pensé que tal vez alguien haya implementado esta característica.

El idioma con el que estoy trabajando actualmente es Objective-C y el SCM que estoy usando actualmente es git, pero me interesaría saber si esta función existe para cualquier SCM / idioma.

Erik B
fuente
1
He visto esa funcionalidad en la propuesta de Git GSoG.
Vi.
¿Es esta la propuesta de la que hablabas? list-archives.org/git/…
Erik B
@lpapp, la pregunta aquí se hizo 10 meses antes, la otra pregunta debe marcarse como engañada de esta (si es que son engañados).
Dirty-flow
2
@lpapp Esas son dos preguntas completamente diferentes. Posiblemente podría escribir un script que resuelva el nombre de una función en un rango de líneas y luego usar la técnica de esa pregunta para obtener el historial de esas líneas, pero en sí mismo no responde a esta pregunta.
Erik B

Respuestas:

100

Las versiones recientes de git logaprendieron una forma especial del -Lparámetro:

-L: <nombre_func>: <archivo>

Rastree la evolución del rango de líneas dado por "<start>,<end>"(o el nombre de la función regex <funcname>) dentro de <file>. No puede dar ningún limitador de especificación de ruta. Actualmente, esto está limitado a una caminata a partir de una única revisión, es decir, solo puede dar cero o uno argumentos de revisión positivos. Puede especificar esta opción más de una vez.
...
Si “:<funcname>”se da en lugar de <start>y <end>, es una expresión regular que denota el rango desde la primera línea de nombre de función que coincide <funcname>, hasta la siguiente línea de nombre de función. “:<funcname>”busca desde el final del -Lrango anterior , si lo hubiera, de lo contrario desde el inicio del archivo. “^:<funcname>”busca desde el inicio del archivo.

En otras palabras: si le pides a Git que git log -L :myfunction:path/to/myfile.clo haga, ahora imprimirá felizmente el historial de cambios de esa función.

eckes
fuente
16
esto puede funcionar para objetivo-c listo para usar, pero si está haciendo esto para otros lenguajes (por ejemplo, Python, Ruby, etc.), es posible que deba agregar la configuración adecuada dentro de un archivo .gitattributes para que git reconozca la función / método declaraciones en ese idioma. Para python use * .py diff = python, para ruby ​​use * .rb diff = ruby
samaspin
1
¿Cómo rastrea git la función?
nn0p
@ nn0p Supongo que tengo un conocimiento de sintaxis de varios idiomas y, por lo tanto, sé cómo aislar una función y rastrear sus cambios.
JasonGenX
4
Ampliando el comentario de @ samaspin, para otros idiomas puede consultar los documentos aquí: git-scm.com/docs/gitattributes#_generating_diff_text
edhgoose
Esto no funciona para lenguajes como Scala y Java e incluso C que usan llaves anidadas (las expresiones regulares no pueden manejar eso en general), e incluso la forma inicial y final no funciona cuando la función se mueve en el archivo y modificado en el mismo compromiso.
Robin Green
16

El uso git gui blamees difícil de hacer uso de secuencias de comandos, y al mismo tiempo git log -G, y git log --pickaxecada uno puede mostrar que cuando la definición del método apareció o desapareció, no he encontrado ninguna manera de hacer que hagan una lista de todos los cambios realizados en el cuerpo de su método.

Sin embargo, puede usar gitattributesy la textconvpropiedad para armar una solución que haga precisamente eso. Aunque estas características originalmente estaban destinadas a ayudarle a trabajar con archivos binarios, funcionan igual de bien aquí.

La clave es hacer que Git elimine del archivo todas las líneas excepto las que le interesan antes de realizar cualquier operación de diferenciación. Entonces git log, git diffetc. verá sólo el área que le interesa.

Aquí está el esquema de lo que hago en otro idioma; puede modificarlo según sus propias necesidades.

  • Escriba un breve script de shell (u otro programa) que tome un argumento, el nombre de un archivo fuente, y muestre solo la parte interesante de ese archivo (o nada si ninguno es interesante). Por ejemplo, puede utilizar sedlo siguiente:

    #!/bin/sh
    sed -n -e '/^int my_func(/,/^}/ p' "$1"
    
  • Defina un textconvfiltro Git para su nuevo script. (Consulte la gitattributespágina de manual para obtener más detalles). El nombre del filtro y la ubicación del comando pueden ser los que desee.

    $ git config diff.my_filter.textconv /path/to/my_script
    
  • Dile a Git que use ese filtro antes de calcular las diferencias para el archivo en cuestión.

    $ echo "my_file diff=my_filter" >> .gitattributes
    
  • Ahora, si usa -G.(tenga en cuenta .) para enumerar todas las confirmaciones que producen cambios visibles cuando se aplica su filtro, tendrá exactamente las confirmaciones que le interesan. Cualquier otra opción que use las rutinas diff de Git, como --patch, también obtenga esta vista restringida.

    $ git log -G. --patch my_file
    
  • ¡Voilà!

Una mejora útil que podría querer hacer es que su script de filtro tome un nombre de método como primer argumento (y el archivo como segundo). Esto le permite especificar un nuevo método de interés simplemente llamando git config, en lugar de tener que editar su secuencia de comandos. Por ejemplo, podrías decir:

$ git config diff.my_filter.textconv "/path/to/my_command other_func"

Por supuesto, el script de filtro puede hacer lo que quiera, aceptar más argumentos o lo que sea: hay mucha flexibilidad más allá de lo que he mostrado aquí.

Paul Whittaker
fuente
1
La toma de más de un argumento no funcionó para mí, pero poner el nombre de la función en el trabajo duro funciona bien y esto es realmente increíble.
qwertzguy
Brillante, aunque me pregunto qué tan (in) conveniente es cambiar entre muchos métodos diferentes. Además, ¿conoce algún programa que pueda extraer una función completa similar a C?
nafg
12

git log tiene una opción '-G' que podría usarse para encontrar todas las diferencias.

-G Busque diferencias cuya línea agregada o eliminada coincida con la dada <regex>.

Solo dale una expresión regular adecuada del nombre de la función que te interesa. Por ejemplo,

$ git log --oneline -G'^int commit_tree'
40d52ff make commit_tree a library function
81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory
7b9c0a6 git-commit-tree: make it usable from other builtins
lht
fuente
5
No ejecuté el comando, pero me parece que este comando solo mostraría las confirmaciones que tocan la línea que coincide con la expresión regular, que no es el método / función completo.
Erik B
si desea más contexto, puede reemplazar --onelinecon-p
lfender6445
3
Pero, ¿y si se hiciera un cambio de 20 líneas en el método?
nafg
+1. Para mí, la respuesta principal funcionó, pero solo mostró la confirmación más reciente. Posiblemente debido a rebases o la función moviéndose unas cuantas veces, no estoy seguro. Al buscar la línea de código real en lugar de la función en la que se encontraba (aunque en una sola línea), esta respuesta me permitió detectar fácilmente la confirmación que estaba buscando. ¡Afortunadamente, suelo escribir mensajes de confirmación útiles!
Dave S
11

Lo más cercano que puede hacer es determinar la posición de su función en el archivo (por ejemplo, digamos que su función i_am_buggyestá en las líneas 241-263 de foo/bar.c), luego ejecute algo con el efecto de:

git log -p -L 200,300:foo/bar.c

Esto abrirá menos (o un localizador equivalente). Ahora puede escribir /i_am_buggy(o su equivalente en buscapersonas) y comenzar a revisar los cambios.

Esto incluso podría funcionar, dependiendo de su estilo de código:

git log -p -L /int i_am_buggy\(/,+30:foo/bar.c

Esto limita la búsqueda desde el primer hit de esa expresión regular (idealmente su declaración de función) a treinta líneas después de eso. El argumento final también puede ser una expresión regular , aunque detectar eso con expresiones regulares es una proposición más dudosa.

badp
fuente
¡Dulce! FYI, esto es nuevo en Git v1.8.4. (Supongo que debería actualizarme). Aunque una solución más precisa sería buena ... como si alguien escribiera la respuesta de Paul Whittaker.
Greg Price
@GregPrice Aparentemente, los bordes de la búsqueda pueden incluso ser expresiones regulares , por lo que al menos puede tener un punto de inicio más o menos preciso.
badp
Oh wow. De hecho: en lugar de escribir su propia expresión regular, puede simplemente decir -L ":int myfunc:foo/bar.c"y limitar a la función con ese nombre. Esto es fantástico, ¡gracias por el puntero! Ahora, si solo la detección de funciones fuera un poco más confiable ...
Greg Price
3

La forma correcta es usarlo git log -L :function:path/to/filecomo se explica en la respuesta de eckes .

Pero además, si su función es muy larga, es posible que desee ver solo los cambios que han introducido varias confirmaciones, no todas las líneas de función, incluidas sin modificar, para cada confirmación que tal vez toque solo una de estas líneas. Como lo hace una persona normal diff.

Normalmente git logpuede ver diferencias con -p, pero esto no funciona con -L. Por lo tanto, grep git log -Ldebe mostrar solo las líneas involucradas y el encabezado de confirmaciones / archivos para contextualizarlos. El truco aquí es hacer coincidir solo las líneas de color de la terminal, agregando un --colorinterruptor, con una expresión regular. Finalmente:

git log -L :function:path/to/file --color | grep --color=never -E -e "^(^[\[[0-9;]*[a-zA-Z])+" -3

Tenga en cuenta que ^[debe ser real, literal ^[. Puede escribirlos presionando ^ V ^ [en bash, es decir Ctrl+ V, Ctrl+ [. Referencia aquí .

También último -3interruptor, permite imprimir 3 líneas de contexto de salida, antes y después de cada línea emparejada. Es posible que desee ajustarlo a sus necesidades.

Hazzard17
fuente
2

git blame te muestra quién cambió por última vez cada línea del archivo; puede especificar las líneas a examinar para evitar que el historial de líneas esté fuera de su función.

Será
fuente
4
con git gui blamepuede navegar por revisiones anteriores.
Vi.
0
  1. Muestra el historial de funciones con git log -L :<funcname>:<file>como se muestra en la respuesta de eckes y git doc

    Si no muestra nada, consulte Definición de un encabezado de trozo personalizado para agregar algo similar *.java diff=javaal .gitattributes archivo para admitir su idioma.

  2. Mostrar historial de funciones entre confirmaciones con git log commit1..commit2 -L :functionName:filePath

  3. Muestra el historial de funciones sobrecargado (puede haber muchas funciones con el mismo nombre, pero con diferentes parámetros) con git log -L :sum\(double:filepath

Kris Roofe
fuente