
Este artículo es parte de nuestra serie “Advanced Git”. Asegúrate de Síguenos en Twitter o suscríbete a nuestro boletín para conocer los próximos artículos.
La mayoría de los desarrolladores comprenden que es importante usar ramas en Git. De hecho, he escrito un artículo completo sobre estrategias de ramificación en Git, explicando el poderoso modelo de ramificación de Git, los diferentes tipos de ramificaciones y dos de los flujos de trabajo de ramificación más comunes. En resumen: tener contenedores separados, es decir, ramas, para su trabajo es increíblemente útil y una de las principales razones para usar un sistema de control de versiones.
En este artículo, veremos la integración de ramas. ¿Cómo se puede recuperar el código nuevo en una línea de desarrollo existente? Hay diferentes formas de lograrlo. El quinto episodio de nuestra serie “Advanced Git” analiza la integración de cambios en Git, a saber, la fusión y el rebase.
Antes de entrar en detalles, es importante comprender que ambos comandos, git merge y git rebase, resuelven el mismo problema. Integran cambios de una rama de Git a otra rama; simplemente lo hacen de manera diferente. Comencemos con las fusiones y cómo funcionan realmente.
Serie avanzada de Git:
- Parte 1: Creando el compromiso perfecto en Git
- Parte 2: Estrategias de ramificación en Git
- Parte 3: Mejor colaboración con solicitudes de extracción
- Parte 4: Fusionar conflictos
- Parte 5: Rebase vs Merge (¡Estás aquí!)
- Parte 6: Rebase interactiva
- Parte 7: Compromisos de selección de cerezas en Git
- Parte 8: Uso de Reflog para restaurar confirmaciones perdidas
Entendiendo las fusiones
Para fusionar una rama con otra, puede usar el comando git merge. Digamos que tiene algunas confirmaciones nuevas en una de sus ramas, branch-B, y ahora quiere fusionar esta rama en otra, branch-A. Para hacerlo, puede escribir algo como esto:
$ git checkout branch-A
$ git merge branch-B
Como resultado, Git crea un nuevo compromiso de fusión en su rama de trabajo actual (branch-A
en este ejemplo), conectando las historias de ambas ramas. Para lograr esto, Git busca tres confirmaciones:
- El primero es el “compromiso de ancestro común”. Si sigue el historial de dos ramas en un proyecto, siempre tendrán al menos una confirmación en común. En este punto, ambas ramas tienen el mismo contenido. Después de eso, evolucionaron de manera diferente.
- Las otras dos confirmaciones interesantes son los puntos finales de cada rama, es decir, sus estados actuales. Recuerde que el objetivo de una integración es combinar los estados actuales de dos ramas. Entonces, sus últimas revisiones son importantes, por supuesto.
La combinación de estos tres compromisos realiza la integración que buscamos.
Es cierto que este es un escenario simplificado: una de las dos ramas (branch-A
) no ha visto nuevas confirmaciones desde que se creó, lo que es muy poco probable en la mayoría de los proyectos de software. Su última confirmación en este ejemplo es, por lo tanto, también el ancestro común.
En este caso, la integración es completamente simple: Git puede agregar todas las nuevas confirmaciones de branch-B
además del compromiso de ancestro común. En Git, esta forma más simple de integración se denomina combinación de “avance rápido”. Ambas ramas comparten exactamente el mismo historial (y no es necesario un “compromiso de fusión” adicional).
En la mayoría de los casos, sin embargo, ambas ramas avanzarían con diferentes confirmaciones. Entonces, tomemos un ejemplo más realista:
Para hacer una integración, Git tiene que crear una nueva confirmación que contenga todos los cambios y se encargue de las diferencias entre las ramas; esto es lo que llamamos una confirmación de fusión.
Confirmaciones humanas y combinaciones de confirmaciones
Normalmente, un compromiso es creado cuidadosamente por un ser humano. Es una unidad significativa que solo incluye cambios relacionados, además de un mensaje de confirmación significativo que proporciona contexto y notas.
Ahora, una confirmación de fusión es un poco diferente: no es creada por un desarrollador, sino automáticamente por Git. Además, una confirmación de fusión no contiene necesariamente una “colección semántica de cambios relacionados”. En cambio, su propósito es simplemente conectar dos (o más) ramas y hacer el nudo.
Si desea comprender una operación de fusión automática de este tipo, debe echar un vistazo al historial de todas las ramas y sus respectivos historiales de confirmaciones.
Integración con rebases
Antes de hablar de rebase, déjeme aclarar una cosa: una rebase no es mejor ni peor que una fusión, solo es diferente. Puedes vivir una vida feliz (Git) simplemente fusionando ramas y ni siquiera pensar en rebasar. Sin embargo, es útil comprender lo que hace una rebase y conocer los pros y los contras que la acompañan. Tal vez llegue a un punto en un proyecto en el que una nueva base podría ser útil …
¡Esta bien vamos! ¿Recuerda que acabamos de hablar sobre las confirmaciones de fusión automáticas? Algunas personas no están muy interesadas en estos y prefieren prescindir de ellos. Además, hay desarrolladores a los que les gusta que el historial de su proyecto se vea como una línea recta, sin ninguna indicación de que se haya dividido en varias ramas en algún momento, incluso después de que las ramas se hayan integrado. Esto es básicamente lo que sucede durante una rebase de Git.
Rebasing: paso a paso
Repasemos una operación de rebase paso a paso. El escenario es el mismo que en los ejemplos anteriores, y así es como se ve el punto de partida:
Queremos integrar los cambios de branch-B
en branch-A
– pero rebasando, no fusionando. El comando de Git real para esto es muy simple:
$ git checkout branch-A
$ git rebase branch-B
Similar a un git merge
comando, le dice a Git qué rama desea integrar. Echemos un vistazo entre bastidores …
En este primer paso, Git “eliminará” todas las confirmaciones en la rama A que ocurrieron después de la confirmación del ancestro común. No te preocupes, lo hará no Tírelos: puede pensar en esos commits como “estacionados” o guardados temporalmente en un lugar seguro.
En el segundo paso, Git aplica las nuevas confirmaciones de branch-B
. En este punto, temporalmente, ambas ramas se ven exactamente iguales.
Por último, las confirmaciones “aparcadas” (las nuevas confirmaciones de branch-A
) están incluidos. Dado que se colocan en la parte superior de las confirmaciones integradas de branch-B
, están rebasados.
Como resultado, el historial del proyecto parece que el desarrollo ocurrió en línea recta. No hay una confirmación de fusión que contenga todos los cambios combinados y se conserva la estructura de confirmación original.
Posibles escollos del rebase
Una cosa más, y esto es importante entender acerca de Git rebase, es que reescribe el historial de confirmaciones. Eche otro vistazo a nuestro último diagrama. La confirmación C3 * tiene un asterisco. Si bien C3 * tiene el mismo contenido que C3, es efectivamente una confirmación diferente. ¿Por qué? Porque tiene un nuevo padre confirmado después de la rebase. Antes de la rebase, C1 era el padre. Después de la rebase, el padre es C4, en el que se rebasó.
Una confirmación tiene solo un puñado de propiedades importantes, como el autor, la fecha, el conjunto de cambios y su confirmación principal. Cambiar cualquier parte de esta información crea una confirmación completamente nueva, con un nuevo ID de hash SHA-1.
Reescribir el historial como ese no es un problema para las confirmaciones que aún no se han publicado. Pero podría tener problemas si está reescribiendo confirmaciones que ya envió a un repositorio remoto. Tal vez alguien más ha basado su trabajo en la confirmación C3 original, y ahora de repente ya no existe …
Para evitar problemas, aquí hay una regla simple para usar rebase: ¡Nunca use rebases en ramas públicas, es decir, en confirmaciones que ya han sido enviadas a un repositorio remoto! En su lugar, use git rebase
para limpiar su historial de confirmaciones local antes de integrarlo en una rama de equipo compartida.
¡La integración lo es todo!
Al final del día, fusionar y reajustar son estrategias útiles de Git, dependiendo de lo que quieras lograr. La fusión no es destructiva, ya que una fusión no cambia el historial existente. Rebasar, por otro lado, puede ayudar a limpiar el historial de su proyecto al evitar compromisos de fusión innecesarios. Solo recuerde no hacerlo en una sucursal pública para evitar meterse con sus compañeros desarrolladores.
Si desea profundizar en las herramientas avanzadas de Git, no dude en consultar mi “Kit de Git avanzado” (gratuito): es una colección de videos cortos sobre temas como estrategias de ramificación, Rebase interactiva, Reflog, Submódulos y mucho más.
¡Feliz fusión y reestructuración, y nos vemos pronto en la siguiente parte de nuestra serie “Advanced Git”!
Serie avanzada de Git:
- Parte 1: Creando el compromiso perfecto en Git
- Parte 2: Estrategias de ramificación en Git
- Parte 3: Mejor colaboración con solicitudes de extracción
- Parte 4: Fusionar conflictos
- Parte 5: Rebase vs Merge (¡Estás aquí!)
- Parte 6: Rebase interactiva
- Parte 7: Compromisos de selección de cerezas en Git
- Parte 8: Uso de Reflog para restaurar confirmaciones perdidas