Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=c1c7ca26-0e91-4eeb-896e-8e5ebbd86c84

Git: Cómo evitar problemas con cambios de línea al trabajar en equipo

Como seguramente sabrás, cada vez que en tu teclado pulsas la tecla ENTER para cambiar de línea en tu código o en cualquier documento de texto, lo que ocurre es que se inserta un carácter de control que representa ese fin de línea. Estos caracteres de control no se ven, pero están ahí y ocupan memoria (o lo que es lo mismo, espacio en disco).

Como en muchas otras cosas, existen diferencias entre los sistemas operativos a la hora de tratar este tipo de caracteres de control. Y en concreto, Windows, macOs y Linux utilizan caracteres de control diferentes para hacerlo.

Si tienes acceso a dos sistemas de estos, es muy fácil de comprobar. Por ejemplo, abre el bloc de notas en Windows y escribe unas cuantas líneas de texto con un solo carácter en cada una de ellas. Ahora haz lo mismo en un editor de texto plano de Linux o de macOS, escribiendo exactamente las mismas líneas:

 Dos archivos de texto con el mismo contenido en Windows y en macOS

Si te fijas en sus tamaños, resaltados en rojo en la figura, el de Windows ocupa un poco más. En concreto 5 bytes más que el de macOS.

El motivo de esto es que en macOs y Linux se utiliza como carácter de control para cambio de línea un Avance de línea (Line Feed o LF). En Windows, por otro lado, se utilizan dos caracteres: el mismo LF y además, delante de éste, otro llamado Retorno de Carro (en inglés Carriage Return o CR), es decir, Windows usa CRLF, dos caracteres, como control para el cambio de línea. Por eso ocupa 1 byte más por línea y por tanto un 50% más en este caso extremo.

Nota friki #1: esta terminología viene de los tiempos de las máquinas de escribir 😯 El carro era un cilindro que agarraba el papel y que se movía en horizontal con cada letra y hacia arriba por cada línea. Cuando en una de estas máquinas terminabas de escribir una línea, le dabas un golpe al carro con la mano izquierda, que lo que hacía eran dos cosas: moverlo a la derecha (retorno de carro) y girar el carro un poco hacia arriba para subir el papel una línea (avance de línea). En este vídeo se pueden ver ambas cosas en acción: primero el avance de línea y luego el retorno de carro con un golpe más fuerte.

Nota friki #2: en los antiguos sistemas Mac "clásicos" que se comercializaron hasta 2001, el cambio de línea se hacía con un retorno de carro, CR, solamente, al igual que ocurría con los clásicos ZX Spectrum o Commodore 64. El CRLF de Windows se usaba también en sistemas clásicos como los Amstrad CPC, en OS/2, sistemas Atari y muchos otros. Y muchos sistemas embebidos o para mainframes todavía en uso en bancos y grandes aseguradoras utilizan sus propios caracteres de control para cambio de línea. De hecho, el estándar Unicode recoge hasta 8 caracteres de control que se deberían reconocer como terminadores de línea.

Si abrimos los 3 archivos (Windows, Mac clásico y Linux) con Notepad++, que es capaz de mostrar los caracteres de control, vemos las diferencias claramente:

 Los carácteres de control en cada uno de los sistemas

Problemas de diferencias de archivos en Git debidos a retornos de carro

Cuando trabajamos en equipo con el sistema de control de código fuente Git, podemos llegar a tener problemas debido a estas diferencias.

Dependiendo de cómo tengamos configurado Git, si en el equipo tenemos a unos en Windows, a otros en macOS o en Linux, según quién edite en cada momento los archivos, pueden registrarse cambios que son debidos tan solo a estos elementos de control para cambio de línea.

Por regla general, los programas de comparación de archivos hacen caso omiso de los caracteres de control, por lo que no notaremos la diferencia. Pero sí que veremos archivos cambiados en los commits, y a la hora de copiar archivos a otro lado podremos sobrescribir algunos que realmente no han cambiado. Si, por ejemplo, copiamos archivos a otro servidor por la red local o por FTP y el programa de copiado decide, si debe sobrescribir o no, en función de si ha variado la fecha de modificación del archivo o su tamaño, podríamos copiar muchos más archivos de los necesarios solo por esa diferencia en los cambios de línea. O si hacemos despliegue continuo y se van a desplegar en el servidor mediante el propio Git, se podrían cambiar archivos sin querer sólo por estos cambios de línea.

En el asistente de instalación de Git en Windows y Linux se nos muestra una opción específica para controlar este comportamiento:

 Opciones de cambio de línea en la instalación de Git

Se nos ofrecen 3 opciones que ya vienen bien explicadas en la propia pantalla pero que detallo a continuación en español:

  1. Checkout Windows-Style, commit Unix-style: esto implica que cuando nos traigamos los archivos desde el repositorio remoto, Git convertirá todos los cambios de línea en CRLF, es decir, en el estilo para Windows. Esto es lo que tendremos para trabajar localmente. A la hora de enviarlos al repositorio de nuevo, Git los convertirá de nuevo al estilo UNIX, es decir, a LF.
  2. Checkout as is, commit Unix-style: como en la opción anterior, en este caso se convertirán todos los cambios de línea a LF al enviarlos al repositorio remoto y no se hará conversión alguna al traerse los archivos, es decir, se obtendrán tal cual están en el repositorio remoto.
  3. Checkout as-is, commit as-is: con esta opción, Git no hace cambio alguno sobre los cambios de línea.

La última opción es la peor de todas y la que no deberíamos elegir casi nunca. Si en el proyecto trabajan personas con diferentes sistemas o, si nosotros mismos trabajamos con más de un sistema operativo, acabaremos con el problema que describía antes, con varios tipos de cambios de línea mezclados.

Lo que deberíamos hacer es, marcar la opción 1 si trabajamos en Windows y la opción 2 si trabajamos en Linux o Mac. De este modo tendríamos siempre el tipo de cambio de línea apropiado para nuestro sistema, y al mismo tiempo se guardaría de manera homogénea en los repositorios remotos.

Puedes saber qué opción tienes establecida si abres una terminal y escribes:

git config --global core.autocrlf

Por ejemplo, en Windows yo tengo esta opción establecida a true, que es lo correcto según hemos visto antes:

 Opción autocrlf establecida a true

Y los compañeros que trabajen con macOS o Linux la deben establecer como input, para que siempre se use LF como cambio de línea.

Si tienes otro valor, puedes cambiarlo globalmente usando la misma instrucción que antes, pero indicando el valor que le vas a ajustar, por ejemplo:

git config --global core.autocrlf true

para Windows si no la tuvieras correctamente establecida, o con input para Linux/macOS.

No dejar nada al azar: establecer por repositorio

El problema de lo anterior es que dejamos a la responsabilidad de cada desarrollador el establecer la opción correcta, lo cual es propenso a errores. Si no conocen la problemática o cuál es la opción correcta podríamos tener problemas.

La solución a esto es no dar la libertad de elegir y establecer esta opción directamente en todos los repositorios que tengamos. Para ello podemos hacer uso del archivo .gitattributes.

Este archivo se crea en la raíz del repositorio y permite incluir configuración de Git que afectará tan solo al repositorio actual y que, por lo tanto, prevalece sobre lo que haya en la configuración global de Git.

En el caso que nos ocupa podemos establecer el modo en que gestionará Git cada archivo para los cambios de línea añadiendo patrones de archivos y un valor de configuración concreto.

Si queremos la configuración correcta equivalente a autocrlf, lo que debemos hacer es escribir esto en ese archivo:

* text=auto

Esto significa que, para todos los archivos de texto, Git decidirá de manera automática qué hacer respecto de los cambios de línea, lo cual es equivalente a la situación ideal que mencionaba antes: que en Windows usará CRLF y en macOS/Linux usará LF. Además, al enviar los cambios a un repositorio remoto los homogeneizará a LF.

Esto asegura consistencia para los cambios de línea entre todos los desarrolladores, sin importar en qué sistema operativo estén trabajando.

Es una gran idea tener un archivo .gitattributes con configuraciones como esta como parte de las plantillas de proyectos que usemos. Como se mete bajo control de código, aparecerá en los repositorios locales de todos los desarrolladores.

Si queremos configurarlo de manera distinta para algún tipo de archivo concreto podemos hacerlo también. Por ejemplo, los archivos .sln de Visual Studio que guardan información sobre los proyectos que forman parte de una solución son archivos de texto, y dado que Visual Studio sólo funciona en Windows, podríamos forzar a que siempre utilicen CRLF como separador de línea escribiendo esto en el archivo .gitattributes:

* text=auto

# Archivos de soluciones de Visual Studio
*.sln text eol=crlf

De este modo le estamos indicando a Git que lo archivos con la extensión .sln son de texto, pero que debe utilizar siempre CRLF para los cambios de línea, independientemente del sistema operativo en el que se clonen. Así evitamos que si lo abre un colaborador en macOS, cambie ese ajuste. No es que sea necesario, pero podríamos forzarlo así.

Arreglar un repositorio que no esté con cambios de línea homogéneos

Bien, con lo que hemos visto ya podemos crear una configuración homogénea para todo nuestro equipo (o para nosotros si trabajamos en varias máquinas con sistemas operativos diferentes).

Pero, ¿qué pasa si el mal ya está hecho y cuando hemos puesto este remedio en marcha ya tenemos centenares de archivos de código con diferentes estilos de cambio de línea?

Por suerte, Git ya tiene este problema contemplado también, así que podemos hacer lo siguiente:

git add --renormalize .

(¡Fíjate en el punto del final, es importante!)

Al hacer esto, Git recorrerá todos los archivos de contenido textual que haya en nuestro repositorio y se asegurará de que tienen los cambios de línea homogéneos, según la configuración que corresponda.

Tras hacerlo, tendremos en el área de preparación (staging) de Git todos los archivos que se han modificado y sólo tendríamos que hacer un commit y un push al repositorio remoto para persistir los cambios:

git commit -m "Homogeneizados los cambios de línea"
git push

Y listo.

Con estas técnicas sencillas podremos tener la seguridad de que no vamos a tener problemas por culpa de algo tan aparentemente inofensivo como unos cambios de línea.

¡Espero que te resulte útil!

José Manuel Alarcón Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José Manuel Alarcón
Archivado en: Herramientas

¿Te ha gustado este post?
Pues espera a ver nuestro boletín mensual...

Suscríbete a la newsletter

La mejor formación online para desarrolladores como tú

Comentarios (1) -

Cesar Verano
Cesar Verano

Muy buena explicación, muchas gracias ...

Responder

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.