Introducción
Git tiene dos resultados de merge fundamentalmente diferentes: fast-forward, donde la punta de un branch simplemente se mueve, y three-way, donde se crea un nuevo commit con dos padres. La elección depende de si los branches han divergido.
Fast-forward
Si la punta de main es ancestro de feature, no se necesita un merge real. Git solo mueve main hacia adelante:
A---B---C feature
/
main
# después de git merge feature
A---B---C main, feature
Comando:
git switch main
git merge feature
Para forzar un merge commit real incluso cuando es posible fast-forward:
git merge --no-ff feature
Merge three-way
Cuando ambos branches han divergido, Git encuentra la merge base (el ancestro común más reciente) y combina los cambios de ambos lados:
A---B---C feature
/
A---D---E main
# después de git merge feature
A---D---E---M main
\ /
B---C feature
La estrategia de merge predeterminada de Git desde 2.33 es ort; antes era recursive. Ambas calculan un merge three-way por archivo usando la merge base.
Inspeccionando
git merge-base main feature
git log --oneline --graph --all
git show --stat HEAD # ver el merge commit
Estrategias y opciones
git merge -X ours feature # preferir nuestro lado en conflictos
git merge -X theirs feature # preferir su lado
git merge -X ignore-space-change feature
git merge --squash feature # aplicar cambios sin merge commit
--squash crea un solo commit que contiene todos los cambios de feature, sin enlace de padre a él. Útil para historial ordenado pero pierde el detalle por commit.
Abortando y deshaciendo
git merge --abort # a mitad de merge, volver al estado pre-merge
git reset --hard ORIG_HEAD # después de un merge terminado, deshacerlo
Configurando política fast-forward
git config --global merge.ff false # siempre crear merge commit
git config --global merge.ff only # solo fast-forward, rechazar de lo contrario
git config --global pull.ff only # igual para pulls
Drivers de merge y atributos
Algunos archivos se mergean mal con three-way basado en texto (lockfiles generados por máquina, documentación generada). Configura un driver de merge personalizado vía .gitattributes y git config:
# .gitattributes
package-lock.json merge=ours
Gemfile.lock merge=union
git config merge.ours.driver true
git config merge.union.name "Line union merge"
git config merge.union.driver "git merge-file --union %A %O %B"
La estrategia integrada union mantiene ambos lados; ours siempre elige nuestra versión; los drivers personalizados pueden ejecutar cualquier programa. Úsalos con moderación; comportamiento de merge sorprendente confunde a los reviewers.
Errores comunes
Creer que fast-forward es de alguna manera especial o arriesgado; no lo es, es solo un movimiento de puntero. Usar --no-ff en cada merge en un proyecto de movimiento rápido, contaminando el historial con merge commits vacíos. Usar -X ours cuando querías --strategy=ours (que descarta el otro lado por completo) o viceversa. Finalmente, editar manualmente el mensaje de merge commit después del hecho y romper la redacción estandarizada "Merge branch 'X'" en la que las herramientas se basan; si debes editarlo, hazlo antes de git commit.