Un asistente de IA propone eliminar una comprobación de una pequeña función que lee líneas. «El llamador ya proporciona números de línea válidos», dice. La función parece sencilla. Antes de aceptar el cambio, consultas el historial Git y la línea apunta a un commit llamado «Compact comparison spacing».

Eso te da otro lugar donde investigar. La pregunta útil es qué entrada protege la comprobación, cuándo apareció ese comportamiento y si los llamadores actuales siguen necesitándolo. Un historial pequeño que puedas reconstruir permite concretar esas preguntas.

Empieza por la línea sospechosa

Supongamos que un visor de código usa números de línea que empiezan en uno. Su función actual tiene este aspecto:

def read_line(lines, line_number):
    """Read a source line using a one-based line number."""
    if line_number<1:
        raise ValueError("line number must be positive")
    if line_number > len(lines):
        raise IndexError("line number is past the end")
    return lines[line_number - 1]

Para las dos líneas ["alpha", "omega"], la línea 1 debería devolver alpha y la línea 2, omega. Sin embargo, en Python un índice -1 lee el último elemento. Sin la comprobación del límite inferior, el número de línea solicitado 0 se convierte en índice -1 y devuelve omega. El número de línea -1 se convierte en índice -2 y devuelve alpha.

Puedes explicar eso a partir del código actual. El historial responde a otra pregunta: ¿cuándo pasó ese rechazo a formar parte de la función y qué incluía el cambio?

Reconstruye los cuatro commits

Descarga git-history-lab.py en una carpeta de trabajo vacía. Necesitas Git 2.29+ y Python 3.9+; no hay dependencias de paquetes. Ejecuta esto en tu propio terminal:

python3 git-history-lab.py create history-lab --record history-check
cd history-lab
git log --reverse --format='%h %s' HEAD

En Windows, usa tu lanzador de Python 3 en lugar de python3 si es necesario. El flujo usa Git y Python, sin API de macOS. La ejecución registrada usó Git 2.50.1 y Python 3.9.6 en macOS; Windows y Linux no se probaron por separado.

El script crea un repositorio local nuevo, ejecuta las comprobaciones y guarda su salida en history-check. Rechaza un destino existente, usa un autor ficticio solo para sus propios commits y no cambia tu configuración global de Git. Todos los nombres, fechas, mensajes de commit y el escenario del visor son ejemplos didácticos.

ce4c0cf Add one-based source line lookup
e87088e Reject nonpositive source line numbers
7d41ebc Rename line reader module
27a0dc5 Compact comparison spacing

Cada commit tiene también una etiqueta legible:

Etiqueta Cambio
baseline Añade line_reader.py; las pruebas cubren la primera línea, la última y una entrada posterior al final
fix-positive-lines Añade la comprobación inferior y pruebas para 0, -1 y una lista de entrada vacía
rename-module Cambia el nombre del archivo a source_lines.py y actualiza la importación de la prueba
format-spacing Cambia line_number < 1 por line_number<1

El espaciado compacto es deliberadamente llamativo para reconocer fácilmente la última modificación.

Deja que blame te lleve al siguiente diff

Inspecciona la línea 3:

git blame -L 3,3 HEAD -- source_lines.py
git show --format=fuller format-spacing -- source_lines.py

Blame atribuye la línea a 27a0dc57, el commit de espaciado. Su cambio contiene:

-    if line_number < 1:
+    if line_number<1:

Ahora pide a Git que ignore los espacios al rastrear la línea:

git blame -w -L 3,4 HEAD -- source_lines.py

El resultado apunta a e87088e0 y a la ruta antigua, line_reader.py. Git blame sigue automáticamente los cambios de nombre de archivos completos; -w ignora los espacios en la atribución. Aquí, el cambio de nombre no provocó el resultado engañoso; lo hizo la modificación posterior del espaciado. Consulta la documentación oficial de git-blame.

Para esta modificación concreta, el verificador también compara los árboles sintácticos de Python y ejecuta las pruebas antes y después. Ambos resultados coinciden. Ignorar espacios sirve para investigar la atribución, no para probar el comportamiento: en Python pueden importar la sangría y los espacios dentro de cadenas.

Después de inspeccionar un commit de formato conocido, también puedes excluirlo expresamente:

git blame --ignore-rev format-spacing -L 3,3 HEAD -- source_lines.py

Eso también llega a la corrección en este laboratorio. No excluyas un commit solo porque su título diga «format»; lee primero su cambio. Blame registra la atribución de líneas, no el razonamiento de un desarrollador ni su responsabilidad por un error.

Busca el texto que sobrevive a la reescritura

El mensaje de error se mantuvo igual tras el cambio de nombre y el de espaciado. Busca cuándo se introdujo:

git log --format='%h %s' -p -S 'line number must be positive' HEAD -- '*.py'

Esto devuelve la corrección y su cambio en line_reader.py. Poner *.py entre comillas deja la coincidencia de rutas en manos de Git e incluye el nombre antiguo. Este repositorio solo tiene un commit que añade o elimina ese mensaje.

-S selecciona cambios en el número de apariciones de una cadena. No selecciona todas las modificaciones cercanas a una coincidencia. Consulta la explicación de pickaxe de Git.

Prueba una cadena que sí cambió al modificar el formato:

git log --format='%h %s' -p -S 'line_number < 1' HEAD -- '*.py'

Ahora la salida incluye tanto el commit de espaciado, que elimina esa escritura exacta, como la corrección, que la añade. La coincidencia adicional es útil: lee qué cambió y elige un fragmento más estable si hace falta. La ausencia de resultados también requiere interpretación; el texto puede cambiar sin que cambie su número de apariciones.

Para un archivo conocido, este laboratorio también funciona con:

git log --follow --format='%h %s' -p -S 'line number must be positive' HEAD -- source_lines.py

--follow maneja el historial de cambios de nombre de un solo archivo. No es una búsqueda general entre archivos. En una investigación real, comprueba las rutas anteriores o amplía la selección cuando una búsqueda restringida se detenga demasiado pronto.

Sigue las líneas en el tiempo con log -L

Si ya conoces el intervalo relevante, pide sus cambios a lo largo del historial:

git log --format='%h %s' -L 3,4:source_lines.py HEAD

En este repositorio, la salida incluye la modificación de espacios y la incorporación original de la comprobación. Atraviesa el cambio de nombre puro del archivo, pero el bloque renombrado sin modificaciones no recibe un cambio separado. Usa el registro de cuatro commits anterior cuando necesites la secuencia completa.

Para la función entera, ejecuta:

git log --format='%h %s' -L :read_line:source_lines.py HEAD

Esto también llega a la expresión de indexación original en baseline. El laboratorio declara *.py diff=python en .gitattributes para que Git use sus reglas de cabeceras de funciones Python. El reconocimiento de funciones depende del lenguaje y de la configuración de diff; consulta cómo definir una cabecera de bloque personalizada. Una alternativa numérica para este archivo es:

git log --format='%h %s' -L 1,7:source_lines.py HEAD

Con log -L, la ruta forma parte del argumento del intervalo; no añadas un limitador -- path. El intervalo debe existir en la revisión inicial y el recorrido empieza en una sola revisión. Estas restricciones figuran en la documentación oficial de git-log.

Los historiales reales pueden contener merges, código copiado, funciones reescritas o commits antiguos ausentes en un clon superficial. Seguir una línea es un punto de partida para leer cambios; no puede recuperar motivos que nunca se registraron.

Prueba la explicación en ambas versiones

Lee conjuntamente la implementación y las pruebas de la corrección encontrada:

git show --format=fuller fix-positive-lines -- line_reader.py test_lines.py

El cambio añade la comprobación inferior y una prueba que espera ValueError para 0 y -1. El verificador del laboratorio extrae el código y las pruebas de los commits en carpetas temporales. Ejecuta la misma batería de regresión contra la implementación inicial y después ejecuta las pruebas propias de cada commit. No cambia tu copia de trabajo.

Observación de la ejecución registrada Qué respalda
La versión inicial devuelve omega para 0 y alpha para -1 Esas entradas alcanzan la indexación negativa de Python
La batería de la corrección falla sobre la versión inicial para ambas entradas Las aserciones añadidas detectan el comportamiento antiguo
Las versiones de corrección, cambio de nombre y espaciado pasan cinco pruebas cada una Los casos probados se comportan como se esperaba tras la corrección
El cambio de nombre conserva los bytes del código; el espaciado conserva el árbol sintáctico Esas modificaciones posteriores conservan la implementación de la función

Las tres pruebas originales de la versión inicial también pasan. Una batería en verde puede simplemente no incluir la entrada que te interesa. Los resultados esperados completos describen las comprobaciones; tu history-check/transcript.txt contiene los comandos y salidas reales, incluidos los dos fallos esperados.

El mensaje del commit de ejemplo describe cómo la línea cero devuelve la última línea; las pruebas reproducen ese comportamiento. Antes de eliminar la comprobación en tu proyecto, revisa cómo los llamadores actuales rechazan cero y números negativos. El ejemplo supone entradas enteras; otros tipos necesitan validación aparte.

Plantea a la IA una pregunta que pueda comprobar

Cuando tengas los identificadores de revisión, el cambio y la salida de las pruebas, pide al asistente que los relacione:

Explica por qué existe esta comprobación del límite inferior. Usa la corrección proporcionada y su commit padre, muestra el resultado de las entradas 0 y -1 e identifica las aserciones de regresión. Separa lo que muestran el diff y las pruebas ejecutadas de lo que afirma el mensaje del commit. Después inspecciona los llamadores actuales antes de recomendar eliminarla. Si esos llamadores o informes históricos no están disponibles, indica qué conclusión sigue pendiente.

La respuesta útil tiene tres partes: el cambio de comportamiento, la motivación declarada y lo que sigue sin saberse sobre los llamadores actuales. Si afirma que ocurrió un incidente solo porque lo dice el mensaje, pide que limite la afirmación. Si propone eliminar la comprobación, pide que muestre cómo todas las rutas de entrada admitidas excluyen las entradas que fallan. Seguir un valor por código desconocido es un buen siguiente paso para investigar los llamadores; revisar un cambio escrito por IA muestra cómo convertir una explicación propuesta en una prueba de regresión específica.