Un asistente de IA dice haber añadido búsqueda por título a una lista de libros. git diff muestra una importación nueva y unas líneas modificadas. La implementación de búsqueda no aparece y el código propuesto no devuelve un libro que sí está en el catálogo. Antes de decidir si el parche es correcto, averigua qué comparó realmente Git.
Descarga el laboratorio Git diff: una aplicación de seis libros con un parche y pruebas creados para este tutorial. Necesitas Node.js 22 o posterior y Git, sin dependencias ni acceso a la red.
Elige los dos estados antes de leer las líneas
Git mantiene un árbol de trabajo, un índice con la instantánea preparada y las instantáneas confirmadas. Estos comandos responden preguntas distintas:
| Comando | Qué compara |
|---|---|
git diff |
Índice → árbol de trabajo: cambios sin preparar en archivos con seguimiento |
git diff --staged |
HEAD → índice: lo preparado para el siguiente commit |
git diff HEAD |
HEAD → árbol de trabajo: cambio neto en archivos con seguimiento, preparado o no |
git diff base HEAD |
Las instantáneas confirmadas llamadas base y HEAD |
git diff base...HEAD |
Su ancestro común → HEAD: cambios de la rama head desde la divergencia |
Los archivos normales sin seguimiento no aparecen en las tres primeras comparaciones. Un archivo también puede tener cambios preparados y sin preparar a la vez. Consulta estas formas en la referencia de Git diff.
El diff describe estados, no autoría. Si se mezclan cambios manuales previos con los del asistente, el parche no puede identificar qué líneas escribió este último. Conserva el estado previo a la tarea o los identificadores de commit si necesitas distinguirlos. Comparar ramas también exige la base adecuada: elegir otra cambia la pregunta.
Encuentra el archivo que omite el diff simple
Descomprime la descarga y entra en git-diff-reading-lab. Crea un repositorio desechable:
node scripts/create-workspace.mjs review-workspace
cd review-workspace
git status --short --untracked-files=all
El script rechaza destinos existentes. Crea un commit con el listado original, lo etiqueta como base y deja los cambios propuestos sin commit. Su salida se reproduce sin tocar repositorios existentes.
M src/list.mjs
?? src/search.mjs
?? tests/search.test.mjs
En este estado normal sin conflictos, la primera columna describe el índice y la segunda el árbol de trabajo. El espacio inicial de M importa: list.mjs está modificado, pero sin preparar. ?? indica archivos sin seguimiento. La documentación de formato corto de Git status define estas convenciones.
Ejecuta ahora:
git diff --name-status
git diff --stat
git diff -- src/list.mjs
Solo aparece src/list.mjs. Lee por separado src/search.mjs y tests/search.test.mjs en el editor. En macOS o Linux también puedes mostrar el nuevo código como una adición con git diff --no-index -- /dev/null src/search.mjs; el código de salida 1 indica diferencias. El archivo patches/review.patch incluye los tres archivos cambiados, mientras que el diff simple no los incluye todos en este estado sin seguimiento.
git diff --staged está vacío porque no hay nada preparado. git diff base...HEAD también está vacío porque la propuesta no tiene commit y base sigue siendo HEAD. Ninguno ha revisado la propuesta de trabajo.
Lee un bloque completo
Esta es la parte de list.mjs en el parche:
diff --git a/src/list.mjs b/src/list.mjs
index f9ed85e..b551297 100644
--- a/src/list.mjs
+++ b/src/list.mjs
@@ -1,6 +1,8 @@
import { pageBooks } from "./page.mjs";
+import { searchBooks } from "./search.mjs";
// Keep the caller's array unchanged.
-export function listBooks(books, { page = 1, pageSize = 2 } = {}) {
+export function listBooks(books, { page = 1, pageSize = 2, query = "" } = {}) {
const sorted = [...books].sort((a, b) => a.title.localeCompare(b.title, "en"));
- return pageBooks(sorted, { page, pageSize });
+ const currentPage = pageBooks(sorted, { page, pageSize });
+ return searchBooks(currentPage, query);
}
Lee las cabeceras antes de la función:
a/yb/indican los lados antiguo y nuevo; no son nombres de rama.index f9ed85e..b551297contiene identificadores abreviados de objetos de contenido de archivo, no los dos commits.100644indica que se conserva el modo de archivo normal no ejecutable.---identifica el archivo antiguo y+++el nuevo. Un archivo añadido usa/dev/nullen el lado antiguo, como muestra el siguiente archivo dereview.patch.@@ -1,6 +1,8 @@empieza en la línea antigua 1 y abarca 6 líneas antiguas; empieza en la línea nueva 1 y abarca 8 nuevas. Los números son intervalos, no cantidades de eliminaciones y adiciones.- Dentro del bloque, un espacio precede al contexto conservado,
-a una línea eliminada y+a una añadida. Aquí, 4 líneas de contexto y 2 eliminaciones componen el intervalo antiguo; 4 de contexto y 4 adiciones, el nuevo. El comentario conservado también cuenta como contexto.
La documentación del formato de parches de Git explica las cabeceras. Puedes comprobar las cuentas con el archivo base de seis líneas y el propuesto de ocho de la descarga. Son bloques normales de dos lados; un diff combinado de merge usa otro formato.
Sigue la nueva llamada hasta su entrada
La petición es concreta: buscar por título en todo el catálogo sin distinguir mayúsculas y después paginar los libros coincidentes. Mantén el orden por título existente y no modifiques la entrada.
La nueva función filtra la lista que recibe:
export function searchBooks(books, query) {
const needle = query.toLowerCase();
return books.filter((book) => book.title.toLowerCase().includes(needle));
}
La función puede ser correcta mientras quien la llama le pasa la lista equivocada. Abre src/page.mjs, que no cambió: recorta los libros ordenados a una página. En el bloque propuesto, currentPage ya contiene ese recorte cuando llega a searchBooks.
Con páginas de dos libros, la primera original contiene Amber Atlas y Moon Garden. Star Notes aparece después en el catálogo. Buscar sTaR en esa primera página devuelve [], aunque la primera página de resultados debería contener b5. La corrección de patches/fix.patch busca primero en sorted y pasa las coincidencias a pageBooks.
Este es el comportamiento que hay que verificar. Para cambios mayores con muchos consumidores, continúa con análisis del impacto de cambios; la lista de archivos modificados no los enumera por sí sola.
Comprueba qué llamó realmente la prueba que pasa
Vuelve de review-workspace a la carpeta principal del laboratorio y ejecuta estos comandos por separado:
cd ..
node scripts/run-tests.mjs proposed author
node scripts/run-tests.mjs proposed requirement
node scripts/run-tests.mjs fixed requirement
node scripts/example.mjs
La prueba propuesta por el autor pasa: entrega a searchBooks un array completo de dos libros y comprueba una consulta con mayúsculas mezcladas. Nunca llama a listBooks, así que no ejercita la paginación previa a la búsqueda. No se eliminó ni debilitó ninguna aserción antigua.
Los mismos tres requisitos públicos del listado se ejecutan contra cada versión. La descarga también incluye dos comprobaciones de regresión intactas para ordenación, paginación y ausencia de mutación:
| Versión | Regresiones existentes | Nuevos requisitos del listado | Código de salida de requisitos |
|---|---|---|---|
base: sin búsqueda |
2 pasan | 0 pasan, 3 fallan | 1 |
proposed: búsqueda después de paginar |
2 pasan | 1 pasa, 2 fallan | 1 |
fixed: búsqueda antes de paginar |
2 pasan | 3 pasan | 0 |
Ejecuta node scripts/run-tests.mjs base requirement o sustituye versión y suite para reproducir las otras filas. Los fallos de base indican que falta la función nueva, no que el listado antiguo haya sufrido una regresión. Los fallos de la propuesta afectan a una página completa de resultados y a una coincidencia fuera de la primera página original. Ambas suites fallidas terminan realmente con código 1; unirlas con && detendría el resto.
scripts/example.mjs imprime un objeto JSON por versión. En results, el valor de sTaR es ["b1","b2"] en base, [] en proposed y ["b5"] en fixed. Estas comprobaciones acotadas responden sobre el orden de búsqueda, pero no certifican una implementación de producción. Si el parche también cambia aserciones, usa la guía de revisión de pruebas generadas por IA.
Comprende por qué un diff vacío aún puede dejar un parche por revisar
Tras examinar los tres archivos, prepáralos solo en el repositorio desechable:
cd review-workspace
git add -- src/list.mjs src/search.mjs tests/search.test.mjs
git diff
git diff --staged --stat
git diff --staged
El diff simple queda vacío porque los archivos de trabajo coinciden con el índice. El diff preparado contiene los tres archivos, incluidas la nueva función y su prueba. El ejercicio cambia el área de preparación del laboratorio, pero no crea un commit de la propuesta. En una revisión real, evita preparar archivos ajenos solo para hacerlos aparecer en un diff.
Compara la propuesta como dos versiones confirmadas
Para usar el mismo ejemplo en el visor de diffs de Git de Yenpo para Mac, crea otro repositorio de ejercicio con ambas versiones confirmadas. Vuelve a la carpeta git-diff-reading-lab descomprimida —cd .. si aún estás en review-workspace— y ejecuta:
node scripts/create-workspace.mjs committed-workspace --committed
cd committed-workspace
git status --short --untracked-files=all
git rev-parse base
git rev-parse proposal
git diff base proposal --name-status
git diff base proposal
El nuevo modo crea el commit base con los archivos originales, copia y prepara los tres cambios propuestos y crea un segundo commit etiquetado proposal. Imprime los identificadores reales de ambos. proposal es HEAD y base su padre. El review-workspace anterior no cambia.
El comando de estado no debe imprimir nada. Ambas instantáneas tienen commit, por lo que git diff y git diff --staged también están vacíos. La comparación explícita base → proposal sigue conteniendo src/list.mjs modificado y src/search.mjs y tests/search.test.mjs añadidos. Abre también src/page.mjs, sin cambios: el listado aún pasa un subconjunto paginado a la búsqueda nueva.
Estos commits contienen el mismo código que versions/base y versions/proposed. La observación de sTaR sigue siendo ["b1","b2"] antes y [] después; el resultado requerido es ["b5"]. Confirmar la propuesta conserva su problema de orden. La instantánea separada fixed y sus requisitos que pasan siguen en el laboratorio; este modo no crea un commit fixed.
La preparación solo acepta un directorio hijo nuevo y rechaza archivos, carpetas o enlaces simbólicos existentes. Usa una identidad didáctica ficticia y fechas fijas, ignora identidad y configuración heredadas de Git y escribe ajustes solo en el repositorio nuevo. No modifica tu identidad global ni otros repositorios.
Abre committed-workspace en Yenpo. Usa los dos identificadores impresos para comprobar las versiones con la guía de comparación de Git: lado antiguo base, lado nuevo proposal. La comparación se refiere a esas instantáneas confirmadas. No incluye ediciones preparadas o sin preparar ni identifica los cambios de un turno de conversación con IA.