Saltar al contenido

git reset

git reset

Git reset y los tres árboles

El comando git reset es una herramienta utilizada para deshacer cambios. Tiene tres formas de invocación que corresponden a los tres sistemas internos de gestión de estado de Git, llamados los tres árboles de Git. Estos sistemas incluyen HEAD (el historial de commits), el índice de staging y el directorio de trabajo. Vamos a revisar cada uno de estos sistemas.

El directorio de trabajo

El primer árbol es el directorio de trabajo. Representa los archivos del sistema de archivos de tu computadora, que están disponibles para que el editor de código aplique cambios. El directorio de trabajo se considera un commit específico del proyecto extraído. Cuando el proyecto se extrae, esto significa que sus archivos tienen versiones descomprimidas extraídas del repositorio Git.

git reset command

bash
echo 'hello git reset' > edited_file
git status 
#On branch master 
#Changes not staged for commit: 
#(use "git add ..." to update what will be committed) 
#(use "git checkout -- ..." to discard changes in working directory) 
#modified: edited_file

Índice de staging

El siguiente árbol es el índice de staging, que rastrea los cambios preparados en el directorio de trabajo. En general, Git oculta a los usuarios los detalles de rendimiento del área de staging. A veces, al hablar del área de staging, se usan distintas expresiones, como caché, caché de directorio, archivos preparados, área de staging, etc.

Aquí necesitamos git ls-files. command, que se considera una herramienta de depuración, y que comprueba el estado del índice de staging.

git ls-files -s

bash
git ls-files -s
#543644 a32de29bb3c1d643328b29ae775ad8c2e48c3256 0 edited_file

Historial de commits

El último árbol es el historial de commits. El comando git commit confirma los cambios en una instantánea permanente colocada en el índice de staging.

git reset, commit history

bash
git commit -am "edit content of test_file"
#[master ab23324] edit the content of edited_file
#1 file changed, 1 insertion(+)
git status
#On branch master
#nothing to commit, working tree clean

En el ejemplo anterior, puedes ver el nuevo commit con el mensaje "edit content of test_file". Los cambios están adjuntos al historial de commits. En esta etapa, al ejecutar git status no se muestran cambios pendientes en ninguno de los árboles. Al invocar git log, verás el historial de commits. Una vez que los cambios se realizan a través de los tres árboles, se puede usar git reset.

Cómo funciona

A primera vista, el comando git reset tiene algunas similitudes con git checkout, ya que ambos operan sobre HEAD. El comando git checkout opera exclusivamente sobre el puntero de referencia HEAD, mientras que el comando git reset pasa el puntero de referencia HEAD y el puntero de referencia de la rama actual. Comprenderás mejor su comportamiento con la ilustración de abajo:

git reset1

Esta ilustración presenta la secuencia de commits en la rama master. Como puedes ver, la referencia HEAD y la referencia de la rama master apuntan actualmente al commit d. Veremos cómo cambia la imagen en caso de git checkout b y git reset b.

git checkout b

Al ejecutar el comando git checkout, la referencia master sigue apuntando al commit d. En cuanto a la referencia HEAD, se ha movido y ha cambiado el puntero al commit b. Como resultado, el repositorio ahora está en un estado de 'detached HEAD'.

git reset2

git reset b

El comando git reset cambia tanto HEAD como las referencias de la rama al commit definido. Además, cambia el estado de los tres árboles. Hay tres argumentos de línea de comandos --soft, --mixed y --hard que definen la modificación de los árboles del índice de staging y del directorio de trabajo.

git reset3

Opciones principales

De forma predeterminada, el comando git reset tiene los argumentos constantes --mixed y HEAD. Por lo tanto, invocar git reset es lo mismo que invocar git reset --mixed HEAD. Aquí HEAD es el commit indicado. Puedes usar cualquier hash de commit SHA-1 de Git en su lugar.

git reset4

--hard

La opción más utilizada es --hard. Sin embargo, usarla tiene algunos riesgos. Con --hard, los punteros de referencia del historial de commits empiezan a apuntar al commit indicado. Después, el índice de staging y el directorio de trabajo se restablecen para corresponder al commit indicado. Los cambios que estaban pendientes previamente en el índice de staging y en el directorio de trabajo se restablecen para coincidir con el estado del árbol del commit. Cualquier cambio pendiente en el índice de staging y en el directorio de trabajo se perderá. El siguiente ejemplo demostrará lo mencionado arriba. Antes que nada, ejecuta los siguientes comandos:

git reset usage

bash
echo 'test content' > test_file
git add test_file
echo 'modified content' >> edited_file

Se ha creado un nuevo archivo llamado test_file y se ha añadido al repositorio. Además, el contenido de edited_file se modificará. Ahora comprobemos el estado del repositorio con estos cambios usando el comando git status.

git status

bash
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file

Como puedes ver, ahora hay algunos cambios pendientes. El cambio pendiente para el árbol del índice de staging es la adición de test_file, y el del directorio de trabajo son las modificaciones de edited_file. Ahora veamos el estado del índice de staging:

git ls-files -s

bash
git ls-files -s
#123126 7a32454a5477b1bf4765946147c49509a431f963 0 test_file
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

El test_file se ha añadido al índice. El edited_file se ha actualizado, pero el SHA del índice de staging (d7d77c1b04b5edd5acfc85de0b592449e5303770) sigue siendo el mismo. Estos cambios están en el directorio de trabajo. No se han promovido al índice de staging, ya que no hemos usado el comando git add. En este punto, podemos ejecutar git reset --hard y ver el nuevo estado del repositorio:

git reset --hard

bash
git reset --hard
#HEAD is now at ab23324 update content of edited_file
git status
#On branch master
#nothing to commit, working tree clean
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

La opción --hard ejecutó un "hard reset". Git indica que HEAD apunta al commit reciente ab23324. Luego, el estado del repositorio se comprueba con git status. Git indica que no hay cambios pendientes. En cuanto al estado del índice de staging, se ha restablecido a un punto anterior a la adición de test_file. Los cambios de edited_file y la adición de test_file se han eliminado. Esta pérdida no se puede deshacer.

--mixed

El modo de operación es, por defecto, --mixed. Actualiza los punteros de referencia. El índice de staging se restablece al commit indicado. Los cambios deshechos del índice de staging se colocan en el directorio de trabajo.

the git reset command

bash
echo 'new file content' > test_file
git add test_file
echo 'append content' >> edited_file
git add edited_file
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#modified: edited_file
git ls-files -s
#123126 6a32154a5477b1bf4765946147c49509a4323d32 0 test_file
#123126 3c3262db063f9e9426901092c00a3394b4bd3445 0 edited_file

En el ejemplo anterior, se ha añadido un test_file y se ha modificado el contenido de edited_file. A continuación, estos cambios se aplican al índice de staging con la ayuda de git add. Con este estado del repositorio, ahora es el momento de invocar git reset.

git reset --mixed

bash
git reset --mixed
git status
#On branch master
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file
#Untracked files:
#(use "git add ..." to include in what will be committed)
#test_file
#no changes added to commit (use "git add" and/or "git commit -a")
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

--mixed es el modo predeterminado. Tiene el mismo efecto que git reset. git status muestra que hay cambios en edited_file y que test_file es un archivo sin seguimiento. Este es exactamente el comportamiento de --mixed. El índice de staging se ha restablecido y los cambios pendientes se trasladan al directorio de trabajo.

--soft

El argumento --soft actualiza los punteros de referencia y detiene el restablecimiento. Sin embargo, no afecta al índice de staging ni al directorio de trabajo.

git reset --soft

bash
git reset --soft
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
git ls-files -s
#123126 32a252710639e5da6b515416fd779d0741e4561a 0 edited_file

Un soft reset restablece solo el historial de commits. De forma predeterminada, se invoca con HEAD como commit de destino. Ahora vamos a crear un nuevo commit para probar un --soft con un commit de destino que no sea HEAD:

git reset and commit

bash
git commit -m "add changes to edited_file"

Ahora nuestro repositorio tiene tres commits. Para encontrar el primero, necesitamos comprobar su ID, lo cual puede hacerse viendo la salida de git log.

git reset and git log

bash
git log
#commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
#Author: w3docs
#Date: Fri Nov 1 14:02:07 2019 -0800
#add changes to edited_file
#commit ab23324a6da9f0dec51ed16d3d8823f28e1a72a
#Author: w3docs
#Date: Fri Nov 1 11:31:58 2019 -0800
#change content of edited_file
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commit

Este es el ID del commit inicial. Ahora se usará como destino para el soft reset. Antes, necesitamos comprobar el estado actual del repositorio:

git status && git ls-files -s

bash
git status && git ls-files -s
#On branch master
#nothing to commit, working tree clean
#123126 32a252710639e5da6b515416fd779d0741e4561a  0 edited_file

Ahora podemos hacer un soft reset al primer commit:

git reset --soft

bash
git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
git status && git ls-files -s
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
#123126 32a252710639e5da6b515416fd779d0741e4561a  0 edited_file

En el ejemplo anterior, hicimos un soft reset y se invocó el comando combinado git status y git ls-files, que muestra el estado del repositorio. El comando git status muestra que hay algunos cambios en edited_file, destacándolos como cambios preparados para el siguiente commit. La entrada de git ls-files muestra que el índice de staging no ha cambiado y conserva el SHA 32a252710639e5da6b515416fd779d0741e4561a. Examinemos además el estado del repositorio después del soft reset con la ayuda de git log:

git reset

bash
git log
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commit

Como podemos ver, la salida anterior indica que hay un solo commit en el historial de commits. Como en todas las invocaciones de git reset, primero --soft restablece el árbol de commits.

A diferencia de --hard y --mixed, que actúan sobre HEAD, un soft reset retrocedió el árbol de commits en el tiempo.

La diferencia entre los comandos reset y revert

Git revert se considera una forma más segura de deshacer cambios que git reset. Existe una gran probabilidad de que el trabajo pueda perderse con git reset. git reset no elimina un commit, pero puede dejar el commit “huérfano”. Esto significa que no hay ninguna forma directa de acceder a él. Como resultado, Git eliminará todos los commits huérfanos cuando ejecute el recolector interno de basura. De forma predeterminada, Git ejecuta el recolector interno de basura cada 30 días. Los commits huérfanos suelen encontrarse con la ayuda del comando git reflog.

Otra diferencia entre estos dos comandos es que git revert está configurado para deshacer commits públicos, y git reset está configurado para deshacer cambios locales en el directorio de trabajo y el índice de staging.

La inadmisibilidad de restablecer el historial público

No uses git reset <commit> cuando haya instantáneas posteriores a <commit> que se hayan movido a un repositorio público. Cuando publiques un commit, ten en cuenta que otros desarrolladores también dependen de él. Eliminar commits en los que también están trabajando otros miembros del equipo causará muchos problemas. Usa git reset <commit> solo en cambios locales. Para corregir cambios públicos, usa el comando git revert.

Ejemplos

Usa lo siguiente para eliminar el archivo especificado del área de staging, pero sin cambiar el directorio de trabajo. Despreparará un archivo sin sobrescribir cambios:

git reset file

bash
git reset file

Usa lo siguiente para restablecer el área de staging para que corresponda al último commit, pero dejando el directorio de trabajo sin cambios. Despreparará todos los archivos sin sobrescribir cambios, dándote la posibilidad de reconstruir la instantánea preparada desde cero:

git reset staging area

bash
git reset

Usa lo siguiente para restablecer el área de staging y el directorio de trabajo para que correspondan al último commit. También despreparará cambios sobrescribiendo todos los cambios en el directorio de trabajo:

git reset --hard command

bash
git reset --hard

Usa lo siguiente para mover la punta de la rama hacia atrás en el tiempo hasta el commit, restableciendo el área de staging para que coincida, pero sin tocar el directorio de trabajo:

git reset commit

bash
git reset commit

Usa lo siguiente para mover la punta de la rama actual hacia atrás hasta el commit y restablecer el área de staging y el directorio de trabajo para que coincidan:

git reset --hard commit

bash
git reset --hard commit

Eliminación de commits locales

Como se mencionó arriba, puedes usar el comando git reset para eliminar commits en el repositorio local. El siguiente ejemplo muestra un uso de git reset. El comando git reset --hard HEAD~2 retrocede la rama actual dos commits y elimina las dos instantáneas creadas recientemente del historial del proyecto.

git add command

bash
# Create a new file called `yourname.txt` and add some code to it
# Commit it to the project history
git add yourname.txt
git commit -m "Start to develop a project"
# Edit `yourname.txt` again and change some other tracked files, too
# Commit another snapshot
git commit -a -m "Continue developing"
# Scrap the project and remove the related commits
git reset --hard HEAD~2

Despreparar archivos

El comando git reset suele usarse para crear instantáneas preparadas. En el siguiente ejemplo tenemos 2 archivos llamados task.txt e index.txt, que se han añadido al repositorio. git reset nos permite quitar del área de staging los cambios que no están relacionados con el siguiente commit.

git reset unstaging files

bash
# Edit task.txt and index.txt
# Stage everything in the current directory
git add .
# Realize that the changes in task.txt and index.txt
# should be committed in different snapshots
# Unstage index.txt
git reset index.txt
# Commit only task.txt
git commit -m "Edit task.txt"
# Commit index.txt in a separate snapshot
git add index.txt
git commit -m "Edit index.txt"

Practice

What are the features and options of the 'git reset' command in Git?

¿Te resulta útil?

Vista previa dual-run — compárala con las rutas Symfony en producción.