miércoles, 4 de julio de 2018

Otra introducción más a Git

¿Y por qué? No porque las entradas que ya existen sean malas, al revés, hay algunas fantásticas como el resumen de DZone o el propio libro editado por Git, pero normalmente son o muy escuetas o excesivas si se desea aprender los cuatro conceptos básicos.

Lo que planteo es una especie de práctica guiada para aprender a usar Git y un repositorio remoto como Bitbucket o GitHub. Es posible que haya alguna ya hecha del estilo o mejor, pero preferí preparar una para mis alumnos y ya de paso dejarla por aquí por si a alguien le viene bien. Siguiendo esta práctica, deberíais entender las bases de Git.

Empezamos:

Git es un sistema de control de versiones diseñado por Linus Torvalds con el objetivo entre otros de coordinar el desarrollo del kernel de Linux en el que participan varios centenares de programadores distribuidos por todo el mundo. Dicho kernel está compuesto actualmente por más de 9 millones de líneas de código y más de 25000 archivos. Todo esto es una prueba clara de que Git es un buen sistema para controlar versiones de software de forma distribuida. 
Si tras terminar este artículo quieres profundizar, existe un libro gratuito que recomiendo seguir para dominar más este sistema.
Una característica de git que lo hace rápido es que por cada cambio guarda una “fotografía” de todos los archivos de un proyecto lo que lo hace rápido a la hora de recuperar archivos de versiones previas al revés que otros que guardan diferencias y deben reconstruir las versiones previas cuando se desea volver a ellas.
La instalación en un sistema Linux con apt u otro gestor de paquetes es simple:
$ sudo apt install git-all

Esto instala tanto git como otras herramientas que nos pueden hacer falta. Debes tener en cuenta que para usar todo el potencial de git, este debe ser usado desde línea de comando. A esto se le añade la ventaja que no dependes de un interfaz de usuario de un IDE.
Para instalarlo en macOS, Windows o si prefieres otra versión en Linux distinta a la de los repositorios ve a la web oficial donde puedes descargarlo.

Identidad
Tras la instalación lo primero que haremos es especificarnos como usuarios del sistema git. Esto es necesario porque por cada actualización que se envía de un archivo (commit) se guarda la identidad. Lo más sencillo es establecer una identidad global para el equipo en el que te encuentres y que la cojan todos los proyectos. 
Esto se hace cambiando las “variables de entorno” de git de la siguiente forma:
$ git config --global user.name "Cirilla de Cintra"
$ git config --global user.email "ciri@kaermorhen.com"

Si en un momento dado para un proyecto particular se quieren cambiar las credenciales simplemente se ejecuta el comando anterior sin el modificador –global.

Repositorio de proyecto
A continuación se le indica a git que proyecto me puede interesar tener gestionado. Para ello simplemente me voy al directorio de proyecto y ejecuto el comando:
$ git init

Esto crea un directorio .git donde se va a almacenar toda la información necesaria para el control de versiones de dicho proyecto.
Si se prefiere hacer desde modo gráfico, ejecuta el comando:
$ git citool

Esto abre el entorno gráfico de configuración de repositorios de git:


Le das a create new repository y ahí seleccionas el directorio de proyecto que te interesa gestionar. Además este mantiene una lista de repositorios creados. Veremos la clonación más adelante.
Si has creado alguno mediante línea de comando se puede añadir a esta lista mediante Open Existing Directory.
No veremos este entorno gráfico porque al final lo que nos interesará es poder manejar git desde cualquier sitio, y eso se hace mejor desde la consola.

Uso básico
Para ver como funciona git, vamos a ir haciendo la prueba con elementos muy sencillos. En principio está pensado para terminal de Linux/macOS pero si quieres trabajar en Windows va a funcionar igual salvo los comandos propios del sistema operativo, por ejemplo en lugar de mkdir ejecutarás md o en lugar de ls usarás dir. Sigue los siguientes pasos:
Crea un directorio denominado prueba_git y accede a él.
$ mkdir prueba_git
$ cd prueba_git

Crea mediante un editor de texto dos archivos dentro de dicho directorio. Uno denominado texto.txt y otro script.sh. En el primero introduce simplemente las palabras uno, dos, tres cada una en una línea. El segundo será un script con estas dos líneas:
echo Listado completo
ls -l

Pásalo a ejecutable y pruébalo mediante los comandos:
$ chmod a+x script.sh
$ ./script.sh

Añádelo como nuevo repositorio local
$ git init

En este momento tenemos un repositorio git con dos archivos sin rastreo (untracked). Puedes verlo con el comando:
$ git status

Esto significa que aunque estos archivos estén en el directorio, no están gestionados por git. 
Prueba a ejecutar el siguiente comando para añadirlo a archivos rastreado:
$ git add script.sh
$ git status

Ahora verás que texto.txt sigue sin estar rastreado pero script.sh pasa a ser un archivo rastreado por git (tracked). Esto significa que git comprobará si hay cambios desde la última versión. Si embargo aún no está confirmado. Esto es, no se ha realizado una “foto” del estado del archivo en ningún momento, no se ha actualizado la versión. Esto implica que no hay ninguna versión previa. Si un archivo sin confirmar se intenta sincronizar desde otro equipo no lo haría a la última versión.
Por tanto confirmamos o hacemos un commit para establecer una versión del archivo.

$ git commit -m "Confirmación inicial"
$ git status

Aparece solo el aviso de texto.txt que no está en seguimiento. El resto esta guardado como versión.
Al confirmar se añade un mensaje que queda guardado con cada commit. Ahí se suelen indicar los cambios desde la última versión. Si hubiera muchos cambios o el mensaje fuera muy largo se puede hacer 
$ git commit -F mensaje.txt

En el archivo mensaje.txt, lógicamente, iría todo el texto asociado al commit.
Añadimos también texto.txt:
$ git add texto.txt
$ git commit -m “Añadido archivo texto.txt”
$ git status

Tras esto nos dice que está todo en el directorio confirmado. 
Ahora abre el archivo texto.txt y añádele otra línea con la palabra cuatro. Tras ello ejecuta:
$ git status

Verás que git lo toma como “modificado”, pero indica que no hay cambios agregados al commit. Para prepararlo para el siguiente commit hay que ejecutar de nuevo add. Add es un comando que además de añadir a rastreo nuevos archivos, los prepara para el siguiente commit (puede no interesar actualizar en versión algún archivo que está en prueba).
$ git add texto.txt
$ git status

No hagas todavía commit. Si ahora modificas de nuevo texto.txt y observas el estado de git verás que aparece en stage (preparado para commit) y en modificado sin preparar. Si se hace ahora el commit, confirmaría la versión anterior, no la última modificación. Debes de hacer add si quieres incluirlo todo: 
$ git add texto.txt
$ git commit -m “Ampliada la explicación del texto”
$ git status

Vuelve a estar todo limpio. 
Puedes probar a ejecutar de nuevo la herramienta gráfica para ver que lo anterior se puede hacer también desde aquí.
$ git citool

Es posible deshacer cambios pero es la parte más “peligrosa” de git ya que se podrían llegar a perder datos, es por ello que hasta que se coja un poco de práctica es recomendable no acceder a esta parte.
Ciclo de vida de Git
Podemos resumir el ciclo de vida de archivos en el repositorio local con el siguiente esquema que aparece en el libro mencionado anteriormente:

Un archivo puede estar:
  • Untracked: no se gestiona con git.
  • Staged: gestionado y preparado para confirmar en el siguiente commit.
  • Unmodified: Estado del archivo tras el último commit, podemos consiferarlo una “versión” cerrada del mismo.
  • Modified: Un archivo modificado desde el último commit. Es necesario mandarlo mediante add a Stage para indicar que puede confirmarse como válido en el siguiente commit. 
Por tanto se mantienen tres árboles de trabajo. Como si fueran tres directorios:
Directorio de trabajo local: tu propio directorio donde tienes tus archivos.
Index o Stage: Es el almacén donde se colocan los archivos preparados.
HEAD: Que apunta a donde se ha hecho el último commit (ultima versión).

Si el flujo de trabajo habitual me está obligando continuamente a pasar de modificados a la de preparación mediante add y luego confirmar mediante commit, puedo saltarme un paso (o mejor dicho, resumirlo) ejecutando el comando 
$ git commit -a -m "Nueva versión"

Ojo, porque si hay un nuevo archivo estoy obligado a hacer add. Esto es válido solo para modificaciones. Así en un único comando realizas add y commit.
También es cómodo hacerse un script con las acciones repetitivas que hacemos.
Si quieres verinformación sobre la última versión confirmada:
$ git show

Por otro lado, si queremos ver los cambios que se han ido realizando en forma de históricopuedes ejecutar el comando:
$ git log

Para salir usa la q.
Diferencias
Coge ahora el archivo texto.txt, añádele una línea (será la cinco) y elimina otra (la tres, por ejemplo).
Para conocer las diferencias entre un archivo modificado y el que se ha confirmado en la última versión se puede usar el comando 
$ git diff

Esto devuelve una respuesta en “Formato Unificado” (Unified format) el cual es un formato específico para ver diferencias entre archivos. Sin pasar a explicarlo todo, si que podemos entender la parte final donde se relatan las diferencias:
@@ -1,4 +1,4 @@
uno
dos
-tres
cuatro
+cinco

En la primera línea (@@) el – se lee como “archivo previo” y el + como “archivo final”. Es decir, aquí nos indica que se están mostrando las filas de la 1 a la 4 en el archivo origen y en el final. 
En las otras líneas el – es línea eliminada y el más linea insertada. Todo esto se complica con archivos grandes y muchos cambios. Con el tiempo lo irás entendiendo mejor.
Más información leyendo este enlace:

Ignorar archivos
Cuando empiezo a tener una cantidad de archivos importantes suele suceder que hay algunos que no me interesa gestionar a nivel de versiones. Por ejemplo, si estoy programando en java, los archivos .class no tiene sentido gestionarlos. O archivos de texto o directorios temporales (muchas veces se marcan con extensión tmp o acabados en ~).
Para evitar estos archivos se debe crear un archivo con el nombre
.gitignore

Y para el ejemplo anterior podría contener las siguientes lineas:
# ignora los archivos terminados en .class
*.class

# ignora los archivos terminados en ~
*~

# pero no el archivo importante~, aun cuando había ignorado los archivos terminados en ~ en la linea anterior
!importante~


Veamos esto en funcionamiento: 
Añade a tu directorio (o en un subdirectorio) el archivo Hola.java. Por si no lo tienes cópialo de aquí y compílalo. 
class Hola {
    public static void main(String[] args){
        System.out.println("Welcome to the Java World");
    }
}

También crea alguno aunque no tenga nada que acabe en ~ y también uno denominado importante~. Ejecuta:
$ git status

Añade la plantilla anterior al archivo .gitignore. A continuación ejecuta de nuevo:
$ git status

Verás que hay archivos que se ignoran. Haz lo siguiente 
$ git add .
$ git commit -m “Añadidos fuentes en java y archivos importantes”

Con el . le indicamos a add que añada todo lo que está pendiente. También subdirectorios si hubiera.
Dispones de algunas plantillas ya realizadas en :

Eliminar y quitar de seguimiento
Si deseas eliminar archivos lo mejor es hacerlo a través del comando:
$ git rm importante~

Y luego un commit. 
Si lo has eliminado previamente mediante un rm normal (o del en Windows), tendrás que ejecutar también git rm o git rm -f si ya está confirmado el archivo.
Si había algún archivo ya en seguimiento que deseas quitar (por ejemplo los .class), para sacarlos de la zona de seguimiento (HEAD) ejecuta:
$ git reset HEAD *.class

Realmente HEAD es un puntero a la versión de trabajo del repositorio (la última versión confirmada). 
Con esto podemos ver de nuevo el ciclo de vida asociado a los distintos comandos vistos basándonos en el dibujo anterior:

Numeración de versiones
Cuando ejecutas un git log, ves como ya se comentó el historial de versiones. El número identificativo de la versión es un código único que aparece al lado de la palabra commit de cada versión. Por ejemplo:
commit 4b156d03695c859181e5499d074a6a98daeb761b

Este es un número bastante complejo sobre todo si se quiere referenciar en alguna forma, por ejemplo con un show o para recuperar cierta versión en unfuturo. Veamos el primer ejemplo, tendríamos que hacer:
$ git show 4b156d03695c859181e5499d074a6a98daeb761b

pero evidentemente coge uno de los número que te haya salido a ti en el log. 
Para añadir un tag es muy simple. Si se quiere añadir a la versión actual simplemente se escribe:
$ git tag v0.7

Si quieres añadir un tag a una versión anterior, por ejemplo la que se comentó al principio del apartado, simplemente se añade el código hash al final del comando:
$ git tag v0.3 4b156d03695c859181e5499d074a6a98daeb761b 

Si ahora haces:
$ git log --decorate

Verás que en el log salen las versiones. También puedes hacer:
$ git show v0.3

La numeración de versiones puede depender de la empresa, pero es muy habitual una numeración estándar como la que aparece en el siguiente enlace:

Ramas
Una gran utilidad de los sistemas de control de versiones es la realización de ramas, es decir, archivos paralelos a los principales con el objetivo de hacer pruebas o poner en práctica ideas. Luego la rama se puede abandonar, eliminar o mezclar con la rama principal. 
Este es un punto complejo, sobre todo por la solución de conflictos que pueden aparecer, y no vamos a profundizar en él.
Otra ventaja de las ramas en git es que pueden mantenerse solo en el sistema local sin necesidad de llevarlas al remoto. Esto es muy práctico para que programadores en equipo hagan sus pruebas en sus equipos locales sin tener que ramificar el proyecto general de todo el equipo.
Ejecuta el siguiente comando para crear una rama con el nombre Prueba: 
$ git branch Prueba
$ git log --oneline --decorate

Verás que ahora en la versión más reciente aparecen dos ramas, la principal, master, y la que has creado (Prueba). El argumento --oneline simplemente hace el log más breve.
Ahora si sigues cambiando cosas lo haces en master, porque es donde apunta HEAD que es el puntero de trabajo. Para cambiar a la rama de prueba:
$ git checkout Prueba
$ git commit -a -m "Rama para pruebas de código"
$ git log --oneline --decorate

Puedes ver en el log que ahora HEAD apunta a Prueba por lo que todos los cambios será en paralelo con la master noafectando a esta. Es decir los cambios que hagas en cualquier archivo no afectan a la rama principal.
Prueba a cambiar el Hola.java de añadiéndole la línea:
System.out.println("I have no more branches to commit, said the Ent");

A continuación haz el commit que le corresponda:
$ git commit -a -m “Llegan los Ents a Java”
$ git tag v0.5-Ent-Release

Si el experimento convence se puede juntar nuevamente con la rama principal o con otra cualquiera con 
$ git checkout master
$ git merge Prueba

Evidentemente este ejemplo es muy simple pero da una idea de la potencia del uso de ramas. Si quieres profundizar en este apartado se recomienda leer este enlace.

Repositorios remotos: GitHub

Uno de los puntos interesantes de Git y cualquier control de versiones, es la posibilidad de trabajo en equipo teniendo un repositorio al que pueden acceder varios programadores desde su equipo. De esta forma un programador tiene su repositorio local, tal cual lo hemos usado hasta ahora, y luego tiene un repositorio remoto al que vuelca sus modificaciones y desde el que vuelca al suyo modificaciones que hayan hecho terceros si están incluidos en el proyecto.
GitHub es precisamente un proveedor de repositorios Git para que distintos usuarios puedan compartir sus proyectos. El uso es gratuito siempre que sea público. Si se requiere un servidor privado se puede obtener con cierto coste. La web es: 
También existe para estudiantes un pack especial que permite repositorios privados y otras herramientas: https://education.github.com/pack
Conviene añadir además que aunque sea el más conocido, github no es el único servidor de repositorios git. Si necesitas, por ejemplo, uno gratuito pero que sea privado puedes usar:
La ventaja de este último es que para una pequeña empresa donde se desarrolle un proyecto privado con hasta 5 colaboradores la licencia es totalmente gratuita.
Si lo único que necesitas es clonar un proyectode GitHub (o de cualquier otro repositorio remoto), no es necesario tener cuenta. Simplemente ve a tu directorio de usuario y ejecuta:

Esto hace una réplica exacta del repositorio remoto en el directorio actual.
Cuando quieres gestionar tus propios proyecto o colaborar con otro (hacer un fork) es cuando sí debes crearte una cuenta. Conéctate y crea una.
Puedes probar el enlace: 
Que da un curso rápido de manejo de git/github.
Para disponer de un nuevo repositorio para un proyecto lo primero que debes hacer es crear el proyecto en tu cuenta de github para luego sincronizar los archivos locales con el remoto.
Suponiendo que tienes un repositorio denominado MiPrueba donde quieres enlazar el directorio actual ya gestionado por git (si no ejecuta un init primero) haz:

Esto establece un nombre (origin) para el repositorio remoto. Normalmente se suele dejar origin como nombre pero puede cambiarse.
Lo primero es sincronizar el remoto con el local mediante pull:
git pull origin master
Y luego cuando hayas trabajado para “volcar” el contenido
git push -u origin master

Ojo, esto debes probarlo con un repositorio remoto tuyo. Si lo haces contra el anterior te dará error pues no tienes permisos para modificarlo.
Este comando lo que hace es volcar tu rama principal (denominada master) local en el repositorio remoto (origin). El parámetro -u sirve para recordar estos nombres de forma que a partir del primer push simplemente haciendo 
git push

Ya reconoce origin y master. 
Con 
git remote -v

Puedes ver el remoto al que está enlazado (pueden ser varios).
Ve ahora a GitHub y modifica un archivo. Para que esto aparezca actualizado en el directorio local se debe hacer
git pull

Si no se usó previamente el parámetro -u se debe agregar además origin master.
Si no hay conflicto, aunque las modificaciones sean en ambos lados, se realiza la mezcla correctamente. Haz la prueba añadiendo en ambos lados una línea a un archivo.
Si intentas hacer un push te lo va a rechazar diciendo que primero has de incorporar los elementos remotos a tu repositorio local. Por tanto primero:
git pull

Verás que añade la línea remota a tu archivo.
Ahora al hacer el push se ajusta el remoto.
Si lo que cambia es una misma línea de forma distinta, al hacer pull informa del conflicto e indica que se deben reparar los problemas indicados (similar a los conflictos de las ramas). Si abres el archivo de conflicto verás la zona de conflicto. Algo así:
<<<<<<< HEAD
System.out.println("Bienvenido to de C#");
=======
System.out.println("Welcome to de Csharp");
>>>>>>> 77b72c16373b5d7a0bd291b50c4b217030f26f6a

Habría que corregir el conflicto. Como ya se indicó la parte de conflictos es compleja y por ahora no profundizaremos hasta que tengamos más soltura con git.

Epílogo

Y hasta aquí está el manejo básico de git y github. Si quieres aprender más te dejo algunas referencias que usé para este tutorial y que te pueden servir de ayuda.

¡Hasta el próximo post!

No hay comentarios:

Publicar un comentario