Tag: Git

Crear estructuras de carpetas no trackeables en Git

En ocasiones, por cuestiones de diseño es necesario mantener una estructura de carpetas determinada. Si además trabajas con Git te habrás dado cuenta que Git no añade un directorio vacío (ya sea una carpeta o varias anidadas) al proyecto. Siempre tiene que haber un archivo para que Git haga el tracking y añada la estructura de carpetas.

Si además dicho directorio quieres que se excluya del tracking de archivos, la cosa se pone un pelín más complicada, pero nada que no se pueda configurar con un poco de tino 😉 .

Para resolver el problema, en Git es necesario crear un archivo para que la estructura de carpetas se añada al tracking del proyecto. Por ello es necesario generar un fichero "dummy", un fichero vacío en el nivel más bajo del directorio, que sirva de baliza para que Git nos incluya la ruta completa de carpetas que nos permita llegar al archivo. Con esto ya tenemos solucionado el tema del directorio, poniendo ficheros "dummy" en los niveles más bajos del directorio.

Ahora es necesario realizar una acción que nos excluya todos los ficheros del directorio en cuestión, menos los ficheros "dummy" que nos hacen de baliza. Para ello en la raíz del proyecto, definiremos un fichero ".gitignore"  que sirve para manejar exclusiones del tracking de ficheros para el proyecto. Lo ficheros o grupos de ficheros definidos en las reglas de ".gitignore" se comportan como ficheros que no existen para Git, y evita proponerlos para incluirlos en un eventual commit.

Creando un caso simple que sirva de ejemplo, excluiremos la carpeta "test" de la ruta "config/test/" de un proyecto.

git_exclusion_directorio

La configuración del fichero ".gitignore" sería la siguiente:

config/test/*
!config/test/dummy.txt

El fichero consta de dos líneas. Una primera línea que excluye todo el directorio del tracking y una segunda línea que excluye el fichero "dummy" de la exclusión de la primera línea. Es importante el orden de las líneas y no son conmutables, ya que el fichero es interpretado de manera secuencial desde la primera línea hasta la última.

Con estas dos sencillas líneas podemos crear estructuras de ficheros, dentro del proyecto, para las cuales no queremos hacer seguimiento de cambios en sus ficheros. Esto es útil para generar estructuras donde alojar ficheros con los que probar el código del proyecto, pero los cuales no queremos añadir al proyecto porque no nos interese (por la razón que sea). También pueden ser útiles estas estructuras estáticas, en los casos en los que el usuario final, introducirá algún tipo de fichero que consuma nuestro proyecto.


Una mejor opción 🧬 [28/02/2021]

Un iteración más elegante es usar varios ficheros ".gitignore". Sobre todo en el caso de que queramos excluir un directorio y todos sus posibles subdirectorios.

⚠️ El uso de ficheros ".gitignore" de manera indiscriminada y descontrolada puede ser constraproducente si no existe una organización o una estrategia previa en su uso en el repositorio del proyecto. Git busca recursivamente y aplica todos los ficheros ".gitignore" que se encuentre dentro del repositorio.

La estrategia sería definir un fichero ".gitignore" dentro de la carpeta (y subcarpetas) que queramos que sean ignoradas, añadiendo como excepción el propio fichero para que Git nos permita añadirlo. El contenido del fichero sería el siguiente:

# Este directorio y subdirectorios son ignorados
*
# Indicamos que .gitignore es la única excepción
!.gitignore

El fichero consta de dós línea, la primera excluye absolutamente todo y la segunda añade como excepción el propio fichero para que pueda ser incluido en el árbol de versionado Git y no sea también excluido. De esta forma tenemos una forma sencilla en la que añadiendo ese fichero ".gitignore" a cualquier directorio, estaremos ignorando el contenido de dicho directo y subdirectorios.

Actualizar los Submódulos Git de un Proyecto

Cuando tenemos proyectos muy grandes en los que usamos librerías de terceros y estamos usando Git como gestor de versiones, suele ser habitual añadir dichas librerías como submódulos. Esto nos permite que cuando clonamos el proyecto, las librerías también se descarguen. No obstante, la actualización de los módulos no se realiza mediante el comando "pull" del proyecto principal y deberemos realizar un "pull" para cada submódulo que tengamos.

Para realizar el comando "pull" en cada uno de los módulos lo más sencillo es recurrir al siguiente comando:

git submodule foreach git pull origin master

El comando "submodule" junto con el comando "foreach" nos permite ejecutar comandos para cada uno de los submódulos de un proyecto. En este caso ejecutamos el comando "git pull origin master" para traernos los cambios de cada submódulo y tenerlos todos en la ultima versión de la rama "master".

En caso de querer revertir la acción puedes usar el comando "update" para actualizar el árbol de submódulos de manera consistente con el estado almacenado en el proyecto principal. Esto quiere decir nos deshará los cambios realizados.

git submodule update

Internamente en el proyecto principal, para cada submódulo se guarda el commit concreto con el que se trabaja, razón por la que es posible revertir los cambios.

Este comando puede salvarnos, en el caso de que hayamos cambiado la URL remota donde apunta el submódulo y nos coja los cambios de la antigua ubicación. O simplemente puede servirnos para volver a la versión que teníamos, tras haber probado los nuevos cambios del submódulo y no estar contentos con ellos.

En cualquier caso, si por alguna razón hemos cambiado la URL remota de un submódulo en el archivo ".gitmodules", necesitaremos ejecutar el comando "sync" para que sincronice la URL remota con la configuración del archivo ".gitmodules".

git submodule sync

Tras la ejecución del comando podemos volver a ejecutar el comando:

git submodule foreach git pull origin master

Y veremos que los cambios corresponden a la ubicación correcta del submódulo.

Para más información sobre los distintos comandos para los submódulos, consulta la siguiente referencia:

https://git-scm.com/docs/git-submodule

 

¡Maldición! Linux pone mi usuario del sistema al hacer Push con Git

Linux y Git se llevan a las mil maravillas y tal es su integración, que nuestro usuario del sistema operativo es el que se usa por defecto en Git. Además ya tenemos una clave privada y pública generadas para poder trabajar con SSH.

Pero... ¿Qué pasa si mi usuario del repositorio Git con el que tengo que trabajar no es el mismo que el que uso en el sistema?

En Linux, para bien o para mal siempre se rellenará ese usuario automáticamente con el del sistema y nunca te lo preguntará. Únicamente te pedirá la clave, que obviamente no sirve de nada porque el usuario no es el que debiera.

Una forma rápida y sencilla para solucionar este inconveniente, es incluir el usuario dentro de la URL del repositorio.

ssh://user@domain:port/path/repo.git

Suele ser bueno explicitar lo máximo posible la información de conexión dentro de la URL, sobre todo el puerto si no es el de por defecto. En el caso de SSH, el puerto por defecto es 22.

Para modificar la URL del repositorio remoto podemos usar el siguiente comando:

git remote set-url origin [ssh://user@domain:port/path/repo.git]

De esta forma en vez del usuario del sistema, se usará el que especifiquemos por URL y sólo restará meter la clave para poder operar con el repositorio deseado, o si fuera el caso, subiremos la clave pública SSH al servidor del repositorio para poder operar sin usar claves.

Actualizar un Fork con el Proyecto Original

Actualmente es fácil crear y mantener tu código en una plataforma como GitHub usando Git. Existen también otras plataformas como Bitbucket o GitLab que también son unas buenas soluciones para mantener nuestro código usando Git. Pero si buscas otras alternativas menos extendidas y conocidas puedes consultar en AlternativeTo.

Gracias a Git resulta sencillo descargar un repositorio de otro desarrollador para poder trabajar con él. En líneas generales puedes trabajar dicho repositorio usando la URL de clonado o puedes crear un fork del repositorio, que pasa a ser una copia del repositorio bajo tu completa administración. El caso que en este artículos nos atañe toma como premisa el segundo caso.

Cuando hemos hecho un fork y queremos incluir características en nuestro fork que se añadieron en el proyecto original después de que creáramos nuestro fork, es necesario hacer un fetch de la rama que queramos del proyecto original y hacer un merge con nuestro código.

Voy a explicarlo primero por línea de comandos y luego para aquellos que uséis un cliente del estilo TortoiseGit.

Abrid el terminal o consola de comandos y situarlo en el directorio donde esté vuestro proyecto.  Cambiad a la rama con la que se desea fusionar. Por lo general, suele ser la rama master.

git checkout master

A continuación se hace un pull de la rama del proyecto original de donde se quiere fusionar.

git pull https://github.com/PROPIETARIO_ORIGINAL/REPOSITORIOORIGINAL.git NOMBRE_DEL_BRANCH

Es posible hacer primero fetch y luego un merge, que es lo mismo que hacer un pull. Si hubiera algún conflicto se resuelve y se realiza un commit.

git commit -m "mensaje"

Por último haced push a vuestro repositorio remoto y ya tendréis los cambios incluidos en vuestro repositorio remoto para que sean descargados.

git push origin master

 

Si eres usuario de  TortoiseGit, lo más fácil es añadir un remote nuevo. Con el botón derecho del ratón haced click sobre el proyecto, haced luego click en "Settings", e id a "Remote" para crear un nuevo remote  con la dirección URL del proyecto original (https://github.com/XXX/XXXX.git).

TortoiseGit-adding_new_remote

El remoto existente con el nombre origin hace referencia al repositorio original de vuestro proyecto. Nuestro nuevo remoto lo llamaremos upstream, por ser el proyecto de donde hicimos fork.

Cuando hayamos terminado le damos a "Add New/Save" y nos sugerirá hacer un fetch del nuevo remoto, o sino hacemos click con el botón derecho del ratón en el proyecto y le damos a "Fetch". Si usamos la segunda opción recordar hacer el fetch de upstream y no de origin. Una vez hecho esto sólo queda hacer merge de nuestro master de origin con el de upstream, para ello previamente hacemos un "Checkout" a master con el botón derecho del ratón y luego de nuevo el botón derecho y "Merge".

TortoiseGit-merge

Si hay algún conflicto se resuelve seleccionando los cambios que nos quedamos de cada parte (origin o upstream).

Una vez resueltos los conflictos, si los hubiera, haced un "Commit" guardar los cambios y un "Push" para subirlo al repositorio remoto.

En la ayuda de GitHub, puedes encontrar más información:

https://help.github.com