Guía10 min

git merge para coding agents: commit de fusión, no rebase ni -X ours

Resumen

git merge une historias. Fast-forward mueve HEAD; si no, crea un commit con dos padres. Un agente no mergea con working tree sucio, no resuelve -X ours/theirs y aborta ante conflicto. Man git-merge y GitHub Docs.

GitHub
Ramas topic y master unidas en un commit de fusión H con dos padres

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 une dos o más historias en la rama actual. El man (Git 2.50.1 / Apple Git-155): incorpora los commits nombrados desde que divergieron. git pull lo usa por debajo. No reescribe SHA: o hace fast-forward (mueve el puntero) o crea un commit de fusión con dos padres.

Con topic = A—B—C encima de E, y master = D—E—F—G, git merge topic deja H con padres G y C. Antes, ORIG_HEAD apunta a G.

GitHub Docs: el botón Merge pull request usa --no-ff. Un coding agent que “limpia” con rebase + force no es este flujo. Ante conflicto, GitHub desactiva el merge hasta que alguien elija el contenido final.

Esta guía no sustituye worktrees ni PRs con gh. El contrato: cuándo un agente puede mergear, qué flags están prohibidos, y cómo abortar.

Fast-forward vs true merge

El man parte el comando en tres salidas:

CasoQué pasaCommit de fusión
Named commits ya son ancestros de HEADSale con Already up to date.No
HEAD es ancestro del named commit (--ff, default)HEAD + index apuntan al named commitNo
Historias divergieronCommit nuevo con ambos padres (ort, default para un head)

--no-ff: siempre crea el commit de fusión, aunque se pudiera fast-forward. Es lo que GitHub hace al mergear un PR.

--ff-only: si no es fast-forward, rehúsa y sale ≠ 0. Útil para “solo actualizar tracking”, no para integrar un topic.

--no-commit: para el merge antes de crear el commit, para inspeccionar. El man: un fast-forward no se detiene con --no-commit (no hay merge commit). Si el agente necesita “no mover la rama todavía”, usa --no-ff --no-commit.

Fast-forward mueve el puntero; true merge añade H con dos padres

Lo que el agente sí / no corre

QuieroComandoTrampa
Traer origin/main a mi topicgit fetch origin && git merge origin/mainMerge con dirty tree. El man lo desaconseja
Parar a mitadgit merge --abort--quit deja index y disco como están
Seguir tras resolvergit add paths + git merge --continue--continue llama a commit solo si hay merge interrumpido
Integrar el PR en maingh pr merge, no git merge en main localMerge local a main + push salta review
Aceptar el mensaje auto--no-edit o GIT_MERGE_AUTOEDIT=no--edit abre editor. Headless no es ese flujo

Prohibido en autónomo:

  • Merge con cambios uncommitted no triviales. PRE-MERGE CHECKS: si el index no coincide con HEAD, aborta; si untracked/edits solapan paths que el merge actualizaría, aborta. El warning: “Running git merge with non-trivial uncommitted changes is discouraged.” --abort a veces no reconstruye esos edits.
  • -X ours / -X theirs. El man: -X ours resuelve hunks en conflicto a favor de “ours”; el resto del otro árbol sí entra. No es -s ours, que tira el otro árbol entero. Un agente no elige un lado.
  • -s ours, octopus (git merge a b), --allow-unrelated-histories. Sin ancestro común el default rehúsa; no hay config para activarlo.
  • --squash. Deja el árbol como un merge pero no escribe MERGE_HEAD; el siguiente commit no es de fusión. --commit con --squash falla.
  • --no-verify. Salta pre-merge y commit-msg. Default es correrlos.
  • --autostash “por si acaso”. El man: el apply final puede pelear.
  • git mergetool. Interactivo.
  • Resolver marcando a ciegas <<<<<<< / ======= / >>>>>>>. GitHub: “You must resolve all merge conflicts before you can merge a pull request.” Default del agente: --abort y reportar paths (git diff --name-only --diff-filter=U).

--abortgit reset --merge si hay MERGE_HEAD (salvo MERGE_AUTOSTASH, que re-aplica el stash). Ver reset. --quit olvida el merge y no limpia.

Receta (60 segundos)

Solo en un worktree propio, working tree limpio:

git status -sb          # no dirty; no merge in progress
git fetch origin
git merge origin/main --no-edit

Conflicto:

git diff --name-only --diff-filter=U
git merge --abort

--abort: “reconstruct the pre-merge state.” Si arrancaste sucio, puede no devolver esos edits. Por eso el status primero.

Tras un merge limpio: push normal a la rama del PR. Cero --force. El merge commit es historia hacia adelante.

Si el ticket es “actualizar main”: no. Abre o actualiza el PR; el humano (o gh pr merge) integra con --no-ff.

Conflictos en un archivo: stages 1/2/3 en el index (git ls-files -u). Stage 1 = ancestro, 2 = HEAD, 3 = MERGE_HEAD. El working tree trae marcadores. ort (default) también escribe AUTO_MERGE. El agente no pisa stages a mano.

Conflicto: --abort vuelve al pre-merge; -X ours no es resolver

Merge vs rebase vs GitHub

  • merge: commit nuevo (o FF). SHA viejos siguen. Eso va a un PR.
  • rebase: reaplica y cambia SHA. Otra guía; no es el default para una rama ya pusheada.
  • GitHub merge commit: --no-ff. Squash and merge aplana; Rebase and merge reescribe. Un agente no elige squash/rebase del PR salvo orden escrita.

git pull sin args = fetch + merge del upstream configurado. Un agente nombra origin/main. Sin upstream, el merge sin <commit> no tiene a quién unir.

Checklist

  • status -sb limpio. Cero merge a medias (MERGE_HEAD).
  • Fetch primero. Upstream explícito (origin/main).
  • Conflicto → --abort + lista de paths. Cero -X ours/theirs.
  • Cero --squash, -s ours, octopus, --no-verify, --autostash.
  • Cero merge a main local. Integración = PR.
  • Push normal. Cero force.
  • --quit no es abortar.

FAQ

¿git merge sin args? Une el upstream de branch.<name>.merge. Sin upstream, aborta. Nombra el ref.

¿--no-ff siempre? En un topic que solo quiere ponerse al día con main, el default --ff basta si no divergiste. Si divergiste, sale true merge. En el botón de GitHub, sí: --no-ff.

¿Puedo dejar marcadores y commitear? No. El merge no termina hasta git add de cada path en conflicto + --continue. Commitear marcadores rompe el build.

¿merge.conflictStyle=diff3? Añade ||||||| con el ancestro. Útil para un humano. Un agente que aborta no lo necesita.

¿Hooks? pre-merge y commit-msg corren. --no-verify los salta. No lo uses.

El curso instalar un agente cubre el loop local. Merge no reescribe SHA; no es sandbox ni permiso para tocar main.

Verificado 2026-09-03 contra git-merge(1) (Git 2.50.1 / Apple Git-155), GitHub Docs About merge conflicts y About pull request merges (HTTP 200).