Control de versiones con Git: Guía completa

Enviado por Chuletator online y clasificado en Informática y Telecomunicaciones

Escrito el en español con un tamaño de 82,6 KB

1. Control de versiones

Los sistemas de control de versiones o sistemas de control de código fuente son programas que mantienen el histórico de todos los cambios realizados sobre archivos y carpetas a lo largo del tiempo.

Esto permite:

  • Volver a cualquier punto del pasado en cualquier momento.
  • Comparar un punto del tiempo con otro para conocer los cambios exactos que se han producido entre ambos.
  • Además, almacenan quién es el autor de cada cambio y facilitan mucho el trabajo en paralelo de varias personas en varias características.

Lo que muchas personas hacen cuando quieren conservar un archivo antes de hacerle cambios, si es que hacen algo, es guardar en el propio sistema de archivos diferentes versiones de los ficheros antes de cambiarlos. Esto complica mucho la gestión, no es un sistema estricto, multiplica las necesidades de almacenamiento y, en general, acaba siendo un verdadero caos.

Un VCS acaba con este problema de una manera sistemática y elegante y permite, entre otras cosas:

  • Hacer que cualquier archivo (o el proyecto entero) pueda volver a cualquier punto en el pasado.
  • Saber exactamente qué cambios se han producido en un archivo o en un proyecto entre dos puntos en el tiempo.
  • Trabajar en paralelo sobre los mismos archivos pero en características diferentes, sin temor a crear conflictos continuamente por tocar el mismo código.
  • Ver quién es el responsable de un determinado cambio y cuándo se efectuó.
  • Recuperar cualquier archivo o carpeta que se haya borrado o perdido por error.
  • Almacenar esta información de manera eficiente, ya que los VCS normalmente no guardan todo el contenido de los ficheros, sino solamente las diferencias, por lo que el espacio ocupado es muy pequeño y mucho más eficiente que guardar copias enteras de cada uno de ellos.

El uso de un VCS es indispensable en casi cualquier desarrollo, incluso cuando seamos el único miembro del equipo (es decir, cuando trabajemos solos).

1.1 Desarrollos colectivos

En función de la forma de trabajar y almacenar la información de los archivos, los sistemas de control de versiones se pueden clasificar en tres tipos:

  • Locales: son simples bases de datos locales que guardan el histórico en la propia máquina del desarrollador. Son poco útiles y muy arcaicos, estando totalmente en desuso.
  • Centralizados: llevan el concepto anterior a un nivel más útil, almacenando todos los datos en un servidor central al que todos los desarrolladores se conectan para enviar y recibir cambios.

Esto ayuda a saber en qué punto está todo el mundo en cada momento, pero este tipo de sistemas tienen varias desventajas. Por ejemplo, que debes estar conectado para poder trabajar contra ellos y que existe un único punto de fallo. Si el servidor central falla, nadie puede trabajar y se arriesga a perder los cambios.

Algunos ejemplos de este tipo de VCS son el mítico CVS o Apache Subversion, también conocido como SVN, uno de los más utilizados.

  • Distribuidos: los sistemas distribuidos (DVCS) trabajan contra uno o más nodos que tienen la información replicada, y cada uno de los desarrolladores tiene una copia local de todo el repositorio.

Esto, que a priori puede parecer un problema, posee múltiples ventajas, por ejemplo:

  1. Que los desarrolladores pueden trabajar sin conexión.
  2. Que pueden replicar la información a más de un nodo para ser almacenada (en contraste con un único sistema central), como todos tienen copia de todo es casi imposible que se pierdan los datos incluso si no hay copias de seguridad adecuadas... Estando optimizados además para este tipo de forma de trabajo, son extremadamente eficientes y rápidos.

Los más conocidos de este tipo son Mercurial y, por supuesto, Git.

La tendencia actual es a abandonar el uso de los VCS centralizados y utilizar VCS distribuidos. Y el que sin duda se ha impuesto es Git.

Git, es un VCS distribuido que sirve como herramienta para la gestión de proyectos de desarrollo de diferentes tamaños.

Los principales atributos de Git son:

  • Velocidad.
  • Diseño sencillo.
  • Desarrollo paralelo (a través de ramas).
  • Completamente distribuido.
  • Capacidad de gestionar proyectos grandes de manera eficiente.

Actualmente es, sin duda, el sistema de control de versiones más popular del mundo.

Existe un gran ecosistema montado a su alrededor, empresas como GitHub o GitLab, permiten la creación de repositorios remotos de una forma muy sencilla, o Heroku, una plataforma de despliegue de aplicaciones que se sirve de Git como base para subir los diferentes proyectos de una forma fácil y rápida (algo que hoy en día hacen multitud de herramientas en la nube).

1.2 Herramientas de control de versiones: Git. Utilidad. Características. Estructura

1.2.1 Utilidades

Desde un punto de vista externo Git puede parecer igual a cualquier otro VCS como Subversion o Perforce, pero modela y gestiona la información de manera muy diferente.

a) Gestión de archivos

La principal diferencia entre Git y el resto de VCS se encuentra en cómo modelan sus datos.

Por lo general, el funcionamiento de un VCS se encuentra en un proyecto base con el que se inicia el repositorio y sobre él se realizan "x" cambios.

La herramienta registra esos cambios, en vez de generar un archivo nuevo, como si fueran pasos en el ciclo de vida de un archivo. Esto, por supuesto, era una idea muy buena y funcionaba a la perfección, pero hacía que proyectos grandes fueran degradándose poco a poco con el paso del tiempo.

Git, por el contrario, utiliza una filosofía absolutamente distinta. Cuando registras un cambio, Git lo que hace es generar una instantánea del proyecto, guardando sólo aquellos archivos que han sido modificados en ese momento en concreto.

Lo que hace que Git parezca más un minisistema de archivos ultrasimplificado, con algunas herramientas adicionales que le dotan de un poder y una velocidad tremenda.

b) Distribuido vs Centralizado

Como ya hemos visto, en el mundo de las herramientas del control de versiones existen dos grandes tipos:

    • DVCS: sistema de control de versiones distribuido.
    • CVCS: sistema de control de versiones centralizado.

La principal diferencia entre estos dos modelos radica en dónde se encuentra el repositorio, es decir, el archivo de versiones de tu código:

  • En sistemas centralizados (CVCS), el repositorio se encuentra en algún servidor con acceso a la red.
  • En DVCS el repositorio lo tienes tú, en tu disco local, sin necesidad de requisitos de red.

¿Qué quiere decir esto? Pues es en este simple detalle donde está la tremenda potencia y velocidad de Git respecto a otros famosos CVS centralizados como Subversion o CVS.

Por ejemplo, si quisieras navegar por el histórico del proyecto en los sistemas centralizados tradicionales tendrías que acceder al servidor para ello. En el caso de Git ya lo tienes todo en tu sistema local, con lo que el acceso es casi instantáneo.

Otro problema bastante común es que con Subversion o CVS (sistemas centralizados), si no estás conectado a Internet apenas puedes trabajar, mientras que con Git puedes gestionar un desarrollo completo sin Internet (aunque en algún momento deberás conectarte para persistir tus cambios en algún sitio, aparte de tu propia máquina).

¿Quiere decir esto que Git trabaja sin un servidor? No, pero podría.

Generalmente tendrás uno, o más, sistemas remotos que albergan de manera centralizada tus repositorios y con los que te sincronizarás para hacer copia de seguridad y para colaborar con otros desarrolladores. Pero esos repositorios remotos tienen una copia idéntica a la tuya de los archivos y su histórico, por lo que no dependes de ellos.

c) Integridad del repositorio

El repositorio, sea DVCS o CVCS, guarda información vital para una empresa, de incalculable valor. Esto quiere decir que la integridad del repositorio debe ser exquisita.

En el caso de Git, absolutamente todo lo que afecte a su integridad es validado con una suma de comprobación (checksum, usando el algoritmo de hash SHA-1) y el archivo es identificado en el repositorio mediante dicha suma.

Esta funcionalidad está integrada en Git al más bajo nivel: no puedes perder nada de información durante su transmisión o corrupción de archivos sin que Git lo detecte.

1.3 Principios y funcionamiento de Git

Esta es una de las cuestiones más importantes de Git, ya que es el primer flujo de trabajo que tendremos que aprender para manejarlo.

1.3.1 Estados principales

Git tiene tres estados principales en el proceso de trabajo común:

a) Staged:

Preparado. Es decir, has marcado un archivo para que vaya en tu próxima confirmación al hacer un commit.

b) Commited:

Confirmado. Significa que los datos están guardados de forma segura en la base de datos del repositorio. Para que los cambios que has hecho pasen a este estado debes hacer un commit.

c) Modified:

Modificado. Esto es, los datos han sufrido modificaciones pero aún no se han guardado. Un archivo en el que hayas cambiado algo está en este estado.

1.3.2 Entornos principales

Estos tres se corresponden con las tres secciones principales de un proyecto de Git:

a) Git directory:

Donde se almacenan los metadatos y la base de datos del proyecto.

Este directorio es el núcleo del repositorio y lo que se copia realmente cuando clonas un proyecto.

Dentro de una carpeta de código gestionada por Git existe una carpeta llamada .git que es la que contiene toda la información del repositorio, la base de datos y, es este "directorio de Git" del que hablamos.

b) Working directory o directorio de trabajo:

Es la copia de los archivos de una versión del proyecto y se sacan de la base de datos del directorio de Git.

Es la carpeta en la que estás trabajando, que en un momento dado tiene una determinada versión de los archivos del proyecto.

c) Staging area o área de preparación:

Es un simple archivo dentro de la carpeta de Git que especifica qué es lo que va a ir en tu próxima confirmación o commit.

La información que contiene se refiere a los archivos que están en estado staged y que son los que quieres confirmar (commit).

Si cuando tengas una carpeta gestionada por Git (enseguida veremos cómo crear una) no ves la carpeta .git es porque está oculta. Debes activar la visibilidad de archivos y carpetas ocultos en tu explorador del sistema de archivos.

1.3.3 Tu primer flujo de trabajo

El flujo de trabajo en Git suele ser el siguiente:

  1. En primer lugar realizas una serie de modificaciones en tu directorio de trabajo. O sea, añades, modificas y eliminas archivos. Estos, por el momento, están sin gestionar (untracked).
  2. Cuando vayas a guardar una instantánea del estado actual de los archivos, con Git los añades (git add) al área de preparación (staging). Quedan listos para confirmar y enviar al control de versiones.
  3. Almacenas para siempre la versión actual de los archivos que están en el área de preparación a través de una instantánea en tu repositorio de Git haciendo un commit (confirmándolos).
  4. Posteriormente, puedes realizar más cambios sobre los archivos ya confirmados, por ejemplo modificándolos y pasando al estado modified, en cuyo caso podrás volver a repetir el ciclo de añadir y confirmar o eliminándolos, lo que provoca que desaparezcan del repositorio de trabajo y por lo tanto vuelvan a dejar de estar gestionados (aunque las versiones confirmadas permanecerán en el histórico de Git para siempre).

Si una versión concreta de un archivo está ya en el directorio de Git, se considera confirmada (committed).

Si ha sufrido cambios desde que se obtuvo del repositorio, pero ha sido añadida al área de preparación, está preparada (staged).

Y si ha sufrido cambios desde que se obtuvo del repositorio, pero no se ha preparado, está modificada (modified).

El área de staging es algo que no existe en otros sistemas de gestión de archivos tradicionales y que puede confundir un poco a los que comienzan con Git.

Generalmente harás el staging y el commit en un solo paso, pero disponer de un área como esta te ayuda a dividir muchos cambios en varios commit para gestionarlos mejor, a ir confirmando cambios poco a poco a medida que lo revisas y, como veremos, a gestionar mejor los conflictos en las mezclas, entre otras cosas.

En un sistema centralizado como Subversion seguro que te ha tocado sufrir la corrupción de un repositorio durante la transmisión. Es una pesadilla. Eso con Git no ocurre.

Estos valores del hash que genera Git se encuentran por todo el repositorio y es con lo que identifica cada uno de los archivos.

1.3.4 Seguridad de los datos

Git es tremendamente seguro al trabajar, ya que no elimina información, simplemente la añade.

O dicho de otro modo, es muy difícil conseguir que Git borre algo que no se pueda deshacer, ya que una vez envías tus datos a otro repositorio (haces "Push" como veremos) se registra con el hash y ya nunca se elimina, por lo que siempre podrás volver a cualquier estado.

Esto hace que Git sea muy interesante para probar flujos de trabajo nuevos o probar comandos y/o acciones que puedan ser peligrosas en un principio para el proyecto.

1.4 Instalación de Git

1.4.1 Instalación de Git en Mac

Para instalar Git en Mac yo recomiendo hacerlo a través de un programa llamado Homebrew. Este programa es un gestor de paquetes potentísimo y muy útil para cualquier desarrollador por diferentes razones:

  • Te mantiene en una sola carpeta todas las herramientas que necesites en tu sistema con lo que conservas el SO totalmente limpio.
  • Instalaciones y actualizaciones de los paquetes simples y seguras.
  • Posibilidad de gestionar versiones diferentes de un programa. Muy útil si quieres probar cosas entre PHP 5.6 y PHP 7, por poner un ejemplo.

Una vez Homebrew está instalado lo único que tendríamos que hacer sería ejecutar el siguiente comando:

brew install git

1.4.2 Instalación de Git en Linux

Lo más recomendable para instalar en Linux es usar los propios gestores de paquetes que ya vienen con el sistema operativo.

En el caso de Debian o Ubuntu sería:

apt-get install git

Y en el caso de Fedora:

yum install git-core

1.4.3 Instalación en Windows

https://git-scm.com/download/win

1.5 Trabajo básico con Git

Aprenderemos primero a manejar Git contra un repositorio local y con la línea de comandos. Aunque en el día a día luego lo utilices con algún cliente visual, es muy importante conocer también las instrucciones de línea de comandos pues muchas veces nos ayudarán a salir de problemas.

Para ello, de momento, aprenderemos a crear un repositorio local y trabajar con éste para confirmar cambios y enviarlos.

Un repositorio en Git es un espacio en el que se almacena toda la información sobre tu código y su histórico.

En el repositorio (muy comúnmente llamado simplemente "repo", quédate con esta palabra) se guardan datos precisos sobre los cambios que se han realizado en cada archivo controlado: cuáles han sido, quién los ha realizado, cuándo, etc... Es en donde reside la esencia del control de código.

Existen esencialmente dos tipos de repositorios: locales y remotos.

Tanto los repos locales como los remotos, una vez sincronizados, guardan exactamente la misma información en el caso de Git, que como hemos visto es un VCS de tipo distribuido.

El modo básico de trabajar es el mismo siempre y se ilustra en la siguiente figura:

Este proceso consiste básicamente en:

  • Crear o clonar un repositorio, para tener un área de trabajo donde gestionar cambios. Cuando clonas un repo remoto te traes una copia exacta a tu sistema y es con la que trabajas en local.
  • Añadir archivos y hacer cambios en ellos.
  • Agregar los archivos al área de preparación, como ya hemos visto en la introducción, listos para confirmar los cambios (algo que haremos cuando estemos seguros de que queremos guardar una instantánea de cómo están los archivos).
  • Confirmarlos, es el proceso que genera una "instantánea" (commit) de nuestro código. Es el punto en el tiempo que queremos guardar.
  • Obtener posibles cambios del repositorio o repositorios remotos (puede haber más de uno en realidad). Pasos:
    • Verificar primero (fetch) si otras personas han añadido otras instantáneas con cambios en los archivos, o bien, añadido o quitado archivos.
    • Obtener esos cambios (instantáneas o commits) del histórico (pull).
  • Enviar nuestros cambios al repositorio o repositorios remotos, para que otros programadores puedan obtener nuestros cambios (push).

1.6 Comandos básicos de Git

1.6.1 Creación de un repositorio

Lo primero que debes aprender para usar Git es a crear un repositorio, ya que sin uno no podrás ejecutar el resto de comandos para trabajar.

Para crear un repositorio de Git tendrás que crear una carpeta en el sistema de archivos, y una vez dentro de ella ejecutar la siguiente instrucción desde la línea de comandos de tu sistema operativo (sea Windows, Linux o Mac):

git init

Nota: asegúrate de ejecutar este comando dentro de una carpeta sobre la que tengas derechos de acceso y escritura.

Una vez ejecutemos este comando, Git creará una carpeta llamada .git en la que se encontrará toda la configuración del repositorio.

Dentro de dicha carpeta se crearán diferentes elementos entre los que caben destacar dos archivos:

  • config
  • description

Que guardan la principal configuración modificable por el usuario.

Recuerda que esta carpeta está oculta, por lo que es probable que no la veas. Y por una buena razón: por regla general no deberíamos tocar directamente dentro de ella nunca. Pero es importante que sepas que existe.

1.6.2 Clonación de un repositorio

Ya has aprendido a crear un repositorio desde cero, pero la verdad es que lo que harás normalmente será clonar otros repositorios (propios o de otras personas o empresas).

Un repo local no vale de mucho si no tiene un respaldo en algún lugar remoto o no hacemos copias de seguridad.

Tampoco vale para colaborar con otros desarrolladores.

Por ello, lo más habitual es trabajar contra un repositorio remoto en el que se almacena la información del repositorio de manera centralizada y nos aseguramos de que hay copias de seguridad y podremos colaborar si es necesario.

Para clonar un repositorio remoto existe el comando git-clone que podremos configurar dependiendo de si queremos clonar un repositorio local o un repositorio que esté en la nube. Veamos los fundamentos de dicho comando.

  • Para clonar un repositorio local:
git clone /ruta/al/repositorio/local

Los repositorios remotos normalmente son accesibles a través de una conexión web segura (HTTPS) o a través del protocolo SSH.

  • Para clonar un repositorio remoto se utiliza el comando de la misma manera, pero cambiando la ruta local por un URL remoto:
git clone https://servidor-git.com/empresa/proyecto.git

Esto creará automáticamente, dentro de la carpeta en la que ejecutes el comando, una subcarpeta con el nombre del repositorio (por ejemplo ./proyecto), y copiará dentro de ésta todo el repositorio, lo cual incluye no solo la carpeta .git, sino todos los archivos y carpetas del proyecto.

Nota: una vez más, no te olvides de repasar los derechos que tengas sobre la carpeta en la que ejecutes el comando git-clone. Recuerda que todos los comandos de Git que mencionemos se deben ejecutar sobre la carpeta raíz de nuestro repositorio local, en el que reside toda la información del repositorio.

Si quieres especificar un nombre concreto para la carpeta puedes usar un parámetro adicional tras la dirección del repo remoto:

git clone https://servidor-git.com/empresa/proyecto.git MiProyecto

Que creará el repositorio local dentro de la carpeta ./MiProyecto.

Existen otras muchas opciones para el comando git-clone, pero no vamos a entrar en ellas de momento y generalmente te llegará con estas.

1.6.3 Flujo de trabajo - Registra tus cambios

Ahora nos toca trabajar en él, para lo cual usaremos el siguiente flujo que ya hemos visto anteriormente:

  • En primer lugar realizas "x" modificaciones en tu directorio de trabajo.
  • A continuación añades los archivos al área de preparación.
  • Almacenas los archivos que están en el área de preparación creando una instantánea en tu repositorio de Git haciendo un commit.

1.6.4 Gestionar las modificaciones

A medida que hemos ido trabajando en el código se han hecho modificaciones en los archivos gestionados en el repositorio. La pregunta es: ¿cuáles son estos cambios? Por defecto no hay una forma visual de saberlo en Git, pero podemos utilizar el siguiente comando:

git status

Después de ejecutar git-status Git nos presentará algo como lo siguiente: en la sección de Untracked files aparece el archivo documento_nuevo.txt, el cual aún no se ha añadido a la zona de preparación.

Nota: en el caso de haber también modificaciones de archivos de los cuales ya había histórico indicará Changes not staged for commit.

Algunas opciones interesantes para usar con este comando son:

Opción

Descripción

-s

Muestra un estado simplificado.

-vv

Aparte de mostrar los archivos, muestra las modificaciones que tienen. Es un sustituto del comando git diff --cached, que haría lo mismo.

-uno

Muestra sólo los archivos que están en la zona de preparación.

1.6.5 Preparar los cambios para registrar (git-add)

Para añadir un documento a la zona de preparación (staging), previo a poder confirmar los cambios y crear una versión, debemos usar el comando git-add:

git add nombre_archivo.ext

Si tuviésemos varios nuevos archivos y quisiésemos incorporar sólo algunos de ellos, podemos usar un comodín para lograrlo:

git add /www/*.html

Que añadiría todos los archivos con extensión .html dentro de la carpeta wwww y sus subcarpetas.

Podemos incorporar todos los archivos que se hayan añadido, podemos usar directamente:

git add *

Lo que nos dejaría con la siguiente situación en nuestro pequeño repositorio, que podemos comprobar con git status:

Fíjate que ahora ha cambiado el texto y pone "Changes to be commited", ya que este cambio está preparado para ser registrado, lo tenemos en lo que denominamos zona de preparación. Lo que nos quedaría ahora es simplemente hacer un commit.

Una forma alternativa de obtener un listado de todos los archivos que están en el área de staging es mediante el comando git diff --name-only --cached, por si lo necesitas para un script o similar.

Las diferentes variantes de git-add y una advertencia

Una forma muy habitual de agregar al área de preparación todos los cambios pendientes es utilizar un punto en lugar del asterisco:

git add .

Aparentemente hacen lo mismo. Sin embargo, hay una importante diferencia;

El punto lo interpreta Git y no el sistema operativo (como pasa con el asterisco) y tiene un significado en particular: añadir todos los archivos en la carpeta actual y sus subcarpetas, incluyendo además aquellos archivos cuyo nombre empiece por un punto (que son muchos, algunos propios de Git).

Además, existe una diferencia importante al usar el asterisco en Windows y en Linux/macOS con el comando add.

En Windows, como hemos visto, se añaden los archivos que cumplan con el criterio indicado en la carpeta actual y en todas las subcarpetas. En sistemas basados en UNIX, sin embargo, el asterisco sólo se expande sobre la carpeta actual, por lo que los comandos anteriores con asterisco no incluirían archivos contenidos en subcarpetas de la actual.

Esto es así porque el asterisco no es un comando de Git sino algo perteneciente al shell subyacente (cmd o PowerShell en Windows, y bash o similar en UNIX). Para lograrlo en Linux/macOS basta con poner una barra inclinada delante del asterisco, por ejemplo así: git add wwww/*.html y se añadirán todos los archivos HTML que estén bajo la carpeta wwww y sus subcarpetas.

Por ello suele ser más habitual y más recomendable usar el punto a la hora de añadir los cambios al área de preparación.

No obstante, git add . no tiene en cuenta los archivos eliminados. Es decir, que si teníamos un archivo bajo control de código (con una o más versiones ya almacenadas) y lo eliminamos, este comando no hará nada con él y esa eliminación no se verá reflejada en el repositorio. Hay que tenerlo en cuenta.

Para añadir todos los archivos, incluso los eliminados, al área de staging y poder confirmar los cambios el comando más recomendable es:

git add -A

Que tendrá en cuenta todos los cambios.

Si queremos hacer caso omiso de los archivos nuevos pero pasar a preparación todos los demás, podemos utilizar:

git add -u

Este cuadro resume bien estas variantes y lo que hacen:

Comando

Nuevos

Modificados

Eliminados

git add .

git add -u

git add -A

Deshaciendo un add

Antes de hacer un commit y confirmar los cambios, vamos a ponernos en una situación bastante común: queremos deshacer un cambio que ya hemos mandado al área de confirmación.

Esto por supuesto debe hacerse antes de crear la instantánea (o sea, antes de hacer el commit) ya que una vez que registras un cambio no hay marcha atrás (bueno, siempre hay formas, pero eso ya lo veremos). Veamos cómo se hace:

    • Podemos simplemente quitar el archivo de la zona de preparación y volver a la situación que teníamos antes de añadirlo, para lo cual tenemos dos opciones:
git rm --cached nombre_archivo

Pudiendo usar un asterisco para recuperarlos todos.

    • O bien:
git reset HEAD nombre_archivo

Pudiendo usar también el asterisco para recuperarlos todos.

Lo anterior no nos elimina los cambios. Simplemente quita los archivos de la zona de preparación. Pero podemos, no sólo retirar de la zona de staging los archivos, sino también deshacer completamente los cambios sufridos por un archivo de modo que quede como si nunca hubiera pasado nada, utilizando:

git checkout nombre_archivo

Esto te deja el archivo tal y como lo tenías originalmente, antes de hacer cambio alguno sobre él, es decir, como estaba en el último commit.

Por supuesto puedes usar el asterisco, git checkout * para dejar todo como estaba y no solo un archivo o archivos determinados basados en su nombre.

1.6.6 Registramos los cambios

Vale, con todo lo anterior, pero fundamentalmente con el comando add, ya hemos decidido que hemos terminado de realizar los cambios que queremos registrar en este momento, los tenemos en el área de preparación y deseamos terminar el proceso y confirmarlos. Queremos hacer el famoso commit (¡por fin!). Para ello necesitamos ejecutar el siguiente comando en la carpeta de nuestro repo:

git commit -m "Mensaje del commit"

Con esto registraremos todas las modificaciones actuales de nuestra zona de preparación en el repositorio de forma local, especificando un mensaje que indique de qué se tratan dichos cambios.

Nota: ten en cuenta que Git es un VCS distribuido. Por eso los commit se almacenan en nuestro repositorio local, pero no se almacenan en los repositorios remotos, a disposición de los otros programadores, hasta que se lo indiquemos. Esto nos permite trabajar sin conexión, pero también almacenar ciertos commit solo localmente mientras no estemos listos para enviarlos al remoto. Al estudiar los remotos ya nos meteremos en este proceso. De momento trabajaremos sólo en local para comprender bien todos los conceptos básicos.

Algunas de las otras opciones interesantes que tiene este comando commit son:

Opción

Descripción

--author=<name>

Para cambiar el nombre del autor y poner uno distinto al nuestro por defecto al hacer el commit.

--date

Sobrescribe la fecha de creación.

-a

Esta opción añade todos los archivos de tu zona de trabajo al commit sin usar el comando git-add.

Esta última opción es interesante ya que si quieres directamente hacer un commit de todos los cambios de tu directorio de trabajo actual te puedes ahorrar el paso de git-add escribiendo simplemente:

git commit -a -m "Mensaje descriptivo de los cambios"

1.7 Trabajar con el histórico de Git

Lo último imprescindible que requiere un trabajo diario con Git es, por supuesto, consultar el histórico de cambios. De poco nos serviría tener un control de versiones si no podemos realizar consultas de los cambios que se han hecho.

1.7.1 Visualización del histórico de cambios

Para ver un histórico con todos los commits realizados en el repositorio tenemos el comando git-log:

git log --graph --oneline --decorate --all

Importante: Git identifica cada commit, que es un conjunto de cambios, con un identificador único en forma de hash.

Este identificador es la manera que tenemos para poder referirnos a ellos en caso de necesitarlo.

Por ejemplo, si queremos volver a un punto anterior del histórico o comparar entre sí dos commits, nos referiremos a ellos generalmente a partir de su hash.

El hash es un número muy largo, por regla general llega con utilizar los 3 o 4 primeros caracteres para identificarlo, ya que las probabilidades de colisión son muy pequeñas.

Es por eso que git-log muestra tan solo los 7 primeros caracteres de este identificador.

Ese comando es el que más uso personalmente y es uno de los comandos más útiles y más complejos que tiene Git. Algunas opciones que se pueden añadir a git log son:

Opción

Descripción

--author=

Muestra sólo los commits de dicho autor

--oneline

Comprime cada commit en una sola línea mostrando el hash y el mensaje del commit

--graph

Dibuja el histórico como si fuera un gráfico

--decorate

Muestra las referencias a los diferentes branches, tags, punteros y otros elementos de Git en el histórico

--all

Hace que se muestren todas las referencias del repositorio

-nX

Muestra los últimos X commits

--skip=X

Se salta los últimos X commits

El comando git-log nos proporciona, de forma muy visual (aunque basada en texto), el estado de un repositorio de forma general.

Si tienes muchos commits en el histórico y usas git-log acabarás con un listado inicial que va mostrando nuevos commits cada vez que pulsas ENTER para poder ir viéndolos poco a poco.

Para poder salir de este listado y terminar de verlos debes pulsar la tecla q. Es la única manera de salir y si no lo sabes te puede desesperar.

2.7.2Ver el detalle de un commit en concreto

Es muy común que, como gestores de un equipo de desarrolladores, queramos revisar los cambios que introduce cada commit. Para ello utilizamos git-show:

git show hash_commit>

De esta forma podremos ver qué archivos han cambiado y qué cambios han sufrido. Adicionalmente, al igual que con el comando log, tenemos varias opciones para modificar la presentación:

Opción

Descripción

--pretty=oneline

Muestra el hash y el mensaje del commit en una sola línea

--pretty=fuller

Muestra toda la información de un commit

2.7.3 Alias de comandos

Cuando trabajas a menudo con algunos comandos de Git complejos o con muchas opciones, una manera de ahorrar tiempo y equivocaciones es crear alias para los mismos. Estos alias son comandos "nuevos" que puedes añadir a tu instalación de Git de modo que puedas usarlos como un comando más pero de manera abreviada.

Un candidato habitual para usar alias es precisamente el comando git-log, ya que como ves, tiene bastantes parámetros que solemos usar.

Así, por ejemplo, podemos crear un nuevo alias llamado lg que nos sirva como sustituto de, por ejemplo, log --oneline --decorate --all --graph. Para ello usamos el comando de configuración de Git asi:

git config --global alias.lg "log --oneline --decorate --all --graph"

Fíjate en que el nombre del nuevo alias se pone a continuación del punto en alias. y el comando que quieres sustituir va entre comillas dobles y sin git delante. El modificador --global implica que creas el comando para que funcione en cualquier repositorio, no solo en el actual.

A partir de ahora si quieres sacar este listado sólo tienes que escribir:

git lg

y obtendrás el mismo resultado. ¡Mucho más rápido y fácil de usar!

Nota: en este caso le hemos puesto el nombre lg porque no podemos usar un nombre que ya exista, como log, y además es más corto, pero le puedes dar el nombre que prefieras, más descriptivo. Por ejemplo, como este comando saca también un pequeño gráfico con las ramas que haya, podrías llamarle lgg (de Log gráfico).

1.8Descartando archivos para el control de cambios

2.8.1  .gitignore

Aunque siempre querremos gestionar con Git todos nuestros archivos de código, existen muchos otros archivos que no nos interesará que aparezcan en el repositorio. Es más, habrá archivos que sería un grave error que estuviesen allí.

Por ejemplo, los archivos de preferencias de usuario para el proyecto que generan los diferentes editores de código son personales e intransferibles, y nunca deberían pasar a Git o sobrescribiríamos las preferencias de los demás usuarios con los que colaborásemos. Así, deberíamos excluir los archivos .suo que genera Visual Studio, la carpeta .vscode en el caso de Visual Stdio Code (aunque no siempre) o .idea/workspace.xml en el caso de IntelliJ, por poner ejemplos representativos.

Otros recursos típicos a excluir son:

    • Ejecutables y DLLs resultantes de compilar proyectos, así como archivos .class de Java o .pyc de Python.
    • Las carpetas obj, out, target o similares, con los objetos intermedios usados para la compilación
    • Carpetas con archivos auxiliares que se generan localmente por el programador, como por ejemplo la carpeta node_modules en proyectos Front-End o Node.js o la carpeta packages para paquetes NuGet en la plataforma .NET.
    • Archivos ocultos o temporales del sistema, como los típicos (y horribles) Thumbs.db de Windows para vista previa de iconos, o los horripilantes .DS_Store con los atributos personales de la carpeta.
    • Archivos de log o similares, generados en tiempo de ejecución.

Y, por supuesto, dentro del grupo de "sería una tragedia si fueran a parar a Git" están todos aquellos archivos con información privada o secreta, como claves de bases de datos o de cifrado, secretos para llamar a APIs y cosas así.

Una forma de lograrlo es, claro está, no incluirlos nunca dentro del área de staging para que no puedan formar parte del commit. Pero esto implica acordarse de cuáles no deben ir ahí para no meterlos, o quitarlos si se han incluido con git add. Obviamente esto no tiene ningún sentido pues sería demasiado laborioso y propenso a errores.

Por suerte, Git nos ofrece la posibilidad de hacer esto automáticamente de manera muy sencilla.

Lo único que tenemos que hacer es crear en la carpeta raíz de nuestro proyecto un archivo llamado .gitignore (sí, así, empezando por un punto, todo extensión).

Se trata de un simple archivo de texto en el que podemos incluir tantas líneas como necesitemos con nombres de archivos y carpetas o bien patrones de estos nombres. Todas las coincidencias que haya se excluirán automáticamente del control de código fuente.

Este es un ejemplo de un archivo .gitignore sencillo:

# Archivos de vista previa de imágenes
Thumbs.db
# Configuración personal de la carpeta
[Dd]esktop.ini
# Ejecutables y DLLs
*.exe
*.dll
# Archivos de log de nuestra aplicación
logs/**/*.log
!compilation.log

De este contenido es fácil extraer algunas conclusiones:

  • Se pueden poner comentarios para saber por qué hemos incluido una determinada línea, para lo cual sólo hace falta ponerle una almohadilla (#) al principio.
  • Si pones simplemente el nombre de un archivo, este se excluirá del control de código. Distingue entre mayúsculas y minúsculas.
  • Es posible especificar conjuntos de letras, números o incluso rangos usando corchetes. Por ejemplo, en nuestro archivo, la expresión [Dd] sirve para indicar que ahí puede ir un "D" mayúscula o minúscula. Pero puedes indicar rangos separándolos con un guión: [a-zA-Z] o [0-9] como en las expresiones regulares (¡pero no lo son!).
  • Los asteriscos son expresiones de tipo Glob de Linux, aunque estés en Windows. En el enlace tienes una explicación detallada, pero es muy simple: los asteriscos * sustituyen a cualquier cosa, las interrogaciones ? a un carácter cualquiera y los dobles asteriscos con barra **/ a cualquier subcarpeta. Así, en nuestro ejemplo, logs/**/*.log quiere decir que se excluirá cualquier archivo con extensión .log que esté en cualquier subcarpeta de cualquier nivel de la carpeta logs que tenemos en la raíz. Sencillo, pero potente.
  • Podemos crear excepciones precediéndolas con un cierre de admiración: !.Esto quiere decir que en el caso anterior, si uno de los archivos de log se llama exactamente compilation.log sí que se incluirá en el control de código. Es muy útil para algunos casos.

Debes tener en cuenta que el orden de las líneas es importante, ya que si dos reglas para archivos se contradicen, la última gana. Así, en nuestro ejemplo, si movemos la línea con !compilation.log delante de la anterior en vez de a continuación, no tendría efecto. Por lo que, cuidado con esto.

La buena noticia es que, por regla general, no vas a tener que construir tus propios archivos .gitignore. Como mucho retocarlos para meter alguna cuestión inicial.

Existen herramientas y colecciones de este tipo de archivos en las que podrás encontrar la que más te convenga:

  • Este repositorio de GitHub tiene infinidad de archivos .gitignore ya preparados y listos para usar con casi cualquier entorno de desarrollo o lenguaje que se te ocurra.
  • gitignore.io te permite buscar por palabras clave de sistema operativo, lenguaje, entorno... y obtener un archivo .gitignore apropiado.
  • Existen extensiones para los editores y entornos que te ayudan a generarlas. Por ejemplo, para Visual Studio Code tienes gitignore para añadir ayuda contextual mientras escribes este tipo de archivos, y Gitignore Generator te los genera automáticamente incluso mezclándolo con el que ya tengas para evitar conflictos.

Es posible tener más de un archivo .gitignore gobernando las diferentes subcarpetas, aunque es una mala idea salvo en casos excepcionales. Mejor usa solamente un archivo en la raíz y tendrás menos posibles problemas de conflictos y que se te queden archivos fuera por culpa de eso.

En una emergencia puedes forzar que un archivo que está excluido se incorpore a un commit utilizando la opción --force (o abreviada, -f) de git-add, así:

gitadd --force miArchivo.ext

y pasará al área de preparación, por lo que se incorporará al commit cuando lo hagas.

Si hay un archivo que querrías tener en Git y que no se incorpora a los commits puedes saber exactamente qué regla lo está bloqueando usando el siguiente comando: git check-ignore -v miArchivo.ext.

2.8.2 Los archivos .gitkeep

Una cuestión que no tiene clara todo el mundo, incluso cuando llevan bastante tiempo trabajando con la herramienta, es el hecho de que Git no es capaz de gestionar carpetas vacías.

Este hecho se debe al diseño del área de staging del índice de un repo Git, que solamente permite listar archivos. Esto significa que las carpetas no se añaden al control de código con Git: se añaden archivos únicamente.

Cuando añades una carpeta y todos sus contenidos a un commit para gestionarlos, lo que ocurre es que Git añade los archivos que encuentra dentro de esa carpeta y en sus metadatos va incluida la carpeta en la que están. Pero si la carpeta está vacía, ese comando no hará nada.

Es muy habitual, no obstante, que los programadores creen una estructura de carpetas vacías al inicio de un proyecto con el objetivo de ir organizando posteriormente los archivos dentro de ellas: controladores, vistas, modelos, bibliotecas auxiliares, controles, etc... Si el proyecto se comparte entre varias personas (lo habitual) es un fastidio que esa estructura inicial no llegue a todo el mundo hasta que se empiezan a añadir archivos dentro de las mismas.

Para saltarse la limitación de Git de no gestionar carpetas vacías y poder solucionar el problema de compartir estructuras de carpetas iniciales, una convención que se suele utilizar es la de crear dentro de cualquier carpeta vacía un archivo vacío llamado .gitkeep.

Estos archivos no ocupan nada, ya que están vacíos, por lo que no añaden apenas nada tampoco a la carga del remoto de Git ni a la transferencia de datos. Es como si no estuviesen. Pero como sí que están, Git los registra en el control de código y por tanto añade también la carpeta que los contiene.

Alguna gente añade con el mismo objetivo archivos .gitignore vacíos en lugar de .gitkeep. Personalmente no lo veo adecuado puesto que puede llevar a confusión y más de uno pensará que la idea es obviar esa carpeta. También hay quien crea un archivo simplemente con el nombre .keep, pero no me parece tan buena idea puesto que no estamos indicando de forma tan clara como con .gitkeep el hecho de que es un archivo que se refiere específicamente a Git. Que no te sorprendan de todos modos si los llegas a ver en alguna ocasión.

Puede que a pesar de que el directorio no está vacío siga teniendo ese archivo en su interior. Eso es que en algún momento estuvo vacío y que nadie lo ha borrado. Es también bastante típico.

1.9Creando y clonando repositorios remotos

Hasta ahora, aparte del comando clone que apuntamos antes, sólo hemos estado trabajando de forma local. Pero ¿qué pasa si trabajas con un repositorio remoto como los ofrecidos por GitHub y servicios similares? ¿Cómo te sincronizas o subes tus cambios?

De hecho, esta es la forma normal de trabajar: usar un repositorio remoto con el que te sincronizas, de modo que tengas disponibilidad, seguridad, colaboración con otros desarrolladores...

Antes de nada vamos a ver qué es un repositorio remoto y lo básico sobre gestionarlos. De momento no crearemos repos remotos en ningún servicio en la nube, que será lo que harás habitualmente sino que, para no complicarnos, vamos a crear repositorios remotos locales (aunque suene a contradicción). Es decir, serán repositorios que actuarán como remotos pero estarán en nuestro equipo para poder hacer pruebas fácilmente sin ni siquiera estar conectado a Internet.

2.9.1 Crear un repositorio "desnudo" para simular un repo remoto

Un repositorio remoto es idéntico a cualquier repositorio local con el que trabajemos, con una excepción: no contiene archivos de trabajo.

Como has visto, un repositorio convencional con el que trabajamos, aparte de los archivos y carpetas de nuestro proyecto, contiene una carpeta especial llamada .git con la configuración del repositorio. Bien, pues un repositorio remoto es un repo algo especial llamado "desnudo" (o "bare", en inglés) porque no va a gestionar archivos del proyecto, sino solo sus versiones. Así que en lugar de tener una subcarpeta .git, el contenido de ésta es ya lo que hay directamente en su raíz.

Los repositorios en la nube que utilizarás normalmente, utilizan repos de tipo "bare" para el remoto. Tú puedes crear un repo "bare" propio en una carpeta local o en una unidad de red (pero no en un disco cloud estilo Dropbox o OneDrive, ojo) y trabajar con él para simular un remoto, creando una carpeta vacía y desde dentro de ésta ejecutando esta instrucción:

git init --bare

Una vez que tienes un repositorio "bare" en alguna carpeta, puedes clonarlo a otra carpeta local diferente para poder tener un repositorio de trabajo. Para ello usarás el comando git-clone que ya vimos en la primera lección de este módulo (si lo necesitas, dale un repaso antes de continuar) ejecutándolo desde la carpeta en la que quieras crear el repositorio de trabajo:

git clone C:\temp\git\remoto repoLocal

En la que indicamos que vamos a clonar el repositorio remoto en una carpeta que se llamará repoLocal.

En este caso, como estoy bajo Windows, utilizo una ruta con la letra de la unidad y las barras inclinadas a la izquierda. En un sistema UNIX sería una ruta del estilo /ruta/al/remoto/ o similar. Por ejemplo, en este minivídeo te enseño a crear estos dos repositorios (el "remoto" simulado y la copia local para trabajar) en la carpeta de documentos de un sistema macOS:

En el vídeo ves también el contenido de los dos repositorios. El repolocal se ha clonado a partir del remoto, y como puedes observar tiene su subcarpeta .git con la información de los archivos y sus versiones (aunque al crearlo está vacío). El repositorio llamado remoto es un repositorio de tipo "bare" que contiene únicamente la información pero no va a trabajar nunca con archivos reales, por lo que toda la información está directamente en la raíz.

2.9.2 Configuración del usuario

Cada vez que haces un commit en Git se utiliza tu nombre y tu dirección de correo electrónico. Estos datos son especialmente importantes cuando se envían los commits al remoto, ya que determinan quién los ha hecho, a quién se le notifican los comentarios que se hagan en estas herramientas, etc..

Estos datos se suelen configurar la primera vez que te conectas a un remoto, pero puedes cambiarlos con el comando git-config, que sirve para configurar multitud de cosas en la herramienta.

Las configuraciones que nos interesan para esto son user-name y user.email.

Puedes ver el valor asignado a cualquiera de ellas escribiendo:

git config user.name

Y puedes modificarlos escribiendo el valor a continuación:

git config user.name "David García Valiñas"

Fíjate en que si el valor contiene espacios debe ir entre comillas dobles para que lo registre correctamente

Esto realizará el cambio solamente para el repositorio actual (la carpeta en la que estés, con subcarpeta .git). Si quieres grabarlo de manera global y que sea el valor por defecto para cualquier repositorio futuro que clones, puedes añadirle el switch --global:

git config --global user.name

Resumen comandos más usuales:

git init --bare //crea repositorio remoto

git clone ./remoto/usuario1 //repositorio local usuario1

git status//chequea repositorio de usuario local

git commit;--actualiza

git add index.html  - //añadimos a nuestra zona de stagin

git commit-m "añadimos un fichero de texto" //la actualizamos

git push //sube al repositorio remoto

git log --graph --oneline --decorate –all //vemos estado de los commits

git pull // te traes los cambios del repositorio remoto

git fetch // vemos si se han producido modificaciones

git commit -a -m "Modificación del texto" //hace el add y el commit a la vez../

1.10Ramas y mezclas

Una rama o branch es una bifurcación dentro del historial de código que nos permite gestionar los cambios de los mismos archivos en dos historias paralelas.

Las ramas en Git, al contrario que en otros VCS, son extremadamente ágiles y livianas y no penalizan ni el almacenamiento ni la velocidad. Por eso en Git se recomienda usar tantas ramas como creamos necesarias sin preocuparnos por su impacto.

De este modo, por ejemplo, en un proyecto es posible abrir una rama nueva por cada característica diferente de la aplicación en la que se esté trabajando, sin miedo a interferir en el trabajo de otras personas ni "ensuciar" la rama "maestra" con código a medio terminar.

Gracias a esta característica podríamos tener la rama master (u otra) con el código estable, tal cual está en el servidor para los clientes, e ir desarrollando cosas nuevas en otras ramas. Si de repente se produce un error en producción que tenemos que arreglar, podemos cambiarnos a la rama master y abrir desde ella otra rama exclusivamente para resolver el bug, sabiendo que en ella partimos del código exacto que hay en producción y que no nos interferirán los nuevos desarrollos que tengamos en marcha:

Una vez terminado de arreglar el bug y habiéndolo probado bien, podemos mezclar la rama de nuevo en nuestro master para almacenar el estado final, e incluso deshacernos de la rama que ya no necesitamos:

Al mismo tiempo, la otra rama, en la que se está desarrollando la característica grande, puede haber evolucionado por su cuenta, totalmente ajena a los cambios que se han hecho en otras ramas, como la que hemos utilizado para solucionar el bug, hasta que la mezclemos con el master u otra rama.

2.8.1  Introducción a las ramas y sus conceptos relacionados

Hasta ahora hemos aprendido a registrar cambios haciendo commits directamente sobre el master, aunque no entramos a definirlo todavía para no complicarte más. Entonces, ¿qué es el master cuando trabajamos con Git?

El master es el branch (o rama) por defecto de Git.

Cuando inicias un repositorio en Git, creas un repositorio vacío. Al registrar tu primer cambio ocurren tres cosas:

  • Se crea el branch principal de Git llamado por defecto master
  • Se actualiza un puntero llamado HEAD que es lo que indica a Git en dónde estamos situados dentro del repositorio
  • Se registra ese primer commit sobre la rama master

Por regla general, a master se la considera la rama principal y la raíz de la mayoría de las demás ramas. Lo más habitual es que en master se encuentre el "código definitivo", que luego va a producción. Es la rama en la que se mezclan todas las demás, tarde o temprano, para dar por finalizada una tarea e incorporarla al producto final, aunque no siempre es así.

Como ya hemos comentado, en un repositorio puedes tener un número ilimitado de ramas, que puedes crear por cada característica en desarrollo, bug, etc...:

Estos branches adicionales son muy útiles para trabajar en tu propio entorno sin que nadie te moleste, evitando, en la medida de lo posible, los famosos conflictos.

§Grafo de commits

Git recoge en su histórico los diferentes commits que has realizado. Cada commit se relaciona con los demás mediante un puntero, único, que apunta hacia atrás de modo que se forma un grafo. El histórico de Git no es más que un conjunto de commits relacionados de esta manera y la representación más correcta de un repositorio y sus ramas debería ser más bien así:

Como puedes observar, cada commit apunta siempre hacia el commit anterior que le precede y solamente hacia éste. Analizando estas relaciones es fácil determinar las rutas/ramas que sigue el histórico dentro del repositorio de código.

Un commit no puede apuntar hacia más de un commit anterior, aunque un commit sí que puede recibir referencias de más de un commit, como se ve en la figura anterior en las ramas. Por ejemplo, el primer commit de master, a la izquierda, recibe referencias desde otros dos commits, el siguiente en master y el primero de la rama llamada "Característica pequeña".

Una rama simplemente es un nombre otorgado a una de estas bifurcaciones para poder trabajar con esa historia paralela en concreto. Moverse entre ramas implica solamente cambiarse al commit apropiado.

§El concepto de HEAD

En Git se denomina HEAD al commit en el que está tu repositorio posicionado en cada momento.

Por regla general HEAD suele coincidir con el último commit de la rama en la que estés, ya que habitualmente estás trabajando en lo último. Pero si te mueves hacia cualquier otro commit anterior, entonces el HEAD estará más atrás.

A este estado, cuando HEAD no está en el último commit de la rama, se le denomina "Detached HEAD" o "HEAD desvinculado" en español (aunque raramente lo verás utilizado en este idioma).

Siempre es posible referirse al commit actual usando el nombre HEAD y, de hecho, es posible referirse a otros commits usando su relación con HEAD de forma relativa.

Así, por ejemplo, puedes referirte a un commit que está dos por detrás del actual usando la expresión HEAD~2 (es una "virgulilla", no un "menos"):

git log HEAD~2

Con él se ven los detalles de todos los commit a partir del "abuelo" del commit actual (2 por detrás).

El símbolo ~ siempre sigue al primer "padre" del commit referenciado (el HEAD en nuestro ejemplo). Pero si existe más de una rama que parte de un commit, podemos movernos por éstas usando el símbolo ^ para indicar el orden de las mismas. Por ejemplo: HEAD^2 obtendría el primer commit de la segunda rama que parte del commit en el que está HEAD en ese momento.

Se pueden combinar ambos para moverse por las ramas. Así, HEAD^2~3 indica que se obtendrá el tercer commit a partir del primer commit de la segunda rama que parte del commit actual, es decir, 4 commits hacia atrás por la segunda rama (el primer ^ se refiere al primer commit hacia atrás por esa rama y luego indicamos otros 3).

Puede parecer lioso, pero no lo es tanto si lo ves en un diagrama. Este nos muestra todas las formas de llegar al commit A desde los distintos commits existentes en el grafo:

 Como ves, estas referencias relativas valen para cualquier commit, no solo para HEAD, y se referencia a partir de su hash (en la figura, por simplicidad hemos usado letras).

§Trabajando sobre master

El flujo de trabajo que hemos aprendido hasta ahora se denomina comúnmente "trabajar sobre master", cuando todos los commits se hacen sobre la rama de dicho nombre, que es la rama por defecto. Si trabajas sobre master significa que todos los cambios los vas a registrar directamente sobre la rama master, sin crear ramas diferentes.

Esta forma de trabajar se usa muchas veces con proyectos personales o de poca complejidad, en los que sabes, de antemano, que será raro coincidir con otro desarrollador subiendo cambios.

  • Ventajas

Es un flujo muy fácil de aprender y muy rápido de ejecutar. Básicamente lo que necesitas es crear el repositorio y empezar a trabajar con lo que ya conoces.

  • Desventajas

No es apto, en general, para proyectos grandes, con más de un desarrollador o que necesiten de un sistema de despliegue y versiones más o menos serio.

2.8.2  Trabajo con ramas

La forma habitual de trabajar en equipo con Git es haciendo uso de los branches. Si trabajas directamente sobre el master con varias personas, te encontrarás, más temprano que tarde, con un conflicto en algún archivo que te impedirá subirlo a tu repositorio y continuar trabajando.

Las ramas también tienen un beneficio muy interesante a la hora de usarlas y, es que, por naturaleza, a nosotros los desarrolladores no nos gusta que nos toquen nuestras cosas mientras trabajamos (¡y menos otro desarrollador!). Al crear un branch local estamos creando un entorno de desarrollo propio donde sólo nosotros registramos cambios que más adelante llevaremos al master a través de un merge.

Nota: otra opción puede ser que trabajes con muchos desarrolladores en un mismo equipo y que unos pocos queráis trabajar en un desarrollo aparte. Lo que tendrías que hacer sería tomar ese branch local y subirlo al repositorio remoto para compartirlo transformándolo en un branch remoto al que todo el equipo tuviese acceso.

§Crear tus propios brunch

Para crear una rama el comando apropiado es:

git branch nombre_branch

Y con esto ya tendríamos nuestra rama creada.

Fíjate especialmente en cómo coincide en el mismo commit tanto el HEAD, como el master, como la rama nueva. Pero el hecho de que crees la rama NO quiere decir que puedas trabajar ya en ella.

Por defecto, cuando creas una rama sólo la creas, pero no te mueves a ella para trabajar. Eso se hace con el comando git-checkout:

git checkout nombre_branch

Compara ahora las dos últimas imágenes y fíjate especialmente en el puntero HEAD (el que se muestra entre paréntesis en color azul, al final de cada línea de comandos), para ver a dónde está apuntando. Esto indica que estamos en la nueva rama que hemos creado, y que podemos empezar a trabajar en ella usando el flujo que hemos aprendido antes, git-add y git-commit.

Nota: como ya hemos dicho, el flujo de git-add y git-commit es básico para cualquier paso en Git y el resto de flujos parten de él (incluido este que estamos aprendiendo con las ramas).

Otra opción para crear una rama y cambiarnos a ella es ejecutar directamente el comando git-checkout con el modificador -b:

git checkout -b nombre_branch

Con la opción -b le indicamos a Git que, si no existe esa rama, nos la cree y adicionalmente nos mueva a ésta. Dicho de otra forma, hace que el HEAD apunte a la nueva rama.

El comando git-checkout también sirve para mover el HEAD a cualquier otro commit.

Si especificas el hash de un commit determinado, por ejemplo git checkout 6e86 (normalmemente llega con los 4 primeros caracteres para determinarlo) moverás el HEAD a ese commit en concreto y quedará en estado "detached", como ya hemos comentado, porque no está en el último de la rama.

El propio Git te avisará de este hecho y te informará sobre cómo crear una rama a partir de ese commit si hace algún cambio. Puedes volver a poner el HEAD en el último y dejar ese estado usando un simple git checkout nombreRama.

Una vez que estamos en una rama, podemos trabajar en ella como veníamos haciendo hasta ahora en master, solo que en una rama diferente.

Así, añadiremos cambios al área de staging, crearemos commits para generar versiones concretas y podremos hacer push y pull (¡y fetch!) con el repositorio remoto si lo consideramos necesario. La única diferencia es que los commits se almacenarán en esa rama y no en la principal. Pero los principios de trabajo son exactamente los mismos.

De todos modos, las ramas tienen un par de particularidades que necesitamos aprender:

  • Cómo actualizar nuestra rama con cambios posteriores que suban otras personas desde la rama de la que hemos partido, si es que los necesitamos para estar al día (rebase).
  • Cómo llevar nuestros cambios a master o a otras ramas una vez finalicemos de trabajar en ella (merge).
§Actualizar nuestro brunch con rebase

Tenemos la siguiente situación con dos ramas, Característica Pequeña y Característica Grande:

Como puedes ver, entre estas dos ramas hay varios commits de gente (o nosotros mismos) que ha ido actualizando código. Pongámonos en la siguiente situación: estamos desarrollando algo en la rama Característica Pequeña, pero para continuar necesitamos de un desarrollo que ha subido un compañero a master, que va por delante de nosotros. Una situación bastante común.

Lo que debemos hacer es actualizar nuestra rama con lo que se conoce como rebase.

El concepto es muy sencillo. Simplemente aplicamos los commits de nuestra rama sobre la rama con la que queremos actualizarnos, usando el comando git-rebase:

git rebase rama_destino rama_final

Este comando se ejecuta estando en la rama que vamos a actualizar. En este caso Característica pequeña:

git rebase master "Característica pequeña"

Y lo que conseguimos ejecutando este comando es actualizar nuestra rama con los commits posteriores de la rama que definamos como destino, en este caso master:

La segunda opción del comando, lo que denominamos branch_final, sirve para que, una vez terminado el rebase nos quedemos en la rama que le especifiquemos, sino automáticamente haríamos un checkout a la rama de destino.

Y con esto tendríamos actualizada nuestra rama, sin movernos de ella, y podríamos seguir trabajando con los commits que necesitábamos del master.

§Mezclar las ramas

Ya podemos actualizar nuestro branch con los cambios de los demás si lo necesitamos, pero nos queda saber cómo incorporar los cambios de nuestra rama a master para que sean visibles para el resto. Esta acción de actualizar el master (o cualquier otra rama) con cambios de otra rama se denomina Merge o Mezcla y se lleva a cabo con el comando git-merge.

git merge nombre_branch

Fíjate cómo lo aplicamos en la siguiente imagen:

En este caso tenemos la rama "nuevo_branch" con un cambio que no tenía el master, por eso aparece por delante. Lo que se ha hecho para actualizar el master ha sido lo siguiente:

  • git checkout master: necesario para ir al master, ya que es la rama que vamos a actualizar.
  • git merge nuevo_branch: hacemos un merge de la rama local en el master.

Es muy importante recalcar que para hacer la mezcla de dos ramas siempre debemos movernos primero a la rama que vamos a actualizar, indicando la otra rama a mezclar en el comando git-merge.

Una vez hecho el merge fíjate que tanto el master como la rama "branch_local" se encuentran situados en el mismo commit, actualizados.

Como ya hemos terminado el desarrollo y lo hemos mezclado desde la rama que estábamos utilizando, no es necesario mantener esa rama local. Para eliminarla sólo debemos ejecutar:

git branch -d nombre_rama

Con esto eliminamos las ramas del repo local. Para eliminar una rama del repositorio remoto si la habíamos sincronizado antes, hay que añadir la opción -r que se puede combinar con la anterior como -rd:

git branch -rd nombre_branch

§Conflictos

Hemos aprendido los conceptos básicos de Git con los que poder trabajar en cualquier repositorio y con cualquier equipo. Eso sí, si todo va bien. Pero a veces las cosas no salen como uno se lo espera y resulta que cuando vas a subir tus cambios al master alguien ha tocado el mismo archivo en la misma línea que tú y la cosa se empieza complicar. Entonces surgen los conflictos.

Esta situación ocurre muchas veces, sobre todo con equipos grandes ya que, como es normal, la gente está trabajando y el código evoluciona. Un conflicto sucede cuando dos personas realizan una modificación sobre el mismo punto en un archivo. Por lo general, Git es muy bueno gestionando los conflictos, pero es cierto que a veces es indispensable la interacción del desarrollador para resolverlos.

Pongámonos en la siguiente situación. Tenemos el siguiente archivo HTML:

Modulo 2

En paralelo, en su repositorio, otra persona del equipo modifica la etiqueta para poner "Modulo 3". Y tú en tu propia rama modificas también el contenido de la misma etiqueta y escribes "Modulo 3 - Branches". Esto genera un conflicto ya que, como puedes ver, ninguno está mal y Git no puede discernir cuál de las modificaciones es la correcta.

Ahora, al ejecutar el comando git-merge Git iniciará el proceso llamado de resolución de conflictos, que consta de los siguientes pasos:

  • Git detecta un conflicto que no puede resolver automáticamente y entra en el proceso de resolución de conflictos.
  • Git añade a la zona de trabajo los archivos que debes revisar, con los conflictos que se generan.
  • Debes modificar los archivos notificados por Git, arreglándolos de la manera que veas conveniente.
  • Una vez editados y con las resoluciones ya decididas, los añades a la zona de preparación con git-add.
  • Antes de terminar el proceso puedes utilizar el comando git-diff para estudiar los cambios realizados:
     gitdiff branch_origen branch_destino

 Una vez finalizado los cambios podemos optar por dos comandos:

        git commit -a -m "mensaje_commit"

Que indica a Git que debe confirmar los cambios de los archivos que se han modificado o eliminado (el -a), y nos permitirá continuar con el git-merge.

O bien:

git merge --abort

Lo que impedirá que se lleve a cabo el git-merge y nos dejará en la situación anterior a la de intentar subir los cambios.

Resumen comandos más usuales - ejemplos:

git branch feature1 //crea rama

git commit;

git checkout feature1 //para moverme a ella

git rebase master // hacer cambios en la master

Ejemplos:

1. Nos movemos a la master

git checkout master

2. Hacemos merge de la rama que queremos actualizar

git merge feature1

-- Para que feature2 coja el cambio de master

1. Nos movemos a la rama feature2

git checkout feature2

2. hacemos un rebase

git rebas master

1.11Herramientas visuales

En el día a día, lo más habitual es que trabajemos con alguna herramienta gráfica que actúe de intermediaria entre nosotros y Git. Su uso nos facilita mucho la vida porque, aparte de poder realizar operaciones simplemente pulsando un botón, nos ayudan a visualizar los cambios y diferencias entre versiones, a resolver los conflictos, a ver de manera gráfica los commits y las ramas...

Vamos a ver cuatro de las principales herramientas gráficas que te puedes encontrar en la mayoría de empresas que trabajen con Git.

Desarrollaremos con todas ellas un flujo de trabajo muy sencillo con Git para ver en dónde se encuentran las principales opciones en cada una.

Para ello partiremos de una página HTML de ejemplo, con un contenido inicial, que iremos modificando tanto desde el master como desde nuevas ramas que abriremos para desarrollar nuevas "características" sobre ellas, antes de mezclarlas con la principal. También provocaremos conflictos para ver cómo gestionarlos en cada herramienta.

Será sencillo pero nos servirá para ver las principales opciones de Git en cada entorno visual de los estudiados.

§Git GUI

Git GUI es la herramienta por defecto que trae Git preinstalada y como tal, será la primera herramienta gráfica que veamos. No es la más utilizada, ni mucho menos, pero también es cierto que ofrece soporte para la mayoría de las tareas mínimas que se puede esperar de una interfaz para Git:

  • Visualización del histórico
  • Visualización de cambios
  • Interfaz para hacer commits
  • Gestión de ramas
  • Merge interactivo
  • Resolución de conflictos interactivo
  • Gestor de repositorios remotos
§Github desktop

es la plataforma por excelencia para alojar repositorios remotos de Git, sobre todo proyectos Open Source. La alta cantidad de usuarios que utiliza su sistema para trabajar hace que sea muy común encontrarse con su GUI en más de un proyecto, y la mayoría de bibliotecas y utilidades que usarás para programar estarán alojadas casi seguro en esta plataforma.

Esta GUI es la herramienta oficial de GitHub para la gestión de repositorios de Git. Sus principales virtudes son:

Interfaz muy visual e intuitiva

Gran integración con la plataforma de GitHub

Se puede descargar desde aquí, y también podremos obtener ayuda y documentación desde la misma web.

Su instalador tanto en Mac como en Windows es un simple asistente de pulsar "Siguiente", así que no tendrás dificultad para ponerlo en marcha.

§Source Tree

es una gran empresa australiana, de las más importantes especializadas en herramientas para desarrolladores. Sus productos los podrás encontrar en uso en multitud de empresas ya que son muy populares. SourceTree es la herramienta oficial de esta empresa para la gestión de repositorios, tanto de Git como de Mercurial.

Las principales virtudes de SourceTree son:

Interfaz sencilla e intuitiva

Posibilidad de usar Git-flow como workflow por defecto

Integración con las principales herramientas de Atlassian: BitBucket, Jira, Trello...

BitBucket es un repositorio de código remoto de Git que, junto a GitLab, puede que sea el máximo competidor actualmente de GitHub. De la misma forma que GitHub ha conseguido una hegemonía indudable alojando repositorios de código abierto, BitBucket ha conseguido una gran porción del pastel en el sector de la industria. De hecho ¡el proyecto de este curso se ha gestionado con Git y BitBucket!:

No será raro que te encuentres en una empresa donde utilicen las herramientas de Atlassian como soporte para su trabajo, entre ellas SourceTree, y conviene conocerlo, además de que es una de las mejores y más completas herramientas visuales para Git.

§Tortoisegit

Se trata de un cliente gratuito y Open Source para Git, que está integrado por completo con el explorador de Windows, de modo que podemos trabajar con Git desde cualquier carpeta del disco duro y sin necesidad de usar un programa dedicado.

TortoiseGit nació como alternativa a un producto similar existente desde hace muchos años para los equipos que trabajaban con SubVersion (SVN, que si recuerdas es un VCS no distribuido). Como muchas empresas han usado durante años SVN, utilizar una herramienta similar con Git les evita un cambio demasiado radical en sus herramientas y su flujo de trabajo. Por eso te lo vas a encontrar en muchas empresas por ahí, sobre todo en las que tienen ya unos cuantos años y usaban gestores de código fuente antes de que se creara Git.

No obstante, es una herramienta de las más potentes y completas, y muy útil en todo caso, por lo que conviene conocerla.

Sus principales características son:

  • Buena integración con los menús contextuales de Windows
  • El cambio no es tan drástico como con otras herramientas visuales
  • Al usar apenas una GUI completa su usabilidad es bastante sencilla si dominas los conceptos de Git

Nota: si trabajas en Mac o Linux no podrás utilizar TortoiseGit, porque es exclusiva para Windows. No obstante existe una muy parecida llamada SnailGit (caracol en vez de tortuga) que tiene una funcionalidad casi idéntica, aunque la verdad es que no tengo experiencia usándola. En Linux lo más parecido es RabbitVCS, que ofrece también una funcionalidad similar con Nautilus, el explorador de archivos de GNome.

Otra de las grandes características de Tortoise es que te permite resolver conflictos de manera visual, comparando las dos versiones del archivo que entran en conflicto, una al lado de la otra en un comparador de archivos:

Aunque en el vídeo no se muestra porque es muy sencillo y ya lo hemos visto con los demás clientes visuales, conviene saber que tenemos esta capacidad y que nos facilita resolver este tipo de problemas mejor que con otras herramientas.

Nota sobre los iconos superpuestos de TortoiseGit

En el vídeo que verás a continuación, tras haber creado el repositorio, en circunstancias normales deberían verse unos iconos superpuestos (overlays) sobre los iconos de las carpetas y archivos que están bajo control de código fuente con Git, ya que esta es una de las características de TortoiseGit.

Este sería el aspecto de estos iconos según el estado de cada archivo o carpeta respecto al commit:

El problema es que Windows tiene una limitación en cuanto al máximo de overlays de este tipo que pueden existir en un mismo sistema. Este límite es muy, muy pequeño y en cuanto instalas más de 2 o 3 programas que usen este tipo de iconos superpuestos, unos se pisan a otros. Por eso, en ocasiones, como pasa en el vídeo siguiente, dejan de verse, ya que otros programas han sobrescrito a los iconos overlay de TortoiseGit. Y este es el motivo de que no se vean en el vídeo.

Para solucionarlo puedes recurrir a lo que se explica en este artículo, pero a lo mejor te vuelve a pasar porque al instalar alguna actualización de otro programa que interfiera, se vuelven a perder. Esta es una de las grandes "pegas" de usar Tortoise, aunque no se le puede achacar a este programa sino a una limitación histórica de Windows que parece mentira que todavía no hayan solucionado.

2.Documentación

2.1Uso de comentarios.

2.2Herramientas integradas en el entorno de desarrollo para generar documentación automáticas de clases.

2.3Alternativas.

Entradas relacionadas: