En la lección anterior, cada vez que Git creó un commit de fusión nos dijo lo mismo: Merge made by the 'ort' strategy. Pasamos de largo, pero esa palabra esconde una parte importante de cómo funciona Git.

Una estrategia de fusión es el algoritmo que Git emplea para combinar dos (o más) líneas de trabajo. No hay una sola: Git trae varias, elige una por defecto según el caso, y te permite imponer otra. Además, cada estrategia admite opciones que modifican su comportamiento en detalles concretos, como qué hacer ante un conflicto o si ignorar cambios de espacios en blanco.

En la práctica, el 95 % de las fusiones que harás usarán la estrategia por defecto sin que tengas que pensar en ello. Pero el 5 % restante —una fusión con cientos de conflictos triviales, una rama abandonada que hay que "cerrar" formalmente, una funcionalidad hecha con veinte commits caóticos que no quieres meter en main— es exactamente donde conocer las alternativas ahorra horas.

Esta lección cubre también una decisión que va más allá del algoritmo: qué forma de integrar elegir según el historial que quieras acabar teniendo. Fusión normal, --no-ff, squash… o rebase, que veremos en el módulo 5 pero que conviene situar ya en el mapa.

Contenido

  1. Qué es exactamente una estrategia de fusión
  2. ort: la estrategia por defecto
  3. resolve: la veterana
  4. octopus: varias ramas de una vez
  5. La estrategia ours: descartar el contenido, conservar la historia
  6. subtree: fusionar proyectos anidados
  7. Opciones de estrategia con -X
  8. La trampa: estrategia ours frente a opción -X ours
  9. El squash merge
  10. --no-commit: revisar antes de sellar
  11. Tabla de decisión: qué forma de integrar elegir

  1. Qué es exactamente una estrategia de fusión

Cuando git merge tiene que combinar de verdad —es decir, cuando no es un fast-forward—, necesita responder a una pregunta por cada fichero y por cada línea: dado lo que había en el ancestro común y lo que hay en cada lado, ¿qué pongo aquí?

El conjunto de reglas que responde a esa pregunta es la estrategia. Se elige con -s o --strategy:

git merge -s <estrategia> <rama>

Y sus opciones internas con -X o --strategy-option:

git merge -X <opcion> <rama>

Las dos se pueden combinar, y -X se puede repetir:

git merge -s ort -X ignore-space-change -X patience <rama>

Comparemos primero todas las estrategias disponibles, y después veamos cada una:

Estrategia Ramas que admite Cuándo la usa Git Para qué sirve
ort Dos Por defecto desde Git 2.34 Fusión general; es la que quieres casi siempre
recursive Dos Por defecto de Git 2.0 a 2.33 Predecesora de ort; hoy es un alias de ort
resolve Dos Nunca por defecto Algoritmo antiguo y simple; un único ancestro común
octopus Tres o más Por defecto al fusionar 3+ ramas Integrar varias ramas triviales de golpe
ours Dos o más Nunca por defecto Registrar la fusión descartando el contenido de la otra rama
subtree Dos Nunca por defecto Fusionar un proyecto dentro de un subdirectorio

  1. ort: la estrategia por defecto

ort significa Ostensibly Recursive's Twin ("el gemelo aparente de recursive"), un nombre con sentido del humor que delata su origen: es una reescritura desde cero de la estrategia recursive, diseñada para dar los mismos resultados pero mucho más rápido y con menos casos raros.

Es la estrategia por defecto desde Git 2.34 (noviembre de 2021). Antes lo era recursive. Hoy, si escribes -s recursive, Git usa ort de todas formas.

Lo que hace, resumido:

  1. Calcula el ancestro común de las dos ramas.
  2. Hace la fusión a tres bandas fichero a fichero y línea a línea, con la tabla de reglas que vimos en la lección 03-03.
  3. Detecta renombrados: si en una rama moviste estilos.css a css/estilos.css y en la otra lo modificaste, aplica los cambios al fichero en su nueva ubicación en lugar de darte un conflicto absurdo.
  4. Si hay varios ancestros comunes, los fusiona entre sí recursivamente hasta obtener uno virtual, y usa ese como base.

Ese punto 4 es el que da nombre a la familia y merece un momento. Puede haber más de un ancestro común cuando el historial tiene fusiones cruzadas:

graph RL
    A["A"]
    B["B"] --> A
    C["C"] --> A
    D["D"] --> B
    D --> C
    E["E"] --> C
    E --> B
    F["F<br/>rama-1"] --> D
    G["G<br/>rama-2"] --> E

Aquí, tanto D como E son ancestros comunes de rama-1 y rama-2, y ninguno es antepasado del otro. Una estrategia simple tendría que elegir uno arbitrariamente, con el riesgo de producir conflictos falsos. ort los fusiona entre sí para construir una base virtual que combine la información de ambos.

No necesitas provocar esta situación para trabajar: solo saber que existe y que Git la resuelve solo. Es la razón de que las fusiones en historiales complejos funcionen sorprendentemente bien.

  1. resolve: la veterana

git merge -s resolve funcionalidad/filtro-pendientes

resolve es la estrategia clásica: fusión a tres bandas con un único ancestro común, elegido si hay varios. No hace recursión y detecta renombrados de forma mucho más limitada.

¿Cuándo usarla? Prácticamente nunca. Su nicho es el caso raro en el que ort produce un resultado que no te convence en un historial con fusiones cruzadas y quieres probar una base distinta. Es más un recurso de diagnóstico que una herramienta de trabajo.

Está en el temario porque la verás mencionada en documentación antigua y porque conocerla ayuda a entender por qué ort hace lo que hace.

  1. octopus: varias ramas de una vez

git merge no se limita a un argumento. Puedes nombrar varias ramas:

git merge rama-a rama-b rama-c

Cuando hay tres o más, Git cambia automáticamente a la estrategia octopus, que crea un único commit de fusión con tantos padres como ramas más una.

Imaginemos que el equipo ha acumulado tres correcciones diminutas e independientes, cada una en su rama:

git switch main
git merge correccion/typo-readme correccion/favicon correccion/idioma-html
Trying simple merge with correccion/typo-readme
Trying simple merge with correccion/favicon
Trying simple merge with correccion/idioma-html
Merge made by the 'octopus' strategy.
 README.md  | 2 +-
 index.html | 3 ++-
 3 files changed, 4 insertions(+), 2 deletions(-)
git cat-file -p HEAD | head -5
tree 5f8b2e1c9a3d7f4b6e2a8c1d5f9b3e7a4c6d2f8b
parent f7a3e92b5d1c8f4a6e3b7d9f2a5c1e8b4d6f3a9c
parent 2f6a3c8e9b1d5f7a3c2e8b4d6f1a9c5e7b3d2f4a
parent 7d2f9a1c5e8b3d6f2a4c9e1b7d5f3a8c2e6b4d9f
parent 3c9d4a2f8e1b5d7c3a9f2e6b4d8c1a5f7e3b9d2c

Cuatro padres. El primero es main y los otros tres, las puntas de las ramas fusionadas.

gitGraph
   commit id: "f7a3e92"
   branch typo-readme
   commit id: "2f6a3c8"
   checkout main
   branch favicon
   commit id: "7d2f9a1"
   checkout main
   branch idioma-html
   commit id: "3c9d4a2"
   checkout main
   merge typo-readme
   merge favicon
   merge idioma-html id: "pulpo"

La limitación crucial de octopus: se niega a trabajar si hay cualquier conflicto.

Merge with strategy octopus failed.

No hay resolución posible, no hay marcadores en los ficheros, no hay nada que arreglar: la operación se aborta entera. La razón es de diseño: resolver conflictos entre cuatro versiones simultáneas sería inmanejable para una persona.

Por eso su uso realista es muy concreto: integrar de golpe varias ramas que no se pisan entre sí. Si alguna conflictúa, tendrás que fusionarlas de una en una. En el día a día de un equipo pequeño la verás poco; en proyectos con muchísimas aportaciones independientes (el núcleo de Linux es el ejemplo canónico, y no por casualidad, ya que fue el proyecto para el que se escribió Git) tiene todo el sentido.

  1. La estrategia ours: descartar el contenido, conservar la historia

Esta es la estrategia más peculiar del conjunto y la que más se malinterpreta.

git merge -s ours <rama>

Lo que hace: crea un commit de fusión con los dos padres normales, pero el árbol resultante es exactamente el de la rama actual. El contenido de la otra rama se descarta por completo.

Suena absurdo. ¿Para qué fusionar algo que vas a tirar? Para registrar en el historial que esa rama ya está considerada, sin traer su código.

El caso de uso real en el proyecto de Ana: el equipo abrió hace un mes la rama experimento/almacenamiento-indexeddb para probar un sistema de almacenamiento distinto. El experimento se descartó —al final se quedaron con localStorage—, pero la rama sigue ahí y de vez en cuando alguien pregunta si hay que integrarla.

git switch main
git merge -s ours experimento/almacenamiento-indexeddb -m "Descartar el experimento con IndexedDB

Se probó IndexedDB para el almacenamiento de tareas. Se descarta:
la complejidad no compensa para el volumen de datos que manejamos.
Se mantiene localStorage. La rama queda registrada como integrada."
Merge made by the 'ours' strategy.

Fíjate en que no lista ningún fichero cambiado: no ha cambiado nada.

git diff HEAD~1 HEAD
(sin salida)

Cero diferencias respecto al commit anterior de main. El código es idéntico. Pero:

git branch --merged
  experimento/almacenamiento-indexeddb
* main

La rama consta como fusionada, con lo que aparecerá en las listas de ramas que se pueden borrar sin perder nada (lección 03-06), dejará de salir en los avisos de "ramas pendientes de integrar", y el historial contiene una explicación permanente de por qué se descartó.

Otro uso clásico: cuando dos ramas han divergido tanto que reconciliarlas a mano es inviable y quieres que gane una entera, dejando constancia de la decisión.

Advertencia importante: -s ours descarta todo el contenido de la otra rama sin preguntar y sin avisar. No hay conflictos, no hay revisión, no hay marcha atrás automática. Es una decisión deliberada, no un atajo para salir de una fusión difícil. Y no la confundas con -X ours, que es algo completamente distinto y que veremos en el apartado 8.

  1. subtree: fusionar proyectos anidados

git merge -s subtree <rama>

subtree es una variante de ort para el caso en que una rama contiene un proyecto que en la otra vive dentro de un subdirectorio.

Supón que el equipo desarrolla aparte una pequeña biblioteca de componentes, en su propio repositorio, y que en gestor-tareas esa biblioteca vive en vendor/componentes/. Al fusionar la rama de la biblioteca, una estrategia normal intentaría casar sus ficheros con la raíz del proyecto y produciría un desastre. subtree detecta el desplazamiento de rutas y aplica los cambios donde corresponde.

También se puede indicar el prefijo a mano, que es más fiable que dejar que Git lo adivine:

git merge -X subtree=vendor/componentes rama-de-la-biblioteca

Este mecanismo es la base del comando git subtree, una de las dos formas de anidar repositorios. La otra son los submódulos, que tienen lección propia en el módulo 6. Por ahora basta con que sepas que la estrategia existe y para qué escenario está pensada.

  1. Opciones de estrategia con -X

Las opciones no cambian el algoritmo: lo ajustan. Estas son las que valen la pena:

Opción Efecto
-X ours Ante un conflicto, resuelve automáticamente a favor de la rama actual
-X theirs Ante un conflicto, resuelve automáticamente a favor de la rama fusionada
-X ignore-space-change Ignora cambios en la cantidad de espacios al comparar
-X ignore-all-space Ignora todos los espacios en blanco
-X ignore-space-at-eol Ignora espacios al final de línea
-X ignore-cr-at-eol Ignora el retorno de carro final (útil entre Windows y Unix)
-X renormalize Normaliza los finales de línea antes de comparar, según .gitattributes
-X find-renames=<n> Ajusta el umbral de similitud para detectar renombrados
-X no-renames Desactiva la detección de renombrados
-X patience Usa el algoritmo de diff "paciencia", que a veces alinea mejor
-X diff-algorithm=histogram Elige otro algoritmo de comparación

El caso de los espacios en blanco

Este es el que más veces te va a salvar. Situación real: Bruno ha configurado su editor para reindentar automáticamente al guardar, y ha reindentado app.js entero en su rama. Ana ha modificado tres líneas del mismo fichero en la suya.

git merge funcionalidad/filtro-pendientes
Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
Automatic merge failed; fix conflicts and then commit the result.

Ochenta conflictos, y ninguno real: son todos diferencias de sangrado. Solución:

git merge --abort
git merge -X ignore-space-change funcionalidad/filtro-pendientes
Auto-merging app.js
Merge made by the 'ort' strategy.
 app.js | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

Los ochenta conflictos falsos desaparecen y quedan solo los cambios reales.

Dos matices importantes:

  • Estas opciones afectan a cómo se comparan las líneas, no al resultado escrito. El fichero final conserva el sangrado de una de las versiones (la de ours en los tramos ambiguos), así que revisa el resultado.
  • Si el problema del sangrado es recurrente en el equipo, la solución de fondo no es -X sino .gitattributes y un formateador automático compartido. Es el tema de la lección 08-04.

-X theirs en la práctica

Un ejemplo típico: un fichero generado automáticamente (un bundle.js, un fichero de bloqueo de dependencias) que conflictúa siempre y en el que la versión de la rama entrante es la correcta:

git merge -X theirs funcionalidad/filtro-pendientes

Es cómodo, pero usa -X ours/-X theirs con la cabeza: resuelven todos los conflictos del mismo lado, sin distinguir cuáles eran importantes. Si el resultado te importa, es mejor resolver a mano (lección 03-05) o limitar el atajo a los ficheros concretos que lo merecen, que también se puede hacer y veremos allí.

  1. La trampa: estrategia ours frente a opción -X ours

Se escriben casi igual y hacen cosas radicalmente distintas. Es una de las confusiones más caras de Git, así que vamos a dejarla clavada.

-s ours (estrategia) -X ours (opción)
Qué es Un algoritmo de fusión completo Un ajuste de la estrategia ort
Qué hace Descarta todo el contenido de la otra rama Fusiona normal, y solo ante un conflicto elige tu lado
Cambios sin conflicto de la otra rama Se pierden todos Se incorporan con normalidad
Resultado El árbol es idéntico al de tu rama El árbol combina las dos ramas
Cuándo usarla Cerrar formalmente una rama descartada Resolver en bloque conflictos sin importancia

Un ejemplo concreto. La rama funcionalidad/filtro-pendientes aporta dos cosas: un fichero nuevo filtros.js (que no conflictúa con nada) y una modificación de la línea 12 de app.js (que sí conflictúa con un cambio de Ana).

git merge -s ours funcionalidad/filtro-pendientes

Resultado: no aparece filtros.js y la línea 12 es la de Ana. Se ha descartado todo, incluso lo que no molestaba a nadie.

git merge -X ours funcionalidad/filtro-pendientes

Resultado: filtros.js sí aparece, porque no había conflicto con él, y la línea 12 es la de Ana, porque ahí sí lo había. Fusión real con desempate automático.

Regla mnemotécnica: -s es la estrategia, y una estrategia decide todo. -X es una opción, y una opción solo decide los empates.

Y una precisión sobre el vocabulario, porque también confunde: en una fusión, ours es siempre la rama en la que estás y theirs la que has nombrado en el comando. Suena obvio, pero durante un rebase los papeles se invierten respecto a lo que uno esperaría, y ese es uno de los motivos por los que el rebase merece su propia lección (05-01).

  1. El squash merge

Cambiamos de tercio. El squash merge no es una estrategia: es una forma distinta de integrar, y en muchos equipos es la más usada de todas.

Situación en el proyecto: Bruno ha desarrollado la exportación del listado a CSV en la rama funcionalidad/exportar-csv. Ha funcionado, pero el historial de la rama es un desastre:

git log --oneline main..funcionalidad/exportar-csv
e9a2c5f Ahora sí
7e2a9c4 Quitar el console.log
1d6b4f8 wip 2
5a8e2b9 Arreglar el separador
9f3a2c1 wip
4a1c7e3 Primer intento de exportación

Seis commits, cuatro de los cuales no aportan nada a nadie que lea el historial dentro de seis meses. Bruno no quiere meter eso en main.

git switch main
git merge --squash funcionalidad/exportar-csv
Updating f7a3e92..e9a2c5f
Fast-forward
Squash commit -- not updating HEAD
 app.js     | 34 ++++++++++++++++++++++++++++++++++
 index.html |  2 ++
 2 files changed, 36 insertions(+)

Lee bien: Squash commit -- not updating HEAD. Git ha calculado el resultado de la fusión y lo ha dejado en el área de preparación, pero no ha confirmado nada.

git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   app.js
	modified:   index.html

Ahora Bruno escribe un mensaje decente y confirma:

git commit -m "Añadir exportación del listado de tareas a CSV

Genera un fichero CSV con el título, el estado y la fecha de
creación de cada tarea. Se descarga desde el botón nuevo de la
cabecera."
[main 3b9e7d1] Añadir exportación del listado de tareas a CSV
 2 files changed, 36 insertions(+)

En qué se diferencia de una fusión normal

graph TB
    subgraph squash["SQUASH MERGE"]
        S1["f7a3e92<br/>main"] --> S2["3b9e7d1<br/>main (un solo commit,<br/>UN padre)"]
        S3["4a1c7e3 → e9a2c5f<br/>exportar-csv<br/>(desconectada)"]
    end
    subgraph normal["FUSIÓN NORMAL"]
        N1["f7a3e92"] --> N3["merge<br/>main (DOS padres)"]
        N2["4a1c7e3 → e9a2c5f<br/>exportar-csv"] --> N3
    end

La diferencia esencial:

Fusión normal Squash merge
Commits nuevos en main 1 (de fusión) 1 (normal)
Padres de ese commit Dos Uno
Commits de la rama en el historial de main Sí, los seis No, ninguno
¿Queda registrada la relación con la rama? No
¿Qué dice git branch --merged? Que está fusionada Que no lo está
Autoría de los commits originales Se conserva Se pierde (queda quien confirma)

Ese "no queda registrada la relación" tiene una consecuencia práctica muy concreta. Después de un squash, main no es descendiente de funcionalidad/exportar-csv:

git branch --merged
* main

La rama de Bruno no aparece, aunque su contenido esté íntegramente en main. Si intentas borrarla:

git branch -d funcionalidad/exportar-csv
error: The branch 'funcionalidad/exportar-csv' is not fully merged.
If you are sure you want to delete it, run 'git branch -D funcionalidad/exportar-csv'.

Git no miente: desde el punto de vista del grafo, esos seis commits no están en main. Habrá que borrarla con -D sabiendo lo que se hace. Volveremos sobre esto en la lección 03-06.

Cuándo usar squash

A favor:

  • main tiene un commit por funcionalidad, limpio y con un mensaje escrito con calma.
  • El desorden de la rama de trabajo (los wip, los "ahora sí") no contamina el proyecto.
  • git bisect (lección 06-02) funciona mejor: cada commit de main es un estado completo y funcional.
  • Es el modelo por defecto del botón "Squash and merge" de las plataformas de alojamiento, y por tanto el más extendido hoy en equipos que trabajan con propuestas de cambio.

En contra:

  • Se pierde el detalle del desarrollo. Si la funcionalidad son 40 commits bien escritos, aplastarlos es destruir información útil.
  • Se pierde la autoría individual: un commit hecho con aportaciones de tres personas queda a nombre de quien lo confirma.
  • Las ramas hay que borrarlas con -D, sin la red de seguridad de -d.
  • Si sigues trabajando en la rama después del squash y vuelves a fusionar, Git no sabe que aquello ya estaba integrado y puede reproducir conflictos ya resueltos.

Regla práctica: squash para ramas cortas de una sola persona con commits desordenados; fusión con --no-ff para funcionalidades grandes con un historial que merece conservarse.

  1. --no-commit: revisar antes de sellar

git merge --no-commit hace la fusión de verdad pero se detiene justo antes de crear el commit:

git merge --no-commit funcionalidad/filtro-pendientes
Automatic merge went well; stopped before committing as requested
git status
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
	modified:   app.js
	modified:   estilos.css
	modified:   index.html

En este punto puedes:

# Ver exactamente qué va a entrar
git diff --cached

# Ejecutar las pruebas con el resultado ya combinado
npm test

# Ajustar algo antes de confirmar

Y después, confirmar o abortar:

git commit                  # sella la fusión (con sus dos padres)
# o bien
git merge --abort           # deshace la fusión y vuelve al estado anterior

La diferencia con --squash es fundamental y a menudo se confunde, porque las dos "paran antes de confirmar":

--no-commit --squash
¿Se registra la fusión? : al confirmar sale un commit con dos padres No: sale un commit normal
¿Se puede abortar? Sí, con git merge --abort No: no hay fusión en curso que abortar
Estado de .git/MERGE_HEAD Existe No existe
Para qué sirve Revisar o probar antes de sellar Aplastar una rama en un commit

Ese fichero MERGE_HEAD es el detalle técnico que lo explica todo: con --no-commit hay una fusión en curso que Git recuerda, y por eso el commit resultante tendrá dos padres y --abort funciona. Con --squash no hay ninguna fusión en curso: solo hay unos cambios preparados en el índice.

Usos habituales de --no-commit:

  • Ejecutar la batería de pruebas sobre el resultado combinado antes de dejarlo en main.
  • Revisar una fusión grande fichero a fichero.
  • Ajustar detalles menores (un import duplicado, un espacio) y meterlos en el propio commit de fusión.

  1. Tabla de decisión: qué forma de integrar elegir

Recopilamos todo lo visto en este módulo, más el rebase que llega en el módulo 5, en una única tabla de decisión:

Quiero… Uso Historial resultante
Integrar y que no se note que hubo rama git merge (fast-forward) Lineal, sin nodo de fusión
Dejar constancia de que se integró una funcionalidad git merge --no-ff Con nodo de fusión y burbuja de commits
Un solo commit limpio por funcionalidad git merge --squash + git commit Lineal, un commit por rama
Prohibir los nodos de fusión en main git merge --ff-only Lineal, o falla
Probar el resultado antes de sellarlo git merge --no-commit El que decidas después
Integrar varias ramas triviales a la vez git merge a b c (octopus) Un nodo con muchos padres
Cerrar una rama descartada sin traer su código git merge -s ours Con nodo de fusión, sin cambios
Poner mi rama al día con main sin nodo de fusión git rebase main05-01 Lineal, con commits reescritos

Sobre la última fila, lo justo para situarla: el rebase no fusiona, sino que reescribe los commits de tu rama como si los hubieras hecho a partir del estado actual de main. El resultado es un historial lineal sin nodos de fusión, a cambio de que los commits cambian de hash y por tanto son commits nuevos. Eso tiene implicaciones serias cuando el trabajo ya se ha compartido, y por eso tiene lección propia. Aquí solo necesitas saber que es la tercera alternativa junto a la fusión y el squash.

Y una recomendación de partida

Si estás empezando y no tienes una política de equipo que seguir:

  1. Fusión normal para todo, dejando que Git decida entre fast-forward y tres bandas.
  2. --no-ff cuando integres algo que quieras poder identificar como una unidad en el futuro.
  3. --squash cuando la rama sea tuya, corta y desordenada.
  4. No toques -s ni -X hasta que un problema concreto te pida una de esas opciones.

Y sobre todo, que el equipo entero haga lo mismo. Un historial coherente vale más que un historial óptimo pero irregular. Las convenciones de flujo con nombre —Git Flow, GitHub Flow, Trunk Based Development— son precisamente eso: paquetes de decisiones ya tomadas. Tienen su sitio en el módulo 7.

Errores Comunes y Consejos

Error 1: confundir -s ours con -X ours. Es el error más caro de esta lección. -s ours descarta silenciosamente todo el trabajo de la otra rama, incluidos los ficheros que no daban ningún problema. Si lo que querías era desempatar conflictos, la opción es -X ours. Si alguna vez dudas, git diff HEAD~1 HEAD después de la fusión te dirá enseguida si has traído algo o nada.

Error 2: usar -X theirs como atajo universal. Resuelve todos los conflictos del mismo lado, incluidos aquellos en los que tu versión era la correcta. Se pierde trabajo sin que nada avise. Reserva estas opciones para conflictos que sabes que son irrelevantes.

Error 3: creer que --squash fusiona. No lo hace: prepara los cambios y se va. Si olvidas el git commit posterior, te quedas con un montón de cambios preparados en main y una sensación rara de que "la fusión no ha funcionado". Y aunque confirmes, la rama seguirá figurando como no fusionada.

Error 4: hacer squash de una rama y seguir trabajando en ella. Como el commit resultante no tiene relación con los originales, la siguiente fusión de esa rama volverá a traer todo lo anterior y reproducirá conflictos ya resueltos. Tras un squash, la rama se cierra: se borra y se abre otra.

Error 5: buscar la estrategia perfecta para un conflicto complicado. No existe. Si ort da conflictos, es porque hay una decisión humana que tomar. Cambiar de estrategia solo desplaza el problema o lo esconde. La respuesta correcta es la lección siguiente.

Consejo 1: -X ignore-space-change es el que más veces usarás. Cuando una fusión reviente con decenas de conflictos que al mirarlos son solo sangrado, aborta y reinténtalo con esa opción. La mejora es espectacular.

Consejo 2: comprueba lo que ha hecho una fusión rara. Después de cualquier fusión con -s o -X, mira el resultado antes de seguir:

git diff HEAD^1 HEAD --stat    # qué ha cambiado respecto a main
git log --oneline -1

Consejo 3: --no-commit es tu red de seguridad. En fusiones grandes o delicadas, párate antes de confirmar, ejecuta las pruebas y revisa. Cuesta treinta segundos y evita meter en main una combinación que nadie ha comprobado.

Consejo 4: documenta la política del equipo. Escribe en el README.md o en el CONTRIBUTING.md del proyecto cómo se integra en main. Es la clase de decisión que, sin escribir, cada persona interpreta de una forma.

Ejercicios

Ejercicio 1: demostrar la diferencia entre -s ours y -X ours

Monta un repositorio de pruebas donde una rama aporte dos cosas: un fichero nuevo que no conflictúa y una modificación que sí conflictúa con la rama principal. Fusiona de las dos formas (en dos intentos independientes) y demuestra con comandos qué queda en cada caso.

Ejercicio 2: squash frente a fusión normal

Crea una rama con tres commits y intégrala en main de las dos formas, en dos repositorios distintos o deshaciendo entre medias. Después responde:

  1. ¿Cuántos commits tiene main en cada caso?
  2. ¿Cuántos padres tiene el último commit de main en cada caso?
  3. ¿Qué dice git branch --merged en cada caso?
  4. ¿Se puede borrar la rama con -d en cada caso?

Ejercicio 3: elegir la forma de integrar

Para cada situación, elige entre fusión normal, --no-ff, --squash, -s ours, octopus o --no-commit, y justifícalo:

  1. Una rama de un compañero con 25 commits bien escritos y mensajes cuidados, que implementa el módulo de informes.
  2. Tu propia rama con 7 commits, cuatro de los cuales se llaman wip.
  3. Cinco ramas de traducción, cada una tocando un fichero de idioma distinto.
  4. Una rama de un experimento que se ha decidido no llevar adelante, y de la que quieres dejar constancia.
  5. Una fusión que afecta a la lógica de facturación y que quieres validar con las pruebas antes de que entre en main.

Soluciones

Solución 1:

mkdir /tmp/practica-ours && cd /tmp/practica-ours
git init -b main
echo "linea original" > comun.txt
git add . && git commit -m "Base"

# La rama aporta un fichero nuevo Y un cambio conflictivo
git switch -c aportacion
echo "contenido nuevo" > exclusivo.txt
echo "version de la rama" > comun.txt
git add . && git commit -m "Añadir exclusivo.txt y cambiar comun.txt"

# main cambia la MISMA línea de comun.txt
git switch main
echo "version de main" > comun.txt
git commit -am "Cambiar comun.txt en main"

Intento A, con la estrategia:

git merge -s ours aportacion -m "Fusión con estrategia ours"
ls
cat comun.txt
comun.txt
version de main

exclusivo.txt no está. Se ha descartado todo el contenido de la rama, incluido el fichero que no daba ningún problema.

git diff HEAD^1 HEAD
(sin salida)

Cero cambios respecto a main. Es la firma inconfundible de -s ours.

Intento B, con la opción:

git reset --hard HEAD~1     # deshacemos el intento A
git merge -X ours aportacion -m "Fusión con opción -X ours"
ls
cat comun.txt
comun.txt  exclusivo.txt
version de main

exclusivo.txt sí está, porque no había conflicto con él. Y comun.txt tiene la versión de main porque ahí sí lo había y -X ours desempató a nuestro favor.

Conclusión: -s ours descarta todo; -X ours fusiona de verdad y solo desempata.

Solución 2:

mkdir /tmp/practica-squash && cd /tmp/practica-squash
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"
git switch -c funcionalidad
echo "a" >> f.txt && git commit -am "Paso 1"
echo "b" >> f.txt && git commit -am "Paso 2"
echo "c" >> f.txt && git commit -am "Paso 3"
git switch main
echo "cambio en main" > otro.txt && git add . && git commit -m "Avanzar main"

Fusión normal:

git merge --no-edit funcionalidad
git log --oneline | wc -l
6
git rev-parse HEAD^1 HEAD^2 > /dev/null && echo "tiene dos padres"
tiene dos padres
git branch --merged
  funcionalidad
* main
git branch -d funcionalidad
Deleted branch funcionalidad (was 2c9d4e6).

Squash (repitiendo el montaje desde cero o deshaciendo con git reset --hard HEAD~1):

git merge --squash funcionalidad
git commit -m "Añadir la funcionalidad completa"
git log --oneline | wc -l
3
git rev-parse HEAD^2
fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.

Un solo padre.

git branch --merged
* main
git branch -d funcionalidad
error: The branch 'funcionalidad' is not fully merged.

Respuestas:

  1. 6 commits con fusión normal (base + 3 de la rama + avance de main + fusión); 3 con squash (base + avance de main + el commit aplastado).
  2. Dos padres con fusión normal; uno con squash.
  3. Con fusión normal, la rama aparece como fusionada; con squash, no.
  4. Con fusión normal, -d funciona; con squash, hay que usar -D.

Solución 3:

  1. Fusión con --no-ff. Veinticinco commits bien escritos son información valiosa que no hay que destruir, y el nodo de fusión documenta la integración del módulo como una unidad identificable.

  2. --squash. Rama corta, propia y desordenada: el caso de manual. Un commit limpio en main con un mensaje escrito con calma vale más que siete commits de los que cuatro no dicen nada.

  3. octopus (git merge trad-es trad-ca trad-en trad-fr trad-de). Cinco ramas que tocan ficheros distintos y por tanto no pueden conflictuar: es exactamente el escenario para el que sirve. Si alguna conflictuara, octopus fallaría y habría que integrarlas de una en una.

  4. -s ours. Registra en el historial que la rama se ha considerado y descartado, sin traer su código, y deja de aparecer como pendiente de integrar. Aprovecha el mensaje del commit para explicar por qué se descartó.

  5. --no-commit. Permite ejecutar la batería de pruebas sobre el resultado ya combinado y, si algo falla, salir limpiamente con git merge --abort sin que main llegue a contener nada.

Conclusión

Ya sabes que "fusionar" no es una sola cosa:

  • Una estrategia es el algoritmo que combina las ramas, y se elige con -s. ort es la de por defecto desde Git 2.34: fusión a tres bandas con detección de renombrados y resolución recursiva cuando hay varios ancestros comunes. resolve es su predecesora simple; octopus fusiona tres o más ramas de golpe pero falla ante cualquier conflicto; subtree casa proyectos que viven en subdirectorios.
  • La estrategia ours (-s ours) crea un commit de fusión y descarta todo el contenido de la otra rama. Sirve para cerrar formalmente una rama que se ha decidido no integrar.
  • Las opciones -X ajustan la estrategia, no la sustituyen. -X ours/-X theirs desempatan solo los conflictos; -X ignore-space-change elimina de un plumazo los conflictos falsos por sangrado.
  • -s ours y -X ours no tienen nada que ver. La estrategia decide todo; la opción decide solo los empates.
  • El squash merge (--squash) deja el resultado preparado en el índice sin confirmar y sin registrar la fusión: el commit final tiene un solo padre y la rama no consta como fusionada. Ideal para ramas cortas y desordenadas; malo para funcionalidades cuyo historial merece conservarse.
  • --no-commit hace la fusión de verdad pero se detiene antes de confirmar, permitiendo revisar y probar. A diferencia de --squash, la fusión sigue en curso y --abort funciona.
  • La elección de cómo integrar es una decisión de equipo sobre el historial que se quiere tener, no una cuestión técnica.

Lo que viene

Hemos dado por hecho durante toda la lección algo que en el mundo real no siempre ocurre: que la fusión sale bien. Pero Ana y Bruno no van a tener tanta suerte la próxima vez. Los dos están a punto de tocar las mismas líneas de la función que pinta el listado de tareas en app.js, cada uno en su rama y por motivos distintos.

Cuando eso pasa, Git no puede decidir. Se detiene, escribe unos marcadores extraños dentro del fichero y te pasa la pelota. En la siguiente lección, Resolviendo Conflictos de Fusión, provocaremos un conflicto de verdad y lo resolveremos paso a paso: aprenderás a leer los marcadores <<<<<<<, ======= y >>>>>>> (y el estilo diff3, que además te enseña qué había en el ancestro común), a orientarte con git status y git diff mientras la fusión está a medias, a usar los atajos --ours/--theirs, a abortar con git merge --abort cuando la cosa se tuerza, y a configurar una herramienta gráfica para los conflictos difíciles.

Dominando Git: De Principiante a Avanzado

Módulo 1: Introducción a Git

Módulo 2: Operaciones Básicas de Git

Módulo 3: Ramas y Fusión

Módulo 4: Trabajando con Repositorios Remotos

Módulo 5: Operaciones Avanzadas de Git

Módulo 6: Herramientas y Técnicas de Git

Módulo 7: Estrategias de Colaboración y Flujo de Trabajo

Módulo 8: Mejores Prácticas y Consejos de Git

Módulo 9: Solución de Problemas y Depuración

Módulo 10: Git en el Mundo Real

© Copyright 2026. Todos los derechos reservados