Guía9 min

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.

GitHub
Dos tree objects de Git se comparan en raw; el working tree no entra

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 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

QuieroComandoTrampa
¿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 commitshow o diff-p aquí es el parche al contexto
Listar un tree, no restarlols-treediff-tree no es un ls
Diff de un PRgh pr diff o A...B en porcelain--merge-base no es la UI

Prohibido en autónomo:

  • -p / -u / --patch / --cc. --cc es parche. El default raw ya es denso.
  • Sin <tree-ish> (129) y sin adivinar HEAD.
  • Dump del raw al prompt. Parsea --name-only / --name-status o el exit de --quiet.
  • --stdin leyendo 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 / --pretty como “log barato”. -m en --stdin muestra diffs de merges que el default omite. -s silencia el diff; solo tiene sentido con -v. Eso es territorio de log.
  • -t para “ver más”. Mezcla trees y blobs. El parser del agente cuenta sub como path.
  • --find-copies-harder / -C / -M autónomos. El man de -C: very expensive en proyectos grandes.
  • --combined-all-paths sin 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.

dos trees a izquierda y derecha; el working tree queda fuera del recuadro

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

  1. Correr git diff-tree sin args y “arreglar” el 129 con HEAD. El usage no es una pista: es un contrato. El tree se pasa explícito.
  2. Olvidar -r y reportar que “solo cambió src”. Cambió un tree; los archivos viven debajo.
  3. Pegar el raw (dos SHAs de blob + mode) al contexto. El LLM no necesita ce01362…. Necesita paths y un sí/no.
  4. Confundirlo con git diff HEAD^ HEAD. El porcelain puede pintar color, pager y working tree. diff-tree no mira el disco: unstaged no aparece.
  5. --cc “para ver el merge”. Implica -p. El combined diff no se aplica con patch -p1 (el man lo dice: hunk headers con @@@).
  6. --root en cada commit. Solo el inicial contra el NULL tree. En el resto es ruido de creación falsa.
  7. --stdin con git rev-list enorme. 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 -r o --name-status -r, nunca -p.
  • Un commit: cuenta el SHA extra, o pasa --no-commit-id.
  • Directorios: -r. Sin -r un tree 040000 no es un archivo.
  • Cero --cc / -c / --pretty / --stdin autó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.

raw de un renglón por path; -r baja al archivo, no al directorio