Continuamos la serie de Git y GitHub para aprender en este caso a trabajar con las branches (ramas), tanto las locales como las remotas, merge de ramas, resolución de conflictos, y comprensión de cómo es la metodología de trabajo colaborativo con GitHub.
Te recomendamos que te pases por nuestro artículo de Primeros pasos con Git, donde creamos un repo con algunos commits que usaremos en este artículo.
Para este taller y sucesivos es necesario utilizar la terminal de macOS/Linux o Git Bash de Windows y opcionalmente tener Python con Anaconda instalado. Revisa el artículo de instalación y el de configuración si todavía no lo has hecho.
Git – Branches en local – git branch, git checkout
Vamos a simular que queremos añadir una nueva funcionalidad a nuestra aplicación. No queremos tocar el código productivo (rama main), y por ello tendremos que llevar a cabo el desarrollo del nuevo código en una rama paralela a la main, de tal manera que la main se quede totalmente intacta hasta que acabemos la nueva feature y decidamos unirlo a la main posteriormente.
¿Por qué se llama rama main?
Antes de octubre de 2020, la rama por defecto en Git solía ser llamada “master”. Sin embargo, como parte de un esfuerzo por promover un lenguaje más inclusivo y eliminar términos que pudieran considerarse ofensivos o relacionados con la esclavitud, la comunidad de Git y GitHub decidieron cambiar el nombre predeterminado de la rama principal a “main”.
Primero vamos a ver qué ramas hay creadas:
git branch -a
Parece que la rama main no está sola, sino que la acompaña otra que se llama remotes/origin/main, es decir, la rama main del repositorio remoto. Esta rama está disponible en local y la podríamos consultar. Ahora no tiene sentido ya que son exactamente iguales, pero piensa que cuando desarrolles commits nuevos, tu rama main será distinta de la remotes/origin/main.
Fíjate también que aparece un asterisco en la rama main. Eso quiere decir que es la rama activa.
Recuerda que partimos del siguiente diagrama:
Creamos una nueva rama, que llamaremos income_feat:
git checkout -b income_feat
Ahora ya hay tres ramas y Git está apuntando a la nueva, a income_feat, tal y como indica el asterisco. Hay tres y no cuatro ramas porque el repo remoto todavía no sabe nada de la que acabamos de crear.
A partir de aquí podremos desarrollar todo lo que queramos sin que afecte a la rama main. Además, fíjate que otra cosa que ha cambiado es el indicador de rama de la derecha.
Si te quieres mover entre ramas:
git checkout <branch>
Ahora vamos a desarrollar la nueva funcionalidad en la rama que acabamos de crear, dejando main totalmente aislada y sin modificar. Añadimos las siguientes lineas de código al script de Python:
products['Amount'] = [1000, 1500, 4200]
products['Income'] = products['Price'] * products['Amount']
No es necesario, pero prueba ahora a cambiar a main, verás que se mantienen los cambios de código que acabas de hacer, pero te indica que se ha modificado sales.py. Estamos viendo main pero con los cambios en la Working Area sin guardar en el repo. Vuelve a income_feat y commitea los cambios:
git add .
git commit -m "Amount and Income added to products"
TIP: git add .
sirve para añadir todos los archivos con cambios a la Staging Area.
Ya tienes los nuevos cambios guardados en la rama income_feat, y no en main.
También lo puedes comprobar mediante git log
.
Vamos a acabar el programa añadiendo una exportación de datos. Pon estas líneas al final del script de Python y commitea los cambios (también dese la rama income_feat).
# Export data
products.to_csv("sales.csv", sep=";", index=False)
git add .
git commit -m "Export data to csv in sales script"
Merge de ramas – git merge
Ya tenemos el programa acabado, y procedemos a juntar las ramas. Esto lo haremos cuando hayamos validado que todo lo que hay en la rama income_feat funciona correctamente. Ahora queremos que los cambios realizados en income_feat se vean reflejados en la rama main:
git checkout main
git merge income_feat
git log
TIP: si tienes algún problema con el merge, o aparecen conflictos que por el momento no sabes solucionar, siempre tienes la opción de cancelarlo: git merge --abort
.
Lo normal es que aparezca un nuevo commit indicando el merge, pero como no hay cambios en la rama main, Git hace algo parecido a un rebase (veremos más adelante), lo que nos simplifica el árbol.
Ahora tenemos los commits de income_feat incorporados en la línea temporal de main.
Para realizar esta operación sin ninguna complicación (este caso), no tiene que haber conflictos en el merge. El problema viene cuando en dos ramas hay líneas de código de un mismo archivo pero con contenido diferente. Es algo muy habitual y veremos más adelante como resolverlo.
¿Y si en la rama main hubiese más commits? Seguiría la línea original de main, intercalando sus commits nuevos, y además se crearía un commit extra en referencia al merge.
Aprovechamos y subimos también los cambios al repo remoto. Estamos haciendo un push de la rama main, por lo que la income_feat se queda como está en nuestro local.
git push
Si lo deseamos, podemos eliminar la rama de income_feat puesto que ya no la vamos a necesitar, y sus commits ya se incorporaron a la principal.
git branch -d income_feat
git branch -a
pull-request. Ejemplo de corrección de bug con proyecto colaborativo
Vamos a llevar a cabo una simulación muy común en el día a día. Se ha producido una errata en el código, en concreto en la columna Amount del DataFrame, y hay que corregirlo. Para ello, tendremos que crear una nueva rama con los cambios, la subiremos al repo para que la revise un compañero y cuando esté todo ok, realizaremos el merge.
Creamos rama:
git checkout -b fix_bug_amount
La corrección es en la columna Amount, línea 7. La sustituimos por:
products['Amount'] = [100, 150, 420]
Ahora simplemente commiteamos la nueva rama, y la subimos, es decir, no tocamos nada de main. Todo el rato trabajando desde fix_bug_amount:
git add .
git commit -m "fix the x10 factor in amount column"
git push origin fix_bug_amount
A continuación vamos a GitHub y comprobamos que está la nueva rama, y además vemos que hay un cartelito sugiriéndonos que llevemos a cabo una acción. Básicamente lo que nos sugiere GitHub es que hagamos un pull request, es decir, creemos una solicitud para añadir los cambios de una rama a otra. En este caso, de fix_bug_amount a main.
Pinchamos en el botón de Compare & pull request y la rellenamos:
¿Para qué hacemos esto si ya habíamos visto una forma de mergear los cambios que funcionaba bien? Porque vamos a trabajar en proyectos colaborativos y los cambios y nuevos desarrollos los van a revisar y aprobar otros compañeros. No solo eso, sino que además nos sirve para llevar una traza más detallada de cómo se han llevado a cabo los cambios, ya que en el proceso de merge de estas dos ramas se va a producir un diálogo entre desarrolladores, pudiendo comentar código, añadir commits nuevos, aprobar/rechazar la pull request, etc…
Una vez creada la PR, podemos acceder a la conversación de los desarrolladores, revisar qué archivos se han cambiado, o echar un vistazo a los commits de la rama.
Se puede añadir comentarios en el propio código.
Como lo hemos revisado y está todo correcto, mergeamos la rama con main.
Se crea un commit nuevo indicando el merge, y se mantiene igualmente la rama fix_bug_amount.
Ya tendriamos resuelto el bug, y solo quedaría actualizar en local el merge mediante un git pull
.
Actualización de nuevas ramas de GitHub
Veamos cómo procedemos si en remoto hay nuevas ramas y nosotros no las tenemos en local. Ya sabemos que con git pull
podemos descargar y mergear los nuevos commits de una rama, pero ¿y si hay ramas nuevas? ¿cómo las bajamos a local?
Vamos a GitHub y creamos una nueva rama. Menu de branches -> New branch. Que parta de main, y le ponemos de nombre changes_readme.
Ahora simplemente modifica el README para añadir un commit nuevo. ¡Asegúrate de que estás en la branch changes_readme cuando hagas el commit!
En este momento hay una rama en el repo remoto que se ha utilizado para actualizar un archivo, y nosotros en local no tenemos esa rama. Si quisiésemos continuar el trabajo del “compañero” tendríamos que bajarnos la rama y hacer un checkout.
Por tanto, actualizamos los cambios con git fetch
. Vemos que aparece la nueva rama remota. Con git checkout
creamos una rama local que sea réplica de la remota, y con esta ya podemos empezar a trabajar.
git fetch
git branch -a
git checkout -t origin/changes_readme
git branch -a
¿Y si lo que queremos es eliminar la rama tanto en local como en remoto?
git checkout main
git push origin -d changes_readme
git branch -D changes_readme
Primero nos cargamos la rama remota y luego la local.
Corrección de conflictos en merge
Hasta ahora hemos llevado a cabo los merges de forma sencilla, pero ¿qué ocurre si al realizar el merge hubiese una línea de un mismo archivo, que resulta diferente en cada rama? ¿Por cuál se decantaría Git? Lo cierto es que por ninguna de las dos, sino que seremos nosotros los encargados de indicarle cuál es la correcta.
Creamos una nueva rama para añadir una funcionalidad extra. En este caso la idea es crear una columna nueva en el DataFrame indicando la ciudad de las ventas del producto y además modificar otra línea de código que ya estaba hecha.
git checkout -b city_shop_feat
Vamos a añadir dónde se produjeron las ventas (una línea nueva) y a modificar el precio de las camisas a 70 (misma línea). El script quedaría así:
import pandas as pd
# Data load
products = pd.DataFrame({'Name': ['Shirts', 'Shorts', 'Trousers'], 'Price': [70, 35, 60]})
# Calculate incomes
products['Amount'] = [100, 150, 420]
products['Income'] = products['Price'] * products['Amount']
products['City'] = ['Madrid', 'Barcelona', 'Valencia']
# Export data
products.to_csv("sales.csv", sep=";", index=False)
Commiteamos los cambios de la rama city_shop_feat.
Vamos a la rama main y simulamos un trabajo en paralelo en el precio de las camisas. Esto va a provocar un conflicto. En main ponemos el precio de las camisas a 40 y comiteamos.
Procedemos a mergear city_shop_feat -> main.
git checkout main
git merge city_shop_feat
Git nos dice varias cosas. Lo primero es que en main tenemos cambios sin subir. Pero lo más importante es que hay un conflicto que deberíamos resolver en sales.py.
¿Qué tenemos que hacer? Digamos que Git ha entrado en modo merge. Esto quiere decir que ha modificado todos los archivos donde nos indica que hay conflicto, señalando dónde se produce tal conflicto.
Parece que la línea nueva la ha mergeado sin problemas, pero no ha conseguido mergear automáticamente la línea 4, puesto que cada rama indica cosas diferentes. En HEAD, donde apunta el repo, (rama main) tiene el precio a 40, mientras que city_shop_feat tiene un precio de 70. Simplemente modificamos el archivo como deseemos para que se resuelva el conflicto.
Finalmente lo dejamos en 60. Commiteamos estos cambios y ya habríamos acabado el merge.
git add .
git commit -m "fixed merge conflict in shirt price"
git log
Fíjate que Git sugiere que está en un merge. Resuélvelo y luego continuas con el resto del programa. Recuerda que siempre te puedes salir del modo merge mediante git merge --abort
.
En cuanto a los logs, tendríamos un commit nuevo, a continuación de los anteriores.
Resumen
Has visto cómo podemos paralelizar las versiones mediante las branches de Git. El no seguir una única línea temporal nos va a permitir trabajar mejor y más limpio en equipo cuando estemos creando nuevas funcionalidades o resolviendo bugs en el código. Merge es uno de los comandos fundamentales en Git para juntar líneas temporales, por lo que es importante saber desenvolverse ante un conflicto en el merge.
SIGUIENTE EPISODIO – Te recomendamos que continúes tu travesía por Git y GitHub con nuestro artículo de navegación entre commits, revertir cambios, corregir errores, alternativas al merge y tags.
Resumen de comandos utilizados
Comando | Descripción |
---|---|
git branch -a | Para ver qué ramas hay en el repo |
git checkout -b <new_branch> | Creamos una rama nueva y nos cambiamos directamente a esa rama |
git checkout <branch> | Cambiar de rama |
git merge <branch> | Juntar dos ramas |
git branch -d <branch> | Eliminar la rama |
Pingback: We Learn Data
Pingback: We Learn Data