En la primera parte de este artículo, analizamos los conceptos claves del control de versiones utilizando la herramienta Git, desde los fundamentos de cómo funciona hasta las operaciones básicas y elementos que nos permiten gestionar de forma eficiente el código fuente de nuestros proyectos. En esta segunda entrega, vamos a llevar a la práctica toda la teoría que vimos en la parte inicial.

A través de un ejemplo concreto, realizaremos las tareas esenciales de Git replicando un flujo de trabajo real. Primero, la creación de un repositorio, la gestión de ramas y la colaboración con otros usuarios. También replicaremos la resolución de conflictos y revisar el histórico de versiones. Para esto, utilizaremos como apoyo los servicios de Azure DevOps que nos facilitarán la gestión del repositorio de GIT además de implementar otros elementos de la metodología DevOps.

Para desarrollar esta demostración se utilizarán las siguientes herramientas:

  • IDE: Visual Studio 2022
  • Servicio para control de versiones: Azure DevOps

En cualquier caso, se puede trabajar sobre cualquier entorno de desarrollo (IDE) que permita trabajar con GIT o desde la propia consola de comandos. También es posible alojar el repositorio remoto utilizando otros servicios como GitHub, GitLab, etc.

Crear el repositorio

El repositorio remoto del código se creará desde Azure DevOps para después clonarlo en nuestro entorno de desarrollo y trabajar en el repositorio de forma local. Para ello, debemos acceder a Azure DevOps y dentro de un Proyecto, crear un nuevo repositorio.

Dentro de un mismo proyecto de Azure DevOps podemos tener varios repositorios, si es la primera vez que creamos un proyecto, nos obligará a crear al menos un repositorio. En este caso, crearemos en el proyecto “AXZ Lucía” un nuevo repositorio aparte de los que ya existen.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Indicamos el nombre del repositorio y confirmamos la creación con el botón «Create». Como indica en el mensaje, el repositorio se inicilizará por defecto con la rama principal «main».

Gestión de código – GIT y DevOps. Parte 2 Axazure

Estos serían los pasos necesarios para crear un repositorio de GIT alojado en Azure DevOps. También se creará un archivo README.md que habitualmente se utiliza para describir los detalles de la solución que se almacena en el repositorio y las instrucciones necesarias para trabajar con el código.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Clonar el repositorio para trabajar en Local

Una vez que hemos creado el repositorio, el siguiente paso es clonar o conectarlo a nuestra máquina local para comenzar a trabajar en el entorno de desarrollo. En este caso, utilizaremos Visual Studio 2022, que cuenta con integración nativa de Git, facilitando la gestión del control de versiones directamente desde el IDE. Visual Studio nos permitirá realizar operaciones con GIT sin necesidad de abandonar el entorno de desarrollo, lo que optimiza el flujo de trabajo.

Para clonar el repositorio, abrimos Visual Studio y desde la vista de «Git Changes» pulsamos en clonar repositorio. Desde aquí, se abrirá una ventana donde debemos copiar la URL del repositorio que hemos creado en Azure DevOps.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Para conseguir esta URL, volvemos a Azure DevOps y pulsamos en el botón «Clone» que abrirá una ventana donde podremos copiar la URL del repositorio para pegarla en Visual Studio.

Gestión de código – GIT y DevOps. Parte 2 Axazure
Gestión de código – GIT y DevOps. Parte 2 Axazure

Esta dirección la pegamos en Visual Studio e indicamos la carpeta Local donde guardaremos el repositorio.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Con este proceso, habremos creado el repositorio en el entorno de trabajo local donde realizaremos los cambios en el código fuente. Esto nos permitirá trabajar en el proyecto de forma local haciendo un seguimiento de los cambios que realizamos para posteriormente mantener la trazabilidad en el repositorio remoto con las nuevas versiones que vayamos generando.

Gestión de ramas

Siguiendo las recomendaciones del ciclo de vida de Git, vamos a crear diferentes ramas de trabajo a partir de la rama principal «main». Para este ejemplo, crearemos una rama que llamaremos «develop» y se utilizará como rama de destino para los diferentes desarrollos que se incluyan en el proyecto. En «main» tendremos el código que se haya validado y probado, es decir, que funcione correctamente. Si trabajásemos con diferentes entornos, diríamos que la rama «main» contiene el código de Producción y la rama «develop» el código de Pre-Producción o Pruebas.

Las ramas de «main» y «develop» se mantendrán durante todo el ciclo de vida del proyecto. Después, crearemos una rama temporal por cada tarea/evolutivo/corrección que realicemos en el código fuente. Estas ramas comenzarán y terminarán con el desarrollo al que estén vinculadas, es decir, las eliminaremos cuando las fusionemos con la rama de «develop».

Para crear la rama de «develop», en la barra de herramientas abriremos la gestión de ramas de git desde Git > Manage Branches.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Desde aquí, haremos click derecho sobre la rama «main» y pulsamos «New Local Branch From…». Esto nos permitirá crear una rama local basada en la rama «main», es decir, con el mismo código que haya en «main».

Gestión de código – GIT y DevOps. Parte 2 Axazure

Ahora podemos ver la nueva rama local «develop». Por ahora, esta rama solo existe en el repositorio Local del ordenador donde la hemos creado, para que forme parte del repositorio remoto y otros colaboradores puedan acceder a ella, tenemos que publicarla. Esto se hace con la acción «Push» haciendo click derecho sobre la rama.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Ahora, las dos ramas están disponibles tanto en el repositorio Local como en el remoto. Es importante analizar y comprender cómo funcionan las ramas locales y remotas. Para poder trabajar con el código de una rama remota, se creará automáticamente una rama local con el mismo nombre cuando bajemos los cambios de la rama utilizando las operaciones «Pull» o «Fetch». De la misma forma, para poder publicar los cambios de una rama local en remoto, tenemos que usar la operación «Push». Esta operación se podrá comportar de dos formas:

  1. Si existe una rama remota con el mismo nombre que la rama local, los cambios se publicarán sobre esa rama.
  2. Si no existe ninguna rama remota con ese nombre, se creará una rama nueva donde se publicarán los cambios de la rama local.
Gestión de código – GIT y DevOps. Parte 2 Axazure

Como hemos comentado en la creación de ramas, cuando tenemos que realizar un desarrollo, las buenas prácticas de Git recomiendan crear una rama para esa tarea y cuando terminemos, fusionarla con la rama de origen mediante una Pull Request.
En este caso, vamos a crear una tarea en DevOps para simular una situación real. Después, crearemos una rama basada en la rama de «develop» y sobre esta rama haremos la tarea.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Seguimos los mismos pasos que hemos hecho en el punto anterior y creamos una rama local para la tarea con el nombre «AXZ_5259_DemoGit». Cuando la hayamos creado, nos posicionamos sobre esta rama (checkout) y podemos empezar a trabajar.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Para asegurarnos de que estamos en la rama correcta, en el gestor de ramas veremos la rama en la que estamos trabajando resaltada con negrita: AXZ_DEV_DemoGit y en la parte inferior derecha de la ventana, también podemos ver la rama y movernos a otras.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Es una buena práctica, tratar de hacer bastantes confirmaciones del código a lo largo del desarrollo, sobre todo para desarrollos complejos donde se modifica o añade una gran cantidad de líneas y objetos. Cuando hacemos Commit, estos cambios se confirman en el repositorio local. Para que los cambios se reflejen en el repositorio remoto, tenemos que hacer «Push» de la rama.

Desde Visual Studio, es muy sencillo ver si tenemos cambios confirmados en Local que no están en Remoto y viceversa. Desde la gestión de ramas, a la derecha del nombre de cada rama hay dos números. El de la izquierda indica el número de confirmaciones en la rama local que no están en la rama remota. El de la derecha lo contrario. La situación ideal es que esos números siempre sean 0, para estar alineados con el repositorio remoto y evitar conflictos de código por no tener la versión actualizada del repositorio cuando trabajamos en él.

Gestión de código – GIT y DevOps. Parte 2 Axazure

En esta imagen podemos ver que tenemos una confirmación pendiente en local que tenemos que pasar al repositorio remoto con un «Push».

Gestión de código – GIT y DevOps. Parte 2 Axazure

En este ejemplo, solo tenemos un «Commit» para el desarrollo porque era un requerimiento sencillo, pero sería el mismo proceso si tuviésemos más confirmaciones. También podemos dejar las confirmaciones en la rama local y no enviarlas a la rama remota hasta que no terminásemos todas las modificaciones, pero no es del todo seguro porque este código sería vulnerable a la pérdida de los archivos en el dispositivo donde esté almacenado el repositorio local y, además, no permitiría a otros colaboradores ver estos cambios.

Como hemos finalizado el desarrollo, es el momento de fusionar esta rama con la rama «develop», de la que partíamos al inicio de la tarea. Para ello necesitamos hacer una Pull Request. Se puede hacer desde Visual Studio o desde Azure DevOps. En esta demostración lo haremos desde Azure DevOps, puesto que es más intuitivo y permite más personalizaciones, pero desde Visual Studio podría hacerse desde aquí:

Gestión de código – GIT y DevOps. Parte 2 Axazure

Después, seleccionamos la rama con la que queremos fusionar, validamos los cambios y confirmamos la Pull Request.

Desde Azure DevOps, el proceso es similar. Entramos en el menú Repos > Pull Requests > New Pull Request.

Gestión de código – GIT y DevOps. Parte 2 Axazure
Gestión de código – GIT y DevOps. Parte 2 Axazure

En este caso, Azure DevOps autocompleta algunos campos como la tarea vinculada o el titulo de la Pull Request. Todo esto se puede modificar pero son facilidades que nos da Azure DevOps en función de las tareas que hemos vinculado a los «commits» de la rama que estamos fusionando. Una vez que hemos comprobado los archivos y los cambios que vamos a fusionar, creamos la Pull Request.

Si no hay ningún conflicto en la fusión y la rama no tiene configurada ninguna «Policy» como por ejemplo requerir una revisión de otro usuario o vincular una tarea, podemos completar la fusión.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Cuando completemos la Pull Request, podemos borrar la rama de origen o mantenerla. La recomendación es eliminarla, puesto que la tarea ha finalizado al fusionarse el desarrollo con la rama principal. Si hubiera que hacer modificaciones en el código, crearíamos una rama nueva. En realidad, esto depende siempre de la metodología de trabajo del equipo de desarrollo o el proyecto en concreto, a veces la gestión de ramas es diferente a la recomendada por Git.

La realidad no es tan perfecta… Rollback y conflictos

A veces, la gestión del código no es tan sencilla como lo que hemos visto en los pasos anteriores. Podemos encontrarnos con situaciones en las que tenemos que deshacer nuestros cambios porque provocan algún error en el código y también puede darse el caso en el que modifiquemos el mismo archivo que otro colaborador y Git no sea capaz de hacer la fusión de forma automática porque las líneas afectadas coinciden, por ejemplo.

En estos casos, Git nos ofrece diferentes soluciones. Vamos a comenzar por deshacer los cambios que hayamos confirmado. Como vimos en la Parte 1 de este artículo, podemos hacer «Rollback» de nuestro código con dos operaciones: «Revert» y «Reset».

A modo de recordatorio, «Revert» hace una uneva confirmación (commit) donde se modifican los archivos para dejarlos en el mismo estado que estaban antes, manteniendo la trazabilidad de los cambios que se han deshecho. «Reset», elimina la confirmación y los archivos quedan en la forma de la anterior confirmación a la que hemos deshecho. De este modo, perdemos la trazabilidad de los cambios, es más recomendable hacer «Revert».

Estas dos operaciones se pueden hacer desde Visual Studio. Desde la gestión de Ramas, en la rama donde queremos deshacer un cambio, buscamos el «commit» que queremos revertir y ejecutamos una de las dos operaciones.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Esta operación crea un nuevo «Commit» que deshace los cambios. Si hacemos click en él, podemos ver los detalles de los cambios.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Para hacer una operación de «Reset» también debemos ir a la gestión de ramas. En este caso, elegimos el punto del código al que queremos volver, esto eliminará todos los «commits» posteriores (opción menos recomendada).

Gestión de código – GIT y DevOps. Parte 2 Axazure
    Esta operación elimina todos los cambios posteriores y el histórico de los cambios.
Gestión de código – GIT y DevOps. Parte 2 Axazure

El otro problema que podemos tener a la hora de trabajar en un repositorio remoto con diferentes ramas y colaboradores son los conflictos. Vamos a provocar un conflicto de forma forzada modificando el archivo «HelloWorld.cs» en la rama remota y tratando de hacer un «Push» desde la rama local que también lo modifique.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Antes de hacer el Push como hay cambios en la rama remota que no tenemos en local, tenemos que hacer Pull. Vamos a hacer Pull y a resolver el conflicto desde Visual Studio.

Gestión de código – GIT y DevOps. Parte 2 Axazure

En la pestaña «Git changes» podemos ver los archivos que no se han podido fusionar automáticamente por los conflictos.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Si abrimos el archivo que ha dado conflicto, Visual Studio abrirá una pestaña donde podemos ver a la izquierda los cambios de la rama local, a la derecha los de la rama remota y abajo, el resultado de la fusión. Elegimos los cambios que queremos conservar, pueden ser todos, solo algunos o modificar el código de forma manual.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Una vez confirmado, hacemos Push para actualizar la rama remota y el conflicto estaría resuelto.

Con esto, el conflicto quedaría resuelto. Otro punto donde podemos encontrar conflictos es al hacer Pull Request. Se resuelven de la misma forma, pero con el visor de Azure DevOps. Elegimos los cambios que queremos conservar y confirmamos la fusión.

Gestión de código – GIT y DevOps. Parte 2 Axazure

Buenas prácticas

Esto sería todo para cerrar este artículo de demostración de las operaciones básicas de Git desde Visual Studio y Azure DevOps. Antes de cerrar, me gustaría dejar algunas recomendaciones para evitar los problemas de conflictos que hemos visto en este último punto, así como otras recomendaciones para trabajar con Git.

  1. Hacer confirmaciones de cambios pequeñas, para que la trazabilidad sea mayor y el riesgo de perder código o generar conflictos sea menor. Además, facilitará que los demás colaboradores tengan el código más actualizado.
  2. No incluir credenciales o datos privados en el código, puesto que se almacenará en un repositorio remoto.
  3. Actualizar los repositorios locales frecuentemente, para tener versiones actualizadas del código cuando lo vayamos a modificar, así evitaremos conflictos y trabajaremos con el código real.
  4. Trabajar adecuadamente con las ramas siguiendo las recomendaciones del GitFlow Workflow.

About the Author: Equipo Axazure

Gestión de código – GIT y DevOps. Parte 2 Axazure

¿Quieres compartir?