Guía9 min

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.

GitHub
Dos ramas se combinan en un tree object; el working tree y HEAD no cambian

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 basemain 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 etapas 100644 … 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-only en conflicto: 1. Tras el OID, solo f.txt (sin mode/oid/stage). Los mensajes humanos siguen.
  • --no-messages en conflicto: 1. OID + etapas. Sin Auto-merging / CONFLICT.
  • Working tree sucio (M f.txt): --quiet --write-tree main other sigue 0. git status igual. El comando no mira el índice.
  • HEAD en other antes 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.
  • --stdin con main other: 0. Bytes 1\0<OID>\0\0 (1 = clean en el protocolo stdin; el man: con --stdin el 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.

--quiet solo deja el exit; conflicto = 1, limpio = 0

Lo que el agente sí / no corre

QuieroComandoTrampa
¿Estas dos ramas fusionan?git merge-tree --quiet --write-tree -- "$a" "$b"merge “a ver”
Lista de paths en conflicto--name-only --write-treeparsear el bloque humano
Tree limpio para un humano--write-tree sin --quiet y pararcommit-tree + update-ref
Ancestro comúnmerge-base--merge-base a ciegas
Aplicar de verdadmerge en un worktree propio, con confirmaciónmerge-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 merge para “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 --stdin el 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-base inventado. Si no sabes el base, deja que lo calcule. El flag es incompatible con --stdin.
  • -X ours / -X theirs autó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. Cero git 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.

El índice y el working tree siguen iguales; solo aparece un OID o un 1

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, cero commit-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.