Guía9 min

git diff-files para coding agents: working tree vs index, no el parche

Resumen

git diff-files compara el working tree con el index en raw. Default exit 0 aunque haya sucio; --quiet implica --exit-code. -q no es silencio: oculta paths inexistentes. Cero dump al LLM. Distinto de git diff, status y diff-index. Git 2.50.1.

GitHub
Working tree sucio frente al index: git diff-files lista el desvío en raw, no el parche

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

git diff-files compara el working tree con el index. El man (git-diff-files(1); git-scm.com/docs/git-diff-files HTTP 200, last-modified 2026-08-31; pie del man local Git 2.50.1.428.g0e8243, 2025-07-22; binario Git 2.50.1 / Apple Git-155): Compares the files in the working tree and the index. DESCRIPTION: When paths are specified, compares only those named paths. Otherwise all entries in the index are compared. El formato default es el mismo raw que git-diff-index y git-diff-tree.

No es git diff: ese porcelain elige dos extremos y, sin flags, imprime un parche unificado para humanos. No es status: status también lista untracked e index vs HEAD. No es git diff-index: ese verbo pide un <tree-ish> y compara un árbol con el working tree o, con --cached, con el index. diff-files no toma tree-ish. Solo index ↔ disco.

Contrato: no vuelques git diff-files -p al LLM. Inspección = --name-only / --name-status / --quiet. Restaurar = restore. Stage = add con paths. Cero -p autónomo. Cero dump raw de mil paths.

Qué hace (y qué no)

Lista de paths del index → blob del index vs archivo en disco. Untracked no aparece. HEAD no entra. Si el path está staged y el disco coincide con el index, la salida está vacía.

Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155), repo mínimo con a.txt commiteado:

CasoComandoSalidaExit
Working tree limpiogit diff-filessilencio0
Disco sucio vs indexgit diff-filesraw :100644 100644 <src> 0{40} M\ta.txt0
Mismo suciogit diff-files --name-onlya.txt0
Mismo suciogit diff-files --quietsilencio1
Mismo suciogit diff-files --exit-codeel raw1
Mismo suciogit diff-files -qel raw0
Path inexistente sin --git diff-files missing.txtfatal: ambiguous argument128
Path inexistente con --git diff-files -- missing.txtsilencio0
Tras git add (index = disco)git diff-filessilencio0
Archivo borrado del discogit diff-files --name-statusD\ta.txt0
Untracked b.txtgit diff-files --name-onlyno lista b.txt0

El SHA de destino es todo ceros cuando el working tree no coincide con el index. El man (RAW OUTPUT FORMAT): The sha1 for "dst" is shown as all 0's if a file on the filesystem is out of sync with the index. Verificado: 0000000000000000000000000000000000000000 en la columna dst.

-q no es --quiet. El man: -q — Remain silent even for nonexistent files. En el lab, -q imprimió el raw del path sucio y salió 0. --quiet Disable all output of the program. Implies --exit-code. Ese sí es el gate de scripts: 0 = limpio, 1 = hay diff.

--exit-code: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and 0 means no differences. Sigue imprimiendo. Si quieres solo el código, --quiet.

index a la izquierda, working tree a la derecha: diff-files solo mira ese par

Lo que el agente sí / no corre

QuieroComandoTrampa
¿Hay sucio vs index?git diff-files --quietgit diff-files a pelo sale 0 con sucio
Lista de paths suciosgit diff-files --name-only-p es el parche entero
Status humanostatus -sbdiff-files no ve untracked ni staged vs HEAD
Diff stagedgit diff --cached --name-onlydiff-files deja de ver lo que ya está en el index
Parche para reviewgit diff --stat-p de plumbing al contexto

Prohibido en autónomo:

  • -p / --patch / -U / --stat sin techo. El man genera parche unificado. Un agente no se lo traga.
  • Confundir -q con --quiet. -q no silencia diffs reales; solo paths inexistentes.
  • Tratar exit 0 del default como “working tree limpio”. El default no implica --exit-code.
  • Path sin -- si puede no existir: 128, unknown revision or path not in the working tree.
  • Usarlo como “qué cambió el PR”. Eso es git diff A...B o el three-dot de GitHub, no este verbo.
  • Encadenarlo con checkout-index -f “para alinear”. Detectar sucio ≠ pisar.

El porcelain de diff ya cubre “muéstrame el unstaged”. Este verbo existe para scripts que necesitan raw estable: The output format is the same as for git diff-index and git diff-tree. Repeatability, no lectura humana.

Receta (60 segundos)

Solo en un worktree propio:

git status -sb
git diff-files --quiet
git diff-files --name-only

Tres líneas. status te da el panorama (incluye untracked). --quiet es el semáforo: 0 sigues, 1 hay desvío index↔disco. --name-only lista qué paths, no el parche.

Si --quiet es 1 y el ticket pide commitear, el siguiente paso es add de paths explícitos, no git add .. Si pide descartar, restore -- <path>.

Para un path concreto:

git diff-files --quiet -- src/app.ts
echo $?

-- antes del path. Verificado: sin --, un filename que no está en el árbol revienta con 128; con --, el path ausente es no-op 0.

Trampas que ya vimos

  1. “Exit 0 = limpio.” Solo con --quiet o --exit-code. Default sucio = 0 + una línea raw.
  2. -q es quiet.” No. -q = no grites si el archivo no existe. El sucio real se imprime igual. En el lab, -q y el default fueron idénticos.
  3. “Es lo mismo que git diff.” git diff sin args también es WT vs index, pero en formato parche y con color/pager. diff-files default es raw de una línea por path. Distinto contrato, distinto volumen.
  4. “También ve staged.” Tras git add, diff-files se queda mudo: el index es el disco. Lo staged vive en git diff --cached.
  5. “También ve untracked.” No. El man compara entries in the index. b.txt nuevo no está en el index; no existe para este verbo. status --porcelain=v1 -z sí lo lista.
  6. Path sin --. 128. El mensaje habla de revision. No es que Git “no encuentre el diff”; es que parseó el token como rev.
  7. dst SHA a ceros = blob vacío. No. Ceros = work tree out of sync with the index. El contenido sucio está en disco, no en un blob.
  8. -1/-2/-3 en un merge. El man: diff contra base / ours / theirs; default -2 (ours) y paths limpios. -0 omite unmerged y solo dice Unmerged. Un agente en conflicto lee status, no improvisa stages.
  9. Creer que mueve HEAD o el index. Solo lee. El inverso de escribir al index es update-index; al disco, checkout-index o restore.

Checklist

  • Worktree propio, no el checkout del humano.
  • git status -sb leído antes.
  • Quiero index ↔ disco, no HEAD, no untracked, no un PR.
  • Gate = --quiet (exit 0/1). Cero asumir el default.
  • Lista = --name-only o --name-status. Cero -p.
  • Paths con --. Cero filenames sueltos que puedan 128.
  • -q--quiet. No los intercambies.
  • Si hay que restaurar: restore, no force.
  • Si hay que stagear: add de paths, no ..

FAQ

¿diff-files mira HEAD? No. Solo index y working tree. HEAD entra con git diff HEAD o git diff-index HEAD.

¿Por qué el default no sale 1? Plumbing histórico: el raw se parsea; el código de salida no era el contrato. --exit-code / --quiet existen precisamente para scripts que sí quieren diff(1).

¿Cuándo sí -p? Cuando un humano pide el parche de un path en un worktree propio. Nunca como primer vistazo ni sobre el árbol entero.

¿Y untracked? status --porcelain=v1 -z, o git ls-files -o. Este verbo no los ve.

¿Dónde encaja esto? En el hub de comparativas y decisiones, junto a git diff y status. Si estás armando el primer agente, empieza por el curso.

--quiet es el semáforo; -q no apaga el raw del path sucio