Guía9 min

git mktree para coding agents: tree desde ls-tree, no desde el index

Resumen

git mktree lee stdin en formato ls-tree no recursivo y escribe un tree. Normaliza el orden. Verifica que cada hash exista, salvo --missing. Gitlinks pueden faltar. Cero -r, cero --missing autónomo, cero REST Create a tree. Distinto de write-tree, hash-object y read-tree. Git 2.50.1.

GitHub
Entradas ls-tree en stdin se serializan a un tree object; el index y HEAD no se mueven

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 mktree no lee el index. El man (git-mktree(1); git-scm.com/docs/git-mktree HTTP 200, last-modified 2026-08-31; last updated in 2.43.0, 2.43.1 → 2.55.0 sin cambios; pie del man local Git 2.50.1.428.g0e8243, 2025-07-22; binario Git 2.50.1 / Apple Git-155): Build a tree-object from ls-tree formatted text. SYNOPSIS: git mktree [-z] [--missing] [--batch]. Lee stdin en formato no recursivo de ls-tree. Imprime el SHA del tree a stdout. HEAD, index y working tree no se mueven.

El orden de las entradas se normaliza. El man: The order of the tree entries is normalized by mktree so pre-sorting the input is not required. Por defecto verifica que cada hash exista. --missing apaga ese check. Los gitlinks (160000) siempre pueden faltar: This option has no effect on the treatment of gitlink entries (aka "submodules") which are always allowed to be missing.

GitHub Docs (REST API endpoints for Git trees, HTTP 200 2026-09-06): Create a tree pide Contents write y responde 201. Un agente de lectura no llama el POST. mktree es el equivalente local: fabrica un tree sin commit y sin rama.

Contrato: stdin de un solo nivel, cero --missing, cero --batch autónomo, cero REST de escritura.

Qué hace (y qué no)

Crea un objeto tree a partir de líneas ls-tree. El mismo input canónico, el mismo SHA. Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155): echo hello > a.txt && git add a.txt && git commit -qm init. git ls-tree HEAD100644 blob ce013625… a.txt. Ese pipe a git mktree imprime 2e81171448eb9f2ee3821e3d447aa6b2fe3ddba1. write-tree imprime el mismo SHA. cat-file -ttree, -s33. git status -sb sigue ## main. HEAD no se movió.

No es write-tree. write-tree serializa el index. mktree serializa texto. Si el index y el texto no coinciden, los SHA tampoco.

No es hash-object. hash-object hashea un blob (o un tipo con -t). mktree ensambla nombres + modos + hashes en un directorio. El SHA de a.txt no es el SHA del tree.

No es read-tree. read-tree escribe el index. mktree no toca el index.

No es commit. No hay mensaje, no hay padre, no hay rama. El plumbing siguiente sería git commit-tree <tree> — un agente no lo encadena.

Stdin vacío: printf '' | git mktree4b825dc642cb6eb9a060e54bf8d69288fbee4904 (empty tree), exit 0. No es un error.

mktree ensambla líneas ls-tree; el index y HEAD quedan quietos

Lo que el agente sí / no corre

QuieroComandoTrampa
Tree idéntico a HEADgit ls-tree HEAD | git mktreeasumir que es un commit
Tree del indexwrite-treealimentar mktree con ls-files
Ver el contenidols-tree <sha>pegar el tree al LLM
¿Falta un blob?mktree sin --missing--missing “por si acaso”
Submódulolínea 160000 commit <sha>\tpathexigir que el commit exista

Prohibido en autónomo:

  • --missing. El man: The default behaviour (without this option) is to verify that each tree entry's hash identifies an existing object. Verificado: 100644 blob deadbeef… missing.txt sin flag → fatal: entry 'missing.txt' object deadbeef… is unavailable, 128. Con --missing → SHA 74c59fb4…, 0. Un tree que apunta a blobs que no están es un objeto podrido.
  • Input recursivo. git ls-tree -r HEAD | git mktree con sub/b.txtfatal: path sub/b.txt contains slash, 128. El man dice non-recursive ls-tree output format. Un nivel. Los subdirectorios van como entrada 040000 tree <sha>\tdir.
  • --batch autónomo. El man: Allow building of more than one tree object before exiting. Each tree is separated by a single blank line. Verificado: dos copias de ls-tree HEAD separadas por blanco → dos SHA iguales, 0. Sirve para scripts de import; no para “arma el repo”.
  • Create a tree (POST /repos/{owner}/{repo}/git/trees). GitHub Docs: Contents write, array tree obligatorio, 201. Si omites base_tree, los paths no listados salen borrados en el próximo commit. Un agente de lectura no lo llama.
  • Encadenar a commit-tree + update-ref. Eso fabrica historia. Si el ticket pide un commit, add + commit -m en un worktree propio.
  • Tratar argumentos extra como tree-ish. Verificado: git mktree HEAD con stdin vacío imprime el empty tree y sale 0. Ignora HEAD. No “hace el tree de HEAD”. El input es stdin, punto.

-h: usage, 129. --foo: *unknown option foo'*, **129**. Fuera de un repo: *fatal: not a git repository*, **128**. Línea malformada: *fatal: input format error: …*, **128**. Espacio en vez de tab: el mismo 128. No hay -n` dry-run. No hay pathspec.

Receta (60 segundos)

Solo en un worktree propio, y solo si un humano pide el SHA de un tree armado a mano:

git status -sb
git ls-tree HEAD
git ls-tree HEAD | git mktree

Tres líneas. status confirma que no estás fabricando sobre un merge sucio por accidente. ls-tree te muestra el formato. mktree imprime 40 hex. Si el SHA coincide con write-tree, el texto y el index hablan del mismo directorio.

Para un tree nuevo (blob ya en el object store):

printf '100644 blob %s\t%s\n' "$(git hash-object -w a.txt)" a.txt | git mktree

hash-object -w escribe el blob. Sin -w, mktree sin --missing falla 128 si el objeto no existe. Un agente no inventa modos (100755, 120000) ni nombres con slash.

-z solo si la fuente es git ls-tree -z. El man: Read the NUL-terminated ls-tree -z output instead. Mezclar -z con newlines parte mal las líneas.

Trampas que ya vimos

  1. “mktree es write-tree”. No. write-tree lee el index. mktree lee texto. Mismo SHA solo si el texto es el ls-tree de ese index.
  2. “Le paso -r para no perder subdirs”. 128, contains slash. Los trees anidados se arman de abajo hacia arriba: mktree del hijo, luego una línea 040000 tree <sha>\tnombre en el padre. --batch existe para eso; un agente no lo improvisa.
  3. --missing “el SHA lo pongo después”. El check existe para no serializar punteros huecos. Apagarlo fabrica un tree que fsck va a odiar. Los gitlinks ya están exceptuados: verificado, 160000 commit deadbeef… submod sin --missing sale 0.
  4. git mktree HEAD como atajo. Stdin vacío → empty tree. Crees que leíste HEAD; escribiste el árbol vacío. El atajo correcto es git ls-tree HEAD | git mktree o, si quieres el index, write-tree.
  5. REST Create a tree + Create a commit + Update a ref. Tres writes. El equivalente local ya está prohibido. Además, sin base_tree GitHub borra lo no listado.
  6. Duplicar el path. Verificado: dos líneas a.txt iguales → SHA distinto, exit 0. Git no protesta. El tree queda raro. No dedupes a ciegas: aborta.
  7. Dump al modelo. Un ls-tree de un monorepo es un volcado. Pathspec en ls-tree, no mktree de mil líneas.
  8. Orden “hay que sort”. No. mktree normaliza. No pongas sort en el pipe “por si acaso”: el formato lleva tabs y modos; un sort mal puesto parte líneas.

Checklist

  • Worktree propio, no el checkout del humano.
  • Quiero un tree SHA a partir de texto ls-tree, no un commit.
  • Input de un nivel. Cero paths con /.
  • Cada blob/tree hash existe (git cat-file -e). Cero --missing.
  • Un solo git mktree. Sin flags, salvo -z si la fuente es -z.
  • Si necesito el index: write-tree, no este verbo.
  • Cero commit-tree, cero update-ref, cero POST de trees.
  • Cero --batch sin un script de import que el humano nombró.
  • Si el ticket pide “commitear”, commit -m.

FAQ

¿mktree cambia archivos? No. Solo lee stdin y (si hace falta) escribe un objeto tree. El working tree y el index quedan iguales.

¿Puedo usarlo como dry-run de commit? No. Te da un tree. No valida mensaje, no corre hooks, no mueve la rama. El tree del próximo commit, si el index está limpio, sale más barato con write-tree.

¿Por qué el empty tree sale con stdin vacío? Porque cero entradas es un tree válido. Git reusa 4b825dc642cb6eb9a060e54bf8d69288fbee4904. No lo hardcodes: pídelo con stdin vacío o con write-tree en un index vacío.

¿Y hash-object -t tree? Eso hashea bytes crudos de un tree. mktree parsea líneas ls-tree, ordena y arma el objeto. No son el mismo contrato. Un agente no pega un dump binario a hash-object.

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

un path con slash o un blob ausente no produce tree; 128 y el objeto no se fabrica