Hamburger Icon
Una introducción a Git

Una introducción a Git

Git es una herramienta básica para la mayoría de desarrolladores. Da igual si trabajas solo o en equipo. Con Git podremos tener un histórico de cambios de nuestros proyectos, podremos viajar en el tiempo de cualquier trabajo realizado.

Aunque sea pretencioso, lo voy a decir: es algo que necesitas usar.

Introducción

Cuando se habla de Git se suelen usar muchos términos diferentes que, como no se tengan por la mano, pueden hacer que te pierdas fácilmente. Vamos a empezar definiendo una serie de conceptos clave que nos serán de utilidad más adelante para entender todo lo que podemos hacer con Git.

Repositorio: un repositorio es un conjunto de commits, que son un histórico de cómo ha ido evolucionando el proyecto. También el HEAD, que se define más abajo, pero que básicamente identifica la rama o commit de la que deriva el árbol de trabajo actual. Un repositorio puede contener una o más ramas y tags o etiquetas, que sirven para identificar los commits con un nombre fácil de recordar.

El índice: los cambios que hacemos en el árbol de trabajo se guardan en un índice antes de ejecutar el commit. Puede ser que al índice se le llame también área de staging (como en VS Code por ejemplo).

Árbol de trabajo: es cualquier directorio que tenga asociado un repositorio, lo cual puedes saber si hay dentro una carpeta oculta .git.

Commit: un commit es una imagen de tu árbol de trabajo en un momento dado. A través del conjunto de commits obtenemos un historial de cambios hechos en una rama de un repositorio.

Rama: es una referencia usada para hacer commits. Podemos tener diferentes ramas para poder trabajar en paralelo y generar múltiples historiales de cambios.

Tag: cuando se hace un commit, Git le da un nombre auto generado. Podemos usar un tag o etiqueta para que sea un alias más fácil de recordar para ese commit en concreto.

master/main: la rama por defecto que habrá al crear un repositorio se llamará master o main. Es simplemente el nombre que se le da por defecto, esta rama no tiene nada de especial.

HEAD: lo usa un repositorio para saber qué está seleccionado en un momento dado. Git tiene un comando llamado checkout que nos permite apuntar HEAD a una rama o tag en concreto. Nuestro árbol de trabajo variará en función de dónde apunte HEAD. Los cambios que hagamos (con su posterior commit) afectarán a la rama dónde apunte HEAD.

Normalmente, el flujo de evento cuando trabajamos con Git es algo similar a: 1) se crea un repositorio y se trabaja sobre él; 2) cuando se alcanza un punto concreto de progreso, se añaden los cambios al índice. 3) Cuando el índice ya contiene todos los cambios que necesitamos, se hace el commit para que éstos queden registrado en el historial del repositorio.

Repositorio

Un repositorio contiene una serie de commits o imágenes de su contenido, que a su vez forman el histórico de cambios que se han dado. Básicamente es eso. Dentro del repositorio encontraremos un árbol de contenido formado por directorios y/o archivos.

Todo este árbol de contenidos es inmutable, es decir, que no se puede modificar. Cuando se hacen cambios, lo que ocurre es que un commit o imagen más actualizada se añade al repositorio.

Inicializando el primer repositorio

Vamos a crear un directorio en nuestro sistema de archivos llamado proyecto-demo. Dentro del directorio crearemos un archivo llamado hola.txt en el que simplemente añadiremos el texto "Hola mundo".

Una vez hecho, con la consola de comandos nos situamos en el directorio proyecto-demo y ejecutaremos estos comandos:

git init
git add hola.txt
git commit -m "añadido archivo hola"

El primer comando sirve para indicar que el directorio en el que nos encontramos será un repositorio. Al ejecutarlo se crea el directorio .git dentro del mismo.

El segundo comando añade cambios al índice, mientras que el tercero ejecuta el commit con un mensaje descriptivo.

Haciendo los primeros cambios

Con el comando status podemos saber en todo momento si tenemos cambios pendientes de commit o si tenemos archivos modificados que no hemos añadido al índide todavía.

git status

On branch master
nothing to commit, working tree clean

Si modificamos de nuevo el archivo y ejecutamos de nuevo el comando status veremos otra cosa:

git status

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   hola.txt

no changes added to commit (use "git add" and/or "git commit -a")

Se nos informa que hay cambios en el archivo hola.txt que no hemos añadido al índice, además de informarnos de que no hay nada en el índice pendiente de commit. Si añadimos al índice estos cambios, mira de nuevo lo que pasa con el status:

git add hola.txt
git status

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   hola.txt

Nos dice los cambios en el índice listos para el siguiente commit. Si hacemos otro commit confirmando estos cambios, podremos ver la utilidad del comando log:

git log

commit 44c60c5e5d0133dfe1d6cc2bf0d5ed4d87ad990c (HEAD -> master)
Author: Pol <mail@mail.com>
Date:   Fri Apr 29 18:04:55 2022 +0200

    modificado archivo hola

commit f49582178d273940dc574e8ca95caa27d3fdf5c1
Author: Pol <mail@mail.com>
Date:   Fri Apr 29 17:34:39 2022 +0200

    añadido archivo hola

Ramas

Como ya hemos comentado, cuando hacemos un commit estamos modificando la rama actual de trabajo. Podemos trabajar simultáneamente en diferentes ramas, lo cual puede ser útil para organizar el desarrollo de cualquier aplicación.

Podemos crear una nueva rama con este comando:

git branch <nueva-rama>

Para apuntar el HEAD a otra rama, usamos este comando:

git checkout <nombre-de-la-rama>

Podemos crear una rama nueva y apuntar el HEAD a ella con este comando que combina los dos anteriores:

git checkout -b desarollo

La opción -b hace que primero se cree la rama y luego se apunte el HEAD a ella. Puedes ver las ramas que existen y a cuál apunta el HEAD con el comando branch:

git branch

* desarrollo
  master

Hemos creado una nueva rama y al hacerlo, ésta apunta al último commit que hemos hecho en la rama donde estábamos antes, master. En otras palabras, al crear una nueva rama, los archivos y directorios que contendrán serán los mismos que haya en la rama en la que estábamos en el momento de crearla.

Trabajando con repositorios remotos: pull y push

Habitualmente, todo lo que hagamos en local en Git lo tendremos que migrar a un repositorio remoto para que otras personas tengan acceso a los cambios que hemos hecho.

Para hacer esto se añaden repositorios remotos. En estos ejemplos vamos a ver cómo usar repositorios de GitHub. Las operaciones básicas son:

  • Push: para enviar cambios al repositorio remoto
  • Pull: para descargar datos del repositorio remoto

Para añadir un repositorio remoto a nuestro entorno local, haremos lo siguiente:

git remote add <nombre-del-remoto> <url-del-remoto>

Habitualmente, al remoto se le suele llamar origin por defecto, así que nos quedamos con ese estándar. Para hacer estas pruebas, he creado un repositorio en GitHub:

git remote add origin https://github.com/Zeppelin17/testing-git.git

Podemos ver los remotos que tenemos disponibles con este comando:

git remote -v

origin  https://github.com/Zeppelin17/testing-git.git (fetch)
origin  https://github.com/Zeppelin17/testing-git.git (push)

Para subir cambios al remoto, usaremos este comando:

git push <nombre-remoto> <nombre-rama-remota>

Si has seguido los mismos pasos que yo hasta este punto, ahora mismo estás en la rama local desarrollo, compruébalo. Esta rama no existe en el repositorio de GitHub, ¿qué pasará si subimos los cambios? Veámoslo:

git push origin desarrollo

Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 465 bytes | 77.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/Zeppelin17/testing-git.git
 * [new branch]      desarrollo -> desarrollo

Se ha creado la rama desarrollo en el repositorio remoto porque no ha encontrado ninguna con ese nombre y se han subido los cambios. Puedes comprobarlo en Github, donde la rama desarrollo tendrá los cambios que acabamos de subir.

Fusión de ramas

Supongamos que los cambios que tenemos en desarrollo los damos por buenos y queremos pasarlos a la rama principal. Para ello tendremo que hacer una operación merge:

git merge <rama>

Esta operación fusionará la rama que indiquemos en el comando con la rama en la que estemos ubicados. Para pasar los cambios de desarrollo a master, primero nos situaremos en la rama master y luego ejecutaremos el merge. Vale la pena recalcar que esto lo estaremos haciendo localmente en nuestro equipo, no en el repositorio remoto.

Antes de hacerlo, haremos un pequeño cambio en el archivo hola.txt, ya que en este punto contiene exactamente lo mismo que el archivo de la rama master y si no, nuestro merge no tendría mucho sentido. Cuando tengamos algún cambio hecho en el archivo hola.txt de la rama desarrollo (recuerda añadir los cambios al índice y hacer el commit), estaremos listos para ejecutar este comando:

git checkout master
git merge desarrollo

Updating 44c60c5..04a9346
Fast-forward
 hola.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Ahora nuestra rama master contiene lo mismo que nuestra rama desarrollo, en local. Si quisiéramos ya podríamos subir los cambios al remoto, pero antes de hacerlo, por si acaso, es mejor que descarguemos posibles modificaciones que haya habido en el remoto, para ver si hay algún conflicto con nuestro archivo local. Para hacerlo, usaremos la operación pull:

git pull origin master

fatal: couldn't find remote ref master

¿Qué ha pasado? Nos dice que no ha encontrado la rama master en el repositorio remoto. Esto quizá sea debido a que, efectivamente, no existe. GitHub pasó a llamar a la rama principal main. Lo que podemos hacer, es renombrar nuestra rama master local a main.

Para hacerlo, nos aseguramos que estemos situados en la rama master y ejecutamos este comando:

# git branch -m <nuevo-nombre>
git branch -m main

Ahora, si repetimos la operación pull anterior, podremos obtener los cambios que haya en el remoto en la rama main:

git pull origin main

fatal: couldn't find remote ref main

¿¿Cómo?? Tampoco funciona. ¿Por qué? Pues porque hemos creado un repositorio vacío en GitHub que no contenía ninguna rama. Cuando hemos hecho el primer push de la rama desarrollo, se ha creado esa primera rama en GitHub, y esa es la única rama que existe. Por lo tanto, para crear nuestra rama local main en GitHub podemos repetir la operación push de antes:

git push origin main

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 280 bytes | 140.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'main' on GitHub by visiting:
remote:      https://github.com/Zeppelin17/testing-git/pull/new/main
remote:
To https://github.com/Zeppelin17/testing-git.git
 * [new branch]      main -> main

Vemos que se ha creado la rama main en GitHub, pero también vemos que se ha creado en el remoto algo llamado pull request.

Nota: en este punto, como hemos creado primero la rama desarrollo, ésta es la que se ha marcado como principal.

Una forma de evitar este problema, es crear primero el repositorio vacío en GitHub y posteriormente clonarlo en local, con lo que ya tendrá enlazado el remoto y no tendremos que añadirlo manualmente ni inicializar el repositorio. Lo podemos hacer con este comando:

git clone <url-repositorio>

Como este post es meramente educacional, lo dejaremos así, pero ten en cuenta que en un entorno real la rama principal será otra y, seguramente, no podamos hacer push de forma directa, sinó que tendremos que hacer una pull request.

Pull Request

Una pull request, también llamada merge request, es una solicitud de un desarrollador o desarrolladora para fusionar sus cambios con el código de la rama principal del proyecto. El responsable del repositorio ve las solicitudes que tiene y comprueba que estén listas para ser fusionadas con la rama principal, en caso de estar todo bien, se hace la fusión o merge.

Esto se hace para evitar fusionar cambios prematuramente. En nuestro ejemplo, tenemos una rama desarrollo y una rama main. En la rama main acabamos de subir los cambios más recientes, que no están en la rama desarrollo.

Vamos a crear una pull request en la rama desarrollo para solicitar que se fusionen los cambios. Esto lo haremos directamente desde GitHub:

Pull Request en GitHub

Vemos que como base está indicada la rama desarrollo y vamos a añadir los cambios hechos en main. Simplemente tendremos que hacer clic en el botón Create pull request.

Una vez hecho, veremos que ya aparece una pull request en el repositorio. Para confirmar y fusionar los cambios, la abriremos y nos indicará si hay conflictos o si está todo listo. En nuestro caso simplemente tendremos que hacer clic en Merge pull request. Los cambios se habrán funsionado en desarrollo.

GitHub CLI

Si usas GitHub, una herramienta muy útil es GitHub CLI. Es la herramienta oficial de GitHub para interactuar con los repositorios.

Muchas veces se trabaja con repositorios privados, configurar las claves SSH para autenticar con tu cuenta desde tu equipo local puede ser un poco engorroso si es algo que no tienes por la mano.

Esta herramienta nos pone las cosas muy fáciles. Una vez instalada, tenemos estos comandos para hacer las operaciones más básicas:

# login y logout de la cuenta
# en pocos pasos tendremos enlazada nuestra cuenta
gh auth login
gh auth logout

# Listar nuestros repositorios
gh repo list

# clonar un repositorio
gh repo clone <nombre-repo>

Con estos comandos puedes autenticarte y descargar los repositorios de forma muy sencilla.

También facilita mucho la gestión de issues y pull requests desde la consola de comandos, en la documentación oficial tienes todos los detalles.

Conclusiones

Git es una herramienta básica para casi cualquier desarrollador. Antiguamente era muy habitual usar FTP y tener múltiples carpetas en nuestro PC para gestionar versiones y subir el código a producción. Trabajar así a día de hoy, a parte de suponer un riesgo es algo totalmente arcaico y poco eficaz.

Git nos proporciona mecanismos para establecer un flujo de trabajo en equipo muy eficiente, sin duda fomenta una buena colaboración y gestión del código.

Git es un mundo, hay una infinidad de comandos que quedan fuera del alcance de este post que nos permiten hacer muchas cosas diferentes, pero quizás con lo visto sea suficiente para poder dar los primeros pasos.

Si has visto algo en el post que no te cuadra, por favor házmelo saber, estaré encantado de corregir y/o ampliar cualquier parte que sea incorrecta o inexacta.