Git: how to salir de esta, commits
Tags: git, howto, commit
Published: 2021-06-15 12:29:37


## How to
* [Introducción](#introduccion)
* [Cambiar el mensaje de un commit](#cambiar-mensaje-commit)
* [Deshacer un commit: cómo revertir un cambio](#revertir-commit)
* [Deshacer un commit: borrón y cuenta nueva](#borron-cuenta-nueva)
* [Deshacer un git reset](#deshacer-git-reset)
* [Añadir  o quitar algo de un commit](#modificar-commit)
* [Hacer commit con algunos cambios de un archivo, no del archivo completo](#commit-parcial)
* [Compartir archivos y cambios sin conflictos](#como-compartir)
* [Cómo hacer un commit paso a paso<br/>
¿o por qué no se ha añadido un archivo o un cambio al hacer commit?
](#commit-paso-a-paso)

## Introducción <a name="introduccion"></a>


Los commits son la unidad básica de Git. Se identifican con una clave hash única y son inmutables, es decir, **un commit no se puede modificar una vez creado**, pero es muy fácil reemplazarlo por otro con una clave hash diferente y  los cambios que se deseen.
Dicho esto, y aunque se que lo correcto sería decir *"reemplazar el commit X por un commit X', que es como X pero con algunos cambios"*, yo suelo decir *"modificar el commit X"* porque es más corto y me abstrae de las complejidades de lo que hace Git por debajo; en el contexto de mi día a día prefiero un lenguaje conciso y expresivo, que transmita el resultado final, a una parrafada detallada y precisa que sea difícil de entender y donde sea fácil perderse en los detalles. 

Un ejemplo de clave hash sería `7f347041e695151cbe8493b8ed842949150095c4`. Muchas veces, por no llenar la pantalla con claves largas, se suele usar la una clave abreviada, por ejemplo `7f34704`, que vienen a ser los primeros caracteres de la clave hash. Nada garantiza que una clave abreviada sea única, pero si se detecta que esta repetida solo hay que mostrar más caracteres, hasta que deje de haber conflicto, por ejemplo `7f347041e`.

Algo muy importante a tener en cuenta con los commits es que  **no hay que modificar, deshacer o borrar  un commit que ya este en un repositorio remoto**, ya que otro desarrollador puede haber partido de ese commit para su desarrollo y cuando se quieran unir las dos versiones pueden surgir conflictos.

## Cambiar el mensaje de un commit <a name="cambiar-mensaje-commit"></a>

*Corregir una falta de ortografía, mejorar el texto del commit,incluir la clave de la tarea del issue tracker, etc*

Para modificar el mensaje de un commit lo más fácil es recurrir a un GUI, click derecho en el commit a modificar y click en la opción de editar el mensaje del commit.

Para entender un poco lo que  pasa por debajo, además de saber como hacerlo cuando no se tiene una GUI, lo mejor es recurrir a la linea de comandos.

```bash
# Acabo de hacer un commit con un error en el texto.
$ git commit -m "mi tercre commit" 
	[master 7f34704] mi tercre commit
	1 file changed, 1 insertion(+)

# Para cambiarlo

# Primero me aseguro de que no haya ningún cambio en el área de stage.
$ git status
	En la rama master
	Cambios a ser confirmados:
	  (usa "git reset HEAD <archivo>..." para sacar del área de stage)
	
		modificado:     dates.txt

# Si los hay los saco del área de stage.
$ git reset HEAD dates.txt 
	Cambios fuera del área de stage tras el reset:
	M	dates.txt

# Y modifico el mensaje del commit.
$ git commit --amend -m "mi tercer commit"
	[master 19ffda6] mi tercer commit
	 1 file changed, 1 insertion(+)
```

Esto habrá reemplazado el último commit, con hash `7f34704`, por otro con el mismo contenido, pero con otro mensaje y hash `19ffda6`.

Cuando se usa `git commit --amend -m`, es importante asegurarse qué se va a commitear para no incluir nada que se desee.

Si se tuviese que modificar el mensaje de un commit más antiguo, por ejemplo el `077c45d` , se podría hacer algo como esto

```bash
# La virgulilla es importante
$ git rebase -i 077c45d~ 
# Se abrirá un editor de texto con algo como
##--------------
	pick 077c45d mi segunod commit
	pick 19ffda6 mi tercer commit
	
	# Rebase bced58d..11a0995 en bced58d (2 comandos)
	#
	# Comandos:
	#  p, pick = usar commit
	#  r, reword = usar commit, pero editar el mensaje
      #  .... 
## ----------------
# Edito el archivo para tener
	r 077c45d mi segundo commit
	pick 19ffda6 mi tercer commit
## Guardo.
## Se abre un editor de texto con el mensaje a modificar, 
## escribo el texto correcto y guardo.
```

Esto habrá remplazado el commit `077c45d`, por otro con  el mensaje correcto y un nuevo hash, así como  todos los commits que vengan después.

```bash
$ git log --pretty=format:"%h %s" --graph
	* 52190c2 mi tercer commit
	* 247f469 mi segundo commit
	* bced58d first commit
```

Aunque ni el contenido ni el mensaje del commit con hash `19ffda6` hayan cambiado, si ha cambiado el commit que venía antes que él, y por lo tanto ha hecho falta reemplazarlo para reflejar ese cambio.

## Deshacer un commit: cómo revertir un cambio <a name="revertir-commit"><a>

*Un commit que produce un bug; un commit hecho por error; un commit con trazas hecho por error; etc*

Cuando lo que se quiere deshacer esta bien acotado en un commit, y se está seguro de que si ese commit desapareciese todo estaría como debería, se puede usar `git revert <hash a deshacer>`, o en el GUI, click derecho en el commit a deshacer, y *"Revert commit"*.

`git revert` no elimina el commit objetivo, si no que crea un commit con cambios para deshacer el commit objetivo.

¿Y si se quiere deshacer el commit resultado de un `git revert`? Se puede hacer un `git revert` de ese commit.

¿Y si no se quiere deshacer el commit entero, si no sólo parte?
En algunos GUIs,  *"Revert commit"*  abrirá una ventana con los cambios del *revert commit*; si se cierra esa ventana, se podrá: ver cambios del *"revert commit"*; añadir o quitar cosas; y finalmente hacer un nuevo commit.
Por consola lo equivalente sería `git revert --no-commit <hash a deshacer>`, revisar el *"revert commit"* y `git commit`

```bash
$ git log -1 73d8766 --patch-with-stat 
	commit 73d8766f13198c5da71e2759b1c659f5c5a3bf8a (HEAD -> master)
	    commit con error
	---
	 log.txt | 1 +
	 1 file changed, 1 insertion(+)
	
	diff --git a/log.txt b/log.txt
	index f14e7df..7c5c54a 100644
	--- a/log.txt
	+++ b/log.txt
	@@ -1,5 +1,6 @@
	 dom may 24 16:17:36 CEST 2020: log
	+esto no es un log
	 dom may 24 16:17:37 CEST 2020: log

$ git revert 73d8766
	[master e9bac4c] Revert "commit con error"
	 1 file changed, 1 deletion(-)

$ git log -1 e9bac4c --patch-with-stat
	commit e9bac4c003bc41640c29a12b21b70a5eb0d99193 (HEAD -> master)
	    Revert "commit con error"	  
	    This reverts commit 73d8766f13198c5da71e2759b1c659f5c5a3bf8a.
	---
	 log.txt | 1 -
	 1 file changed, 1 deletion(-)
	
	diff --git a/log.txt b/log.txt
	index 7c5c54a..f14e7df 100644
	--- a/log.txt
	+++ b/log.txt
	@@ -1,6 +1,5 @@
	 dom may 24 16:17:36 CEST 2020: log
	-esto no es un log
	 dom may 24 16:17:37 CEST 2020: log
```

¿Y si el commit es de un *merge commit*? 
```bash
$ git revert 5139b956
	error: el commit 5139b9560d954dd0c20320ecb3bac404e8bee7dc es una fusión pero no se proporcionó la opción -m.
	fatal: falló al revertir
```
El comando dará un error, el cual se verá en el [post dedicado a ramas](https://dev.to/narkha/git-how-to-salir-de-esta-ramas-4f74#deshacer-un-merge).

## Deshacer un commit: borrón y cuenta nueva <a name="borron-cuenta-nueva"><a>

*Empezar desde de cero una feature que se está complicando; estar a medias en una resolución de conflictos de un merge, perder el hilo por alguna razón, y casi que mejor empezar de 0 ; etc.*

`git reset <hash>`  hará que todo sea como en el commit de indicado, incluido el histórico de commits. 
¿Qué pasara con las diferencias que hay entre el commit actual y el commit de destino? Bien, `git revert` tiene varios modos:

- `--soft` deja todos los cambios como *staged*.
- `--mixed` (el modo por defecto) deja todos los cambios como *unstage* o *untracked*.
- `--hard` descarta todos los cambios. Hay que intentar hacerlo solo cuando se este seguro, porque no siempre es posible recuperar todos los cambios. Para más detalles ver [Deshacer un git reset](#deshacer-git-reset).
- `--merge`, nunca he llegado a entenderlo...
- `--keep`, nunca he llegado a entenderlo...

Si se usa un GUI, la diferencia entre `--soft` y `--mixed` no es apreciable, ya que el GUI hace su magia por debajo y el resultado final es el mismo; si se usa consola, con `--soft` se podría hacer directamente un commit con todos los cambios, sin tener que añadir los archivos con `git add`, mientras que con `--mixed` si habría que hacerlo.

Para hacerlo desde un GUI, click derecho en el commit al que se desee volver y click en *"Reset current branch to Here..."*.

Por consola:

```bash
# Consulto el histórico de commits.
$ git log --pretty=format:"%h %s" --graph
	* b2cd87b mi tercer commit
	* 247f469 mi segundo commit
	* bced58d first commit

$ git reset --hard bced58d
	HEAD is now at bced58d reset

$ git log --pretty=format:"%h %s" --graph
	* bced58d first commit
```

Para los usuarios de consola, y adelantando un poco contenidos del futuro post de ramas, decir que se pueden hacer cosas como `git reset master`, para así hacer que el commit de destino sea el último commit de la rama *master*.

## Deshacer un git reset <a name="deshacer-git-reset"><a>

*He hecho 'git reset —hard' donde no era y he perdido todos los cambios; He hecho 'git reset'  al commit donde no era y he perdido algunos cambios.*

Cuando se hace `git reset --hard`  se pueden perder dos cosas:

- Commits que no estén en ninguna otra rama.
- Cambios de los que todavía no se hubiese hecho ningún commit.

Lo primero tiene solución haciendo otro `git reset` al punto del que que se venía
¿Pero y si no se conoce el hash del commit al que se quiere volver?
`git reflog` muestra un histórico de todos los commits por los que sea ha pasado, ya sea porque se ha hecho un commit, se ha cambiado de rama, o se ha hecho un `git reset`. 
Un ejemplo sería

```bash
fc2f7e1 HEAD@{1}: commit: amazing stuff
```

- `fc2f7e1`: es el hash del commit por el que se ha pasado.
- `HEAD@{1}`: es un alias algo más amigable de ese hash. El indice indica hace cuando se produjo el cambio, siendo `HEAD{0}` el punto actual, `HEAD{1}`  el punto anterior, `HEAD{2}` lo que vino antes, etc.
Estos alias no son constantes, si por ejemplo se hiciese otro `git reset` u otro `git commit`, `HEAD{1}` pasaría a referenciar lo que referenciase `HEAD{0}` antes del cambio y sucesivamente.
- acción: en este caso commit, pero podría ser un reset, un checkout, un merge, etc.
- mensaje: una descripción textual de la acción.

Poniéndolo en un ejemplo

```bash
# Partiendo de un repositorio a estrenar.
$ git log --pretty=format:"%h %s" --graph
	* 00fd1f0 inicializar repo

# Hago algunos cambios en el proyecto y hago commit.
$ git commit -am "amazing stuff"
	[master fc2f7e1] amazing stuff
	 1 file changed, 1 insertion(+), 1 deletion(-)

# Hago un git reset por alguna razón.
$ git reset --hard 00fd1f0
	HEAD is now at 00fd1f0 inicializar repo

# Reviso el histórico y me doy cuenta de que he perdido 
# cosas por error.
$ git log --pretty=format:"%h %s" --graph
	* 00fd1f0 inicializar repo

$ git reflog 
	00fd1f0 (HEAD -> master) HEAD@{0}: reset: moving to 00fd1f0
	fc2f7e1 HEAD@{1}: commit: amazing stuff
	00fd1f0 (HEAD -> master) HEAD@{2}: commit (initial): inicializar repo

# Hago reset al punto al que quiero volver.
$ git reset --hard HEAD@{1}
	HEAD is now at fc2f7e1 amazing stuff

$ git log --pretty=format:"%h %s" --graph
	* fc2f7e1 amazing stuff
	* 00fd1f0 inicializar repo
```

¿Y si es el segundo caso?
En esos casos me apoyo en el IDE, ya que desconozco si hay alguna manera de hacerlo con Git.

La mayoría de los IDEs suelen tener un *"Local history"* donde almacenan todos los cambios que se han hecho, y se puede acceder a él para ver cuando se produjo un cambio o restaurar algo.
Para acceder suele ser algo como como seleccionar una carpeta o un archivo en el IDE, click derecho, *"Local history"*, *"Show history"* y a jugar.

![historial local IDE](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qh9iz8pix0egy9zcfwm.png)

## Añadir o quitar algo de un commit <a name="modificar-commit"><a>

*Añadir un archivo que por alguna razón se ha olvidado, quitar algo que se ha olvidado quitar; quitar algo que se ha commiteado por error, etc.*

La solución más sencilla es hacer un nuevo commit con lo que se quiere añadir o quitar, y la mejor si ha se ha hecho `git push` ; quedará un commit de más en la rama (se si usa `git merge--squash` ni siquiera eso), pero es una solución rápida y a prueba de errores.

Ahora bien, si eres de esos a los que les gusta tener un árbol de commits donde cada uno de ellos represente un paso lógico dado para crear una nueva feature, para así tener un histórico bien detallado, hay comandos para modificar los commits.

Si se usa una GUI, en el menú del commit suele existir la opción de *"Amend Commit"*, que a grandes grandes rasgos crea un nuevo commit con la unión del último commit de la rama y el commit que se esté haciendo y  reemplazará el último commit de la rama con ese nuevo commit.

```bash
# Antes del "Amend commit".
$ git log --pretty=format:"%h %s" --graph
	* b2cd87b mi tercer commit
	* 247f469 mi segundo commit
	* bced58d first commit

# Después del "Amend commit".
$ git log --pretty=format:"%h %s" --graph
	* d9822c9 mi tercer commit
	* 247f469 mi segundo commit
	* bced58d first commit
```

Para hacerlo en consola

```bash
# Modificao un archivo.

# Revisar el status.
$ git status
	En la rama master
	Cambios no rastreados para el commit:
	  (usa "git add <archivo>..." para actualizar lo que será confirmado)
	  (usa "git checkout -- <archivo>..." para descartar los cambios en el directorio de trabajo)
	
		modificado:     dates.txt

# Añado el archivo modificado al área de stage.
$ git add dates.txt

# Hago un commit con la opción ammend para modificar 
# el commit y el mensaje.
$ git commit --amend -m "mi tercer commit más un cambio"                     
	[master d9822c9] mi tercer commit más un cambio
	 1 file changed, 2 insertions(+)
```

¿Y si el commit no es el último, si no uno anterior?
`git rebase -i` (ver [Cambiar el mensaje de un commit](#cambiar-mensaje-commit), a parte de *pick* y *reword* tiene la opción de editar o borrar un commit. Es una opción que existe, pero intento evitarla ya que es probable que implique resolver conflictos varias veces: si se edita un archivo en un commit antiguo, es probable que ese mismo archivo haya sido editado commits posteriores y habrá resolver conflictos por cada uno de esos commits.

## Hacer commit con algunos cambios de un archivo, no del archivo completo <a name="commit-parcial"><a>

*Estoy con un bug y creo que ya lo he solucionado, así que voy probarlo en la máquina de CI; por si acaso no quiero borrar todavía las trazas de debug que he metido, pero tampoco quiero hacer un commit con ellas.*

Algunos GUIs dejan revisar que cambios en un archivo se quieren commitear al inspeccionar los archivos para el commit

![Inspeccionar commit en IDE](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/blfay7h6rtpclieoas0e.png)

Desde consola también se puede hacer  con `git add -p` (o `--patch`) y seleccionar que trozos de un archivo se quieren añadir; o con `git commit -i` (o `--interactive`) , que permite elegir como tratar todos los archivos que se incluirían en el commit al hacer `git commit -a`.

Estos comandos abren un dialogo en la consola donde se puede seleccionar como tratar los cambios. 

```bash
$ git add -p recursive.php 
	diff --git a/recursive.php b/recursive.php
	index 215ddd0..590b3ac 100644
	--- a/recursive.php
	+++ b/recursive.php
	@@ -1,11 +1,14 @@
	 <?php
	 
	     function recursiveFunction(Closure $action, Closure $stopCheck, $data) : void {
	+        var_dump($data);
	+
	         if ($stopCheck($data)) {
	+            var_dump("stop contidion matched");
	             return;
	         }
	 
	-        $action($data);
	+        $data = $action($data);
	 
	         recursiveFunction($action, $stopCheck, $data);
	     }
	\ No newline at end of file
	Stage this hunk [y,n,q,a,d,s,e,?]? ?    
	y - stage this hunk
	n - do not stage this hunk
	q - quit; do not stage this hunk or any of the remaining ones
	a - stage this hunk and all later hunks in the file
	d - do not stage this hunk or any of the later hunks in the file
	s - split the current hunk into smaller hunks
	e - manually edit the current hunk
	? - print help
	Stage this hunk [y,n,q,a,d,s,e,?]? s
	Split into 3 hunks.
	@@ -1,4 +1,6 @@
	 <?php
	 
	     function recursiveFunction(Closure $action, Closure $stopCheck, $data) : void {
	+        var_dump($data);
	+
	         if ($stopCheck($data)) {
	Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? n
	@@ -4,4 +6,5 @@
	         if ($stopCheck($data)) {
	+            var_dump("stop contidion matched");
	             return;
	         }
	 
	Stage this hunk [y,n,q,a,d,K,j,J,g,/,e,?]? n
	@@ -5,7 +8,7 @@
	             return;
	         }
	 
	-        $action($data);
	+        $data = $action($data);
	 
	         recursiveFunction($action, $stopCheck, $data);
	     }
	\ No newline at end of file
	Stage this hunk [y,n,q,a,d,K,g,/,e,?]? y

$ git status
	On branch test
	Changes to be committed:
	  (use "git reset HEAD <file>..." to unstage)
	
	        modified:   recursive.php
	
	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git checkout -- <file>..." to discard changes in working directory)
	
	        modified:   recursive.php

$ git diff --staged 
	diff --git a/recursive.php b/recursive.php
	index 215ddd0..b865da0 100644
	--- a/recursive.php
	+++ b/recursive.php
	@@ -5,7 +5,7 @@
	             return;
	         }
	 
	-        $action($data);
	+        $data = $action($data);
	 
	         recursiveFunction($action, $stopCheck, $data);
	     }
	\ No newline at end of file
```

Y permite ir tan al detalle como seleccionar que lineas concretas de un pedazo de código se quieren incluir en el commit, opción que no todos los GUI permiten.

![IDE seleccionar pedazo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szpr09ydvyt647he98ya.png)

## Compartir archivos y cambios sin conflictos<a name="como-compartir"><a>

*Traerme de otra rama en desarrollo una nueva interfaz, un cambio o un fix; etc*

Si los cambios estuviesen ya en *master*, o en cualquier otra rama compartida, sería tan sencillo como mergear esa rama, pero hay ocasiones en las que dos desarrolladores (pongamos Fulano y Mengano) están trabajando cada uno en su rama y la cosa se complica:

- Fulano podría mergearse la rama de Mengano, pero ello implicaría traerse también cosas todavía no estables o no finalizadas, y a la larga le podría dar problemas.
- Fulano podría pedirle por email o chat los cambios a Mengano y aplicarlos manualmente en su rama.
- O Fulano, si Mengano hubiese subido su rama al repositorio común, podría usar `git cherry-pick`

`git cherry-pick <hash>` crea un commit que añade exactamente los mismos objetos que los que contiene el commit indicado (para más información ahí va [un buen artículo](https://aboullaite.me/deep-dive-into-git/) de como se organiza Git por debajo).

Si hay un commit que sólo contiene los cambios que se desea traer (una nueva interfaz, clase, archivo, fix, etc), es sencillo.

Desde el GUI, buscar el commit con los cambios, click derecho, y *"Cherry-Pick"*

Desde consola `git cherry-pick -x <hash>`  ( `-x` añade al commit el texto `cherry picked from commit <hash>`, lo cual puede ser un buen añadido)

```bash
# La rama amazing contiene una una interfaz que 
# quiero traerme.
(amazing)$ git log -2 --stat 
	commit c30dd31081fc390465d9ef1a7f3d0a8422d90408 (HEAD -> amazing)
	
	    AMAZING: implementación en proceso
	
	 src/AmazingImplementation.php | 10 ++++++++++
	 1 file changed, 10 insertions(+)

	commit e10c9b91860d182b4985c843a502de653e44c500
	
	    AMAZING: new inteface
	
	 src/AmazingInterface.php | 7 +++++++
	 1 file changed, 7 insertions(+)
```

```bash
# Mi rama actual es comercial, y me traigo los cambios 
# que me interesan.
(comercial)$ git cherry-pick -x e10c9b91860d182b4985c843a502de653e44c500
	[comercial 1ef28f9] AMAZING: new inteface
	 1 file changed, 7 insertions(+)
	 create mode 100644 src/AmazingInterface.php

# Hago algún otro commit.

$ git log -2 --stat 
	commit f55e27bb46ae08c6dfb97a69e3f9ab4dc2ef18e3 (HEAD -> comercial)
	
	    COMERCIAL: Comercial.php
	
	 src/Comercial.php | 15 +++++++++++++++
	 1 file changed, 15 insertions(+)
	
	commit 1ef28f9e31adb6d6191d1b2f224daf5357dd217d
	
	    AMAZING: new inteface
	    
	    (cherry picked from commit e10c9b91860d182b4985c843a502de653e44c500)
	
	 src/AmazingInterface.php | 7 +++++++
	 1 file changed, 7 insertions(+)

```

```bash
# Cuando en el futuro se haga el merge no debería 
# haber conflictos...
# a menos que se hayan tocado mucho los archivos en 
# las dos ramas.
(master)$ git merge --no-ff amazing 
	Merge made by the 'recursive' strategy.
	 src/AmazingImplementation.php | 10 ++++++++++
	 src/AmazingInterface.php      |  7 +++++++
	 2 files changed, 17 insertions(+)
	 create mode 100644 src/AmazingImplementation.php
	 create mode 100644 src/AmazingInterface.php

(master)$ git merge --no-ff comercial
	Merge made by the 'recursive' strategy.
	 src/AmazingInterface.php |  1 +
	 src/Comercial.php        | 15 +++++++++++++++
	 2 files changed, 16 insertions(+)
	 create mode 100644 src/Comercial.php
```

Algo a tener en cuenta a la hora de hacer `cherry-pick` de archivos que no están en la rama, es que si por ejemplo ese archivo se ha creado en un commit y modificado en otro, y se intenta hacer `cherry-pick` de ese último commit, se producirá un conflicto.
Se puede resolver el conflicto o hacer `git cherry-pick -x <hash-primer-commit> <hash-segundo-commit>`

¿Qué ocurre si `<hash-commit-X>` contiene cambios que no queremos, como por ejemplo la clase que implementa la interfaz que queremos traernos?

Desde consola se puede hacer `git cherry-pick -x --no-commit <hash-commit-X>` , que lo dejara todo listo para hacer el commit. En ese momento se podría quitar alguno de los archivos que no se quisiesen.

```bash
# Hay una rama que en uno de sus commits tiene la interfaz
# que me interesa junto con más cosas.
$ git log -2 --stat
	commit dbcf29746184e8d9d3969fc4e008a80f140d7df6 (HEAD -> amazing-II)
	
	    implementación de método
	
	 src/AmazingImplementation.php | 10 ++++++++++
	 1 file changed, 10 insertions(+)
	
	commit 61b38431b7b6cdcc279515b3467370d1d8cd64ca
	
	    añadida interfaz y algo más
	
	 log.txt                  | 3 +++
	 src/AmazingInterface.php | 6 ++++++
	 2 files changed, 9 insertions(+)
```

```bash
# Estoy en la rama comercial-II, y me quiero traer 
# la interfaz
(comercial-II)$ git cherry-pick -x --no-commit 61b38431b7b6cdcc279515b3467370d1d8cd64ca

# Reviso los cambios.
(comercial-II)$ git status
	On branch comercial-II
	Changes to be committed:
	  (use "git reset HEAD <file>..." to unstage)
	
	        modified:   log.txt
	        new file:   src/AmazingInterface.php

# Quito los cambios que no necesito.
(comercial-II)$ git reset HEAD log.txt
	Unstaged changes after reset:
	M       log.txt

# Hago el commit.
(comercial-II)$ git commit -m "añadida solo interfaz"
	[comercial-II 16908dd] añadida solo interfaz
	 1 file changed, 6 insertions(+)
	 create mode 100644 src/AmazingInterface.php

# Reviso el status, donde veo que tengo cambios que no he 
# commiteado y que tendré que quitar.
(comercial-II)$ git status
	On branch comercial-II
	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git checkout -- <file>..." to discard changes in working directory)
	
	        modified:   log.txt
	
	no changes added to commit (use "git add" and/or "git commit -a")

# Reviso los cambios que he incluido en el commit.
(comercial-II)$ git log -1 --stat 
	commit 16908dd4cf9d15e2bf8feafce1d1b3e296f1bede (HEAD -> comercial-II)
	Author: claudio <claudio.sanchez@coches.com>
	Date:   Sun Aug 23 13:32:11 2020 +0200
	
	    añadida solo interfaz
	    
	    (edited cherry picked from commit 61b38431b7b6cdcc279515b3467370d1d8cd64ca)
	
	 src/AmazingInterface.php | 6 ++++++
	 1 file changed, 6 insertions(+)
```

También se puede hacer `cherry-pick` de rangos de commits

```bash
# la ~ en el primer commit es importante
$ git cherry-pick -x <hash-primer-commit>~..<hash-ultimo-commit>
```

## Cómo hacer un commit paso a paso<br/>¿o por qué no se ha añadido un archivo o un cambio al hacer commit? <a name="commit-paso-a-paso"><a>

![ciclo commit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6tnf8wlhoz6ccjir2p4.png)

¿Por qué un cambio o un nuevo archivo no pueden llegar a incluirse en un commit al hacer `git commit`? 

Podría ser porque el archivo está *untracked,* porque se ha hecho `git commit` cuando el archivo no estaba en área de *stage* o porque cumple alguna de las expresiones del archivo `.gitignore`

Para entender la respuesta a los dos primeros casos hay que entender cómo es el ciclo de vida de un archivo en Git y hacer un commit siguiendo todos los pasos, sin atajos como `git commit -a`

Para Git los archivos pueden estar en dos estados:

- *Tracked*: son todos aquellos archivos sobre los que Git lleva un registro, y que alguien tendría si se clonase el repositorio.
Los archivos en este estado pueden estar a su vez modificados, no modificados o en el area de *stage*.
- *Untracked*: son todos aquellos archivos que están dentro de la carpeta del repositorio pero que nunca se le ha dicho a Git que tiene que registrar.
Git no hace nada que no se le diga, así que un archivo *untracked* estará así hasta que se indique explícitamente que debe cambiar al estado *tracked*.

A grandes rasgos, cuando se crea un nuevo archivo en la carpeta del repositorio:

- El archivo empieza en el estado *untracked*.
- Con `git add <nuevo archivo>`  se le dice a Git que lo tiene que registrar; el archivo pasara a estar en el estado  *tracked* y se moverá al área de *stage*.
- Cuando se haga `git commit` se incluirá en el nuevo commit, se sacará del área de *stage* y pasará a estar no modificado.

Un detalle importante, es que `git commit -a` no registra archivos en estado *untracked*, y por lo tanto estos no se incluirían en el commit. Hay que incluirlo con `git add`. 

¿Y cuando se modifica o borra un archivo que ya estaba registrado?

- El archivo pasa a estar como modificado.
- Con `git add <nuevo archivo>`  se moverá al área de *stage*.
- Con `git commit`  se incluirá en el nuevo commit, se sacará del área de *stage* y pasará a estar no modificado.

Para archivos en estado *tracked* si funciona `git commit -a`, así que es posible ahorrarse el `git add`.

Otra cosa que suele hacer soltar una exclamación es que si se modifica un archivo, se hace `git add` , se vuelve a modificar el archivo y se hace `git commit`, los últimos cambios no estarán incluidos en commit.
Después de cada cambio en un archivo habría que volver a hacer `git add`.

En cualquier momento se puede verificar el estado de los archivos con `git status`, tanto antes como después del commit. 
Si eres un usuario de consola te gustará saber que es posible personalizar el prompt de la shell para saber de un vistazo si hay algo modificado o pendiente de hacer commit (para zsh hay multitud de temas, y para bash al final hay un ejemplo).

Pero antes un ejemplo práctico de toda la explicación de arriba.

```bash
$ git status
	On branch master
	nothing to commit, working tree clean

# Creo un archivo.
$ echo "TODO" > README.md

# Git status indicará que hay un archivo nuevo
$ git status
	On branch master
	Untracked files:
	  (use "git add <file>..." to include in what will be committed)
	
		README.md
	
	nothing added to commit but untracked files present (use "git add" to track)

# Si intento hacer un commit fallará, ya que README.md 
# esta untracked.
$ git commit -m "creación de README.md"
	On branch master
	Untracked files:
		README.md
	
	nothing added to commit but untracked files present

$ git add README.md 

# Tras git add README.md pasa a estar tracked y en 
# el area de stage.
$ git status
	On branch master
	Changes to be committed:
	  (use "git reset HEAD <file>..." to unstage)
	
		new file:   README.md

# Ahora si que es posible hacer commit.
$ git commit -m "creación de README.md"
	[master ccd62fa] creación de README.md
	 1 file changed, 1 insertion(+)
	 create mode 100644 README.md

$ git status
	On branch master
	nothing to commit, working tree clean

$ echo "# Introducción" > README.md

$ git status
	On branch master
	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git checkout -- <file>..." to discard changes in working directory)
	
		modified:   README.md
	
	no changes added to commit (use "git add" and/or "git commit -a")

$ git add README.md 

$ echo "# Instalación" >> README.md

# Los cambios de antes antes de git add están en 
# el area de stage; y los de después están fuera.
$ git status
	On branch master
	Changes to be committed:
	  (use "git reset HEAD <file>..." to unstage)
	
		modified:   README.md
	
	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git checkout -- <file>..." to discard changes in working directory)
	
		modified:   README.md

$ git commit -m "añadir secciones"
	[master f4df7c2] añadir secciones
	 1 file changed, 1 insertion(+), 1 deletion(-)

# Al haber hecho el commit sin la opción -a, solo los cambios
# en el area de stage se han incluido en el commit;
# si el commit se hubise hecho con -a, se habrían incluido 
# todos los cambios
$ git status
	On branch master
	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git checkout -- <file>..." to discard changes in working directory)
	
		modified:   README.md

	no changes added to commit (use "git add" and/or "git commit -a")

$ git diff
	diff --git a/README.md b/README.md
	index 28429bb..ab9e81e 100644
	--- a/README.md
	+++ b/README.md
	@@ -1 +1,2 @@
	 # Introducci<C3><B3>n
	+# Instalaci<C3><B3>n
```

Incluyendo lo siguiente en el `~/.bashrc`,  el prompt incluirá, cuando se esté dentro de un repositorio de Git, la rama actual y un `*` si hay cambios pendientes de commitear 

```shell
YELLOW="\[\033[0;33m\]"
GREEN="\[\033[0;32m\]"
LIGHT_BLUE="\[\033[1;34m\]"
WHITE="\[\033[1;37m\]"
COLOR_NONE="\[\e[0m\]"
function parse_git_dirty {
[[ $(git status 2> /dev/null | tail -n1) != "nothing to commit, working directory clean" ]] &&
[[ $(git status 2> /dev/null | tail -n1) != "nothing to commit, working tree clean" ]] &&
[[ $(git status 2> /dev/null | tail -n1) != "nada para hacer commit, el árbol de trabajo está limpio" ]] &&
   echo "*"
}
function parse_git_branch {
  git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/(\1$(parse_git_dirty))/"
}
PS1="$GREEN\u$WHITE@$YELLOW\H:$LIGHT_BLUE\w${COLOR_NONE}\$(parse_git_branch)\$ "
```

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

## Créditos
Cover: https://pixabay.com/es/photos/monta%C3%B1a-moj%C3%B3n-roc-cumbre-2702405/