En la guía anterior vimos que cuando main no tiene commits propios mientras trabajamos en una rama, el merge es sencillo: Git avanza solo. Pero eso no siempre ocurre.
En un equipo real es habitual que dos personas modifiquen el mismo fichero a la vez. Cuando eso pasa y ambos intentan fusionar, Git se encuentra con dos versiones distintas de la misma línea y no sabe cuál es la correcta. En ese momento se detiene y nos pide que decidamos nosotros.
A eso se le llama conflicto, y no es un error. Es Git diciéndonos: "aquí hay una decisión que solo tú puedes tomar".
Seguimos en mi-proyecto. Después de la guía anterior, index.js en main tiene este contenido:
index.js
console.log("Hola mundo");
const saludo = (nombre) => {
return `Hola, ${nombre}`;
};Creamos una rama nueva para añadir una función de despedida:
git switch -c feature/despedidaEditamos index.js en la rama. Cambiamos el console.log inicial y añadimos la función:
index.js
- console.log("Hola mundo");
+ console.log("Iniciando aplicación...");
const saludo = (nombre) => {
return `Hola, ${nombre}`;
};
+ const despedida = (nombre) => {
+ return `Hasta luego, ${nombre}`;
+ };git add index.js
git commit -m "Añade función despedida y actualiza mensaje inicial"Ahora volvemos a main:
git switch mainY aquí hacemos también un cambio en la misma línea del console.log:
index.js
- console.log("Hola mundo");
+ console.log("Aplicación lista");
const saludo = (nombre) => {
return `Hola, ${nombre}`;
};git add index.js
git commit -m "Actualiza mensaje de inicio en main"Este es un buen momento para presentar un atajo que usarás mucho: git commit -am. La -a hace el git add automáticamente por ti, y la -m añade el mensaje. El comando anterior se puede escribir así:
git commit -am "Actualiza mensaje de inicio en main"El resultado es exactamente el mismo. Hay una condición importante: solo funciona con ficheros que Git ya está rastreando. Si creas un fichero nuevo, Git no lo conoce todavía y -am no lo incluirá. En ese caso tendrás que hacer git add primero como siempre.
Ahora tenemos dos ramas que han tocado la misma línea con contenido diferente. Cuando intentemos fusionarlas, Git no sabrá cuál de las dos versiones del console.log es la correcta.
Desde main, intentamos fusionar:
git merge feature/despedidaAuto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.Git ha parado. No ha podido fusionar automáticamente porque index.js tiene cambios incompatibles en las dos ramas.
git statusOn branch main
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.jsboth modified significa exactamente eso: los dos ramas han modificado el mismo fichero. Git lo ha dejado a medias a la espera de que nosotros lo resolvamos.
Si abrimos index.js veremos que Git ha marcado el problema directamente dentro del fichero:
<<<<<<< HEAD
console.log("Aplicación lista");
=======
console.log("Iniciando aplicación...");
>>>>>>> feature/despedida
const saludo = (nombre) => {
return `Hola, ${nombre}`;
}
const despedida = (nombre) => {
return `Hasta luego, ${nombre}`;
}Los marcadores significan:
<<<<<<< HEAD— lo que tienemain(HEAD es la rama en la que estamos)=======— separador entre las dos versiones>>>>>>> feature/despedida— lo que viene de la rama que intentamos fusionar
El resto del fichero, lo que no tiene conflicto, Git lo ha fusionado solo sin problema. La función despedida ya está ahí.
Resolver un conflicto es editar el fichero y dejarlo como queremos que quede definitivamente. Abrimos index.js:
code index.jsSi no tienes VS Code, abre el fichero con cualquier editor de texto. Lo importante no es el editor sino lo que hay que hacer: eliminar los marcadores y dejar el contenido final limpio.
VS Code detecta los marcadores de conflicto y los resalta en color, lo que facilita mucho ver qué viene de cada rama. También muestra botones para aceptar una versión u otra directamente. Pero tanto si usas los botones como si editas a mano, el resultado tiene que ser el mismo: el fichero sin marcadores.
Eliminamos los marcadores y decidimos con qué nos quedamos. En este caso tiene más sentido el mensaje de la rama, así que dejamos:
index.js
console.log("Iniciando aplicación...");
const saludo = (nombre) => {
return `Hola, ${nombre}`;
};
const despedida = (nombre) => {
return `Hasta luego, ${nombre}`;
};Lo importante es que el fichero quede limpio: sin <<<<<<<, sin =======, sin >>>>>>>. Esos marcadores no son código, son anotaciones temporales de Git que hay que eliminar siempre.
Con el fichero resuelto, lo añadimos al staging:
git add index.jsY completamos el merge con un commit:
git commit -m "Merge feature/despedida: resuelve conflicto en console.log"[main a1b2c3d] Merge feature/despedida: resuelve conflicto en console.logPodemos ver el historial para confirmar que el merge se ha completado:
git log --oneline --graph* a1b2c3d (HEAD -> main) Merge feature/despedida: resuelve conflicto en console.log
|\
| * 9f3e1a2 (feature/despedida) Añade función despedida y actualiza mensaje inicial
* | 5c8d4b1 Actualiza mensaje de inicio en main
|/
* 3f8a21c Añade hoja de estilos y la enlaza en el HTML
* 7d9e0f1 primer commitEl --graph muestra visualmente cómo las dos ramas se han separado y vuelto a unir. Esto es exactamente lo que diferencia un merge normal de un fast-forward: aquí sí ha habido trabajo paralelo en las dos ramas, y el historial lo refleja.
Los conflictos forman parte del trabajo diario con Git. Con la práctica se resuelven rápido. La clave es no entrar en pánico cuando aparecen: leer lo que Git te dice, abrir el fichero, decidir qué versión es la correcta y terminar el merge con add y commit.
