Control de versiones con Git

Con este artículo sólo pretendremos hacer una pequeña introducción a los Sistemas de Control de Versiones con Git para poder empezar a utilizarlos en tus trabajos de desarrollo. La mayor parte de la información está obtenida de la documentación oficial de Git.

 

Qué es un Sistema de Control de Versiones

Un Sistema de Control de Versiones (VCS - Version Control System) es un sistema que registra los cambios realizados en un archivo o conjunto de archivos a lo largo del tiempo, de modo que puedas recuperar versiones específicas más adelante. Dicho sistema te permite regresar a versiones anteriores de tus archivos, regresar a una versión anterior del proyecto completo, comparar cambios a lo largo del tiempo, ver quién modificó por última vez algo que pueda estar causando problemas, ver quién introdujo un problema y cuándo, y mucho más.

Los Sistemas de Control de Versiones Distribuidos (DVCS - Distributed Version Control System) permiten que cada cliente tenga una réplica completa del repositorio de trabajo. De esta manera no se depende de un servidor central el cual puede llegar a estar no disponible en algún momento. Además, cualquiera de los repositorios disponibles en los clientes puede ser copiado al servidor con el fin de restaurarlo. Git, Mercurial, Bazaar o Darcs son ejemplo de DVCS.

Esquema de un sistema de control de versiones distribuido

 

Cómo funciona Git

Git maneja sus datos como un conjunto de copias instantáneas de un sistema de archivos miniatura. Cada vez que confirmas un cambio, o guardas el estado de tu proyecto en Git, él básicamente toma una foto del aspecto de todos tus archivos en ese momento, y guarda una referencia a esa copia instantánea. Para ser eficiente, si los archivos no se han modificado Git no almacena el archivo de nuevo, sino un enlace al archivo anterior idéntico que ya tiene almacenado. Git maneja sus datos como una secuencia de copias instantáneas.

Además todos los cambios son locales y almacenan una suma de comprobación (checksum) para evitar fallos o corrupción en su transmisión.

Instantáneas en Git

Git tiene estos estados principales en los que se pueden encontrar tus archivos:

  • No modificado (unmodified): el archivo no se ha modificado desde la última confirmación.
  • Modificado (modified): el archivo se ha modificado pero todavía no se han confirmado en tu base de datos local.
  • Preparado (staged): el archivo se ha modificado y se han marcado en su versión actual para que vaya en la próxima confirmación.
  • Confirmado (committed): el archivo está almacenado de manera segura en tu base de datos local.

Esto nos lleva a las tres secciones principales de un proyecto de Git:

  • Directorio de Git (Git directory): es donde se almacenan los metadatos y la base de datos de objetos para tu proyecto. Es la parte más importante de Git, y es lo que se copia cuando clonas un repositorio desde otra computadora.
  • El directorio de trabajo (working directory): es una copia de una versión del proyecto. Estos archivos se sacan de la base de datos comprimida en el directorio de Git, y se colocan en disco para que los puedas usar o modificar.
  • El área de preparación (staging area): es un archivo, generalmente contenido en tu directorio de Git, que almacena información acerca de lo que va a ir en tu próxima confirmación. A veces se le denomina índice (“index”).

El flujo de trabajo básico en Git es algo así:

  1. Modificas una serie de archivos en tu directorio de trabajo.
  2. Preparas los archivos, añadiéndolos a tu área de preparación.
  3. Confirmas los cambios, lo que toma los archivos tal y como están en el área de preparación y almacena esa copia instantánea de manera permanente en tu directorio de Git.

La línea de comandos es la manera más usual de utilizar Git, pero existen muchos IDEs y herramientas gráficas que permiten también realizar las tareas más importantes de Git. Conociendo su línea de comandos sabrás fácilmente cómo utilizar estas herramientas gráficas, pero no al contrario.

 

Cómo instalar Git

Git puede instalarse en cualquier sistema operativo, Linux, Windows o Mac. En el siguiente enlace puede encontrar más información:

Instalación de Git

 

Configuración de Git

Git utiliza tres archivos de configuración, de más global a más específico, que se van sobreescribiendo en ese mismo orden:

  • /etc/gitconfig | C:\...\gitconfig (system): contiene valores para todos los usuarios del sistema y todos sus repositorios.
  • $HOME/.gitconfig | C:\Users\usuario\.gitconfig (global): contiene configuración específica de dicho usuario.
  • dir_proyecto/.git/config (local): contiene configuración específica a dicho proyecto de desarrollo.

 

Podemos mostrar todos los valores de configuración con el siguiente comando. Si aparecen duplicados es porque se están leyendo los mismos valores en diferentes archivos de configuración:

git config --list

Podemos mostrar valores concretos de algún archivo de configuración con los siguientes comandos. Si nos situamos en el directorio del proyecto también aparecerán las directivas de configuración de dicho proyecto:

git config --global --list
git config --system --list
git config --local --list

 

Lo primero que deberás hacer cuando instales Git es establecer tu nombre de usuario y dirección de correo electrónico. Esto es importante porque las confirmaciones de Git usan esta información, y es introducida de manera inmutable en los commits que envías:

git config --global user.name "John Doe"
git config --global user.email johndoe@example.com

Pero si queremos configurarlos de manera local sólo para un proyecto en concreto tendremos que situarnos en el directorio del proyecto en sí y escribir:

git config user.name "John Doe"
git config user.email johndoe@example.com

 

Cómo inicializar un repositorio en un proyecto existente

Si ya tenemos un directorio con nuestro proyecto y queremos crear un repositorio Git a partir de él, lo que demos hacer es ir al directorio de dicho proyecto y ejecutar el siguiente comando:

git init

Esto crea un subdirectorio nuevo llamado .git, el cual contiene todos los archivos necesarios para mantener dicho repositorio.

 

Cómo manejar los archivos del repositorio

Por defecto, al inicializar un repositorio, éste se encuentra vacío, por lo que tendremos que añadir a él los archivos que queremos que pertenezcan al mismo. Los archivos que añadamos al repositorio se llamarán archivos rastreados (tracked files). El resto de archivos se llamarán archivos sin rastrear (untracked files) y no se almacenarán en el repositorio Git.

Añadimos archivos al repositorio con el siguiente comando:

git add README
git add *.php
git add *

Una vez que los archivos estén añadidos al repositorio, éstos podrán estar en uno de los estados mencionados anteriormente no modificado, modificado, preparado confirmado.

Este mismo comando también nos permite pasar los archivos al estado preparado, para que esté listo para hacer la confirmación del repositorio. Por tanto, cada vez que hagamos una modificación a un archivo rastreado tendríamos que ejecutar este comando para pasarlo al estado preparado (pero ya veremos cómo podemos ahorrarnos este paso). En la siguiente imagen podemos ver el ciclo de vida de un archivo:

Ciclo de vida del archivo en Git

Ciclo de vida del archivo en Git

Con los siguientes comandos de Git podemos eliminar y renombrar un archivo, y al mismo tiempo pasarlo directamente al estado preparado para su próxima confirmación. Si usamos los comandos del propio sistema operativo se eliminaría también pero no cambiaría el archivo al estado preparado.

git rm main.c
git mv main.c principal.c

A veces tendremos algún tipo de archivo que no deseamos que Git añada automáticamente al repositorio, o más aun, que ni siquiera deseamos que aparezca como no rastreado. Este suele ser el caso de archivos generados automáticamente como logs o temporales del sistema de compilación. En estos casos, podemos crear un archivo llamado .gitignore que liste patrones a considerar. Este es un ejemplo de un archivo:

# ignorar archivos con extensión 'a'
*.a 
# ignorar el directorio raíz /logs
/logs
# ignorar todos los archivos en el directorio build/
build/ 
# ignorar todos archivos con extensión 'txt' en ese directorio y todos los subdirectorios
doc/**/*.txt

 

Cómo ver los cambios preparados y no preparados

El comando principal para determinar qué archivos están en qué estado es el comando git status. Si ejecutamos este comando inmediatamente después de clonar o inicializar un repositorio, deberíamos ver algo como esto:

$ git status 
On branch master 
nothing to commit, working directory clean

Esto significa que tenemos un directorio de trabajo limpio, es decir, que no hay archivos rastreados y modificados. Por ahora, la rama siempre será master, que es la rama por defecto, más adelante hablaremos de ello.

Si añadimos un nuevo archivo a tu proyecto, por ejemplo README, tendríamos el archivo sin rastrear de la siguiente manera:

$ git status 
On branch master 
Untracked files: 
  (use "git add <file>..." to include in what will be committed) 

  README 

nothing added to commit but untracked files present (use "git add" to track)

Existe una opción para obtener un estado abreviado, de manera que podamos ver los cambios de una forma más compacta.

$ git status -s 
M README 
MM Rakefile 
A lib/git.rb 
M lib/simplegit.rb 
?? LICENSE.txt

Los archivos nuevos que no están rastreados tienen un ?? a su lado, los archivos que están preparados tienen una A y los modificados una M. El estado aparece en dos columnas - la columna de la izquierda indica el estado preparado y la columna de la derecha indica el estado sin preparar. Por ejemplo, en esa salida, el archivo README está modificado en el directorio de trabajo pero no está preparado, mientras que el archivo lib/simplegit.rb está modificado y preparado. El archivo Rakefile fue modificado, preparado y modificado otra vez por lo que existen cambios preparados y sin preparar.

 

Cómo confirmar los cambios

Cuando ya tengamos el área de preparación como deseamos, podemos confirmar los cambios con el siguiente comando, indicando el mensaje de confirmación. Recuerda que una confirmación sería como hacer una instantánea a nuestro repositorio, para poder volver a él cuando lo deseemos.

$ git commit -m "Confirmación inicial"
[master 463dc4f] Story 182: Fix benchmarks for speed 
 2 files changed, 2 insertions(+) 
 create mode 100644 README

 

A pesar de que puede resultar muy útil para ajustar los commits tal como queremos, el área de preparación es a veces un paso más complejo a lo que necesitamos para nuestro flujo de trabajo. Si queremos saltarnos el área de preparación, Git nos ofrece un atajo sencillo. Añadiendo la opción -a al comando haremos que Git prepare automáticamente todos los archivos rastreados antes de confirmarlos, ahorrándonos el paso de git add:

$ git commit -a -m 'Añadido nuevos temporales' 
[master 83e38c7] added new benchmarks 
 1 file changed, 5 insertions(+), 0 deletions(-)

 

Cómo modificar la última confirmación

En ocasiones, tras hacer una confirmación, nos damos cuenta de errores o modificaciones que se nos han olvidado incluir, pero que no quedaría hacer bien otra confirmación sólo para ello. Existe una manera sencilla de modificar la última confirmación realizada, pero debemos entender el problema de realizar este proceso si la confirmación ya ha sido publicada.

Para realizarlo utilizaremos la opción --amend al comando git commit. Añadir el mensaje de confirmación es opcional, pero si no lo hacemos se nos abrirá el editor de texto por defecto con el mensaje de la confirmación para aceptarlo o modificarlo, para evitarlo puede que sea más aconsejable incluirlo de nuevo para sobreescribirlo.

$ git commit -a --amend -m 'Añadido nuevos temporales' 
[master 1e338c7] added new benchmarks 
 1 file changed, 1 insertions(+), 0 deletions(-)

 

Cómo ver el historial de confirmaciones

Después de haber hecho varias confirmaciones, o si has clonado un repositorio que ya tenía un histórico de confirmaciones, probablemente quieras mirar atrás para ver qué modificaciones se han llevado a cabo.

La herramienta más básica y potente para hacer esto es el comando git log. Por defecto lista las confirmaciones hechas sobre ese repositorio en orden cronológico inverso, es decir, las confirmaciones más recientes se muestran al principio.

$ git log 
commit ca82a6dff817ec66f44342007202690a93763949 
Author: Scott Chacon <schacon@gee-mail.com> 
Date: Mon Mar 17 21:52:11 2008 -0700 

  changed the version number 

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 
Author: Scott Chacon <schacon@gee-mail.com> 
Date: Sat Mar 15 16:40:33 2008 -0700 
  
  removed unnecessary test

 

Si queremos visualizar de manera rápida y resumida los cambios introducidos en cada confirmación:

$ git log --stat 
commit a11bef06a3f659402fe7563abf99ad00de2209e6 
Author: Scott Chacon <schacon@gee-mail.com> 
Date: Sat Mar 15 10:31:28 2008 -0700 
  
  first commit 

 README           | 6 ++++++ 
 Rakefile         | 23 +++++++++++++++++++++++ 
 lib/simplegit.rb | 25 +++++++++++++++++++++++++ 
3 files changed, 54 insertions(+)

 

Y si queremos ver con detalle dichos cambios::

$ git log -p
commit ca82a6dff817ec66f44342007202690a93763949 
Author: Scott Chacon <schacon@gee-mail.com> 
+Date: Mon Mar 17 21:52:11 2008 -0700 

  changed the version number 

diff --git a/Rakefile b/Rakefile 
index a874b73..8f94139 100644 
--- a/Rakefile 
+++ b/Rakefile 
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
- s.version = "0.1.0" 
+ s.version = "0.1.1"

 

Cómo ver los archivos que incluye una confirmación

Si queremos comprobar la lista de archivos y directorios que incluye una confirmación pasada, lo primero que tendremos que conocer es el identificador de la confirmación deseada, mediante el comando git log. Y posteriormente ejecutar el siguiente comando, en el que incluiremos el parámetro -r para listar los subdirectorios:

$ git ls-tree -r b0729d58fefb48e45171fcf2b2810f4de1ed6b36
100644 blob 8116e6d91ba6034afb89c4ea2e616805350c45a6    README.md
100644 blob 76adaa83db3bd8cff9ac6a2ff46322a959fb5fd3    connection.php

 

Cómo trabajar con un repositorio remoto

Un repositorio remoto es aquel que está alojado en un equipo diferente al tuyo, normalmente en Internet.

 

Si deseamos obtener una copia de un repositorio remoto (clonarlo) podemos utilizar el comando git clone. De esta manera se recibe una copia completa del repositorio con cada versión de cada archivo de la historia del repositorio

git clone https://bitbucket.org/rafaticarte/demophp

Esto crea un directorio llamado demophp, en su interior crea otro nuevo directorio llamado .git, en él descarga toda la información de ese repositorio y saca una copia de trabajo de la última versión.

Si quieremos clonar el repositorio en un directorio con otro nombre, podemos especificarlo con la siguiente opción:

git clone https://bitbucket.org/rafaticarte/demophp proyecto_demo_php

Ese comando hace lo mismo que el anterior, pero el directorio de destino se llamará proyecto_demo_php.

En el caso de ser un repositorio privado, nos pedirá la contraseña del mismo para poder descargarlo. En este caso la URL suele venir acompañada del usuario propietario del repositorio:

$ git clone https://rafaticarte@bitbucket.org/rafaticarte/demophp
Clonar en «demophp»...
Password for 'https://rafaticarte@bitbucket.org': 

 

Podemos comprobar los repositorios remotos que tiene asociado un repositorio local con el siguiente comando. Éste nos mostrará el nombre que tiene asignado y las URLs de los repositorios remotos a los que apunta para leer y escribir. Si tuviéramos más de un repositorio remoto configurado nos aparecerá también.

$ git remote -v 
origin https://bitbucket.org/rafaticarte/demophp (fetch) 
origin https://bitbucket.org/rafaticarte/demophp (push)

Además tenemos la opción de renombrar el nombre del directorio remoto, que por defecto es origin:

$ git remote rename origin bitbucket
bitbucket https://bitbucket.org/rafaticarte/demophp (fetch)
bitbucket https://bitbucket.org/rafaticarte/demophp (push)

 

Si por el contrario lo que queremos es subir nuestro proyecto local a un repositorio remoto que ya tenemos creado, lo que tenemos que hacer es iniciar el proyecto, añadir los archivos y crear una primera confirmación (tal como hemos explicado en los puntos anteriores), y a continuación añadir la referencia al repositorio remoto y subir nuestro proyecto. Los repositorios remotos como GithubBitbucket o Gitlab te dan la URL que debes utilizar en los siguientes comandos:

$ git init
$ git add *
$ git commit -a -m "Confirmación inicial"
$ git remote add origin https://rafaticarte@bitbucket.org/rafaticarte/demophp.git
$ git push origin master
Password for 'https://rafaticarte@bitbucket.org': 
Counting objects: 3, done.
Writing objects: 100% (3/3), 210 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://rafaticarte@bitbucket.org/rafaticarte/demophp.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

 

Siempre que queramos volver a subir nuestro proyecto local al servidor remoto tras hacer al menos una confirmación, utilizaremos el mismo comando, indicando el nombre del repositorio remoto y la rama, por defecto master:

git push origin master

 

Si estamos trabajando varias personas en el mismo repositorio o yo esté trabajando desde varios equipos, será necesario descargar el repositorio remoto y al mismo tiempo combinarlo con mi repositorio local para así traernos los últimos cambios hechos en el mismo sin perder los nuestros. Tendremos que indicar el nombre del repositorio remoto y la rama a traer:

git pull origin master

Si tuviéramos alguna modificación realizada después de haber hecho una confirmación y ahora nos descargamos el proyecto, puede que antes nos pida hacer una confirmación previa y resolver los conflictos que puedan surgir entre la confirmación local y la remota.

 

Cómo etiquetar puntos del historial

Git tiene la posibilidad de etiquetar puntos específicos del historial como importantes. Esta funcionalidad se usa típicamente para marcar versiones de lanzamiento (v1.0, por ejemplo). Existen dos tipos principales de etiquetas:

  • Una etiqueta ligera es muy parecido a una rama que no cambia - simplemente es un puntero a una confirmación específica.
  • Una etiqueta anotada se guarda en la base de datos de Git como objetos enteros. Tienen un checksum, el nombre del etiquetador, correo electrónico y fecha, un mensaje asociado, y pueden ser firmadas y verificadas con GNU Privacy Guard (GPG). Normalmente se recomiendan las etiquetas anotadas, para almacenar toda esta información.

 

Podemos listar las etiquetas disponibles. El siguiente comando las lista en orden alfabético, ya que el orden en el que aparecen no tiene mayor importancia:

$ git tag 
v0.1
v1.3

 

Para crear una etiqueta anotada se debe especificar el siguiente comando con la opción -a:

git tag -a v1.4 -m 'Version 1.4'

 

Para crear una etiqueta ligera sólo debemos quitar la opción -a:

git tag v1.4

 

Por defecto, el comando git push no transfiere las etiquetas a los repositorios remotos. Debemos subir las etiquetas de forma explícita con el mismo nombre que la hayamos creado.

$ git push origin v1.5 
Counting objects: 14, done. 
Delta compression using up to 8 threads. 
Compressing objects: 100% (12/12), done. 
Writing objects: 100% (14/14), 2.05 KiB | 0 bytes/s, done. 
Total 14 (delta 3), reused 0 (delta 0) 
To git@github.com:schacon/simplegit.git
 * [new tag] v1.5 -> v1.5

Pero si queremos subir varias etiquetas a la vez, podemos utilizar la opción --tags del comando. Esto enviará al repositorio remoto todas las etiquetas que aun no existen en él.

$ git push origin master --tags 
Counting objects: 1, done. 
Writing objects: 100% (1/1), 160 bytes | 0 bytes/s, done. 
Total 1 (delta 0), reused 0 (delta 0) 
To git@github.com:schacon/simplegit.git
 * [new tag] v1.4 -> v1.4
 * [new tag] v1.4-lw -> v1.4-lw

 

Con el comando git pull sí que nos traeremos todas las etiquetas del repositorio remoto.

 

Enlaces de interés

 

Bibliografía

Basado en la documentación oficial de Git escrita bajo licencia Creative Commons BY-NC-SA 3.0

Licencia Creative Commons

Este artículo publicado en TicArte pertenece a Rafa Morales y está protegido bajo una Licencia Creative Commons.

Ir arriba