# Control de versiones de Git

En mis inicios programando siempre escuchaba algo sobre el control de versiones, era algo que veía un poco complicado y donde no me quería meter porque estaba más enfocado en aprender otras cosas. Lo simplificaba como en qué estado se encuentra mi proyecto y usar versión *alpha*, *beta*, v1.0.0... Lo que ahora veo como una etiqueta que no se altera. Esto me llevó a realizar cambios sin justificación y a perder mucho trabajo que no guardé.

<iframe id="twitter-widget-1" scrolling="no" frameborder="0" allowtransparency="true" allowfullscreen="true" class="" style="position: static; visibility: visible; width: 550px; height: 280px; display: block; flex-grow: 1;" title="Twitter Tweet" src="https://platform.twitter.com/embed/Tweet.html?dnt=false&amp;embedId=twitter-widget-1&amp;frame=false&amp;hideCard=false&amp;hideThread=false&amp;id=1342099157398544385&amp;lang=en&amp;origin=https%3A%2F%2Fpublish.twitter.com%2F%3Fquery%3Dhttps%253A%252F%252Ftwitter.com%252FFlavioAandres%252Fstatus%252F1342099157398544385%26widget%3DTweet&amp;theme=dark&amp;widgetsVersion=889aa01%3A1612811843556&amp;width=550px" data-tweet-id="1342099157398544385"></iframe>


En algún momento al conocer Git me sentí igual que Flavio y por los recuerdos me dio a escribir sobre Git para no olvidar de que vale la pena saber más de unos comandos.

<colors red blue green yellow lightblue purple darkyellow>

## ¿Qué es el control de versiones de Git?

El control de versiones es un **sistema que guarda los cambios en el tiempo** de uno o varios archivos. Se pueden revertir estos cambios a un estado anterior, lo que significa que si tenemos un problema, podemos comparar los cambios en el tiempo, ver quién modificó algo que pudiera causar el problema y poder cambiar el estado actual a uno donde no se presente el problema.

Git es un sistema de control de versiones que **almacena la información como un flujo de *snapshots*** de un sistema de archivos. Cada vez que hay un cambio o se guarda el estado del proyecto, Git toma una imagen de todos los archivos y crea una referencia a ese momento. En un futuro cambio, si un archivo no se modifica Git no vuelve a almacenar el archivo, sino que usa la referencia al momento anterior.

Git contempla tres estados:

- `modified`: Cuando un archivo cambia, pero no se ha enviado a la base de datos local de Git.
- `staged`:  Cuando se añade un archivo modificado para ser enviado en el siguiente `commit`.
- `commited`: Cuando los datos están almacenados en la base de datos local de Git.

## Configuración de git

Para usar Git lo primero que se debe de hacer es instalarlo, se obtiene desde la [página de descarga](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) siguiendo los pasos según el sistema operativo que tengas.

Una vez instalado podremos usar en nuestra consola el comando `git config --list` para ver la configuración de Git. Para modificar los datos para el entorno global usamos `git config --global <configuración> <valor>`.
Lo esencial a modificar tiene que ser el nombre y el email, que son las configuraciones que usan todos los `commits`.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">config --global user.name </span></span><span class="green">"MarcoMadera"</span>
<span class="purple">git</span> <span class="blue">config --global user.email </span><span class="green">"example@email.com"</span>

<span class="purple">git</span> <span class="blue">config --list</span>
http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
core.autocrlf=<span class="red">true</span>
core.fscache=<span class="red">true</span>
core.symlinks=<span class="red">false</span>
pull.rebase=<span class="red">false</span>
credential.helper=manager
core.editor=<span class="green">"C:\Users\marco\AppData\Local\Programs\Microsoft VS Code\Code.exe" </span><span class="blue">--wait</span>
user.name=MarcoMadera
user.email=example@email.com</code></pre>

## Uso básico

El flujo de trabajo en Git sigue el siguiente patrón:

1. Haces modificaciones en tu directorio de trabajo controlado.
2. Selecciona los cambios a añadir en el estado *staged* para ser enviados con el siguiente `commit`.
3. Haces un commit, el cual toma todos los archivos en estado *staged* y almacena la *snapshot* en la base de datos de Git.

Para tener un directorio nuevo de trabajo controlado, en la terminal se dirige a la ruta del proyecto para **inicializar el repositorio** con el comando `git init`. Esto creará el archivo `.git` en la raíz del proyecto, donde se guardará la información de cada *snapshot*.

<pre><code data-lang="CLI"><span class="purple">cd</span> <span class="blue">repositorios/gitpost/</span>
<span class="purple">git</span> <span class="blue">init</span>
Initialized empty Git repository in C:/Users/marco/repositorios/gitpost/.git/</code></pre>

Para no ir a ciegas comando tras comandos podemos **revisar el estado** con `git status`. Nos informará dónde estamos, sobre el estado de los archivos, si se han modificado, agregado o eliminado.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">status</span>
On branch master
Your branch is up-to-date with <span class="green">'origin/master'</span>.
Untracked files:
  (use <span class="green">"git add &lt;file&gt;..."</span>. to include in what will be committed)
  <span class="red">README</span>
nothing added to commit but untracked files present (use <span class="green">"git add"</span> to track)</code></pre>

Para **agregar archivos al *stage*** usamos `git add <archivo>`, `git add *` o `git add .` agrega todos los archivos en el *stage*, `git add *.<extensión>` agrega los archivos con la extensión especificada y `git add /<folder>` agrega todos los archivos dentro de la carpeta especificada.

Si nos equivocamos, para **eliminar archivos del *stage*** sin borrarlo de nuestro directorio usamos `git rm --cached <archivo>`. El *flag* `--cached` hace que no se elimine de nuestro directorio, si no se agrega se eliminaría también de nuestro directorio de trabajo. Si olvidamos agregar el flag no está todo perdido, podemos recuperar el archivo con `git restore <archivo>`.

Para **Ignorar archivos** se crea un archivo `.gitignore` en la raíz del proyecto. Esto hace que todos los archivos que coincidan dentro de `.gitignore` no sean tomados en cuenta para ninguna acción con Git. Puedes revisar la [colección de archivos de .gitignore](https://github.com/github/gitignore) para ver ejemplos o usarlos en tus proyectos.

Ya teniendo todo lo que queramos para **guardar los archivos** usamos `git commit`. Esto abrirá el editor que definimos en la configuración para poner un mensaje descriptivo o igual lo podemos añadir en la consola con el *flag* `-m`. Para añadir archivos que ya habían estado en el *stage* usamos el *flag* `-am` que es la combinación de `-a --add` y `-m --message`.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">commit -m</span> <span class="green">"&lt;mensaje descriptivo&gt;"</span>
<span class="purple">git</span> <span class="blue">commit -am</span> <span class="green">"&lt;mensaje descriptivo&gt;"</span></code></pre>

Mostrar el historial de commits del repositorio usamos `git log`, el resultado de este es un poco feo, por lo que se puede hacer más bonito con el *flag* `--pretty`. `git log` solo mostrará por defecto el historial por debajo de la rama.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">log</span>
<span class="yellow">commit ed3946555db4597294bae2014cfe996b88268bef (<span class="lightblue">HEAD -></span> <span class="green">master</span>, <span class="red">origin/master</span>)</span>
Author: MarcoMadera &lt;example@email.com&gt;
Date:   Mon Jul 6 17:09:50 2020 -0500

    hola mundo

<span class="yellow">commit e150e0079854fa6a5996db6ee692fc1377a1f2ff</span>
Author: MarcoMadera &lt;example@email.com&gt;
Date:   Mon Jun 29 19:28:42 2020 -0500

    hello world

<span class="purple">git</span> <span class="blue">log --oneline</span>
<span class="yellow">65b5a12 (<span class="lightblue">HEAD -></span> <span class="green">master</span>, <span class="red">origin/master</span>)</span> hello world
<span class="yellow">fd14a30</span> hola mundo

<span class="purple">git</span> <span class="blue">log --pretty=format:</span> <span class="green">"%h | %cn | %cr | %s"</span>
<span class="yellow">65b5a12</span> | MarcoMadera | 10 minutes ago | hello world
<span class="yellow">fd14a30</span> | MarcoMadera | 11 minutes ago | hola mundo</code></pre>

Para **mostrar las diferencias entre un commit y otro** de un archivo lo hacemos con el comando `git diff`. `git diff` muestra la diferencia por defecto de lo que has puesto en el *stage* y lo que vas a hacer commit. Muestra las líneas exactas que fueron añadidas o removidas. El comando puede ser selectivo usando el hash de cada commit a comparar.

Usar `git diff` no suele ser muy placentero de ver para archivos largos, se puede explorar el uso de `git difftool` para configurar una herramienta más gráfica para estos casos.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">diff commitA commitB</span>

diff <span class="blue">--git</span> <span class="lightblue">a/index.js b/index.js</span>
index 5e1c309..ade1f58 100644
<span class="red">---</span> <span class="lightblue">a/index.js</span>
<span class="green">+++</span> <span class="lightblue">b/index.js</span>
@@ <span class="red">-</span>1 <span class="green">+</span>1 @@
<span class="red">-</span>Hello World
<span class="green">+</span>Hola Mundo

<span class="purple">git</span> <span class="blue">difftool commitA commitB</span>
Hello World       | Hola Mundo
~                 | ~</code></pre>

## El modelo de ramas

Se puede decir que Git tiene tres árboles donde se agrupan archivos. `HEAD` es el indicador del último *commit* realizado y de la rama actual. `Index` es el espacio donde se agregan/modifican/eliminan los archivos del antes mencionado *stage* antes de realizar un commit. Finalmente está el directorio de trabajo manejado como el `Working tree`

Al realizar el comando `git init` Git crea una rama por defecto que suele ser *master*. Esta no es una rama especial, es como cualquier otra con el detalle que es la inicial, la que Git crea por defecto. Nuestro proyecto puede seguir cualquier rama como principal en cualquier punto.

Cuando un *commit* es creado es mandado al `HEAD`, la rama actual, donde Git guarda la información de los cambios una única vez, lo demás son referencias con cambios, no se vuelve almacenar nada ya creado. Con esto Git permite crear copias de nuestro proyecto en un estado en formas de referencias y experimentar con ellas todo lo que queramos sin haber otro coste más que los nuevos cambios. Estos grupos de referencias en un estado son llamadas ramas que igual tendrá solamente un identificador propio.


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1614037391327/gvlg4kBLn.png)

Ahora que tenemos una idea de lo que son las ramas pasemos al manejo de ellas. Para crear una rama usamos `git branch <nombre de la rama>`, esto creará un indicador llamado `HEAD` que apuntará a la rama en la que estamos para ubicarnos mejor, en este caso aún seguiríamos en la rama *master*. Para cambiar de ramas usamos `git switch <nombre de la rama>` o `git checkout <nombre de la rama>`, esto moverá el apuntador `HEAD` a la nueva línea en la que estaremos trabajando.

En algún punto las ramas pueden volver a unirse a la rama principal o a otra rama, como se muestra en la gráfica anterior. Todo lo que tienes que hacer es ir a la rama donde se van a hacer los cambios y usar `git merge <nombre de rama>`. Git creará una nueva *snapshot* de los cambios y un nuevo *commit* de referencia especial porque tendrá dos ancestros directos.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">checkout master</span>
Switched to branch <span class="green">'master'</span>
<span class="purple">git</span> <span class="blue">merge &lt;nombre de rama&gt;</span>
Merge made by the <span class="green">'recursive'</span> strategy.
index.js | 1 <span class="green">+</span>
1 file changed, 1 insertion(<span class="green">+</span>)
create mode 100644 index.js</code></pre>

No todo es tan bonito siempre. Al momento de unir ramas, si cambias la misma parte del mismo archivo en las dos ramas que se han unido ocurrirá un conflicto, Git no podrá unirlas tan fácil, te indicará dónde está el conflicto y te pedirá que lo arregles.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">merge &lt;nombre de rama&gt;</span>
Auto-merging index.js
<span class="red">CONFLICT</span> (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.</code></pre>

Esto de unir ramas se puede volver un caos y nosotros podemos saber el trabajo que conlleva, pero ¿se lo queremos presentar al público así?. Al final lo que la gente ve es el resultado final y si alguien quiere ver como lo hiciste puede que quieras mostrar algo más coherente.

Se puede aplicar a lo que estás leyendo, duro tres días haciendo el artículo porque escojo el tema, investigo un poco, hago el borrador, creo las imágenes si las necesito, escojo los colores y reviso las faltas de ortografía. Lo primero que hice es hacer la gráfica de las ramas, pero si alguien ve el historial puede que no tenga sentido para nada.

El historial de commits es tal y lo que pasó, cambiar este historial sería cambiar el historial de cómo el proyecto fue construido. Los errores son parte de la historia del proyecto y son necesarias incluso para estudiar las soluciones.

El uso de `git rebase` puede ir en contra de esto porque coloca la base de una rama de un commit a otra rama diferente, pero en algunos casos puede ser útil, simplemente porque tal vez no quieras tener una rama completa para un cambio tan pequeño. Una vez ya acabado el trabajo de unir ambas ramas se puede eliminar la rama porque ahora está apuntando a la rama principal, por lo que ya no sería necesaria. Para eliminar ramas se hace con `git branch -d <nombre de rama>`.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">checkout &lt;nombre de rama&gt;</span>
<span class="purple">git</span> <span class="blue">rebase master</span>
Successfully rebased and updated refs/heads/&lt;nombre de rama&gt;.

<span class="purple">git</span> <span class="blue">checkout master</span>
<span class="purple">git</span> <span class="blue">merge &lt;nombre de rama&gt;</span>
<span class="purple">git</span> <span class="blue">branch -d &lt;nombre de rama&gt;</span>
</code></pre>

## Repositorio Remoto

Todo lo que hemos estado trabajando es sobre el repositorio local de nuestro proyecto. Para que otras personas lo vean y colaboren con él, podemos usar los servicios remotos como GitHub, Bitbucket y GitLab; son servicios que permiten la gestión de proyectos y el seguimiento de trabajo con otros desarrolladores.

Para subir un repositorio local a uno remoto, tendremos que crear un repositorio en algunos de los servicios en el cual obtendremos una *url* del nuestro proyecto, con la que podremos utilizar `git remote add <nombre indentificador> <url>`

Otra forma de obtener un repositorio es **clonar un repositorio remoto** con `git clone <url> <nombre>`. Con esto ya tendremos en nuestro directorio local una copia con la que podremos contribuir al proyecto o simplemente experimentar con su funcionamiento.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">clone</span> <span class="lightblue">https://github.com/MarcoMadera/Blog.git</span> <span class="blue">GitPost</span>
Cloning into <span class="green">'GitPost'</span>...
remote: Enumerating objects: <span class="darkyellow">64</span>, done.
remote: Counting objects: <span class="darkyellow">100%</span> (<span class="darkyellow">64</span>/<span class="darkyellow">64</span>)</span>, done.
remote: Compressing objects: <span class="darkyellow">100%</span> (<span class="darkyellow">45</span>/<span class="darkyellow">45</span>), done.
remote: Total <span class="darkyellow">64</span> (delta <span class="darkyellow">32</span>), reused <span class="darkyellow">45</span> (delta <span class="darkyellow">17</span>)</span>, pack-reused <span class="darkyellow">0</span>
Unpacking objects: <span class="darkyellow">100%</span> (<span class="darkyellow">64</span>/<span class="darkyellow">64</span>), <span class="darkyellow">74.53 KiB</span> | <span class="darkyellow">19.00 KiB/s</span>, done.</code></pre>

Después de usar `git clone`, si usamos `git remote` veremos que tendremos *origin*, este es el nombre para identificar la *url* que Git le pone por defecto a los proyectos obtenidos por `git clone`. Esto sucede al igual que al inicializar un proyecto, Git por defecto crea la rama con nombre *master*. Se puede **renombrar el identificador** con el comando `git remote rename <origin en este caso> <nuevo nombre>`

Ahora que tenemos un repositorio remoto, puede que el contenido del remoto sea diferente por cambios de otros colaboradores. En nuestro repositorio local no se verán reflejados esos cambios. Para actualizar nuestro repositorio local podemos usar `git pull`. Trae los cambios generalmente del servidor al que se clonó y hace un `merge` automático en nuestro repositorio local. Para indicar otro servicio remoto y rama se usa `git pull <remoto> <rama>`

Cuando ya hemos hecho commit de los cambios que queremos compartir en nuestro repositorio, para actualizar el repositorio remoto usamos `git push <remoto> <rama>`, si alguien ya hizo un `git push` antes, nuestros cambios serán rechazados por lo que siempre es bueno hacer `git pull` antes.

## Comandos de Git adicionales

Si no quieres escribir el comando completo cada vez, puedes fácilmente configurar un *alias* para cada comando. Los *alias* en Git nos permiten **crear *shortcuts***, a través de `git config --global alias.<atajo> comando`.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">config --global alias.&lt;atajo&gt; <span class="green">"&lt;comando&gt;"</span></span>
<span class="purple">git</span> <span class="blue">config --global alias.st <span class="green">"status"</span></span>
<span class="purple">git</span> <span class="blue">git config --global alias.slog <span class="green">"log --pretty=format:'%h | %cn | %cr | %s'"</span></span>
</code></pre>

Si ya hicimos commit y olvidamos **añadir un archivo o enmendar algún cambio** podemos hacerlo con el comando `git commit --amend`. Igualmente si nos equivocamos en la descripción de nuestro commit lo podemos arreglar con el mismo comando si lo invocamos inmediatamente después de haber ocurrido el error.

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">commit -m <span class="green">"&lt;mensaje&gt;"</span></span>
<span class="purple">git</span> <span class="blue">add &lt;archivo&gt;</span>
<span class="purple">git</span> <span class="blue">git commit --amend</span>
</code></pre>

Git mantiene un *log* de dónde el `HEAD` y sus referencias han estado. Lo podemos ver con el comando `git reflog` y mostrar más al respecto con `git show HEAD@{<número o referencia en días>}`

<pre><code data-lang="CLI"><span class="purple">git</span> <span class="blue">reflog</span>
<span class="yellow">2673d2d (<span class="lightblue">HEAD -></span> <span class="green">master</span>, <span class="red">origin/master</span>)</span> HEAD@{0}: merge newbranch: Merge made by the <span class="green">'recursive'</span> strategy.
<span class="yellow">4e85459</span> HEAD@{1}: checkout: moving from master to master
<span class="yellow">4e85459</span> HEAD@{2}: commit: hola mundo
<span class="yellow">8c821a7</span> HEAD@{3}: checkout: moving from newbranch to master

<span class="purple">git</span> <span class="blue">show HEAD@{2}</span>
<span class="yellow">commit ed3946555db4597294bae2014cfe996b88268bef</span>
Author: MarcoMadera &lt;example@email.com&gt;
Date:   Mon Jul 6 17:09:50 2020 -0500

    hola mundo

diff <span class="blue">--git</span> <span class="lightblue">a/index.js b/index.js</span>
index 5e1c309..ade1f58 100644
<span class="red">---</span> <span class="lightblue">a/index.js</span>
<span class="green">+++</span> <span class="lightblue">b/index.js</span>
@@ <span class="red">-</span>1 <span class="green">+</span>1 @@
<span class="red">-</span>Hello World
<span class="green">+</span>Hola Mundo
</code></pre>

Para **crear una rama y cambiar directamente** se usa el comando `git checkout -b <nombre de rama>` o `git switch -c <nombre de rama>`. Ahora si quieres volver a la rama anterior se puede usar `git switch -`.

Para **cambiar el nombre de una rama** se usa `git branch --move <rama> <nuevo nombre>`. Para enviar los cambios al repositorio remoto `git push --set-upstream <remoto> <nuevo nombre>`. Para eliminar la rama anterior del repositorio remoto utilizamos `git push origin -d <rama>`.

Cuando estamos trabajando, pero queremos cambiar de rama y no hacer un commit de un trabajo incompleto, usamos `git stash`. Guarda los commits en un estado diferente para poder recuperarlo después con `git stash apply`.

Git tiene una **interfaz gráfica integrada** que podemos utilizar con el comando `gitk` para ver el historial y `git-gui` donde puedes preparar los commits y experimentar lo visto.

## Conclusión

Git nos proporciona una manera elegante de hacer el seguimiento de versiones, nos permite hacer un resguardo y hacer posible la colaboración entre varias personas teniendo espacios de trabajo separados como el local y el remoto. Git también se puede implementar en interfaces gráficas y tiene muchos comandos que no se han tocado en este artículo a fondo, por lo que te invito a investigar más sobre el tema.
