git merge-tree para coding agents: merge en seco, sin tocar el índice
Resumen
git merge-tree fusiona dos ramas en un tree object. No mueve HEAD y no escribe el working tree ni el índice. Exit 0 = limpio; 1 = conflicto. --quiet para el status. Cero --trivial-merge. No es git merge. 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 merge-tree hace el merge y se detiene en un tree. El man (git-merge-tree(1); git-scm.com/docs/git-merge-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): Perform merge without touching index or working tree. SYNOPSIS moderno: git merge-tree [--write-tree] [<options>] <branch1> <branch2>. El de tres args --trivial-merge está deprecated.
No es merge. merge escribe el índice, puede dejar conflictos en disco y, si sale limpio, crea un commit. merge-tree no lee ni escribe working tree ni índice. HEAD no se mueve. No hay commit.
El merge interno es el mismo que el “real” (man): three-way de archivos, rename detection, directory/file conflicts, y consolidación recursiva de ancestros cuando hay más de un merge-base.
Contrato: --quiet --write-tree <branch1> <branch2>. 0 = limpio. 1 = conflicto (no merges). Cero --trivial-merge. Cero commit-tree / update-ref autónomo.
Qué hace (y qué no)
Modo moderno (--write-tree; también el default si pasas dos args): calcula el tree que git merge dejaría en el working tree. Si el merge es limpio, stdout es una línea: el OID del tree. Si hay conflicto, el exit es 1 y el stdout trae el tree (con marcadores embebidos), las etapas 1/2/3 y mensajes Auto-merging / CONFLICT.
--quiet apaga todo el stdout. Sirve cuando solo importa el código de salida. El man: Allows merge-tree to exit early when it finds a conflict.
Verificado 2026-09-04 (Git 2.50.1 / Apple Git-155). Repo base → main cambia f.txt; topic cambia el mismo archivo; other añade g.txt sin tocar f.txt:
git merge-tree --write-tree main other: 0. Una línea, OID de tree. Igual sin--write-tree.git merge-tree --write-tree main topic: 1. OID + tres etapas100644 … 1|2|3 f.txt+CONFLICT (content): Merge conflict in f.txt.--quiet --write-tree main topic: 1, cero stdout.--quiet --write-tree main other: 0, cero stdout.--name-onlyen conflicto: 1. Tras el OID, solof.txt(sin mode/oid/stage). Los mensajes humanos siguen.--no-messagesen conflicto: 1. OID + etapas. SinAuto-merging/CONFLICT.- Working tree sucio (
M f.txt):--quiet --write-tree main othersigue 0.git statusigual. El comando no mira el índice. - HEAD en
otherantes y después: el mismo SHA. Nada staged. - Un arg o cero args: 129, usage.
- Tres args con
--write-tree: 129, usage (el de tres args es el modo deprecated). --foo: 129, unknown option `foo'.- Historias no relacionadas: 128, refusing to merge unrelated histories.
--allow-unrelated-histories: 0, un OID.--stdinconmain other: 0. Bytes1\0<OID>\0\0(1 = clean en el protocolo stdin; el man: con--stdinel exit del proceso es 0 también si hubo conflictos).--trivial-merge <base> main topic: 0 (sí, 0) y un semi-diff con<<<<<<< .our. El man lo marca deprecated: no hace content merge real ni rename detection.- Fuera de un repo: fatal: not a git repository, 128.

Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| ¿Estas dos ramas fusionan? | git merge-tree --quiet --write-tree -- "$a" "$b" | merge “a ver” |
| Lista de paths en conflicto | --name-only --write-tree | parsear el bloque humano |
| Tree limpio para un humano | --write-tree sin --quiet y parar | commit-tree + update-ref |
| Ancestro común | merge-base | --merge-base a ciegas |
| Aplicar de verdad | merge en un worktree propio, con confirmación | merge-tree “ya lo dejó listo” |
Prohibido en autónomo:
--trivial-merge. Deprecated. Verificado: en un conflicto de contenido sale 0 y un diff con marcadores. Un agente que lea “0 = ok” mentiría.git mergepara “probar”. merge escribe el índice. merge-tree existe precisamente para no hacerlo.- Encadenar el OID a
git commit-tree+git update-ref. El man lo muestra como plumbing para scripts de más alto nivel. Un agente no mueve refs. --allow-unrelated-histories“por si acaso”. Verificado: sin el flag es 128. Con el flag mezcla dos raíces. No es el ticket.--stdin+ parsear NULs. El man: con--stdinel exit del proceso es 0 tanto en limpio como en conflicto. El status va dentro del stream (0= conflicto,1= limpio). Al revés que el exit normal.--merge-baseinventado. Si no sabes el base, deja que lo calcule. El flag es incompatible con--stdin.-X ours/-X theirsautónomo. Pasa la estrategia; no es un “resuelve tú”.- Tratar el tree de un conflicto (exit 1) como “casi mergeado”. El man: ese tree puede llevar marcadores embebidos.
- Tres argumentos en modo moderno. Verificado: 129.
- Correrlo fuera de un repo. Verificado: 128.
Receta (60 segundos)
Solo en un worktree propio. Dos refs explícitas:
git status -sb
git merge-tree --quiet --write-tree -- "$branch1" "$branch2"
echo $?
0: el merge sería limpio. No lo aplicaste. Reporta “limpio” y para.1: hay conflicto. Reporta “conflicto” y para. Cerogit merge.- Otro: error (sin repo, args mal, historias no relacionadas). No reintentes con
--allow-unrelated-histories.
Si un humano pide los paths:
git merge-tree --name-only --write-tree -- "$branch1" "$branch2"
Cierre = el código de salida (y, si se pidió, los nombres). Cero commit. Cero push a main.

merge-tree vs merge vs merge-base
merge aplica. Cambia HEAD si termina limpio; si no, deja stages en el índice. merge-tree simula y deja el repo como estaba. Verificado: git status sucio sigue sucio.
merge-base responde “cuál es el ancestro”. No fusiona. merge-tree usa ese ancestro (o varios, y los consolida). Si el ticket es “¿comparten historia?”, merge-base --is-ancestor. Si el ticket es “¿el merge sería limpio?”, este comando.
--trivial-merge no es un atajo del modo moderno. El man: no hace content merge de archivos individuales ni rename detection; el output es para scripts viejos que lo metían al índice. Un agente no lo corre.
El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. merge-tree no es merge: calcula el tree, no mueve HEAD.
Checklist
- Worktree propio.
git status -sb. Dos ramas explícitas. -
git merge-tree --quiet --write-tree -- "$a" "$b". Lee$?. -
0= limpio (no aplicado).1= conflicto (no merges). Otro = error. - Cero
--trivial-merge, cero--allow-unrelated-histories, cerocommit-tree. - Cierre = status. No push a
main.
FAQ
¿git merge-tree A B es lo mismo que --write-tree? Con dos args, sí. Verificado: mismo OID, exit 0. Un arg o tres: 129.
¿HEAD se mueve? No. Verificado: el SHA de HEAD y git status no cambian.
¿--quiet en conflicto imprime el tree? No. Verificado: stdout vacío, exit 1.
¿--stdin usa el mismo exit 0/1? No. El man: el proceso sale 0 si pudo completar; el 0/1 de cada merge va en el stream NUL. No lo uses en autónomo.
¿--trivial-merge “por si el otro falla”? No. Deprecated. Verificado: conflicto de contenido con exit 0. Mentiría al agente.
merge-tree es plumbing. El siguiente paso lo decide un humano, con merge en un worktree propio.
Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git cherry para coding agents: +/− por patch-id, no por SHA

git format-patch para coding agents: mailbox, no un diff suelto

git am para coding agents: mailbox a commits, no un diff suelto
