Buenas praxis con Git

Buenas praxis con Git

Buenas praxis con Git

Tiempo de lectura: 8

Git se ha convertido en una herramienta imprescindible en el desarrollo de software moderno. Es una herramienta de control de versiones que permite a los equipos de desarrollo trabajar juntos en proyectos de software, lo que facilita la colaboración y la entrega de software de alta calidad. Sin embargo, muchas veces se descuida el uso de esta herramienta, perdiendo gran parte de su utilidad.

En este artículo queremos explorar algunas buenas prácticas que nos ayudarán a tener un repositorio más organizado y más útil de cara al futuro.

Formato del commit

El primer punto a tratar es cómo redactar un commit correctamente. Git nos da total flexibilidad a la hora de escribir un mensaje de commit, pero en realidad existen ciertas normas básicas que debemos cumplir si queremos una visualización correcta del contenido.

Según la página man de git-commit, primero debemos escribir una descripción corta, de menos de 50 caracteres (el título), luego una línea en blanco y luego una descripción más detallada (el cuerpo del mensaje).

Esta limitación de 50 caracteres no es estricta, pero sobrepasarla puede provocar que el título no se vea correctamente en algunas aplicaciones.

Otro estándar que se suele seguir es limitar el ancho de las líneas del cuerpo del mensaje a 80 caracteres. Se trata, de nuevo, de facilitar la lectura.

Siguiendo estas reglas, un commit podría verse como el siguiente (tomado del repositorio del kernel de linux):

Más recientemente se han definido otros estándares con un formato más estricto, pensando en poder automatizar ciertas tareas a partir de los mensajes.

El más famoso es el de Angular del que luego se ha generado una especificación formal: Conventional Commits

Algunas ventajas de este formato es poder tagear automáticamente una versión basándonos en los comits que hemos realizado usando semantic-release.

Esta aplicación comprueba los commits realizados desde el último merge y dependiendo del tipo de commit (feat, fix o breaking change) decide cómo aumentar la versión de nuestro software (siempre que sigamos semver).

Otra utilidad similar es conventional-changelog, que generará automáticamente un fichero de cambios (CHANGELOG.md) analizando los mensajes de los commits.

Aunque antes de meternos en como formatear correctamente un commit, podemos ver algunos ejemplos de por qué es útil perder un poco de tiempo en generar unos mensajes de commit descriptivos.

  • Entender por qué se ha usado tal o cual valor: por ejemplo, tal vez estamos usando un repositorio git para infraestructura como código y queremos saber por qué una determinada máquina ha sido configurada con un disco de un determinado tamaño. Usando git blame (nos permite saber en qué commit se modificó cada línea) podríamos ver el commit que modificó esa línea y en el commit debería estar la explicación de por qué se eligió ese valor
  • Encontramos un bug en el código: usamos de nuevo git blame para encontrar el commit responsable del bug. Tal vez podamos entender por qué se realizó ese cambio y proceder a arreglarlo teniendo en cuenta que llevó a su cambio.
  • Entender el funcionamiento del código: aparte de los comentarios del código, en los commits podemos encontrar explicaciones que se han podido tomar para realizar las cosas de una u otra manera, o links a issues, correos, etc. donde haya una discusión más profunda.

Una vez entendidas las utilidades podemos ver más claramente qué información debemos añadir en el mensaje del commit.

Una regla sencilla es pensar que un compañero nos pregunta por qué hemos realizado esos cambios. Ver qué cambios realiza el commit puede ser sencillo de ver, al final son lo que las herramientas nos están enseñando (las líneas modificadas, creadas o eliminadas), pero la razón de esos cambios muchas veces no la podremos extraer del código y esa será una parte muy importante del mensaje de commit.

También debemos hacer los commits suficientemente pequeños para facilitar la revisión. Un commit con muchos cambios, aunque tenga una descripción muy detallada, puede ser muy complejo de revisar.

Es importante que los commits solo realicen un cambio. Mezclar distintos cambios vuelve a ser complejo de revisar y el uso del historial de commits se volverá menos útil.

 

Un pequeño truco para lograr estos commits atómicos es crear un commit vacío antes de realizar ningún cambio. En ese commit vacío escribiremos los cambios que nos disponemos a realizar. Esto nos evitará introducir cambios que tal vez sean necesarios, pero no forman parte de ese cambio simple que queremos realizar.
Para crear un commit vacío usaremos el comando:
git commit --allow-empty

Si nos encontramos con errores mientras estamos haciendo nuestro commit y no queremos dejarlos pasar, también es posible realizar esas modificaciones no relacionadas y no incluirlas en el commit. En un commit podemos añadir solo modificaciones de ciertas líneas de un fichero, tendremos que referirnos a nuestro IDE para ver cómo realizarlo. Si queremos hacerlo directamente desde la línea de comandos usaremos el comando (nos mostrará un menú para ir eligiendo que hunks queremos ir añadiendo al commit):

git add --patch <filename>

Volviendo al mensaje de commit, ya tenemos claro que debemos seguir un formato de título y cuerpo. Que debemos explicar que estamos haciendo, pero sobre todo, el porqué hacemos los cambios e intentar mantener la atomicidad del commit.

Para dar un formato más homogéneo al estilo de escritura usaremos el imperativo. De esta manera, en vez de un título tipo “este commit modificaría la respuesta XXX” o “arreglado el mensaje de respuesta”, escribiremos “modifica la respuesta con el texto XXX”.

Una forma de ayudarnos a escribir el título del commit es pensar como completaríamos la frase “si se aplica, este commit …”.

Una vuelta de tuerca más son los conventional commits nombrados anteriormente, pensados para ser analizados por alguna máquina y realizar tareas de forma automática.

Lo mejor es leerse la especificación, pero a modo resumen, la idea es tener un mensaje con el formato:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Donde el type será fix (arreglo) o feat (nueva funcionalidad), pudiendo usar la exclamación para denotar un breaking change, ejemplo:

feat!: send an email to the customer when a product is shipped

Este formato suele ir ligado al formato de versiones SemVer, donde un fix implicaría un incremento en el patch de la versión, un feat incrementaría la parte minor y un breaking change subiría el major.

Por último, deberemos decidir en qué idioma queremos redactar los mensajes de los commits. Seguramente la mejor elección sea el inglés, al ser bastante conciso y ser el estándar de facto de las computadoras.

Cuidado con lo que comiteamos

Dos fallos comunes al usar git es usarlo para almacenar ficheros binarios o subir información confidencial.

Puede darse el caso de que queramos almacenar en nuestro repositorio unos vídeos de ejemplo, o un fichero binario donde se almacenan unos parámetros necesarios. Git no se pensó inicialmente para gestionar ficheros grandes, por lo que subirlos como un fichero más traerá una peor experiencia de usuario. Principalmente, incrementaremos el tamaño de repositorio, haciéndolo inmanejable si continuamos subiendo distintas versiones de esos ficheros binarios. Para solucionar este problema existe Git LFS, que nos ayuda a subir los ficheros a almacenes de objetos remotos, dejando únicamente en git una referencia al fichero.

El otro error, subir información confidencial, suele ser más por descuido, tal vez al comienzo del repositorio, donde tal vez ni hemos pensado hacerlo público, o andamos sin mucho cuidado al ser una fase inicial. Una vez hemos comiteado cierta información, será complicado eliminarla. Mucha gente simplemente crea un nuevo commit borrando la información, pero esta permanecerá en el histórico.

Para hacer un borrado completo tendremos que hacer uso de herramientas como git-filter-repo, que nos permite borrar cadenas o ficheros de todo el histórico.

Aun así, seguiremos teniendo problemas, ya que el servidor git que estemos usando puede mantener copias en caché, o alguien puede haber forkeado nuestro repositorio y haberse llevado esta información confidencial a un repositorio fuera de nuestro alcance.

Para evitar subir ficheros confidenciales por error, lo mejor es siempre añadirlos al fichero .gitignore, de manera que git no nos permitirá añadir ese fichero a un commit (a no ser que lo forcemos explícitamente).

También podemos configurar un hook pre-commit que compruebe que no estamos subiendo algo que no queremos.

Los hooks de git nos permiten ejecutar un script en determinadas partes del flujo de git (pre-commit, post-commit, pre-push, etc.).

Git comprobará el resultado del script, en caso de error, no podemos seguir con el flujo.

Estos hooks pueden declararse por cada repositorio o crear unos globales.

Para el caso que nos concierne podríamos crear un script que buscase una cadena determinada y fallase en caso de encontrarla.

Para facilitar este proceso existe un package manager de hooks llamado pre-commit. Este nos ayuda a configurar hooks de tipo pre-commit declarándolos en un fichero yaml. Podremos reusar el trabajo que otros desarrolladores han realizado creando esos hooks.

Por ejemplo, podemos configurar detect-private-key, que comprobará si estamos subiendo alguna clave privada (busca texto tipo “BEGIN RSA PRIVATE KEY”).

Documentar nuestro repositorio

Podemos tener un maravilloso repositorio con unos commits perfectamente redactados y una funcionalidad excepcional, pero si no explicamos para qué vale o como usarlo, no será muy útil.

Podemos empezar con poner una pequeña descripción al repositorio (lo permiten la gran mayoría de servidores git), que permitirá, de un vistazo, saber si el repositorio es lo que estamos buscando.

También será imprescindible tener un README, donde expliquemos en más detalle el proyecto, su uso, algunos ejemplos, una pequeña demostración y, tal vez, un apartado de cómo colaborar.

Para facilitarnos el trabajo podemos hacer uso de la aplicación web readme.so, que tiene ya preparada una lista de secciones típicas con ejemplos para ayudarnos a generar un fichero markdown.

Flujo de trabajo

La forma más sencilla de trabajar con git es teniendo una única rama, generalmente llamada main (antiguamente master), e ir comiteando nuestros cambios ahí.

El siguiente paso suele ser crear ramas para añadir determinadas funcionalidades, donde podremos añadir varios commits y una vez tengamos la funcionalidad terminada la mergearemos con la rama principal.

Cuando son varias personas trabajando sobre el mismo repositorio y queremos mantener el soporte a distintas versiones, desplegar sobre distintos entornos, etc. el esquema se va complicando.

Una de las primeras formas de trabajar estandarizadas fue la propuesta por Vicent Driessen, git-flow. En esta, resumiendo mucho, tenemos dos ramas principales, main y develop. Los desarrolladores crean ramas feature a partir de develop y las van mergeando una vez completadas. Cuando se quiere generar una nueva versión, se genera una rama release donde se realizarán las pruebas y unos posibles arreglos de última hora y esa rama se mergeará a main, generando al mismo tiempo un tag.

Este esquema resulta a veces excesivamente complejo y es difícil de integrar con herramientas de CI/CD, por lo que la gente de GitHub se inventó una variante llamada github-flow. Aquí la idea es tener una única rama principal y luego crear ramas para añadir cambios, usando el proceso de pull request para mergear esos cambios.

También Gitlab tiene su flujo: gitlab-flow, donde tendremos una rama por entorno.

Versionado

Para compartir nuestro código con el mundo lo normal es generar versiones en momentos determinados, explicando los cambios realizados desde la última versión (típicamente en un fichero llamado CHANGELOG).

El esquema de versionado más popular, ya nombrado anteriormente, es SemVer. Este sistema usa un formato tipo 2.3.0, donde cada dígito se modifica siguiendo unas reglas.

El primer dígito (empezando por la izquierda) se llama MAJOR, el segundo MINOR y el último PATCH.

El primer dígito solo se modifica si hemos realizado algún cambio que puede romper la compatibilidad con versiones anteriores. Esto se ve muy claro si nuestro código es una biblioteca de código usada por terceros. Si cambiamos el dígito MAJOR estamos indicando a nuestros usuarios que no deben subir la versión sin revisar cómo usan la librería, ya que hay partes que han cambiado. Estos usuarios esperarán encontrar en el CHANGELOG los detalles de esos cambios rompedores y cómo deben proceder para usar la nueva versión.

El dígito MINOR se incrementará cuando añadamos nuevas funcionalidades, siempre que no rompamos la compatibilidad. Tal vez creamos un nuevo método, pero que no afecta en nada al uso que pueden estar haciendo los usuarios actuales.

Por último, PATCH se modifica si hemos arreglado algún fallo, igualmente, sin romper la compatibilidad.

Parece que Semver es prácticamente la única opción hoy en día, pero existen otros tipos de versionados que pueden ser más útiles en determinados casos y conocerlos nos puede ayudar a elegir mejor.

Por ejemplo, Ubuntu usa el esquema YY.MM.VERSION, siendo YY el año, MM el mes y VERSION lo usan cuando quieren sacar mejoras sobre esa versión. Siempre generan versiones en abril (04) y octubre (10). Generando una LTS (long term support) cada dos años en abril.

En Zabbix usan un esquema X.Y, donde Y puede tomar los valores 0 (LTS), 2 o 4 (versiones públicas).

PostgreSQL solo usa MAJOR.MINOR, siendo las actualizaciones entre versiones MAJOR más complejas, al requerir, por lo general, cambios en la estructura interna.

Conclusión

Cuando estemos desarrollando un proyecto debemos frecuentemente cambiarnos la gorra y ponernos en el papel de los futuros desarrolladores o usuarios (¡o de nosotros mismos dentro de unos meses!). Esto nos ayudará a tener un proyecto más amigable y útil.

El medio escrito es la forma de comunicación predominante para este tipo de proyectos, por lo que deberemos hacer un esfuerzo extra para que sea comprensible por nuestros futuros usuarios.

Adrián López
Ana Ramírez

Ana Ramírez

¿Te ha resultado interesante?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

SÍGUENOS

CATEGORÍAS

ÚLTIMAS ENTRADAS