git diff-tree para coding agents: dos trees, no el disco
Resumen
git diff-tree compara blobs de dos tree objects. Sin tree-ish sale 129. Un commit se compara con sus padres y imprime el SHA. Default raw, no parche. Cero -p/--cc/--stdin autónomo. Distinto de git diff, show y ls-tree. Git 2.50.1.

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-tree compara el contenido y el mode de blobs vía dos tree objects. El man (git-diff-tree(1); git-scm.com/docs/git-diff-tree 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 content and mode of blobs found via two tree objects. DESCRIPTION: Compare the content and mode of blobs found via two tree objects. SYNOPSIS: [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty] [-t] [-r] [-c | --cc] [--combined-all-paths] [--root] [--merge-base] [<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...].
Sin <tree-ish> falla. Verificado 2026-09-06: git diff-tree → usage, exit 129. --stdin vacío sí sale 0: no hay líneas que comparar. No adivines HEAD.
No es diff: el porcelain mira working tree e index. Aquí no entra el disco. No es show: show es el parche de un objeto para humanos. No es ls-tree: ls-tree lista un tree; diff-tree resta dos. El man RAW OUTPUT FORMAT lo separa: diff-tree [-r] <tree-ish-1> <tree-ish-2> compara los trees nombrados. diff-index es tree vs disco o index. diff-files es index vs disco. Esos dos verbos no son este.
Contrato: no vuelques el raw ni el parche al LLM. Pregunta binaria = --quiet --exit-code + -r. Lista = --name-only -r / --name-status -r + pathspec. Cero -p. Cero --cc. Cero --stdin autónomo. Cero dump.
Qué hace (y qué no)
Default: raw, no patch. El man: git diff-tree can use the tree encapsulated in a commit object. Un solo <tree-ish> compara el commit con sus padres y imprime el SHA del commit antes del raw. Dos trees no imprimen esa línea. Verificado: git diff-tree HEAD empieza con el SHA de 40 hex; git diff-tree --no-commit-id HEAD la omite; git diff-tree HEAD^ HEAD va directo al :100644 ….
-r: Recurse into sub-trees. Sin -r, un directorio cambiado aparece como tree 040000 … M\tsub, no como sub/b.txt. Verificado: --name-only sin -r lista a.txt y sub; con -r lista a.txt y sub/b.txt. -t: Show tree entry itself as well as subtrees. Implies -r. Verificado: -t muestra las tres líneas: a.txt, el tree sub y sub/b.txt.
--quiet implica --exit-code. Verificado: trees distintos + -r → exit 1; el mismo tree dos veces → 0. Hay diff ≠ error de comando. 1 = “hay diferencias”, 0 = “no hay”.
Tree inválido: unknown revision or path not in the working tree, 128. --merge-base con un solo commit: --merge-base only works with two commits, 128. El man: There must be two <tree-ish>s given and they must both be commits. No es el three-dot de un PR.
--root: the initial commit will be shown as a big creation event. This is equivalent to a diff against the NULL tree. Verificado: el primer commit + --root produce A con src mode 000000 y sha src 0{40}.
--cc implica -c y -p. El man: It implies the -c and -p options y comprime hunks “uninteresting”. Un merge commit con --cc es un parche combinado, no un raw. No lo corras autónomo.
GitHub Docs Committing changes (/en/pull-requests/how-tos/commit-changes, HTTP 200, 2026-09-06) habla de la UI de PRs, no de este plumbing.
Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| ¿El commit cambió algo vs su padre? | git diff-tree --quiet --exit-code -r <sha> | sin -r un dir cuenta como un blob |
| Lista de paths entre dos trees | --name-only -r <a> <b> | sin -r ves sub, no sub/b.txt |
| Raw estable, sin SHA de commit | --no-commit-id -r <sha> | un tree-ish imprime el SHA por default |
| Diff humano de un commit | show o diff | -p aquí es el parche al contexto |
| Listar un tree, no restarlo | ls-tree | diff-tree no es un ls |
| Diff de un PR | gh pr diff o A...B en porcelain | --merge-base no es la UI |
Prohibido en autónomo:
-p/-u/--patch/--cc.--cces parche. El default raw ya es denso.- Sin
<tree-ish>(129) y sin adivinar HEAD. - Dump del raw al prompt. Parsea
--name-only/--name-statuso el exit de--quiet. --stdinleyendo commits a ciegas. El man: líneas con dos trees, un commit, o una lista de commits. Un script que no controla el stdin se come basura.-m/-s/-v/--prettycomo “log barato”.-men--stdinmuestra diffs de merges que el default omite.-ssilencia el diff; solo tiene sentido con-v. Eso es territorio de log.-tpara “ver más”. Mezcla trees y blobs. El parser del agente cuentasubcomo path.--find-copies-harder/-C/-Mautónomos. El man de-C: very expensive en proyectos grandes.--combined-all-pathssin que un humano pida renames en un merge.- Encadenar esto con writes (
checkout,reset,read-tree). Es lectura de objects.
El porcelain cubre “quiero ver el cambio”. Este verbo existe para scripts que necesitan raw estable entre objects, no el working tree. Un tick de agente pregunta si el commit tocó paths, no cómo se ve el hunk.

Receta (60 segundos)
Solo en un worktree propio:
git diff-tree --quiet --exit-code -r HEAD^ HEAD
echo $? # 0 igual, 1 hay diff, 128/129 mal uso
git diff-tree --name-only -r HEAD^ HEAD
Si el exit es 1, lista con --name-status -r. No pases -p. Si necesitas el parche para un humano, show -1 --stat primero.
Un commit merge: no uses -m ni --cc a ciegas. Pregunta binaria sobre el merge result vs un padre: dos tree-ish explícitos (HEAD^1 y HEAD). El default de un solo tree-ish omite merges en --stdin salvo -m.
Errores que delatan al agente
- Correr
git diff-treesin args y “arreglar” el 129 con HEAD. El usage no es una pista: es un contrato. El tree se pasa explícito. - Olvidar
-ry reportar que “solo cambiósrc”. Cambió un tree; los archivos viven debajo. - Pegar el raw (dos SHAs de blob + mode) al contexto. El LLM no necesita
ce01362…. Necesita paths y un sí/no. - Confundirlo con
git diff HEAD^ HEAD. El porcelain puede pintar color, pager y working tree. diff-tree no mira el disco: unstaged no aparece. --cc“para ver el merge”. Implica-p. El combined diff no se aplica conpatch -p1(el man lo dice: hunk headers con@@@).--rooten cada commit. Solo el inicial contra el NULL tree. En el resto es ruido de creación falsa.--stdincongit rev-listenorme. Imprime un raw por commit. Eso es un DoS al contexto; usa--name-only+ un SHA, o log--name-only -1.
Checklist
- Hay al menos un
<tree-ish>explícito. 129 = aborta. - Pregunta binaria →
--quiet --exit-code -r. 1 no es crash. - Lista →
--name-only -ro--name-status -r, nunca-p. - Un commit: cuenta el SHA extra, o pasa
--no-commit-id. - Directorios:
-r. Sin-run tree040000no es un archivo. - Cero
--cc/-c/--pretty/--stdinautónomos. - Cero writes después. Esto no toca index ni disco.
- El parche para humanos es show/diff, no este verbo.
FAQ
¿Por qué no uso siempre git diff? Porque el porcelain mezcla working tree, color y pager. diff-tree habla de objects. Un agente que pregunta “¿este SHA tocó src/?” no debe contaminarse con unstaged.
¿Un tree-ish o dos? Dos trees = A vs B, sin línea de commit. Uno = commit vs padres, con línea de SHA. El man: If there is only one <tree-ish> given, the commit is compared with its parents.
¿-r es opcional? Para un repo plano, el raw coincide. En cuanto hay un subdir, sin -r mientes. Pon -r siempre en scripts.
¿--quiet sin --exit-code? El man de las common diff options trata --quiet como implicando --exit-code en esta familia. Verificado: trees distintos → 1. No asumas 0 “porque no imprimió”.
¿Puedo usarlo como git show --stat? No. --stat aquí es human-facing y denso. show es el verbo. diff-tree es el sensor.
Más plumbing de Git para agentes en comparativas y decisiones. El curso corto: /curso/instalar-agente.

Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git stripspace para coding agents: limpia el mensaje, no el parche

git interpret-trailers para coding agents: parse, no reescribir el mensaje

git patch-id para coding agents: huella del diff, no del SHA
