Está en la página 1de 59

Despliegue de aplicaciones web

Sistemas de control de versiones


UD 1 Sistemas de Control de Versiones

INTRODUCCIÓN 4

TERMINOLOGÍA 5

TIPOS DE SCV 6
3.1 Local 7
3.2 Centralizado 7
3.3 Distribuido 9

GIT 11
4.1 Fundamentos de Git 11
4.2 Instalación 14
4.3 Configuración inicial 14
4.4 Obteniendo un repositorio 15
4.5 Comandos básicos 17
4.6 Comandos avanzados 25
4.7 Trabajar con ramas 28
4.8 Etiquetado 35
4.9 Hooks 39
4.10 Pull Request 46

GIT WORKFLOWS 48
5.1 GitFlow 48
Funcionamiento 49
Extensión flow de Git (Git-Flow) 51
5.2 GitHub Flow 54
Ventajas 54
Inconvenientes 54
5.3 GitLab Flow 55
Ventajas 55
Inconvenientes 56
5.4 Trunk-based Flow 56
Ventajas 56
Inconvenientes 56
5.5 Master-only Flow 57
Ventajas 57
Inconvenientes 57

6. GITHUB PAGES 58

7. BUENAS PRÁCTICAS EN GIT 59

7. BIBLIOGRAFÍA 60

2
UD 1 Sistemas de Control de Versiones

SISTEMAS DE CONTROL DE VERSIONES

Un control de versiones 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. Este tipo de control no sólo se aplica en el mundo del desarrollo, también lo
podemos encontrar presente en acciones tan cotidianas como la gestión de documentos físicos
(sobre los que podemos crear diferentes versiones estableciendo nombres distintos, por
ejemplo), edición de documentos colaborativos (Google Docs) o wikis.

En ingeniería del software el control de versiones podemos definirlo como un sistema que
registra los cambios realizados sobre un archivo o conjunto de archivos a lo largo del tiempo
de tal manera que sea posible recuperar versiones específicas más adelante. Este control
puede llevarse a cabo utilizando herramientas como CVS, Mercurial, Git, etc

Aunque la gestión de los sistemas de control de versiones suele hacerse por línea de
comandos, las versiones más modernas de este tipo de herramientas suelen ofrecer la
posibilidad de realizar la gestión a través de interfaces gráficas fáciles de usar. Además,
muchas de ellas se integran con los los IDE más populares.

1. INTRODUCCIÓN
En proyectos de desarrollo software de una determinada envergadura resulta imprescindible
el uso de un SCV puesto que nos ofrece la posibilidad de deshacer cambios no deseados e
incluso, volver a versiones anteriores de nuestro desarrollo.

La estructura de un sistema de control de versiones se puede interpretar como un árbol,


en el que el tronco es el lugar donde se suben los cambios realizados sobre la
aplicación y sobre el que pueden crearse ramas para poder trabajar sobre nuevas
características o solucionar errores que más tarde se añadirán al tronco si fuese
necesario.

No existe un límite en el número de ramas que podemos crear sobre el tronco principal de
nuestro SCV. Por ejemplo, en la siguiente imagen disponemos de una rama principal y dos
ramas utilizadas para implementar la Little Feature y la Big Feature respectivamente.

3
UD 1 Sistemas de Control de Versiones

2. TERMINOLOGÍA
Los distintos Sistema de Control de Versiones suelen utilizar una terminología muy similar
que es interesante conocer para poder trabajar correctamente. Entre los conceptos más
destacados encontramos:

● Repositorio (repository): hace referencia al lugar donde se guardan los archivos


actuales y el histórico de cambios.
● Tronco (trunk o master): es la rama principal de un repositorio de la que salen el
resto de ramas.
● Rama (branch): es una bifurcación del tronco o rama maestra de la aplicación que
contiene una versión independiente de la aplicación y en la que pueden aplicarse
cambios sin que afecten ni el tronco ni otras ramas.
● Cabeza (head o tip): hace referencia a la versión más reciente de una determinada
rama o del tronco. El tronco y cada rama tienen su propia cabeza, pero para referirse
al jefe del tronco a veces se utiliza el término HEAD, en mayúsculas.
● Copia de trabajo (working copy): hace referencia a la copia local de los archivos
que se han copiado del repositorio, que es sobre la que se hacen los cambios (es
decir, se trabaja, de ahí el nombre) antes de añadir estos cambios al repositorio.
● Bifurcación (fork): consiste en crear un nuevo repositorio a partir de otro. Este
nuevo repositorio, al contrario que en el caso de la clonación, no está ligado al
repositorio original y se trata como un repositorio diferente.
● Clonar (clone): consiste en crear un nuevo repositorio que es una copia idéntica de
otro, ya que contiene las mismas revisiones.

4
UD 1 Sistemas de Control de Versiones

● Subir (commit o check in): es añadir los cambios locales en el repositorio. Cabe
destacar que no los envía al servidor; los cambios quedan almacenados en el
repositorio local que se debe sincronizar.
● Bajar (check out): es copiar en el área de trabajo local una versión desde un
repositorio local, un repositorio remoto o una rama diferente.
● Pull: es la acción que copia los cambios de un repositorio (habitualmente remoto) en
el depósito local. Esta acción puede provocar conflictos.
● Push o fetch: son acciones utilizadas para añadir los cambios del repositorio local a
otro repositorio (habitualmente remoto). Esta acción puede provocar conflictos.
● Cambio (change o diff): representa una modificación concreta de un documento bajo
el control de versiones.
● Sincronización (update o sync): es la acción de combinar los cambios hechos al
repositorio con la copia de trabajo local.
● Conflicto (conflict): se produce cuando se intentan añadir cambios a un fichero que
ha sido modificado previamente por otro usuario. Antes de poder combinar los
cambios con el repositorio deberá resolver el conflicto.
● Bloqueo (lock): algunos sistemas de control de versiones en lugar de utilizar el
sistema de fusiones lo que hacen es bloquear los archivos en uso, por lo que sólo
puede haber un solo usuario modificando un fichero en un momento dado.
● Fusionar (merge o integration): es la acción que se produce cuando se quieren
combinar los cambios de un repositorio local con un remoto y se detectan cambios
en el mismo archivo en ambos repositorios y se produce un conflicto. Para resolver
este conflicto se deben fusionar los cambios antes de poder actualizar los
repositorios. Esta fusión puede consistir en descartar los cambios de uno de los dos
repositorios o editar el código para incluir los cambios del archivo en ambos lados.
Cabe destacar que es posible que un mismo fichero presente cambios en muchos
puntos diferentes que deben ser resueltos para poder dar la fusión por finalizada.
● Versión (versión o revisión): es el conjunto de cambios en un momento concreto del
tiempo. Se crea una versión cada vez que se añaden cambios a un repositorio.
● Etiqueta (tag, label o baseline): permite añadir una etiqueta a una subida para poder
identificar esta subida concreta de una forma más comprensible. Por ejemplo, se
puede etiquetar la primera versión de un software (1.0) o una versión en la que se ha
solucionado un error importante.
● Volver a la versión anterior (revert): descarta todos los cambios producidos en la
copia de trabajo desde la última subida al depósito local.

3. TIPOS DE SCV
Podemos diferenciar 3 tipos de sistemas de control de versiones; local, centralizado y
distribuido.

5
UD 1 Sistemas de Control de Versiones

3.1 Local
El más sencillo de todos. Consiste en copiar los archivos a otro directorio con el fin de
mantener un historial de cambios en local (ya sea copias o mediante registros en una base
de datos). Este método es muy común pero también es tremendamente propenso a
errores. Es fácil olvidar en qué directorio te encuentras y guardar accidentalmente en el
archivo equivocado o sobrescribir archivos que no querías.

Una de las herramientas de control de versiones más popular fue un sistema llamado
RCS, que todavía podemos encontrar en sistemas como Mac OS X. Esta herramienta
funciona guardando conjuntos de parches en un formato especial en disco, y es capaz de
recrear cómo era un archivo en cualquier momento a partir de dichos parches.

3.2 Centralizado
Los CVS locales aunque sencillos de utilizar traen consigo una problemática muy común; no
son válidos cuando lo que se necesita es llevar a cabo un proyecto que implique
colaboración entre varios. Los sistemas de Control de Versiones Centralizados (CVCS por
sus siglas en inglés) fueron desarrollados para solucionar este problema. Estos sistemas
tienen un único servidor que contiene todos los archivos versionados y varios
clientes que descargan los archivos desde ese lugar central. Este ha sido el estándar
para el control de versiones por muchos años.

6
UD 1 Sistemas de Control de Versiones

Como podemos deducir de lo anterior, las principales ventajas que presentan los CVS
centralizados son:
● Todas las personas saben hasta cierto punto en qué están trabajando los otros
colaboradores del proyecto.
● Más fácil administrar un CVCS que tener que lidiar con bases de datos locales en
cada cliente

Como desventajas, podemos remarcar el hecho de que sólo existe un punto único de
fallo que representa el servidor centralizado. Cuando tienes toda la historia del proyecto en
un mismo lugar, te arriesgas a perderlo todo (cosa que también ocurre en los SCV locales).

Algunos ejemplos de SCV centralizados son:

● CVS (Current Versions System). Creado en los 80 y con licencia GNU, se considera
el primer SCV centralizado. Una característica distintiva es que el servidor solo
acepta actualizaciones de los ficheros que estén en la última versión. Esto implica
que los usuarios deben actualizar sus copias locales antes de subirlas al servidor.
● SVN. El proyecto de Subversion surgió en el año 2000 con el objetivo crear un
sistema de control que solventase las carencias y problemas de CVS como por
ejemplo:
○ Commits atómicos, en CVS un commit interrumpido puede dejar datos
inconsistentes.
○ La creación de ramas es más eficiente, con complejidad constante a
diferencia de CVS que es lineal (aumenta con el número de ramas).
○ Manejo de archivos binarios como tal, CVS los trata como archivos de texto.
○ Envía los incrementos de los ficheros en la comunicación cliente­- servidor,
en lugar de los ficheros completos como CVS.

7
UD 1 Sistemas de Control de Versiones

3.3 Distribuido
Los sistemas de Control de Versiones Distribuidos (DVCS por sus siglas en inglés) ofrecen
soluciones para los problemas que han sido mencionados. En un DVCS (como Git,
Mercurial, Bazaar o Darcs), los clientes no solo descargan la última copia instantánea de los
archivos, sino que se replica completamente el repositorio. De esta manera, si un servidor
deja de funcionar y estos sistemas estaban colaborando a través de él, cualquiera de los
repositorios disponibles en los clientes puede ser copiado al servidor con el fin de
restaurarlo. Cada clon es realmente una copia completa de todos los datos.

Algunos ejemplos de SCV distribuidos son:

● Mercurial. Creado en 2005, es un Sistema de Control de Versiones creado por Mat


Mackall escrito en Python y C. Destaca por su rapidez y por ser multiplataforma y

8
UD 1 Sistemas de Control de Versiones

extensible.

● Bazaar. Distribuido y patrocinado por Canonical Ltd., diseñado para facilitar la


contribución en proyectos de software libre y opensource.

● Bitkeeper. Desarrollada por la empresa BitMover, durante un tiempo, se usó como


sistema de control de versiones del código de muchos proyectos de software libre,
pero sin duda el más importante fue el del Kernel de Linux.

Aunque en un principio se trataba de software propietario, en la actualidad se ha


hecho de código abierto adoptando la licencia libre Apache 2.0.

● Git. Sin duda el SCV más utilizado en la actualidad. En el 2005, la relación entre la
comunidad que desarrollaba el kernel de Linux y la compañía que desarrollaba
BitKeeper se vino abajo y la herramienta dejó de ser ofrecida de manera gratuita.
Esto impulsó a la comunidad de desarrollo de Linux (y en particular a Linus Torvalds,
el creador de Linux) a desarrollar su propia herramienta basada en algunas de las
lecciones que aprendieron mientras usaban BitKeeper. Algunos de los objetivos del
nuevo sistema fueron los siguientes:
○ Velocidad
○ Diseño sencillo
○ Gran soporte para desarrollo no lineal (miles de ramas paralelas)
○ Completamente distribuido
○ Capaz de manejar grandes proyectos (como el kernel de Linux)
eficientemente (velocidad y tamaño de los datos)

9
UD 1 Sistemas de Control de Versiones

4. GIT
Como hemos dicho, Git es el Sistema de Control de Versiones más utilizado en la
actualidad. Desde su nacimiento en 2005, Git ha evolucionado y madurado para ser fácil de
usar siempre basándose en una serie de características que le permiten destacar del resto
de soluciones similares que hemos visto en el punto anterior.

4.1 Fundamentos de Git


Entender los fundamentos de cómo funciona Git nos permitirá utilizar la herramienta de
forma mucho más efectiva. Veámos cuáles son:

Copias instantáneas, no diferencias

Conceptualmente, la mayoría de los otros SCV manejan la información que almacenan


como un conjunto de archivos y las modificaciones hechas a cada uno de ellos a través del
tiempo.

10
UD 1 Sistemas de Control de Versiones

Git no maneja ni almacena sus datos de esta forma. 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.

Casi todas las operaciones son locales

La mayoría de las operaciones en Git sólo necesitan archivos y recursos locales para
funcionar. Al no intervenir retardos de red como podría suceder en otros sistemas, Git
resulta mucho más rápido. Además, esta característica, permite que podamos seguir
realizando cambios y confirmándose en nuestro servidor local sin necesidad de estar
conectados a la red.

Integridad

Todo en Git es verificado mediante una suma de comprobación (checksum en inglés) antes
de ser almacenado, y es identificado a partir de ese momento mediante dicha suma. Esto

11
UD 1 Sistemas de Control de Versiones

significa que es imposible cambiar los contenidos de cualquier archivo o directorio sin
que Git lo sepa. No puedes perder información durante su transmisión o sufrir corrupción
de archivos sin que Git sea capaz de detectarlo.

Git guarda todo no por nombre de archivo, sino por el valor hash de sus contenidos. Esta
funcionalidad está integrada en Git al más bajo nivel y es parte integral de su filosofía

Generalmente sólo añade información

Cuando realizas acciones en Git, casi todas ellas sólo añaden información a la base de
datos de Git. Es muy difícil conseguir que el sistema haga algo que no se pueda enmendar,
o que de algún modo borre información.

Tres estados

Sin duda es el fundamento más importante. Git tiene tres estados principales en los que se
pueden encontrar tus archivos:

● Confirmado: Los datos están almacenados de manera segura en tu base de datos


local.
● Modificado: Todo archivo modificado pero que todavía no se ha confirmado en
nuestra base de datos.
● Preparado: Todo archivo modificado que se ha marcado en su versión actual para
que vaya en tu próxima confirmación.

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


1. Modificamos una serie de archivos en nuestro directorio de trabajo.
2. Preparamos los archivos, añadiéndolos a nuestra área de preparación.
3. Confirmamos 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 nuestro
directorio de Git.

12
UD 1 Sistemas de Control de Versiones

4.2 Instalación
Git se encuentra disponible en la mayoría de plataformas, incluyendo Linux, Windows,
MacOS y Solaris. En la página oficial podemos encontrar el enlace para descargarlo para
los diferentes sistemas operativos.

La instalación es muy sencilla. Por ejemplo, en el caso de distribuciones basadas en Debian


como Ubuntu, simplemente debemos ejecutar la siguiente instrucción:

apt-get install git

Una vez hecho esto, podemos comprobar que efectivamente la versión instalada es la
correcta por medio de la instrucción:

git --version

4.3 Configuración inicial


Git nos permite personalizar nuestro entorno. Para ello incluye una herramienta llamada git
config con la que podemos establecer variables de configuración como, por ejemplo,
nuestro editor preferido para ser utilizado cuando necesitemos introducir algún mensaje de
confirmación de cambios. Estas variables pueden almacenarse en tres sitios distintos:

● Archivo /etc/gitconfig: Contiene valores para todos los usuarios del sistema y todos
sus repositorios. Si pasamos la opción --system a git config, lee y escribe
específicamente en este archivo.
git config --system core.editor emacs

● Archivo ~/.gitconfig o ~/.config/git/config: Este archivo es específico de nuestro


usuario. Podemos hacer que Git lea y escriba específicamente en este archivo
pasando la opción --global.

git config --global user.name "Pepe"

● Archivo config en el directorio de Git (es decir, .git/config) del repositorio que
estemos utilizando actualmente: Este archivo es específico del repositorio actual.

13
UD 1 Sistemas de Control de Versiones

Cada nivel sobrescribe los valores del nivel anterior, por lo que los valores de .git/config
tienen preferencia sobre los de /etc/gitconfig.

Si queremos modificar la configuración para un repositorio en concreto, sólo tenemos que


obviar el parámetro --global.

Una vez realizados los cambios en la configuración que deseemos, podemos comprobar
que éstos se han almacenado correctamente. Para ello, solo tenemos que ejecutar:

git config --list

Y si sólo queremos verificar el valor de una determinada propiedad:

git config user.name

4.4 Obteniendo un repositorio


El primer paso para empezar a trabajar con Git en local una vez instalado es disponer de un
repositorio sobre el que almacenar nuestros cambios. En esta sección veremos las dos
principales formas de poder obtener uno; inicializarlo nosotros o clonar uno que ya exista
previamente. En ambos casos, será necesario haber creado previamente en remoto un
repositorio destino (por ejemplo en Github).

Inicializar un repositorio

Transformar un directorio de nuestro sistema en el que almacenamos el contenido de


nuestro proyecto en un repositorio Git es muy sencillo. Tan solo tenemos tenemos que
situarnos en dicha ubicación y usar el siguiente comando:

git init

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

● La carpeta objects guarda el contenido de tu base de datos,


● La carpeta refs guarda los apuntadores a las confirmaciones de cambios (commits)
● El archivo HEAD apunta a la rama que tengas activa (checked out)
en este momento
● El archivo index es donde Git almacena la información sobre tu área
de preparación (staging area)
● El archivo config contiene las opciones de configuración específicas

14
UD 1 Sistemas de Control de Versiones

del proyecto.
● La carpeta info guarda un archivo global de exclusión con los patrones a ignorar
además de los presentes en el archivo .gitignore
● El archivo description se utiliza solo en el programa GitWeb

Una manera de eliminar el control de versiones de un directorio consiste en eliminar la


carpeta .git de su interior

Tras ejecutar el comando git init sobre nuestro directorio, necesitaremos una serie de pasos
extra para poder conectar éste con el repositorio remoto que, como hemos dicho, debemos
haber creado previamente. Todos estos pasos suele exponerlos Github en el momento que
creamos un repositorio desde cero y suelen ser:

Si analizamos los pasos que nos sugiere Github vemos que:


- En primer lugar, creamos el fichero README.md (u otro cualquiera)
- Realizamos el git init que hemos comentado
- Hacemos el add y commit del fichero README.md
- Creamos una rama master (git branch - M master)
- Conectamos nuestro directorio con el repositorio de Github (git remote add origin
….)
- Hacemos el push de todos los cambios locales al repositorio remoto.

Clonar un repositorio

La segunda de las formas de obtener un repositorio Git consiste en obtener una copia o clon
de uno que ya exista. En este caso, la instrucción que deberemos ejecutar será git clone
[url]. Por ejemplo:

git clone https://github.com/dawEstacio/comandosInicialesGit.git

La ejecución del comando anterior creará un directorio llamado comandosInicialesGit,


inicializa un directorio .git en su interior, descarga toda la información de ese repositorio y
saca una copia de trabajo de la última versión.

15
UD 1 Sistemas de Control de Versiones

Si queremos que el proceso de clonación genere un directorio con un nombre distinto al


del proyecto (en el ejemplo anterior netflix.github.com.git), podemos hacerlo incluyendo el
nombre deseado como segundo parámetro del git clone.

git clone https://github.com/dawEstacio/comandosInicialesGit.git


misComandos

La ejecución de este comando hará lo mismo que el anterior pero el directorio donde
descargará toda la información será misComandos

4.5 Comandos básicos

Revisar el estado del repositorio

Como hemos visto, uno de los fundamentos principales de Git consiste en diferenciar los
distintos archivos almacenados según su estado. Por este motivo, es interesante conocer
de qué forma podemos averiguar qué elementos de nuestro proyecto están rastreados y
cuáles no. Los archivos rastreados (tracked files en inglés) son todos aquellos
archivos que estaban en la última instantánea del proyecto; pueden ser archivos sin
modificar, modificados o preparados. Los archivos sin rastrear son todos los demás -
cualquier otro archivo en tu directorio de trabajo que no estaba en tu última instantánea y
que no está en el área de preparación (staging area).

La Herramienta principal para determinar en qué estado están los archivos de un proyecto
con el comando git status. Si ejecutamos este comando inmediatamente después de clonar
un repositorio, obtenemos una salida similar a la siguiente:

16
UD 1 Sistemas de Control de Versiones

El comando te indica en qué rama estás y te informa sobre qué ha variado con respecto a la
misma rama en el servidor.

Imaginémos que añadimos un nuevo fichero (README) en nuestro servidor. Si volvemos a


consultar el estado del repositorio, veremos como la salida de git status nos informa de que
existe un nuevo fichero no rastreado.

Si queremos revertir los cambios de un repositorio para dejarlo en su estado original


podemos utilizar la instrucción git clean.
● git clean -n -> nos indicará los ficheros que van a ser modificados pero no
ejecutará la acción
● git clean -f -> fuerza a realizar la acción de limpieza

Si acto seguido rastreamos el fichero README en nuestro servidor (mediante la instrucción


git add que veremos a continuación) y consultamos el estado de nuevo, obtendremos una
salida similar a la siguiente:

Rastrear ficheros (git add)

17
UD 1 Sistemas de Control de Versiones

En los ejemplos de consulta de estado que hemos visto en el punto anterior, hemos hecho
uso de la instrucción git add. Así pues, git add nos permite rastrear ficheros (pasarlo a la
stage área) de nuestro directorio de trabajo indicando así que se han realizado los cambios
oportunos sobre estos y que formarán parte del próximo commit a nuestro servidor local.

La sintaxis de la instrucción git add admite como parámetro tanto el nombre de un fichero en
concreto, como un patrón. Por tanto, podemos ejecutar:

git add README.md (sólo rastrea el fichero README.md)

o bien:

git add . (rastrea todos los archivos del directorio)

Algunas opciones interesantes de git add son:

● -u: sólo añade al índice aquellos ficheros que ya están siendo monitorizados por git

● -n: opción es muy práctica ya que nos mostrará en pantalla lo que el comando
git-add haría sin actualizar el índice.

Si queremos eliminar de nuestra staged area algún archivo podemos utilizar el comando
git reset HEAD nombreArchivo para devolverlo a su estado de no confirmado. Eso sí,
debe existir algún commit previo en nuestro repositorio para que exista el HEAD.

Eliminar ficheros (git rm)

Para eliminar archivos de Git, debemos eliminarlos de nuestros archivos rastreados (del
área de preparación) y luego confirmar. No basta con eliminarlos físicamente del
directorio de trabajo. Para ello existe el comando git rm que se encarga de eliminar el
archivo de nuestro directorio de trabajo, y además lo añade al área de staged files.

La diferencia entre este comando y eliminar directamente el archivo, es que si sólo lo


eliminamos físicamente aparecerá en el área de unstaged files.

18
UD 1 Sistemas de Control de Versiones

El comando git rm resulta muy interesante cuando queremos renombrar un archivo ya


rastreado. Con un solo paso (por ejemplo git rm README.md README ), hacemos lo
equivalente a:
● Renombrar físicamente el archivo mv README.md README
● Dejar de rastrear el fichero antiguo git rm README.md
● Rastrear el fichero nuevo git add README

Al igual que pasaba con el comando git reset, podemos utilizar git rm --cached
nombreFichero para eliminar archivos de nuestra área de preparación. No obstante, hay
que tener en cuenta que:

● git rm --cached elimina el archivo del índice pero lo deja en el directorio de trabajo.
Esto le indica a Git que ya no desea rastrear el archivo.

● git reset HEAD deja el archivo como un archivo de seguimiento en el índice, pero las
modificaciones almacenadas en caché en el índice se pierden.

Confirmar cambios (git commit)

Una vez tenemos nuestros archivos rastreados, es momento de incluirlos en nuestro


repositorio local. Para tal fin, utilizaremos la instrucción git commit. La forma más sencilla
consiste en ejecutar este comando sin ningún tipo de parámetro. Automáticamente, Git
abrirá un editor (que como hemos visto podemos configurar) para poder introducir los
comentarios asociados a la confirmación de dichos cambios.

19
UD 1 Sistemas de Control de Versiones

Estos dos pasos (realizar el commit y escribir el comentario asociado) pueden ejecutarse al
mismo tiempo si añadimos la opción -m como en el siguiente ejemplo:

git commit -m “Comentario asociado a la confirmación de cambios”

Otras opciones interesantes de git commit son:


● --amend: Permite añadir nuevos archivos al último commit realizado.
● -a: Permite, con un solo paso, pasar a preparados ficheros modificados o eliminados
que ya estén en el área de preparación, y realizar el commit.

Deshacer el último commit

A veces queremos tirar para atrás el último commit que hemos hecho porque hemos
añadido más archivos de la cuenta, queremos hacer commit de otra cosa o, simplemente,
porque ahora no tocaba. Si todavía no has llevado tus cambios al repositorio remoto tienes
dos formas de hacer esto. Ambas son válidas pero dependerá si quieres, o no, mantener los
cambios del commit.

● Si queremos mantener los cambios

git reset --soft HEAD~1

Con el comando reset hacemos que la rama actual retroceda a la revisión que le
indicamos. En este caso le decimos HEAD∼1. Esto significa que queremos volver a
la versión inmediatamente anterior a la que estamos ahora.

El parámetro --soft es el que va a hacer que los cambios que habíamos hecho en el
commit, en lugar de eliminarlos, nos los mantenga como cambios locales en nuestro
repositorio. De hecho, al ejecutar este comando, no se eliminarán los cambios del
commit, sino que se mantendrán como cambios locales. Por lo que podrías volver a
hacer exactamente el mismo commit que antes.

● Si no queremos mantener los cambios

20
UD 1 Sistemas de Control de Versiones

git reset --hard HEAD~1

Es simplemente el mismo comando pero cambiamos --soft por --hard. Esto eliminará
los cambios de los que habíamos hecho commit anteriormente.

Arreglar el último commit

A veces no quieres tirar atrás el último commit que has hecho si no que simplemente
quieres arreglarlo. Aquí hay dos opciones:

● Sólo queremos arreglar el mensaje que hemos usado para el último commit

git commit --amend -m "Este es el mensaje correcto"

● Queremos añadir más cambios al último commit

# Añade los archivos con modificaciones


# que quieres añadir al commit anterior

$ git add src/archivo-con-cambios.js

# Vuelve a hacer el commit con el parámetro amend

$ git commit --amend -m "Mensaje del commit"

Ya sea que sólo queremos cambiar el mensaje de commit o que además queremos añadir
modificaciones en el último commit, lo importante es que esto NO va a crear un nuevo
commit si no que va a solucionar el anterior.

Importante: El parámetro de --amend es muy útil pero sólo funciona con el último commit
y siempre y cuando NO esté publicado en el repositorio remoto. Si ya hemos hecho push
de ese commit, esto no va a funcionar. Deberíamos hacer un git revert en su lugar.

Revisar diferencias entre versiones (git diff)

El comando git diff se utiliza cuando deseamos ver las diferencias entre distintas versiones
de nuestros archivos. Las comparaciones que más se suelen utilizar son:

● git diff (sin ningún parámetro): Revisa las diferencia entre nuestro entorno de trabajo
y nuestra staged área

21
UD 1 Sistemas de Control de Versiones

● git diff --staged o git diff --cached: revisa las diferencias entre nuestra staged area y
la última confirmación o commit
● git diff nombreRama1 nombreRama2: revisa diferencias entre dos ramas distintas.

Un ejemplo de salida del comando git diff sería el que se muestra a continuación. En este
caso, en nuestro entorno de trabajo tenemos el fichero CONTRIBUTIN.md que aún no
hemos añadido al stage area:

Revisar historial de cambios (git log)

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.

En caso de no pasar ningún parámetro a la llamada, git log nos muestra las confirmaciones
hechas sobre ese repositorio en orden cronológico inverso. Es decir, las confirmaciones
más recientes se muestran al principio.

Un ejemplo de salida del comando git log sería el siguiente:

Algunas opciones interesantes de git log son:

● git log -p: muestra las diferencias introducidas en cada confirmación.


● git log -valorNumérico: hace que se muestren únicamente las valorNumérico últimas
entradas del historial.
● git log --oneline: muestra el log en una sola línea, lo que permite que veamos más
cantidad de commits en la pantalla y facilita mucho seguir la secuencia, en vez de
tener que ver un montón de páginas de commits.

22
UD 1 Sistemas de Control de Versiones

El comando git shortlog se utiliza para resumir la salida de git log. Toma muchas de las
mismas opciones que el comando git log pero, en lugar de enumerar todos los commits,
presentará un resumen de los commits agrupados por autor.

Ignorar archivos (.gitignore)

Este no es un comando de git como tal sino una opción que Git nos ofrece. Puede que
existan determinados archivos en nuestro espacio de trabajo que no queremos que Git
rastree. Pues bien, en estos casos tenemos la opción de crear un fichero .gitignore en
donde listaremos los patrones que no queremos que Git tenga en cuenta.

Crear un archivo .gitignore antes de comenzar a trabajar es generalmente una buena idea,
pues así evitamos confirmar accidentalmente archivos que en realidad no queremos incluir
en nuestro repositorio.

Las reglas sobre los patrones que puedes incluir en el archivo .gitignore son las siguientes:

● Ignorar las líneas en blanco y aquellas que empiezan con #.


● Aceptar patrones glob estándar. (https://rico-schmidt.name/pymotw-3/glob/)
● Los patrones pueden terminar en barra (/) para especificar un directorio.
● Los patrones pueden negarse si se añade al principio el signo de exclamación (!).

Un ejemplo de fichero .gitignore podría ser:

23
UD 1 Sistemas de Control de Versiones

GitHub mantiene una extensa lista de archivos .gitignore adecuados a docenas de


proyectos y lenguajes en el siguiente enlace

4.6 Comandos avanzados


git push

Cuando tenemos un proyecto que queremos compartir, debemos enviarlo a un servidor. El


comando para hacerlo es simple: git push [nombre-remoto] [nombre-rama-local]. Por
ejemplo, si queremos enviar nuestra rama master a nuestro servidor origin, podemos
ejecutar el siguiente comando:

git push origin master

Este comando solo funciona si clonaste de un servidor sobre el que tenemos permisos de
escritura y si nadie más ha enviado datos por el medio. Si alguien más clona el mismo
repositorio y envía información antes que nosotros, nuestro envío será rechazado. En ese
caso, tendremos que traernos su trabajo y combinarlo con el nuestro antes de poder enviar
datos al servidor.

git fetch

Puede que el contenido del servidor remoto sea modificado mientras nosotros trabajamos
en nuestro servidor local y necesitemos ver cuáles han sido los cambios efectuados para
poder juntarlos con los nuestros. En estos casos podemos optar por dos opciones:

● Obtener en un primer paso los cambios remotos pero no juntarlos (mergearlos) con
los nuestros y en un segundo paso realizar la unión.
● Obtener y combinar los cambios remotos con un único paso

Para el primero de los casos, Git nos ofrece el comando git fetch [nombre rama remota].
Una vez ejecutado, el comando irá al proyecto remoto y se traerá todos los datos que aún
no tenemos en nuestro servidor local. Luego de hacer esto, tendremos referencias a todas
las ramas del remoto, las cuales podemos combinar e inspeccionar cuando quieras.

24
UD 1 Sistemas de Control de Versiones

En el siguiente ejemplo, un segundo usuario ha creado un fichero (ficheroUsuario2.txt) del


que nosotros aún no disponemos. Si realizamos un git fetch de la rama origen y
posteriormente consultamos el contenido del directorio, vemos como seguimos sin disponer
de él. Lo que sí podemos observar es que se ha creado una nueva rama
(remotes/origin/master)

En cambio, si consultamos el contenido de la nueva rama vemos que el fichero creado por
el usuario 2 sí que está:

Por tanto, es importante destacar que el comando git fetch solo trae datos a nuestro
repositorio local. Ni lo combina automáticamente con nuestro trabajo ni modifica el
trabajo que llevamos hecho. La combinación con nuestro trabajo debemos hacerla
manualmente cuando estemos listos.

En nuestro caso deberíamos hacer un merge (que veremos más adelante pero que
básicamente añade los cambios de una rama en otra) de la rama nueva que se ha creado a
la rama master para poder disponer de los cambios del usuario 2.

25
UD 1 Sistemas de Control de Versiones

git pull

Imaginemos ahora que queremos actualizar nuestro repositorio local con los posibles
cambios que se hayan podido producir en el repositorio remoto. Si trabajamos en un mismo
repositorio con más gente, este es un paso imprescindible para mantener nuestra copia
local del repositorio al día. De hecho, si existen cambios en el repositorio remoto, Git no nos
dejará subir cambios al repositorio hasta que hayamos descargado la versión más reciente
y la hayamos fusionado con nuestra versión local. Incluso si trabajamos solos, es posible
que subamos cambios desde más de un ordenador, por lo que saber cómo descargar los
cambios del repositorio remoto también resulta útil.

La forma más sencilla de actualizar el repositorio local es mediante el comando git pull:

git pull

A continuación se muestra un ejemplo del comando git pull. En este caso, previo a la
ejecución de dicha instrucción, otro usuario con permisos ha modificado un fichero del
repositorio. Al ejecutar el git pull vemos como se descarga el cambio en nuestro repositorio
local.

26
UD 1 Sistemas de Control de Versiones

A diferencia de git fetch, git pull descarga los cambios e intenta fusionarlos sobre nuestra
rama de trabajo.

Es posible, no obstante, que en git nos muestre un error como el siguiente:

Esto ocurre cuando git no puede realizar el merge tras haberse traído de remoto todos los
cambios. En este caso hemos realizado cambios sobre el primerFichero.txt. Tras estos
cambios (sobre los cuales hemos hecho commit), hemos intentado realizar el pull para
actualizar nuestro directorio local. Lo que ha pasado es que, como otro usuario ya había
subido un nuevo cambio sobre primerFichero.txt que nosotros no teníamos en local, git no
sabe cómo combinar ambos y nos delega esta tarea a nosotros.

Si editamos el contenido de primerFichero.txt, veremos que tiene el siguiente aspecto:

Vemos que git nos ha modificado el contenido de primerFichero.txt. La primera línea


(<<<<<<< HEAD) indica que los cambios que hay hasta la línea ====== son los que
nosotros hemos realizado en local. La parte posterior a esta marca, son los cambios
importados del repositorio remoto. Ahora, nuestra tarea consistirá en realizar la combinación
de ambas partes para dejar primerFichero.txt con el contenido definitivo y sin ningún tipo de
marcas que puedan indicar a git que existen conflictos. Una vez hecho esto y tal como nos
marcaba el git pull, realizaremos el git add y git commit del fichero.

4.7 Trabajar con ramas


Las ramas son una de las principales utilidades que disponemos en cualquier sistema de
control de versiones moderno para llevar un mejor control del código. Se trata de una
bifurcación del estado del código que crea un nuevo camino de cara a la evolución del
código, en paralelo a otras ramas que se puedan generar.

27
UD 1 Sistemas de Control de Versiones

Las ramas nos pueden servir para muchos casos de uso. Por ejemplo, para la creación de
una funcionalidad que queramos integrar en un programa y para la cual no queremos que la
rama principal se vea afectada. Esta función experimental se puede realizar en una rama
independiente, de modo que, aunque tardemos varios días o semanas en terminarla, no
afecte a la producción del código que tenemos en la rama principal y que permanecerá
estable.

El trabajo con ramas resulta muy cómodo en el desarrollo de proyectos, porque es posible
que todas las ramas creadas evolucionen al mismo tiempo, pudiendo el desarrollador pasar
de una rama a otra en cualquier momento según las necesidades del proyecto. Si en un
momento dado el trabajo con una rama nos ha resultado interesante, útil y se encuentra
estable, entonces podemos fusionar ramas para incorporar las modificaciones del proyecto
en su rama principal.

git branch

Es importante comprender que las ramas son solo punteros a las confirmaciones. Cuando
creas una rama, todo lo que Git tiene que hacer es crear un nuevo puntero, no modifica el
repositorio de ninguna otra forma. Si empiezas con un repositorio que tiene este aspecto:

Y, a continuación, creas una rama con el siguiente comando:

git branch crazy-experiment

El historial del repositorio no se modificará. Todo lo que necesitas es un nuevo puntero de la


confirmación actual:

28
UD 1 Sistemas de Control de Versiones

Hay que tener en cuenta que este comando solo crea la nueva rama. Para empezar a
añadir confirmaciones, necesitaremos seleccionarla con el comando git checkout que
ahora veremos y, a continuación, utilizar los comandos estándar git add y git commit.

Creación de ramas remotas

Ahora que ya hemos creado una rama en local, necesitamos saber cómo subirla a nuestro
repositorio remoto para que el resto de usuarios pueda tener acceso. Para ello, bastará con
ejecutar el siguiente comando push:

git push -u origin <nombre_rama_creada>

Por ahora, todas las de ramas locales. El comando git branch también funciona con ramas
remotas. Para trabajar en ramas remotas, primero hay que configurar un repositorio remoto
y añadirlo a la configuración del repositorio local.

Eliminación de ramas

29
UD 1 Sistemas de Control de Versiones

Una vez que hayamos terminado de trabajar en una rama y la hayamos fusionado con el
código base principal, podemos eliminar la rama sin perder ninguna historia ejecutando:

git branch -d <nombre_rama_creada>

No obstante, si la rama no se ha fusionado, el comando anterior mostrará un mensaje de


error:

error: The branch 'crazy-experiment' is not fully merged.


If you are sure you want to delete it, run 'git branch -D crazy-experiment'.

Esto nos protege ante la pérdida de acceso a una línea de desarrollo completa. Si
realmente queremos eliminar la rama (por ejemplo, si se trata de un experimento fallido),
podemos usar el indicador -D (en mayúscula):

git branch -D <nombre_rama_creada>

Este comando elimina la rama independientemente de su estado y sin avisos previos, así
que debemos usarla con cuidado.

Los comandos anteriores eliminarán una copia local de la rama pero seguirá existiendo en
el repositorio remoto. Para eliminar una rama remota, ejecuta estos comandos.

git push origin --delete <nombre_rama_creada>

git checkout

30
UD 1 Sistemas de Control de Versiones

El comando git checkout nos permite desplazarnos entre las ramas creadas por git branch.
Al ejecutar la operación de cambio de rama, se actualizan los archivos en el directorio de
trabajo para reflejar la versión almacenada en esa rama y se indica a Git que registre todas
las confirmaciones nuevas en esa rama. Podemos pensar en ello como una forma de
seleccionar en qué línea de desarrollo vamos a trabajar.

En ocasiones, el comando git checkout puede confundirse con git clone. La diferencia entre
ambos comandos estriba en que el segundo extrae código de un repositorio remoto,
mientras que el primero cambia entre versiones de código que ya se encuentran en el
sistema local.

El comando git checkout va de la mano de git branch que ya hemos visto. Una vez creada
una rama con git branch, podemos usar git checkout <nombre rama> para cambiar a esa
rama.

El comando git checkout acepta el argumento -b, que actúa como un práctico método que
creará la nueva rama y cambiará a ella al instante.

De manera predeterminada, git checkout -b basará la rama new-branch en el HEAD


actual. No obstante, git checkout puede combinarse con un parámetro opcional para
ramas adicionales. En el siguiente ejemplo, se añade <existing-branch>, que basa
new-branch en existing-branch en lugar del HEAD actual.

git checkout -b <nueva_rama> <rama_existente>

31
UD 1 Sistemas de Control de Versiones

git merge

La fusión es la forma que tiene Git de volver a unir un historial bifurcado. El comando git
merge permite tomar las líneas independientes de desarrollo creadas por git branch e
integrarlas en una sola rama.

Supongamos que tenemos una rama de funcionalidad nueva que se basa en la rama
master. Ahora, queremos fusionar esa rama de funcionalidad con la master.

Al invocar el comando git merge feature_tip , la rama de funcionalidad especificada


se fusionará con la rama actual, la cual asumimos que es la master. Git determinará el
algoritmo de fusión automáticamente.

Antes de ejecutar una fusión, hay un par de pasos de preparación que llevar a cabo con el
fin de garantizar que la fusión se realice sin problemas.

32
UD 1 Sistemas de Control de Versiones

● Asegurarnos que estamos en la rama receptora. Ejecutando git status podemos


comprobar si el HEAD apunta a la rama de fusión-recepción correcta. En caso
necesario, ejecutaremos git checkout <rama_deseada> para cambiar a la
rama de recepción.
● Recuperar las últimas confirmaciones remotas. Debemos asegurarnos que la rama
de recepción y la rama de fusión están actualizadas con los últimos cambios
remotos. Para ello, como hemos visto, podemos ejecutar git fetch + git
merge o git pull.

Una vez adoptados los pasos comentados anteriormente de "preparación para la fusión", es
posible iniciar una fusión mediante la ejecución de git merge <branch name> donde
<branch name> es el nombre de la rama que se fusionará con la rama de recepción.

Si las dos ramas que tratas de fusionar han cambiado la misma parte del mismo archivo, Git
no podrá averiguar qué versión utilizar. Cuando esto ocurre, Git se detiene justo antes de la
confirmación de fusión para que podamos resolver los conflictos manualmente.

La mayor parte del proceso de fusión de Git consiste en utilizar el conocido flujo de trabajo
de edición, preparación y confirmación para resolver los conflictos de fusión. Cuando se
observa un conflicto de fusión, la ejecución del comando git status muestra qué archivos se
deben resolver.

33
UD 1 Sistemas de Control de Versiones

4.8 Etiquetado
Como muchos SCV, 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).

Listar etiquetas

Listar las etiquetas disponibles en Git es sencillo. Simplemente escribimos git tag:

Este comando lista las etiquetas en orden alfabético.

También podemos buscar etiquetas con un patrón particular. Imaginemos que nuestro
repositorio contiene multitud de etiquetas. Si sólo nos interesa ver las de la versión 1.,
podemos ejecutar:

git tag -l 'v1.*'

Crear etiquetas

Git utiliza dos tipos principales de etiquetas:.

● Una etiqueta ligera es muy parecida a una rama que no cambia, simplemente es un
puntero a un commit específico.

34
UD 1 Sistemas de Control de Versiones

Para crear una etiqueta ligera, no tenemos que pasar las opciones -a, -s ni -m:

git tag v1.4

● Las etiquetas anotadas se guardan en la base de datos de Git como objetos


enteros. Tienen un checksum; contienen el nombre del etiquetador, correo
electrónico y fecha; tienen un mensaje asociado; y pueden ser firmadas y verificadas
con GNU Privacy Guard (GPG). Normalmente se recomienda crear etiquetas
anotadas, de manera que tengamos toda la información.

git tag -a v1.4 -m “Tag de la versión 1.4”

La opción -m especifica el mensaje de la etiqueta, el cual es guardado junto con ella.


Si no especificamos el mensaje de una etiqueta anotada, Git abrirá el editor de texto
para que lo escribamos.

Si una etiqueta coincide con alguno de los commits de nuestro repositorio, podemos verlo
en la salida del comando git log.

Eliminar etiquetas

De igual forma que podemos crear nuevas etiquetas, también podemos eliminarlas. Para
ello, simplemente debemos ejecutar la instrucción:

git tag -d nombre_tag

35
UD 1 Sistemas de Control de Versiones

La anterior instrucción eliminará nuestra etiqueta localmente. Si queremos que el borrado


tambíen se ejecute en el servidor remoto deberemos ejecutar:

git push origin :refs/tags/nombre_tag

Etiquetado tardío

También podemos etiquetar commits mucho tiempo después de haberlos hecho.


Supongamos que nuestro historial luce como el siguiente:

Ahora, supongamos que olvidamos etiquetar el proyecto en su versión v1.2, la cual


corresponde al commit “Segunda modificación”. Para etiquetar un commit pasado, debemos
especificar el checksum del commit (o parte de él) al final del comando:

git tag -a v1.2 -m “Etiquetando versión 1.2” eb9462aaef784a

36
UD 1 Sistemas de Control de Versiones

Si tras ejecutar la anterior instrucción, volvemos a consultar el log:

Compartir etiquetas

Por defecto, el comando git push no transfiere las etiquetas a los servidores remotos.
Debemos enviar las etiquetas de forma explícita al servidor tras haberlas creado. Este
proceso es similar al de compartir ramas remotas - puedes ejecutar git push origin
[etiqueta].

Si queremos enviar varias etiquetas a la vez, podemos usar la opción --tags del comando git
push. Esto enviará al servidor remoto todas las etiquetas que aún no existen en él.

Obtener la versión del repositorio etiquetada

En Git, no puedes obtener (checkout) directamente el código del repositorio asociado a una
etiqueta (no es algo que podamos mover). Si queremos colocar en nuestro directorio de
trabajo una versión de tu repositorio que coincida con alguna etiqueta, debemos crear una
rama nueva en esa etiqueta:

37
UD 1 Sistemas de Control de Versiones

git checkout -b <nombre_nueva_rama> nombre_tag

4.9 Hooks
Igual que ocurre en otros sistemas de versiones, Git dispone de mecanismos que le
permiten ejecutar scripts cuando se producen ciertos eventos. A estos se les suele
denominar hooks (ganchos) y existen de dos tipos: de cliente y de servidor. Mientras los
primeros se ejecutan al producirse operaciones en la parte de nuestro servidor local (por
ejemplo, commit o merge), los ganchos de servidor se ejecutan al producirse cambios en el
servidor remoto (push principalmente). En esta unidad únicamente veremos cómo crear
hooks locales.

Crear un hook local

Los hooks residen en el directorio .git/hooks/ de cada repositorio de Git. Git crea
automáticamente este directorio con scripts de ejemplo cuando inicializa un repositorio. Si
echamos un vistazo dentro de .git/hooks, encontraremos los siguientes archivos:

Estos representan la mayoría de los hooks disponibles, pero la extensión .sample evita que
se ejecuten de forma predeterminada. Para "instalar" uno, todo lo que tenemos que hacer
es eliminar la extensión .sample. O, si está escribiendo una nueva secuencia de comandos
desde cero, podemos agregar un nuevo archivo que coincida con uno de los nombres de
archivo anteriores, menos la extensión .sample.

38
UD 1 Sistemas de Control de Versiones

Es importante tener en cuenta que los hooks del lado del cliente no se copian cuando se
clona un repositorio

Como ejemplo, veamos cómo crear un prepare-commit-msg hook. Para ello:


● Realizamos una copia del fichero original nombrandolo sin el .sample para no perder
el contenido

● Editamos el contenido del script. Como vemos en la imagen, el script de ejemplo ya


dispone de comentarios y código que nos pueden orientar a la hora de modificar el
contenido.

39
UD 1 Sistemas de Control de Versiones

● Modificamos el contenido con lo siguiente:

● Los hooks deben ser ejecutables, por lo que es posible que necesitemos cambiar los
permisos de archivo del script en caso es que lo hayamos creado desde cero. Por
ejemplo, para asegurarnos de que prepare-commit-msg sea ejecutable,
ejecutaremos el siguiente comando:

● Si ahora intentamos realizar un nuevo commit en nuestro repositorio sin añadir un


comentario debería mostrarse el mensaje que hemos puesto en nuestro script

40
UD 1 Sistemas de Control de Versiones

Las secuencias de comandos integradas son en su mayoría secuencias de comandos de


shell y PERL, pero podemos utilizar cualquier lenguaje de secuencias de comandos que
deseemos siempre que se pueda ejecutar como un ejecutable. La línea shebang (#! /bin/sh)
en cada script define cómo debe interpretarse nuestro archivo. Por tanto, para usar un
lenguaje diferente, todo lo que necesitamos es cambiarla a la ruta de su intérprete en
nuestro sistema.

Por ejemplo, podemos escribir un script Python ejecutable en el archivo


prepare-commit-msg en lugar de usar comandos de shell. El siguiente gancho hará lo
mismo que el script de shell de la sección anterior.

Tipos de hook locales

En este apartado vamos a repasar los principales hooks locales de git.

pre-commit

41
UD 1 Sistemas de Control de Versiones

Se ejecuta cada vez que realizamos un commit en git antes de que Git nos pida el mensaje
de confirmación o genere un objeto de confirmación. Podemos utilizar este hook para
inspeccionar el snapshot que está a punto de confirmarse. Por ejemplo, es posible que
queramos ejecutar algunas pruebas automatizadas que se aseguren de que la confirmación
no rompa ninguna funcionalidad existente.

No se pasa ningún argumento al pre-commit script, y en caso que retornemos un


valor distinto de cero, anulamos el commit.

prepare-commit-msg

Este hook (su script asociado) se ejecuta después del anterior hook y, como hemos visto en
el ejemplo inicial, permite mostrarnos el mensaje de commit a añadir a nuestros cambios.
Podemos utilizarlo para modificar estos mensajes a nuestro gusto.

A este hook se le pasan 3 parámetros:

● El nombre del fichero que contiene el fichero temporal con el mensaje


● El tipo de commit:
○ - m si es un mensaje normal
○ -t si es un template
○ merge si el mensaje se hereda fruto de un merge
○ squash si estamos haciendo un commit sobre otro
● El hash SHA1 del commit. Sólo se establece si en la instrucción de commit se añade
la opción -c

Igual que pasaba con el anterior hook, si devolvemos un valor distinto de cero, asumimos
que se ha producido una condición que anulará el commit.

En el siguiente ejemplo, podemos ver un script que se encarga de modificar el mensaje de


commit de manera que, si detecta que estamos haciendo un commit sobre la rama de
issues, le añade el número de issue que se está resolviendo en el mensaje.

42
UD 1 Sistemas de Control de Versiones

Hay que tener en cuenta que este hook, en caso de estar activado, nos propondrá el
mensaje de commit configurado aunque nosotros hayamos establecido uno con el
parámetro -m. Eso sí, siempre podremos modificarlo a nuestro gusto antes de realizar el
commit.

commit-msg

Este hook es muy similar al anterior pero se ejecuta tras haber introducido el mensaje de
commit. Es el sitio ideal donde podemos verificar si el mensaje introducido sigue el patrón
deseado que hayamos marcado como válido.

El único argumento que se le pasa a este hook es el nombre del fichero que contiene el
mensaje y, como con el resto, retornando un valor distinto de cero podremos anular el
commit.

Un posible ejemplo de commit-msg hook sería el siguiente en el que, de forma parecida a


como hemos hecho en el anterior hook, verificamos que el mensaje contenga el número de
issue si el cambio a añadir en el repositorio pertenece a la rama de issues.

43
UD 1 Sistemas de Control de Versiones

post-commit

Este hook se ejecuta tras el hook anterior. No puede anular el mensaje y, por este motivo,
suele utilizarse para realizar notificaciones (por ejemplo, para enviar un email a las personas
implicadas en el proyecto). Este hook no recibe ningún parámetro.

post-checkout

Este hook se ejecutará tras realizar un git checkout sobre nuestro proyecto. Podría ser útil,
por ejemplo, si queremos limpiar nuestro proyecto y realizar una reinstalación tras la acción
de checkout.

Este hook recibe 3 parámetros:


● La referencia previa al HEAD
● La nueva referencia al HEAD
● Un flag indicando si el checkout es de una rama (1) o de un fichero concreto (0)

pre-rebase

Este hook se ejecutará antes de realizar una operación de rebase. Es ideal si queremos
verificar antes que todo va a seguir funcionando antes de realizarlo.

En este caso se reciben 2 parámetros: la rama ascendente desde la que se bifurcó y la


rama que se está rebasando. El segundo parámetro será vacío si estamos haciendo un
rebase sobre la misma rama. Para abortar el rebase, salga con un estado distinto de cero.

Hooks en proyectos colaborativos

44
UD 1 Sistemas de Control de Versiones

Mantener hooks para un equipo de desarrolladores puede ser un poco complicado porque el
directorio .git/hooks se clonará con el resto de nuestro proyecto al ser una carpeta que no
está bajo control de versiones. Una solución simple es almacenar nuestros hooks en el
directorio del proyecto real (arriba del directorio .git). Esto nos permitirá editarlos como
cualquier otro archivo controlado por versión. Para instalar el hook, podemos crear un
enlace simbólico a él en .git/hooks, o simplemente lo copiaremos pegaremos en el directorio
.git/hooks siempre que se actualice algún hook.

Desde la versión 2.9 de GIT también podemos guardar nuestros hooks en otro directorio
distinto de .git/hooks (para poder subirlo al repositorio) y mediante la opción de
configuración siguiente podemos especificar a GIT la nueva ubicación de estos.

git config core.hooksPath hooks

4.10 Pull Request


Los pull requests son la forma de contribuir a un proyecto grupal o de código abierto.
Por ejemplo, un usuario llamado Juan realiza un fork de un repositorio de Luis y le efectúa
algunos cambios. Tras estos cambios, Juan puede hacer un pull request a Luis, pero
dependerá de Luis aceptar o declinar dichos cambios. Es como decir: "Luis, ¿podrías por
favor extraer (pull) mis cambios?"

Pasos para realizar correctamente una pull request

● Realizamos un fork del repositorio haciendo un clic en el botón fork de la parte


superior de la página. Esto creará una instancia del repositorio completo en nuestra
cuenta.

● Una vez que el repositorio esté en nuestra cuenta, lo clonamos para trabajar
localmente.
● Creamos una rama. Es una buena práctica crear una rama nueva cuando
trabajamos con repositorios, ya sea que se trate de un proyecto pequeño o estemos
contribuyendo en un equipo de trabajo.

45
UD 1 Sistemas de Control de Versiones

El nombre de la rama debe ser breve y debe reflejar el trabajo que estamos
haciendo.
● Realizamos los cambios oportunos en el código de la rama que hemos creado y
hacemos add, commit i push para enviarlos, primero a nuestro repositorio local (add
y commit ) y a la rama del repositorio remoto (push). Algunos proyectos de código
abierto especifican claramente el formato de mensaje de commit que esperan.
● Creamos una pull request. Vamos a nuestro repositorio gitHub y pulsamos en la
opción Pull Request. A continuación, detallaremos todos los cambios realizados y
especificaremos claramente desde qué rama de nuestro proyecto queremos aportar
los cambios y a qué rama del proyecto origen queremos hacerlo.

Antes de enviar cualquier pull request al repositorio original debemos sincronizar nuestro
repositorio local con el remoto de origen ya que pueden haberse agregado algunas
prestaciones o funciones adicionales y haberse corregido algunos errores desde la vez
que realizamos un fork de aquel repositorio. Para ello:

● Cambiaremos a la rama master de nuestro repositorio


git checkout master
● Agregaremos el repositorio original como un repositorio upstream
git remote add upstream [HTTPS]
● Obtendremos los cambios del repositorio original y los fusionamos a nuestra rama
master
git fetch upstream
git merge upstream/master

46
UD 1 Sistemas de Control de Versiones

● En este punto nuestra rama local está sincronizada con la rama maestra del
repositorio original. Si deseamos actualizar el repositorio de GitHub, necesitamos
enviar nuestros cambios.
git push origin master

5. GIT WORKFLOWS
Cuando trabajamos en proyectos relativamente grandes formados por diversos
desarrolladores, es interesante disponer de una metodología o forma de trabajar en lo que
al sistema de control de versiones se refiere. A continuación listamos algunos ejemplos de
cómo proceder cuando estamos utilizando Git en un proyecto y debemos trabajar en equipo
y de forma organizada para evitar cualquier posible error.

5.1 GitFlow
Gitflow es un diseño de flujo de trabajo de Git que fue publicado por primera vez y
popularizado por Vincent Driessen en nvie que define un modelo estricto de ramificación
diseñado alrededor de la publicación del proyecto. Es ideal para los proyectos que tienen un
ciclo de publicación programado. Este flujo de trabajo no añade ningún concepto ni
comando nuevos más allá de lo que se necesita para el flujo de trabajo de rama de función.
En su lugar, asigna funciones muy específicas a las diferentes ramas y define cómo y
cuándo deben interactuar.

47
UD 1 Sistemas de Control de Versiones

Funcionamiento
En vez de una única rama maestra, este flujo de trabajo utiliza dos tipos de ramas para
registrar el historial del proyecto:

Las ramas principales

En este esquema hay dos ramas principales con un tiempo de vida indefinido:

● master (origin/master): el código apuntado por HEAD siempre contiene un estado


listo para producción.
● develop (origin/develop): el código apuntado por HEAD siempre contiene los últimos
cambios desarrollados para la próxima versión del software. Cuando el código de la
rama de desarrollo es lo suficientemente estable, se integra con la rama master y
una nueva versión es lanzada.

Las ramas de función

Para labores concretas, pueden usarse otro tipo de ramas, las cuales tienen un tiempo de
vida definido. Es decir, cuando ya no son necesarias se eliminan:

● Ramas de funcionalidad (feature branches). Cada nueva característica debe residir


en su propia rama, la cual puede ser guardada en el repositorio remoto para
respaldo o colaboración. Pero, en vez de ramificar desde master, las ramas feature
usan develop como su rama padre. Cuando una característica esté completa,

48
UD 1 Sistemas de Control de Versiones

debe ser fusionada con develop. Las características nunca deberían ir


directamente a la rama master.

● Ramas de versión (release branches). Una vez la rama de desarrollo haya


adquirido suficientes características para un lanzamiento (o una fecha de
lanzamiento está cerca), se crea una rama de versión desde la rama develop. Al
crear la rama se inicia un nuevo ciclo de lanzamiento, lo cual significa que en esta
rama no deberían agregarse nuevas características sino a la rama develop, sin
embargo, si se encuentran errores en este punto, estos deberían ser corregidos y
actualizados en la rama de lanzamiento tanto como en la rama de desarrollo.
● Ramas de parches (hotfix branches). Se utilizan para reparar rápidamente las
publicaciones de producción. Es la única rama que debería bifurcarse
directamente a partir de la maestra. Una vez que la solución esté completa,
debería fusionarse en la maestra y la de desarrollo (o la rama de publicación actual),
y la maestra debería etiquetarse con un número de versión actualizado.

49
UD 1 Sistemas de Control de Versiones

Rama de función Parte de Se fusiona con Convención de nombres

De funcionalidad develop develop feature/nombreFeature


(feature)

De versión (release) develop develop y master release-*

De parche (hotfix) master develop y master hotfix-*

Extensión flow de Git (Git-Flow)


Git-Flow es un conjunto de extensiones para Git que proveen comandos de alto nivel para
operar repositorios basados en el modelo de ramificaciones de Vincent Driessen.

Instalación y configuración

Para poder empezar a trabajar con la extensión de Git Git-Flow, será necesario instalar
ésta. Para ello, simplemente tenemos que ejecutar la siguiente instrucción:

apt-get install git-flow

Una vez instalado, si nos encontramos en un proyecto nuevo en el que queremos utilizar
git-flow, ejecutaremos:

git-flow-init
No branches exist yet. Base branches must be created now.

50
UD 1 Sistemas de Control de Versiones

Branch name for production releases: [master]


Branch name for "next release" development: [develop]

How to name your supporting branch prefixes?


Feature branches? [feature/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? [] v

Al ejecutar el último comando, entramos en un menú interactivo que nos va preguntando las
opciones que aparecen arriba:

6. Nos pregunta el nombre de la rama de producción y de la rama de desarrollo


(master y develop respectivamente). Seleccionamos los nombres por defecto.
7. Después nos preguntará los prefijos que asignará a las ramas de tipo feature,
release, hotfix y support. Volvemos a seleccionar los nombres por defecto.
8. Por último, nos pregunta el prefijo que queramos utilizar para etiquetar las versiones.

A partir de aquí, se nos habrán creado las dos ramas principales (master y develop) en
nuestro repositorio y podremos implementar nuevas releases, features y hotfix con unos
simples comandos sin necesidad de preocuparnos por la creación y merge de las ramas
necesarias para ello.

Principales instrucciones de git-flow

Estas son las equivalencias entre comandos de git-flow y su alternativa utilizando


simplemente git:

gitflow git

INICIALIZACIÓN

● git flow init ● git init


● git commit --allow-empty -m
"Initial commit"
● git checkout -b develop master

FEATURES

● git flow feature start ● git checkout -b feature/MYFEATURE


MYFEATURE develop

51
UD 1 Sistemas de Control de Versiones

● git flow feature finish ● git checkout develop


MYFEATURE ● git merge --no-ff
feature/MYFEATURE
● git branch -d feature/MYFEATURE

● git flow feature publish ● git checkout feature/MYFEATURE


MYFEATURE ● git push origin feature/MYFEATURE

● git flow feature pull origin ● git checkout feature/MYFEATURE


MYFEATURE ● git pull --rebase origin
feature/MYFEATURE

RELEASES

● git flow release start 1.2.0 ● git checkout -b release/1.2.0


develop

● git flow release finish 1.2.0 ● git checkout master


● git merge --no-ff release/1.2.0
● git tag -a 1.2.0
● git checkout develop
● git merge --no-ff release/1.2.0
● git branch -d release/1.2.0

● git flow release publish 1.2.0 ● git checkout release/1.2.0


● git push origin release/1.2.0

HOTFIX

● git flow hotfix start 1.2.1 ● git checkout -b hotfix/1.2.1

● git flow hotfix finish 1.2.1 ● git checkout master


● git merge --no-ff hotfix/1.2.1
● git tag -a 1.2.1
● git checkout develop
● git merge --no-ff hotfix/1.2.1
● git branch -d hotfix/1.2.1

● git flow hotfix publish 1.2.1 ● git checkout hotfix/1.2.1


● git push origin hotfix/1.2.1

52
UD 1 Sistemas de Control de Versiones

5.2 GitHub Flow

La diferencia principal con GitFlow es la desaparición de la rama develop. Se basa en los


siguientes principios:

● Todo lo que haya en la rama master debe ser desplegado.


● Para cualquier característica nueva, crearemos una rama de master, usando un
nombre descriptivo.
● Debemos hacer commit en esta rama en local y hacer push con el mismo nombre en
el server.
● Si necesitamos feedback, utilizaremos las herramientas de mergeo como pull
request.
● Una vez revisado el código, podemos mergear contra master.
● Una vez mergeado contra master, debemos desplegar los cambios.

Ventajas
● Útil y amigable con las actuales herramientas de CI/CC.
● Recomendado para features de duración corta (diarias o incluso de horas).
● Flujo ligero y recomendado si el proyecto requiere de una entrega de valor
constante.

Inconvenientes
● Inestabilidad de master si no se utilizan las herramientas de testing/PR
correctamente.

53
UD 1 Sistemas de Control de Versiones

● No recomendado para múltiples entornos productivos.


● Dependiendo el producto, podemos tener restricciones de despliegues, sobre todo
en aplicaciones SaaS.

5.3 GitLab Flow

GitLab Flow es una alternativa/extensión de GitHub Flow y Git Flow, que nace debido a las
carencias que adoptan estos dos flujos. Mientras que una de las consignas de GitHub es
que todo lo que haya en master es desplegado, hay ciertos casos en los que no es posible
cumplirlo o no se necesita. Por ejemplo: aplicaciones iOS cuando pasan a la App Store
Validation o incluso tener ventanas de despliegue por la naturaleza del cliente.

El flujo propone utilizar master, ramas features y ramas de entorno. Una vez que una feature
está finalizada hacemos una merge request contra master. Una vez que master tiene varias
features, llevamos a cabo una merge request a preproducción con el conjunto de features
anteriores, que a su vez, son candidatas de pasar a producción haciendo otro merge
request. De esta manera conseguimos que el código subido a producción sea muy estable,
ya que validamos featues tanto a nivel individual como en lote.

La naturaleza de este flujo no requiere generar ramas de releases, ya que cada entorno
será desplegado con cada merge request aceptada.

Ventajas

● Mucha confianza en la versión de producción.


● Ciclo de desarrollo muy seguro. Se revisa el código tanto a nivel individual en la
feature, como a nivel global al pasar a preproducción o producción, mitigando el
impacto.

54
UD 1 Sistemas de Control de Versiones

● Previene el “overheap” de crear releases, taggings, merge a develop.

Inconvenientes
● Requiere de un equipo que valide las MR tanto de las features, como de los
diferentes entornos.
● Tiempo demasiado alto para la entrega de valor. Desde que se crea y se valida la
feature, hasta que llega a producción, tiene que pasar por muchas validaciones.
● Más complejo que GitHub Flow.

5.4 Trunk-based Flow

Este flujo es muy similar a GitHub Flow, con la característica nueva de las releases branch y
el cambio de filosofía que presenta. Los principios que rigen este flujo son los siguientes:

● Los desarrolladores debemos colaborar siempre sobre el trunk (o master).


● Bajo ningún concepto debemos crear features branch utilizando documentación
técnica. En caso de que la evolución sea compleja, haremos uso de features flags
(condicionales en el código) para activar o desactivar la nueva característica.
● Preferiblemente usaremos la metodología Pair-Programming en vez de hacer uso
de PR.

Ventajas

● Muy útil si nuestro proyecto necesita iterar rápido y entregar valor lo antes posible.
● Responde muy bien a proyectos agile pequeños.
● Preparado para equipos que utilizan pair-programming.
● Funciona muy bien con un equipo experimentado y cerrado.

Inconvenientes
● Debemos ser responsables de nuestro código y hacer el esfuerzo de subir código de
calidad.

55
UD 1 Sistemas de Control de Versiones

● El proyecto debe tener QA y CD muy maduro, de lo contrario se introducirán muchos


bugs en la rama trunk.
● No es recomendable utilizarlo en proyectos open-source, ya que requieren de una
verificación extra de código.

5.5 Master-only Flow

El flujo de trabajo usa solo una rama infinita. Usaremos master en esta descripción, ya
que probablemente sea el nombre más común y ya es una convención de Git, pero también
puedes usar, por ejemplo, current, default, mainline, ...

Realizaremos cada feature o hotfix sobre la misma rama, testearemos y haremos commit
en local. Una vez que este cambio se ha aprobado, haremos push contra master en origin,
desplegándose en producción inmediatamente.

Este flujo está orientado a proyectos con desarrolladores muy experimentados y en el


cual se fomente el pair-programming.

Debemos usar features-flags para poder integrar el código en la rama master y evitar
conflictos a lo largo del tiempo.

Ventajas

● Solo mantenemos una rama.


● Tendremos el histórico del proyecto limpio.
● Con el uso de pair-programming y desarrolladores experimentados, la entrega de
valor en producción es inmediata.

Inconvenientes
● No soporta múltiples entornos productivos.
● Requiere de desarrolladores experimentados para no dejar inestable la rama
principal.
● El proyecto requiere de guidelines de código muy estrictas.

56
UD 1 Sistemas de Control de Versiones

6. GITHUB PAGES
GitHub nos brinda herramientas muy útiles para el trabajo en equipo. Una de esas es Github
Pages, la cual permite alojar sitios web estáticos sin necesidad de tener conocimientos en
servidores. GitHub pages permite dos modalidades de publicación:

● La primera es “User site” (solo se podrá tener un sitio de este tipo por cuenta); en
este caso el sitio web será publicado en username.github.io (siendo username el
nombre de usuario de la cuenta).
● La segunda opción es “Project site” (proyectos ilimitados) el cual será publicado en
username.github.io/repository (siendo repository el nombre del repositorio).

En ambos casos, antes de comenzar debemos de tomar en consideración lo siguiente:


● Tener una cuenta de GitHub creada.
● Tener, como no, instalado git en nuestro equipo.

Una vez tengamos nuestro repositorio, el siguiente paso consistirá en subir los ficheros que
vayan a ser utilizados en nuestro site. Estos, pueden ser tanto ficheros html normales como
ficheros en formato .md (pero no combinar ambos tipos para evitar problemas). Además,
dichos ficheros podrán residir en la rama principal (master) o en una rama específica
dedicada únicamente a alojar la documentación o site de nuestro proyecto (suele utilizarse
en este caso una rama denominada gh-pages).

Tras crear nuestro repositorio y disponer en él de todos los archivos del site subidos,
deberemos proceder a habilitar github Pages en nuestro almacén de código. Para ello, nos
dirigiremos a la sección de setting de este y marcaremos el check de la sección de gitHub
pages. Además:

● Seleccionaremos la rama y el directorio donde se encuentran los ficheros del sitio


● En caso de optar por documentar nuestro código en formato .md, podemos aplicar
una plantilla jekyll para dotar de un mejor aspecto final a nuestro site.

57
UD 1 Sistemas de Control de Versiones

7. BUENAS PRÁCTICAS EN GIT


Algunas buenas prácticas que se recomiendan al utilizar Git son:

● Mensajes de commit
Deben ser simples y claros. Es aconsejable tener en cuenta:

○ Separar el asunto del cuerpo del mensaje por una línea en blanco.
○ El asunto no debe superar los 50 caracteres, debe iniciarse con letra
mayúscula y no finalizar con punto. Se debe usar la forma imperativa en su
redacción.
○ El cuerpo del mensaje es aconsejable que tenga líneas con un ancho de no
más de 72 caracteres para facilitar la lectura de este.
○ Utilizar el cuerpo del mensaje para explicar qué se ha resuelto y cómo.
● Añadir solo los archivos con los que hemos trabajado
○ Usar git add . no es la mejor práctica. Lo mejor en estos casos es añadir solo
los archivos en los que hayamos estado trabajando, esto evita que se
añadan muchos archivos “trash” que pueden traer una repercusión grave en
la dimensión del repositorio.
○ En su lugar, es conveniente añadir los ficheros individualmente (git add
documento.txt).

58
UD 1 Sistemas de Control de Versiones

● Evitar commits con códigos incompletos o mezclados.


○ Evitar, en la medida de lo posible, subir al repositorio remoto commits con
cambios de código que supongan la resolución parcial de un desarrollo.
○ Realizar commits por funcionalidad o requerimiento. Esto permite definir un
alcance en los commits. Se debe incluir solo archivos que estén relacionados
con la funcionalidad que estemos desarrollando, esto permite revertir (revert)
confirmaciones en un futuro sin ningún problema.

7. BIBLIOGRAFÍA

Documentación oficial de Git - enlace


Apuntes IOC - enlace
Página oficial de Atlassian - enlace
Git flow cheatsheet - enlace
Git workflows - enlace

59

También podría gustarte