Cuando sabe leer @@ -12,7 +12,9 @@, los diffs dejan de dar miedo

Esta es la línea que casi todo el mundo se salta en la salida de git diff:

@@ -12,7 +12,9 @@ export function calculateTotal(items) {

Todos entendemos + y -. Muchos menos sabrían decir qué significan esos cuatro números. Leerlos con soltura mejora las revisiones y facilita diagnosticar parches fallidos y conflictos de merge.

Esta referencia desmonta el formato unified diff pieza a pieza.

La estructura general

Un diff típico tiene tres capas:

diff --git a/src/cart.js b/src/cart.js     ← de qué archivo se trata
index 83db48f..bf269f4 100644              ← IDs de objeto antiguo/nuevo y permisos
--- a/src/cart.js                          ← el lado "antes" (a)
+++ b/src/cart.js                          ← el lado "después" (b)
@@ -12,7 +12,9 @@ export function calculateTotal(items) {   ← cabecera del hunk
   const subtotal = items.reduce(...)       ← línea de contexto (espacio inicial)
-  return subtotal;                         ← línea eliminada
+  const tax = subtotal * 0.1;              ← líneas añadidas
+  return subtotal + tax;
 }

Los prefijos a/ y b/ son una convención que significa «antes» y «después»: no son directorios reales. Un archivo nuevo muestra --- /dev/null; uno eliminado, +++ /dev/null.

Los cuatro números de la cabecera

@@ -12,7 +12,9 @@
   │  │  │  └── después: 9 líneas desde ahí
   │  │  └───── después: empezando en la línea 12
   │  └──────── antes:   7 líneas desde ahí
   └─────────── antes:   empezando en la línea 12

La forma es -inicio,cantidad +inicio,cantidad. Lo importante: la cantidad es el tamaño del rango que abarca el hunk, no el número de líneas modificadas — las líneas de contexto cuentan. El ejemplo dice que una región de 7 líneas pasó a tener 9, dos más en neto.

Cuando la cantidad es 1 puede omitirse, quedando @@ -1 +1 @@. El texto que sigue al @@ de cierre es un encabezado de sección que git deduce buscando hacia atrás una línea menos indentada: es contexto para humanos, no parte del cambio.

El primer carácter de cada línea lo decide todo

PrefijoSignificado
(espacio)Línea de contexto, sin cambios
-Solo existe en la versión antigua (eliminada)
+Solo existe en la versión nueva (añadida)
\Una nota sobre el archivo (más abajo)

No existe el concepto de línea modificada. Una línea en la que corrigió un único carácter se representa como una eliminación más una adición. Es la mayor fuente de confusión al leer diffs: un muro de rojo y verde puede resultar ser un punto y coma.

Cuando necesite saber qué cambió dentro de la línea, compare a nivel de palabra: git diff --word-diff en git, o pegue ambas versiones en la herramienta de diff de texto del navegador.

El contexto son tres líneas por defecto

Las líneas sin cambios que rodean una modificación son el «contexto», tres por defecto. Quien aplica el parche usa ese entorno para localizar el sitio correcto. Por eso un parche sigue aplicándose aunque los números de línea se hayan desplazado, y por eso mismo falla cuando esas líneas circundantes también se han editado.

git diff -U0     # sin contexto, solo líneas modificadas
git diff -U10    # diez líneas a cada lado

\ No newline at end of file

-const config = {};
\ No newline at end of file
+const config = {};

Esta nota indica que el archivo no termina en salto de línea. Si dos versiones parecen idénticas y aun así hay diff, se añadió o se quitó el salto final. Los editores y .editorconfig (insert_final_newline) lo cambian sin que nadie se dé cuenta, y aparece como ruido en las revisiones.

Por qué un cambio de solo espacios parece una reescritura

Pasar la indentación de tabuladores a espacios, o quitar espacios al final de línea, cambia el contenido de la línea: cada línea afectada aparece como par -/+ aunque en pantalla no se vea diferencia.

git diff -w                    # ignora las diferencias de espaciado
git diff --ignore-blank-lines  # ignora líneas en blanco añadidas o quitadas

Los finales de línea (LF frente a CRLF) provocan el mismo efecto. Si un repositorio compartido entre Windows y Linux marca todas las líneas como modificadas sin que usted haya tocado nada, empiece por aquí.

@@@ en los commits de merge

Ejecutar git show sobre un commit de merge puede producir cabeceras con tres @:

@@@ -12,7 -12,8 +12,9 @@@

Es un diff combinado, que muestra a la vez las diferencias respecto a ambos padres. Hay dos columnas de prefijo en lugar de una: la primera compara con el primer padre y la segunda con el segundo. Solo aparecen las líneas que difieren de ambos, es decir, exactamente lo que se tocó a mano al resolver el merge.

Los renombrados se deducen, no se registran

diff --git a/src/utils.js b/src/helpers.js
similarity index 95%
rename from src/utils.js
rename to src/helpers.js

Git no registra los renombrados: los infiere al calcular el diff a partir de la similitud del contenido, considerando renombrado todo lo que supere el 50% por defecto e indicando la cifra como similarity index. Si renombra un archivo y además lo reescribe a fondo en el mismo commit, git mostrará una eliminación más un archivo nuevo.

Comparar en local

Para texto que no está en un commit —logs, archivos de configuración, respuestas de API— pegue ambas versiones en la herramienta de diff de texto y los cambios quedan alineados y coloreados. Todo se ejecuta en el navegador, así que puede comparar configuración de producción y extractos de logs sin que salgan de su equipo.

Formatear antes de comparar elimina ruido: pase el JSON por el formateador JSON o el SQL por el formateador SQL y solo quedarán las diferencias con significado. Los fundamentos del diff están en cómo leer y usar diferencias de texto.

Resumen

  • @@ -12,7 +12,9 @@ es -inicio,cantidad +inicio,cantidad; la cantidad cubre todo el rango incluido el contexto
  • El carácter inicial lo es todo (espacio / - / +). No hay «modificado», solo eliminado más añadido
  • El contexto son tres líneas por defecto y es lo que permite ubicar un parche
  • \ No newline at end of file habla del salto de línea final, causa habitual de diffs fantasma
  • Los cambios de espaciado y de fin de línea parecen reescrituras; aíslelos con git diff -w
  • @@@ marca un diff combinado en un merge, comparando con ambos padres
  • Los renombrados se infieren por similitud, no se almacenan (similarity index)

Preguntas frecuentes

¿Qué significan los números de @@ -12,7 +12,9 @@?

El lado - describe la versión antigua y el + la nueva, cada uno como «línea inicial, número de líneas». Aquí el archivo antiguo aporta 7 líneas desde la 12 y el nuevo 9 líneas desde la 12. Las cantidades incluyen las líneas de contexto sin cambios, así que no son el número de líneas editadas.

¿Por qué un cambio de un carácter aparece como línea entera modificada?

Porque el formato unified diff no puede expresar «esta línea se modificó»: todo cambio se representa como eliminación (-) más adición (+). Para ver qué cambió dentro de la línea use git diff --word-diff o una herramienta que compare a nivel de palabra.

Todas las líneas salen como modificadas pero no he editado nada

Casi siempre son los finales de línea (LF frente a CRLF) o los espacios de indentación. Si git diff -w hace desaparecer el diff, esa es la causa. Para arreglarlo en todo el repositorio, fije los finales de línea con .gitattributes.

¿Se envía a algún sitio el contenido que pego?

No. La herramienta de diff de texto funciona íntegramente en su navegador: lo que pega no se transmite a ningún servidor, así que puede usarla con configuración de producción y logs.