Guía9 min

git ls-tree para coding agents: tree object, no el disco

Resumen

git ls-tree lista un tree object, no el working tree. Exige tree-ish. Default: un nivel. -r recorre; -d solo trees; -l talla de blobs. 160000 es gitlink. Un agente parsea -z, nombra paths, no usa find ni la REST recursive de GitHub. Git 2.50.1.

GitHub
Un tree object lista blobs, trees y gitlinks; el working tree no se mueve

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 ls-tree no lista el disco. El man (git-ls-tree(1), Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-ls-tree HTTP 200, last-modified 2026-08-31; last updated in 2.42.0, 2.42.1 → 2.55.0 sin cambios) imprime el contenido de un tree object. El synopsis exige <tree-ish>. Sin él, el binario local imprime usage y sale 129. Un blob no es un tree: verificado 2026-09-04, git ls-tree <blob>not a tree object, 128. Nombre inválido o repo vacío sin HEAD → Not a valid object name, 128.

El default no recorre. Sin -r ves un nivel: blobs, el tree de cada directorio, gitlinks. -r entra a sub-trees y omite las entradas tree salvo que pases -t. -d muestra solo el tree nombrado, no sus hijos, e implica -t. Eso no es ls-files (index + working tree) ni show (un objeto concreto, a menudo con diff).

GitHub Docs (REST API endpoints for Git trees, HTTP 200 2026-09-04, API version 2026-03-10): un tree crea la jerarquía entre directorios y files en el remoto. Get a tree pide Contents read. No sustituye el clone.

Contrato: tree-ish explícito, -z para parsear, pathspec acotado, cero dump del árbol al LLM.

Un nivel no es el repo entero

Default equivalente, según el man: %(objectmode) %(objecttype) %(objectname)%x09%(path). SHA de 40 hex si no pasas --abbrev. TAB entre el objeto y el path. Compatible con --index-info --stdin de update-index; un agente no encadena eso en autónomo.

Verificado 2026-09-04 en un repo de prueba (Git 2.50.1):

PreguntaComandoQué imprime
¿Qué hay en el tree de HEAD?git ls-tree HEADun nivel: blobs + trees + gitlinks
¿Solo directorios?git ls-tree -d HEADentradas 040000 tree
¿Todo el árbol?git ls-tree -r HEADblobs (y gitlinks); sin las filas tree
¿También los trees al recorre?git ls-tree -r -t HEADtrees + blobs
¿Tamaño del blob?git ls-tree -l HEADbytes del blob; - en tree y commit

-l / --long: talla en bytes, alineada a 7 caracteres. Solo blobs. Tree y gitlink (objecttype commit) llevan -. Verificado: blob hello\n6; symlink f.txt (modo 120000) → 5; 040000 tree y 160000 commit-.

Los <path> del synopsis no son pathnames crudos: son patrones. Sin -r, git ls-tree HEAD sub imprime la entrada tree de sub, no los files de dentro. Con -r, imprime los blobs bajo ese patrón. El orden de argumentos no importa.

El cwd cuenta. El man: el path es relativo al directorio actual. Desde sub/, git ls-tree HEAD lista dir, no sub/dir. --full-name imprime el path desde la raíz. --full-tree ignora el cwd e implica --full-name. Verificado desde sub/: default → dir; --full-namesub/dir; --full-tree → el tree de HEAD. El man avisa: HEAD:sub + dir desde sub/ pide sub/sub/dir.

Default lista un nivel del tree; -r entra a sub-trees

Lo que el agente sí / no corre

QuieroComandoTrampa
¿Este path está en HEAD?git ls-tree -z --name-only HEAD -- pathfind . / ls -R
Lista parseablegit ls-tree -z HEAD -- pathspecdefault con espacios y core.quotePath
Recursivo acotadogit ls-tree -r -z HEAD -- src/-r sin path en un monorepo
SHA cortogit ls-tree --abbrev=7 HEAD -- pathrecortar 40 hex a ojo
¿Es un tree?git ls-tree --name-only HEAD -- dirasumir que el disco = el commit

Prohibido en autónomo:

  • find / ls -R volcado al LLM. El man compara con /bin/ls -a y luego marca las diferencias: patrones, no pathnames crudos; cwd. El disco incluye untracked, ignored y el scratch del agente. El tree no.
  • Omitir el tree-ish. Verificado: sin argumentos → usage, 129.
  • Tratar un blob como tree. Verificado: not a tree object, 128. Para el contenido del file usa show HEAD:path.
  • -r sin pathspec en un repo grande. GitHub Docs (Get a tree): recursive topa en 100.000 entradas o 7 MB; si truncated es true, baja tree por tree. El prompt también tiene techo. Nombra el path.
  • --name-only junto a --object-only. Verificado: cannot be used together, 129. --format tampoco va con --long / --name-only / --object-only (can't be combined, 129). Talla = -l. Campos = --format solo.
  • Confundir con ls-files. ls-files mezcla index y working tree. ls-tree no ve untracked. Un file sucio en disco no cambia la fila hasta un commit.
  • Pegar la REST Get a tree con recursive=false “para no recorre”. GitHub Docs: cualquier valor del query (0, 1, "true", "false") activa la recursión; omitir el parámetro la evita. Create a tree pide Contents write y no mueve la rama.

Modos verificados (mismo experimento; GitHub Docs lista los mismos para Create a tree): 100644 blob file, 100755 blob ejecutable, 120000 blob symlink, 040000 tree, 160000 commit gitlink. objecttype del gitlink es commit, no tree. No es un directorio.

Modos 100644 / 100755 / 120000 / 040000 / 160000 en un mismo tree

Receta (60 segundos)

Solo en un worktree propio:

git status -sb
git rev-parse --verify --quiet HEAD
git ls-tree -z --name-only HEAD -- src/lib/posts.ts
git ls-tree -d HEAD -- src

Si el ticket pide “árbol de este directorio”:

git ls-tree -r -t -z HEAD -- src/lib

-z: NUL al final, path sin quoting C. Sin -z, core.quotePath puede entrecomillar. Un parser que parte por espacio sobre un path con espacio se rompe. --object-only imprime solo el SHA (40 hex de default). --name-only / --name-status imprimen solo el path. No los combines.

--abbrev[=<n>]: el prefijo más corto de al menos n hex que sigue siendo único. Verificado: --abbrev y --abbrev=7 acortaron a 7 en un repo chico. No es un permiso para recortar SHA a 7 en un script que luego llama rev-parse.

UI de GitHub vs clone local

Get a tree (GET /repos/{owner}/{repo}/git/trees/{tree_sha}): SHA o ref. Contents read; sin auth en repos públicos. Create a tree acepta modos 100644 / 100755 / 040000 / 160000 / 120000 y no actualiza la rama. Un agente de lectura no llama create.

El file finder de github.com busca en el remoto. ls-tree corre en tu objeto. Fetch atrasado = tree atrasado. El SHA es rev-parse; el blob es show.

Checklist

  • Worktree propio. git status -sb. Tree-ish obligatorio.
  • Default = un nivel. Recursivo = -r + pathspec. Trees al recorre = -t (o -d).
  • Parsear -z. Cero --name-only + --object-only. Cero --format + -l.
  • Cwd: --full-tree si el listing no debe recortarse al directorio actual.
  • 160000 commit es gitlink, no un folder. 120000 es symlink.
  • Disco sucio ≠ tree. Index/untracked → ls-files.
  • Cero REST recursive=false “para no recorre”. Cero Create a tree.

FAQ

¿ls-tree es lo mismo que ls-files? No. ls-files mira index y, con flags, el working tree. ls-tree mira un objeto. Untracked no aparece.

¿Por qué -r no muestra src/ como carpeta? El man: -r entra a sub-trees y, sin -t, no imprime la entrada tree. -t o -d la dejan.

¿Puedo usar HEAD:src como tree-ish? Sí. El man: no lo combines con un path relativo al cwd. Prefiere HEAD -- src y --full-tree si hace falta.

El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. ls-tree no es ls-files: lista un tree object, no el index.