Git: how to salir de esta, commits

narkha

Claudio Sánchez

Posted on June 15, 2021

Git: how to salir de esta, commits

How to

Introducción

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

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.

# 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(+)
Enter fullscreen mode Exit fullscreen mode

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

# 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.
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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

$ 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
Enter fullscreen mode Exit fullscreen mode

¿Y si el commit es de un merge commit?

$ git revert 5139b956
    error: el commit 5139b9560d954dd0c20320ecb3bac404e8bee7dc es una fusión pero no se proporcionó la opción -m.
    fatal: falló al revertir
Enter fullscreen mode Exit fullscreen mode

El comando dará un error, el cual se verá en el post dedicado a ramas.

Deshacer un commit: borrón y cuenta nueva

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.
  • --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:

# 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
Enter fullscreen mode Exit fullscreen mode

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

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

fc2f7e1 HEAD@{1}: commit: amazing stuff
Enter fullscreen mode Exit fullscreen mode
  • 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

# 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
Enter fullscreen mode Exit fullscreen mode

¿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

Añadir o quitar algo de un commit

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.

# 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
Enter fullscreen mode Exit fullscreen mode

Para hacerlo en consola

# 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(+)
Enter fullscreen mode Exit fullscreen mode

¿Y si el commit no es el último, si no uno anterior?
git rebase -i (ver Cambiar el mensaje de un 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

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

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.

$ 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
Enter fullscreen mode Exit fullscreen mode

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

Compartir archivos y cambios sin conflictos

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 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)

# 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(+)
Enter fullscreen mode Exit fullscreen mode
# 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(+)

Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode

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.

# 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(+)
Enter fullscreen mode Exit fullscreen mode
# 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(+)
Enter fullscreen mode Exit fullscreen mode

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

# la ~ en el primer commit es importante
$ git cherry-pick -x <hash-primer-commit>~..<hash-ultimo-commit>
Enter fullscreen mode Exit fullscreen mode

Cómo hacer un commit paso a paso
¿o por qué no se ha añadido un archivo o un cambio al hacer commit?

ciclo commit

¿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.

$ 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
Enter fullscreen mode Exit fullscreen mode

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

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)\$ "
Enter fullscreen mode Exit fullscreen mode



Si has llegado hasta aquí, gracias 🙂
Espero que te haya resultado útil, y nos vemos en el próximo post sobre ramas 👋

Créditos

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

💖 💪 🙅 🚩
narkha
Claudio Sánchez

Posted on June 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related