Git: how to salir de esta, ramas
Tags: git, howto, ramas, introduccion
Published: 2021-07-19 15:23:47

## How to

* [Introducción](#introducción)
* [Orientarse dentro de una rama](#orientarse-dentro-de-una-rama)
* [Renombrar una rama](#renombrar-una-rama)
* [Recuperar una rama borrada por error](#recuperar-una-rama-borrada-por-error)
* [Git merge, ese comando problemático](#git-merge-ese-comando-problemático")
* [Mover commits](#mover-commits)
* [Mover ramas](#mover-ramas)
* [Deshacer un merge](#deshacer-un-merge)
* [¿Cómo integrar una rama?](#cómo-integrar-una-rama)
* [HEAD detached o desacoplado](#head-detached-o-desacoplado)

## Introducción

Con un GUI

![Ramas vistas en un GUI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmg19wglikjeo4gmbzuh.png)

O por consola.

```bash
$ git log --graph --format='%C(auto)%h %s%n%C(auto)%d%n' --all 
```

![Ramas vistas en consola](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q294qcmw6oi2l4dk8r4z.png)

Una forma de visualizar los commits de un repositorio es en un árbol, donde los commits son los nudos de las ramas. 

De un commit  pueden salir uno o varios commits (por ejemplo, de *"todos: concilio del anillo"* sólo sale el commit *"todos: moria"*, mientras que de *"todos - gandalf: amon hen"* salen tres, *"aragorn + legolas + gimli: siguiendo a los uruk-hai..."*, *"pipin + merry: camino a isengard"*  y *"frodo + sam: camino a mordor"), y* los commits se pueden unir de dos en dos (por ej. *"aragorn + legolas + gimli + gandalf: fangorn"* es la unión de los commits *"aragorn + legolas + gimli: siguiendo a los uruk-ahi" y "gandalf: cayendo con el balrog").*

Cuando el commit es la unión de dos commits se llama *"merge commit"*, ya que es el resultado de  `git merge`  o `git pull` (sí, porque `git pull` hace merges por debajo), mientras que el resto de los commits suelen ser resultado de `git commit`, aunque también serlo de `git revert` o de `git merge --squash`.

Las ramas son referencias a commits, referencias que cambian al realizar acciones como `git commit` o  `git merge` estando en esa rama (por ej. la rama *"pippin-merry"* estaba en el commit *"pippin + merry: camino a insengard"*, y se modifico automáticamente para referenciar a *"pippin-merry: concilio de los ents"* cuando se hizo dicho commit). 

Múltiples ramas pueden hacer referencia al mismo commit (por ej. las ramas *"gandalf"* y  *"aragorn-legolar-gimli"* hacen referencia al commit `872cac9`).

Este post va a lidiar **solo con casos que pueden surgir al trabajar con ramas locales**, es decir, aquellas ramas creadas localmente y que todavía no se han subido a un repositorio remoto. 
Para manipular ramas en repositorio remoto con `git pull` o sincronizar las ramas locales con las ramas del repositorio remoto  `git push`, hay que tener en cuenta una serie de cosas que se tratarán en el siguiente post.

## Orientarse dentro de una rama

*¿Cómo obtengo el commit anterior?; ¿Cuál era el commit antes del merge?*

`HEAD` siempre indica el commit actual y `HEAD~` siempre indica el commit anterior.
Refiendose a ramas, `master` indica el último commit de la `master` y `master~` indica el commit anterior.
Y refiriendose a commits, `872cac9` indica el commit `872cac9` y `872cac9~` indica el commit anterior.
Estas referencias facilitan las operaciones más o menos cotidianas como pueden ser el deshacer el commit actual; `git reset HEAD~` es mucho más rápido que `git log` para obtener el hash del commit anterior y luego hacer `git reset <hash>`.

Ya a modo de curiosidad, `~` es una *"referencia por ancestro"* que indica el commit padre. Se pueden añadir todos los `~` que se quieran para obtener referencias a commits anteriores; `HEAD~` referencia al commit *"padre"*; `HEAD~~`, o `HEAD~2` referencian al commit *"abuelo"*; `HEAD~3` referencian al *"bisabuelo"*; etc.

También existen `^1`, o simplemente `^`, y `^2`, para hacer referencias a los commits implicados en  *merge commits* ; si `HEAD` es un merge commit, `HEAD^` es equivalente a `HEAD~`, e indica el commit en el que se estaba cuando se hizo `git merge`; `HEAD^2` indica el último commit de la rama con la que se hizo merge.
Y se pueden combinar para hacer cosas como `HEAD^2~2`.

## Renombrar una rama

*Corregir un texto incorrecto; se copio la clave del issute tracker que no era.*

En un GUI se puede hacer click derecho en el nombre de la rama y click en *"Rename".*

Si es por consola hay que usar `git branch -m`.

```bash
# git status para ver la rana actual.
$ git status
	En la rama fature
	nada para hacer commit, el árbol de trabajo está 
        limpio

# O git status para ver todas las ramas, 
# con un * delante de la rama actual.
$ git branch
	  dovolop
	* fature
	  master

# Renombrar la rama actual.
$ git branch -m feature

# Renombrar otra rama.
$ git branch -m dovolop develop

$ git branch
	  develop
	* feature
	  master
```

Estas acciones solo renombran la rama local y no afectan al nombre de la rama en el repositorio remoto; si hay que renombrar una rama en el repositorio hay que tener en cuenta más detalles que se explicará en el post de repositorios remotos.

## Recuperar una rama borrada por error

*Me equivoque al poner el nombre de la rama cuando ejecute el comando; pensaba que sólo era pruebas, pero no...; la he borrado antes de subirla al repositorio remoto; pensaba que ya había subido todo al repositorio remoto, pero faltaba un commit.*

Para borrar una rama en un GUI, click derecho en el nombre de la rama y *"Delete"*. 
Algunos GUI, tras borrar una rama, dan la opción de recuperarla.

![GUI restaura rama](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2u6xew90iydbx6xnl25.png)

Si el mensaje es *"Unmerged commits were discarted"*,  es recomendable prestarle atención, porque puede ser uno de los casos en los que interese recuperar la rama.

Se pueden borrar ramas de dos maneras:

- `git branch --delete <rama>` o `git branch -d <rama>`:  Git comprobará si `<rama>` esta mergeada en la rama actual, y de no estarla, no permitirá borrar la rama, evitando así que puedan perderse commits.
- `git branch --delete --force <rama>` o `git branch -D <rama>`:  borra la rama, este o no mergeada en la rama actual.

Como norma general es preferible usar `git branch -d` para ir sobre seguro, y `git branch -D` solo para esos casos en los que se esta completamente seguro.

Ya en el tema del HOW TO, es posible recrear la rama con  `git branch <rama> <hash>`, donde `<hash>` indicaría el último commit de la rama en el momento de borrarla. 
Si no se conoce ese hash y la rama se borro hace poco, `git reflog` puede ser una buena opción para buscarlo, ya que muestra un histórico de todos los commits por los que se ha pasado. En [Deshacer un git reset](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#deshacer-git-reset) hay más detalles sobre `git reflog`.

Si la rama se borro hace  mucho tiempo, `git reflog` puede no ser la mejor opción, ya que el hash que se busca puede estar enterrado entre muchos otros. Documentándome para el post he visto opciones mejores como [esta](https://stackoverflow.com/a/22303923/1587302) o [esta](https://gist.github.com/jbgo/1944238), pero personalmente no me ha surgido el caso de tener que recuperar una rama borrada hace semanas.

¿Y si la rama se ha borrado en un repositorio remoto? Tras recrear la rama en local habría que subirla al repositorio.

Haciendo un ejemplo práctico:

```bash
$ git branch -vv
	  feature/create-home   6748ee2 crear hoja de estilos                             home.css
	  feature/setup-project 9e75aa0 añadido .gitignore
	* master                9e75aa0 añadido .gitignore

# Como la rama feature/setup-project está mergeada en master
# se borra sin problemas.
$ git branch -d feature/setup-project 
	Eliminada la rama feature/setup-project (era 9e75aa0)..

# Como la rama feature/create-home no está mergeada en master
# borrarla con -d da error.
$ git branch -d feature/create-home 
	error: La rama 'feature/create-home' no ha sido fusionada completamente.
	Si estás seguro de querer borrarla, ejecuta 'git branch -D feature/create-home'.

# Si se fuerza el borrado si que deja.
$ git branch -D feature/create-home 
	Eliminada la rama feature/create-home (era 6748ee2)..

# Pero me doy cuenta de mi error :(

# Así que busco el commit.
$ git reflog 
	...
	9e75aa0 (HEAD -> master) HEAD@{6}: checkout: moving from feature/create-home to master
	

# Y recreo la rama.
$ git branch feature/create-home 6748ee2

$ git branch -vv
	  feature/create-home 6748ee2 crear hoja de estilos home.css
	* master              9e75aa0 añadido .gitignore
```

## Git merge, ese comando problemático

"*git merge" cada vez hace una cosa distinta; después de hacer "git merge" la aplicación ya no funciona.*

`git merge` puede hacer cosas distintas dependiendo de las diferencias que haya entre la rama actual y la rama que se quiere mergear.

El siguiente diagrama de flujo resume la lógica que hay por debajo.

![diagrama flujo de git merge](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/369txvabk90k0698k5xd.png)

Es *"fast forward"*  cuando  la rama que se quiere mergear, `target`,  ha partido del último commit de la rama actual; en este caso `git merge` simplemente modificará la referencia de la rama actual para que apunte al mismo commit que la rama `target`.

Por poner un ejemplo, supongamos que se quiere mergear la rama `feature/create-home` en la rama `master`;  dado que `feature/create-home` parte de `master` `git merge` por defecto hará *fast forward*.

![git merge fast forward antes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64ypccsf4nig57mbxlp8.png)

```bash
$ git status
	En la rama master

$ git merge feature/create-home
	Actualizando 85b13b4..82975ee
	Fast-forward
	 home.css  | 3 +++
	 home.html | 2 ++
	 2 files changed, 5 insertions(+)
	 create mode 100644 home.css
	 create mode 100644 home.html
```

![git merge fast forward despues](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d8dri52hslhogll5xccf.png)

Si no es *fast forward* , y no hay conflictos, `git merge` creará un *merge commit.*

![git merge merge commit antes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7v4q3hvxt1zo8t0x8f6m.png)

```bash
$ git status
	En la rama master

$ git merge feature/create-home
   # Se abre un editor de texto para poder personalizar el 
   # mensaje del commit.
   # Tras guardar el mensaje:
	Merge made by the 'recursive' strategy.
	 home.css  | 3 +++
	 home.html | 2 ++
	 2 files changed, 5 insertions(+)
	 create mode 100644 home.css
	 create mode 100644 home.html
```

![git merge merge commit después](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfik953fkzcvdcrklow8.png)

Si no es *fast forward* , y  hay conflictos... bien, es un momento que a nadie le gusta.
Los conflictos surgen cuando en las dos ramas se han tocado los mismos trozos de código, ya sea editándolos en ambas ramas, modificando el archivo en una y renombrándolo en la otra, etc.
Cuando surjan, Git modificará los archivos con conflictos para indicar dónde están y esperará a que se resuelvan, pero no hará nada más.  Será el momento de analizar que ha pasado y resolver los conflictos. 
Y siempre se puede  ejecutar `git reset --hard` para hacer tabla rasa y empezar de cero, porque hay veces en que los conflictos se resuelven y los tests dejan de pasar, o simplemente porque los conflictos son complicados y se prefiere empezar fresco después de una pausa.

Los conflictos se verán así en el archivo

```bash
<<<<<<< HEAD
<a href="contact.html">Contacto</a>
=======
<h1> Home </h1>
<p> bienvenido </p>
>>>>>>> feature/create-home
```

Donde entre `<<<<<<<<` y `=======` esta lo que hay en la rama actual, y entre `========` y  `>>>>>>` lo que hay en la rama a mergear.

Para estos casos lo más cómodo es recurrir a un GUI:

![git merge resolver conflictos antes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xv9nfaobwqhe7abhj67c.png)

Una vez resuelto

![git merge resolver conflictos despues](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4g0fetee0848ovb5mqe.png)

Ya se podrá hacer commit:.

```bash
$ git merge feature/create-home 
	CONFLICTO (add/add): Conflicto de merge en home.html
	Auto-fusionando home.html
	Fusión automática falló; arregle los conflictos y luego realice 
        un commit con el resultado.

# Resolver conflictos.

$ git commit -a
	[master 1814deb] Merge branch 'feature/create-home'
```

![git merge merge commit tras conflictos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9wwfahfr0su75dg96bw.png)

Cuando haya un *merge commit* de por medio siempre hay que tener cuidado, ya que si bien puede no haber conflictos si que se pueden haber introducir errores. 
Si por ejemplo en la rama actual se renombrase la función `doThings` a `createUser`, junto con todos sus usos, y se mergease una rama que ha introducido un nuevo uso de `doThings` en un nuevo archivo, `git merge`  puede no encontrar conflictos y mergear automáticamente,  pero cuando se invoque `doThings` en el nuevo archivo se producirá un error, ya que `doThings` ha dejado de existir. 
Para evitar estos problemas se pueden usar varias estrategias:

- Ejecutar los tests después de cada merge, y si hay algún problema hacer commits con los fixes.
- Crear un *[hook](https://git-scm.com/docs/githooks)* que impida  completar el merge si fallan los tests.
- Usar algunas de las opciones de `git merge` para controlar como se hace el merge:
    - `--ff-only`  para indicar que solo se quiere hacer merge si es  *fast forward*, fallando en caso contrario ( por ej. `git merge --ff-only feature/create-home`).
    Puede ser útil para actualizar ramas importantes como `master` con total tranquilidad.
    - `--no-commit` fuerza a que si el merge implica un *merge commit*, Git se detenga justo antes de hacer el commit, como haría si hubiese conflictos; en ese momento se podrán revisar los cambios, ejecutar los tests, arreglar alguna cosilla su fuese necesario, y finalmente, hacer el commit. 
    Si el merge es  *fast forward* este se hará automáticamente usando la estrategia de *fast forward*, ignorando el flag `--no-commit`.

## Mover commits

*He hecho un commit en la rama que no era; se me olvido crear la rama antes de hacer los commits.*

Lo primero sería llevarse los commits a la rama adecuada.
Si la rama en la que se deberían haber hecho los commits no existe, es tan sencilla como crearla en el punto actual: 

- Si se usa un GUI, click derecho en la rama actual y crear una nueva rama.
- Si se usa consola `git branch <rama>`.

Si la rama ya estaba creada, basta con ir al ella y hacer [`git cherry-pick`](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#como-compartir) de los commits que se quieren mover.

Y los segundo y último es [restaurar la rama](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#borron-cuenta-nueva) en la que se han hecho los commits por error al punto correcto .

Al final muchas situaciones en Git se resuelven combinando varias acciones. 

## Mover ramas

*Saque la rama del hotfix de mi rama de feature en lugar de sacarla de master; saque mi rama de feature de master en lugar de la rama de integración.*

Un ejemplo de esta situación sería sacar la rama `hotfix` de la rama de integración `develop`, en lugar de haberlo hecho de la rama principal `master`.

Si no se ha hecho ningún commit bastaría con modificar la rama `hotfix` para que parta de `master`:

- Si se usa un GUI, click derecho en `master` y click en  *"Reset current branch to here"* o similar.
- Si se usa consola `git checkout -B hotfix master`.

Si ya se ha hecho algún commit, la opción que menos comandos requiere es `git rebase`, aunque hay que visualizar muy bien lo que se quiere hacer antes de usarlo.
Otra opción algo más laboriosa sería:

1. Apuntarse el hash de los commits que han hecho en la rama `hotfix`.
2. Resetear la rama `hotfix` para que apunte al punto correcto con *"Reset current branch to here"* si se una GUI o `git checkout -B hotfix master` si se usa consola.
3. Recuperar los commits con apuntados en el paso 1 con `git cherry-pick`

La forma por defecto de `git rebase` es `git rebase <rama-destino> <rama-a-mover>`, o, si ya se está en `<rama-a-mover>`, simplemente `git rebase <rama-destino>`; haciendo un símil, sería como "trasplantar la rama".

![git rebase sencillo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2cjtaqaprhorwljmfrj.png)

En el ejemplo, `git rebase master hotfix`  movería los commits de la rama `hotfix` para que salgan del commit `G` en lugar del commit `E` .

(Como nota aclaratoria, añado un apostrofe para indicar que el commit con hash `A'`  es una copia `A` pero con distinto padre; decir "mover" no es del todo correcto, pero es una [simplificación  bastante útil](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#introduccion)).

Por defecto `git rebase`  mueve  todos los commits de  `<rama-a-mover>` que no tiene `<rama-destino>`, incluso aunque alguno de esos commits estén compartidos con otra rama.

![git rebase complejo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v36dcjpqa82kebqr2tba.png)

En el ejemplo, `git rebase master hotfix` movería los commits `E y F`, que solo tiene  `hotfix`, y copiaría los commits `C, D y F`, que `hotfix` comparte con la rama  `develop` y que no están en en `master`.

Si el algún momento se llegasen a mergear las ramas `hotfix` y `develop` en `master` no habría conflictos porque, aunque los commits `C', D' y F'` estén duplicados en la rama  `develop`, Git detectaría que son lo mismo.

Se puede indicar de una manera más precisa los commits que hay que mover con el comando
`git rebase --onto <rama-destino> <desde-la-rama> <rama-a-mover>`

![git rebase onto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v36dcjpqa82kebqr2tba.png)

En el ejemplo `git rebase --onto master develop hotfix`  movería solo los commits  `E y F`, que están en la rama  `hotfix` y salen de la rama `develop` .

Como se decía al principio, `git rebase` es lo que menos comandos requiere, pero en contra tiene que lleva su tiempo visualizarlo.

Otra nota sobre `git rebase` es que puede implicar resolver conflictos varias veces si las ramas de origen y destino son muy distintas; si eso ocurre, siempre se puede dar marchar atrás con  `git rebase --abort` y replantarse la estrategia.

## Deshacer un merge

*Un merge que produce un error; un merge hecho donde en una rama que no era; un merge hecho con la rama que no era.*

Si se acaba de hacer el merge y no se ha subido a un repositorio remoto lo más sencillo es [revertir la rama al commit](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#borron-cuenta-nueva) en el que estaba antes del merge.

Si se han hecho commits después del merge o ya se han subido al repositorio remoto, se pueden deshacer con `git revert`.

Si el merge fue *fast forward*, habría que hacer un `git revert` de cada uno de los commits que se han añadido con el merge, empezando por el más reciente.
Más sobre como revertir commits simples en [Deshacer un commit: como revertir un cambio](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#revertir-commit).

Si el merge implico un *merge commit* se puede usar `git revert -m1 <hash>`, donde `-m1` indica que se desean deshacer los cambios que vinieron de `<rama>` cuando se hizo  `git merge <rama>`.

También se puede usar, aunque es menos común, `git revert -m2 <hash>` para indicar que se desean deshacer las diferencias que había en la rama en la que se hizo `git merge <rama>` con  respecto de `<rama>`, para que tras el `git revert` se tenga lo mismo que en `<rama>`.

Estos comando generarán un commit con los cambios para deshacer el *merge commit*.

Con un ejemplo práctico.

```bash
# Acabo de hacer un merge y lo quiero deshacer.

# Hago git log -1 para ver la información del último commit.
# 'Merge: 6512051 6748ee2' me dice que 
# estaba en el commit 6512051 cuando hice el merge 
# de otra rama que a su vez estaba en el commit 6748ee2.
$ git log -1
	commit 7a12efcf980753856b67b72b040a73a93cdb3c9b (HEAD -> master)
	Merge: 6512051 6748ee2
	
	    Merge branch 'feature/create-home'

# Compruebo cuales son las diferencias entre el commit
# 6512051 y el commit en el que estoy.
$ git diff 6512051 HEAD 
	diff --git a/home.css b/home.css
	new file mode 100644
	index 0000000..6748ee2
	--- /dev/null
	+++ b/home.css
	...
	diff --git a/home.html b/home.html
	new file mode 100644
	index 0000000..6141d5d
	--- /dev/null
	+++ b/home.html
	...

# Hago el revert del merge indicando con -m1 que quiero
# tomar como referencia lo que había antes del merge.	
$ git revert -m1 7a12efcf980753856b67b72b040a73a93cdb3c9b
	[master f65033c] Revert "Merge branch 'feature/create-home'"
	 1 file changed, 2 deletions(-)

# log -1 me indica que hay nuevo commit.
$ git log -1 f65033c
	commit f65033c1cb53ab664482d9d7ee3f836a493e249f
	
	    Revert "Merge branch 'feature/create-home'"
	    
	    This reverts commit 7a12efcf980753856b67b72b040a73a93cdb3c9b, reversing
	    changes made to 651205183ba4da49663163b881901c74e8c98606.
	
# Compruebo los cambios y no hay diferencias.
$ git diff 6512051 HEAD
```

¿Qué ocurriría si se mergease de nuevo la rama cuyo merge se ha revertido? 
Si no se ha hecho ningún commit adicional en `<rama>`, `git merge <rama>`  dirá que "la rama ya está actualizada" y no hará nada, aunque quizá lo natural sería pensar que volvería a aplicar los cambios de `<rama>`.
Si se ha hecho algún commit adicional en `<rama>`, `git merge <rama>` solo aplicará los cambios de los nuevos commits; la "buena" noticia es que en `git merge` notificará unos conflictos bastante extraños que harán ver que algo raro pasa.
Todo eso resulta un poco contraintuitivo, pero  `git revert` sólo modifica archivos, no modifica el histórico de commits, así que cuando se le pide a Git que mergee commits que ya tiene en su histórico, se limita a ignorarlos porque ya los tiene.

¿Por qué alguien querría mergear de nuevo algo que acaba de revertir? 
Si un *merge commit* causa problemas en producción, una forma rápida de salir del paso es revertir ese *merge commit*, arreglar el problema en la rama y volver a mergearla.  
Pero como se sigue de la pregunta anterior no es algo directo.
En [https://github.com/git/git/blob/master/Documentation/howto/revert-a-faulty-merge.txt](https://github.com/git/git/blob/master/Documentation/howto/revert-a-faulty-merge.txt) se explican muy bien dos posibles maneras para hacerlo.

1. Resolver los problemas en la rama cuyo *merge commit* se ha revertido, revertir el commit que revirtió el *merge commit*  y por último mergear la rama con el fix. 
2. Replicar en una nueva rama todos los commits de la rama cuyo *merge commit* se ha revertido (los commits tendrían los mismos cambios, pero como tendrían un hash distinto para Git serían distintos), resolver los problemas en la nueva rama y mergearla.

![https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif](https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif)

## ¿Cómo integrar una rama?

*He terminado mi flamante primera feature, los tests están en verde, ha pasado las revisiones de código,  y ahora ¿como la integro?*

Supongamos que se quiere integrar la rama `feature/create-home` en la rama `master`.

![escenario integrar rama](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vskkjj8whcu9sgyz94pd.png)

Algunas de las estrategias más habituales son:

- Merge commit
- Merge squash
- Rebase
- Trunk base development

### Merge commit

Crear un *merge commit* cada vez que se mergee una rama en master: `git merge --no-ff <rama>`. 
`--no-ff` sirve para forzar que se cree un *merge commit*, aunque se pudiese hacer *fast forward.* 

```bash
# Me aseguro de que estoy en la rama master, 
# que es donde quiero hacer el merge.
$ git merge status 
	En la rama master
	nada para hacer commit, el árbol de trabajo está limpio

$ git merge --no-ff feature/create-home 
	Merge made by the 'recursive' strategy.
	 home.css  | 3 +++
	 home.html | 2 ++
	 2 files changed, 5 insertions(+)
	 create mode 100644 home.css
	 create mode 100644 home.html
```

![resultado integrar rama con merge commit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgmhfk8vhr7od7vk5yjc.png)

### Merge squash

Condensar todos los commits de la rama a mergear en un único commit: `git merge --squash <rama>`.

Después del merge será necesario un hacer `git commit`, cuyo mensaje por defecto será un sumario de todos los commits de `<rama>`, aunque se podrá personalizar.

```bash
# Me aseguro de que estoy en la rama master, 
# que es donde quiero hacer el merge.
$ git merge status 
	En la rama master
	nada para hacer commit, el árbol de trabajo está limpio

$ git merge --squash feature/create-home
	Actualizando 85b13b4..82975ee
	Fast-forward
	Commit de squash -- no actualizando HEAD
	 home.css  | 3 +++
	 home.html | 2 ++
	 2 files changed, 5 insertions(+)
	 create mode 100644 home.css
	 create mode 100644 home.html

$ git commit 
	[master 1a5ee55] Squashed commit of the following:
	 2 files changed, 5 insertions(+)
	 create mode 100644 home.css
	 create mode 100644 home.html

$ git log -1
	commit 1a5ee55ca09d28b8469385fa6675211ddc2d23ca (HEAD -> master)

	    Squashed commit of the following:
	    
	    commit 82975ee0f7f4fcab93e2b0d0d6e9d2f915510881	    
	        crear hoja de estilos home.css
	    
	    commit b9632f136aef8ae41306fc4468aa979800fb330a	    
	        crear home.html
	
	A       home.css
	A       home.html
```

![resultado integrar rama con merge squash](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xe0mkjmutbgi2iqsx7o.png)

### Rebase

Mover todos los commits de la rama a mergear en la rama actual, o, haciendo un símil, "replantar la rama": `git rebase <rama-destino> <rama-a-mover>`.
Si actualmente se está en `<rama-a-mover>`, se puede hacer directamente `git rebase <rama-destino>`.

Después del merge será necesario un hacer `git merge` para añadir los commits en la rama de integración.

![escenario integrar con rebase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0c05kwa3yc6a1rhtumm.png)

```bash
# "Replanto" feature/create-home en master.
$ git rebase master feature/create-home 
	En primer lugar, rebobinando HEAD para después reproducir 
        tus cambios encima de ésta...
	Aplicando: crear home.html
	Aplicando: crear hoja de estilos home.css

# Hago checkout en master.
$ git checkout master 
	Cambiado a rama 'master'

# Hago un merge fast forward de la rama a integrar.
$ git merge --ff-only feature/create-home 
	Actualizando e157378..d63cc7b
	Fast-forward
	 log.txt | 2 ++
	 1 file changed, 2 insertions(+)
```

![escenario integrar con rebase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mskuhl5r94505uq8s4ol.png)

Algo a tener en cuenta con `git rebase` es que puede llegar a ser necesarios resolver conflictos  si  `<rama-a-mover>` parte de un commit antiguo de `<rama-destino>`. 
Si `<rama-a-mover>` tiene varios commits puede llegar a ser necesario  resolver conflictos una vez por cada commit. 
Hay proyectos en los que por defecto utilizan `git rebase`, y excepcionalmente,  cuando se encuentran un caso donde la resolución de conflictos se complica, usan `git merge` para así solo resolver conflictos una única vez.

Cuando surjan conflictos en un `git rebase` seguir las instrucciones que da Git como salida del comando.

### Trunk base development

Sólo hay una rama, `master`, y todos los commits se hacen sobre ella. 
Adelantándome al post de repositorios remotos, cuando se quiera subir esa rama al repositorio remoto y surjan conflictos porque la rama local `master` ha divergido respecto de  la rama `master` del repositorio remoto, habrá que decidir si optar por merge commits o por rebase.

¿Por que se llama *Trunk base development*? *Trunk* es el nombre de la rama principal en los primeros sistemas de control de versiones.

### Conclusión

Cada opción tiene sus pros y sus contras: 

- `git merge` preserva todo el histórico de commits tal y surgió, pero si no se sigue un buen flujo de ramas puede resultar en un árbol de commits muy complejo, donde sería difícil  determinar cuando se introdujo un bloque de código o un bug.
- `git merge --squash` deja un árbol de commits muy sencillo, con una única rama y un único commit por cada feature, pero puede dejar commits muy grandes y se pierde información de los commits individuales.
- `git rebase` produce una única rama con todos los commits que se han hecho, pero se pierde la información de en que punto se inicio una rama y los merges con conflictos pueden ser muy dolorosos.
- *Trunk base development* reduce a 0 la gestión de ramas, pero cuando las ramas divergen hay que optar por `git merge` o `git rebase`.

¿Qué usar? La decisión dependerá de cuanta gente trabaje en el proyecto, de las convenciones del equipo,  del tipo de proyecto, de como de grandes sean las features, ... 

Esto intenta ser un post agnóstico, así que

![bomba de humo](https://media.giphy.com/media/B37cYPCruqwwg/giphy.gif)

## HEAD detached o desacoplado

*"git status" muestra un texto extraño en lugar del nombre de la rama; me he cambiado de rama y ya no encuentro el último commit.*

Lo normal es hacer `git checkout` a ramas, pero también se puede hacer a tags y a commits. En estos últimos casos se pasa al estado *"detached HEAD"*, o *"HEAD desacoplada"* si se tiene Git configurado en castellano .

```bash
$ git log --graph --format='%C(auto)%h %s%n%C(auto)%d'
	* d63cc7b crear hoja de estilos home.css
	|  (HEAD -> master, feature/create-home)
	* 1676ceb crear home.html
	| 
	* e157378 añadido .gitignore
	| 
	* 85b13b4 set up proyecto
	| 
	* 417ceac commit inicial

# git status indica que estoy en la rama master.
$ git status
	En la rama master
	nada para hacer commit, el árbol de trabajo está limpio
	
# Al hacer checkout a un commit se muestra un mensaje 
# que da respeto.
$ git checkout e157378
	Nota: cambiando a 'e157378'.
	
	Te encuentras en estado 'detached HEAD'. Puedes revisar por aquí, hacer
	cambios experimentales y hacer commits, y puedes descartar cualquier
	commit que hayas hecho en este estado sin impactar a tu rama realizando
	otro checkout.
	
	Si quieres crear una nueva rama para mantener los commits que has creado,
	puedes hacerlo (ahora o después) usando -c con el comando checkout. Ejemplo:
	
	  git switch -c <nombre-de-nueva-rama>
	
	O deshacer la operación con:
	
	  git switch -
	
	Desactiva este aviso poniendo la variable de config advice.detachedHead en false
	
	HEAD está ahora en e157378 añadido .gitignore

# git status en lugar de mostrar un nombre de rama 
# muestra un mensaje bastante raro....
$ git status
	HEAD desacoplada en e157378
	nada para hacer commit, el árbol de trabajo está limpio

# git branch, para ver las ramas, también muestra 
# el mensaje raro.
$ git branch
	* (HEAD desacoplada en e157378)
	  master
```

Los checkouts a tag y a commits pueden ser una suerte de máquina del tiempo con la que, sin necesidad de crear o modificar ramas, poder consultar como estaba el código hace unas semanas, o confirmar que los tests funcionaban antes de integrar una feature.

Para volver a ver nombres de ramas normales basta con hacer `git checkout` a una rama.

Los problemas pueden venir cuando estando en *detached HEAD* se hace un commit y después  `git checkout` a otra rama, ya que ese commit se puede llegar a perder a menos que se conozca su clave hash o se use `git reflog` (ver [Deshacer un commit: como revertir un cambio](https://dev.to/narkha/git-how-to-salir-de-esta-commits-455b#borron-cuenta-nueva) para más información sobre `git reflog`).
Si el commit es de prueba no habrá problema, ya que desaparecerá sin dejar rastro, pero si es algo con lo que se quiere seguir trabajando siempre es posible crear una rama a partir de él, o recuperarlo con `git cherry pick`.

```bash
$ git checkout e157378
	Nota: cambiando a 'e157378'.
	
	Te encuentras en estado 'detached HEAD'. ...

# Hago algunos cambios en el .gitignore.
$ git commit -am "ignorar más cosas"
	[HEAD desacoplado aec5722] ignorar más cosas
	 1 file changed, 1 insertion(+)

# Cambio de rama por alguna razón y Git da la señal de
# alarma, indicando ademas como se puede recuperar el commit.
$ git checkout master 
	Peligro: estás saliendo 1 commit atrás, no está conectado
	a ninguna rama:
	
	  aec5722 ignorar más cosas
	
	Si quieres conservarlo creando una nueva rama, este es un buen momento
	para hacerlo:
	
	 git branch <nuevo-nombre-de-rama> aec5722
	
	Cambiado a rama 'master'

# Si por alguna razón no hubiese conservado el hash del
# commit, siempre es posible consultarlo en con git reflog.
$ git reflog 
	d63cc7b (HEAD -> master, feature/create-home) HEAD@{0}: checkout: moving from aec57222a12d12ca6b87f5329ce67c10b5289d0a to master
	aec5722 HEAD@{1}: commit: ignorar más cosas
	e157378 HEAD@{2}: checkout: moving from master to e157378
	d63cc7b (HEAD -> master, feature/create-home) HEAD@{3}: checkout: moving from e15737895b61e4c7e205a8729abad775b6679f5d to master
	e157378 HEAD@{4}: checkout: moving from master to e157378
	claudio@jupiter2:~/sandbox/git(master)$ git checkout e157378

# Si quiero crear una rama para poder seguir trabajado a 
# partir de él,hago como ha dicho Git
$ git branch nuevo-nombre-de-rama aec5722
$ git checkout feature/configure-project 
	Cambiado a rama 'feature/configure-project'
$ git status
	En la rama feature/configure-project
	nada para hacer commit, el árbol de trabajo está limpio
```



<br/>
Si has llegado hasta aquí, gracias 🙂
Espero que te haya resultado útil, y nos vemos en el próximo post sobre repositorios remotos :wave:

## Créditos
Cover: https://pxhere.com/en/photo/149424
Generaciónd de diagramas: https://excalidraw.com/