W3docs

git reset

Aprende a usar el comando git reset, los tres árboles de Git y su relación con git reset, con ejemplos prácticos.

git reset

git reset es el comando principal para deshacer cambios en tu repositorio local. Dependiendo del modo que elijas, puede quitar un archivo del área de preparación, descartar cambios preparados o mover una rama hacia atrás a un commit anterior. Dado que puede reescribir hacia dónde apunta tu rama y descartar trabajo, es potente pero fácil de usar de forma incorrecta.

Esta página explica los tres "árboles" sobre los que trabaja git reset, recorre sus tres modos (--soft, --mixed, --hard) con ejemplos, y muestra cuándo usar git revert en su lugar.

Cuándo usar git reset

Recurre a git reset cuando quieras reescribir el historial local o el área de preparación que aún no has compartido:

  • Quitar un archivo del área de preparación que agregaste por error: git reset <file>.
  • Limpiar todo el área de preparación para reconstruir tu próximo commit desde cero: git reset.
  • Descartar commits y cambios locales completamente: git reset --hard <commit>.
  • Combinar o reescribir los últimos commits antes de hacer push.

Si los commits que deseas deshacer ya han sido enviados a una rama compartida, usa git revert en su lugar — registra un nuevo commit que revierte los cambios sin reescribir el historial del que otros dependen.

Git reset y los tres árboles

git reset tiene tres formas de invocación que corresponden a los tres sistemas internos de gestión de estado de Git, conocidos habitualmente como los tres árboles de Git:

  • HEAD — el historial de commits (la instantánea a la que apunta actualmente tu rama).
  • Índice de preparación — los cambios preparados para el próximo commit.
  • Directorio de trabajo — los archivos en disco en tu editor.

Veremos cada uno de estos sistemas a continuación.

El directorio de trabajo

El primer árbol es el directorio de trabajo. Representa los archivos en el sistema de archivos de tu computadora que tu editor puede modificar. El directorio de trabajo está sincronizado con un commit específico del proyecto que se ha extraído: cuando se extrae un proyecto, Git descomprime versiones de los archivos del repositorio en el disco.

Si editamos un archivo rastreado, git status lo muestra como un cambio modificado y no preparado en el directorio de trabajo:

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 preparación

El segundo árbol es el índice de preparación, que rastrea los cambios preparados para el próximo commit. Git normalmente oculta los detalles internos del área de preparación. También lo verás referenciado con varios otros nombres: caché, caché de directorio, archivos preparados o área de preparación.

Para inspeccionar el índice de preparación directamente podemos usar git ls-files -s, una herramienta de depuración que muestra las entradas preparadas junto con sus hashes de objeto:

git ls-files -s
#543644 a32de29bb3c1d643328b29ae775ad8c2e48c3256 0 edited_file

Historial de commits

El tercer árbol es el historial de commits. El comando git commit toma lo que hay en el índice de preparación y lo registra como una instantánea permanente en el historial:

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 se añaden al historial de commits. En este punto, ejecutar git status no muestra cambios pendientes en ninguno de los tres árboles. Al invocar git log, verás el historial de commits. Una vez que los cambios han pasado por 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 modifica tanto el puntero de referencia HEAD como el puntero de la rama actual. Entenderás mejor su comportamiento con la ilustración a continuación:

git reset1

Esta ilustración presenta la secuencia de commits en la rama master. Como puedes ver, la ref HEAD y la ref 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 ref master sigue apuntando al commit d. En cuanto a la ref HEAD, se ha movido y cambiado el puntero al commit b. Como resultado, el repositorio está ahora en estado de 'HEAD desasociado' (detached HEAD).

git reset2

git reset b

El comando git reset mueve tanto HEAD como la ref de la rama actual al commit especificado. También puede cambiar el estado del índice de preparación y del directorio de trabajo. Tres opciones de línea de comandos — --soft, --mixed y --hard — controlan hasta dónde llega ese restablecimiento:

git reset3

Opciones principales

Por defecto, git reset se ejecuta con los argumentos --mixed y HEAD. Así que invocar git reset es equivalente a git reset --mixed HEAD. El HEAD aquí es el commit de destino — puedes reemplazarlo con cualquier referencia de commit, como un hash SHA-1 (git reset 0a1b2c3) o un puntero relativo (git reset HEAD~2).

La tabla a continuación resume qué árboles toca cada modo:

ModoHistorial de commits (HEAD)Índice de preparaciónDirectorio de trabajo
--softmovidosin cambiossin cambios
--mixed (predeterminado)movidorestablecido al destinosin cambios
--hardmovidorestablecido al destinorestablecido al destino (cambios perdidos)

Una forma útil de recordarlo: --soft mantiene tus cambios preparados, --mixed los mantiene como ediciones no preparadas, y --hard los descarta completamente.

git reset4

--hard

--hard es la opción más potente y más peligrosa. Mueve la ref del historial de commits al commit de destino y luego obliga al índice de preparación y al directorio de trabajo a coincidir con ese commit. Cualquier cambio pendiente en el índice de preparación y en el directorio de trabajo se descarta — y esta pérdida no puede deshacerse con git reset.

Advertencia: --hard elimina el trabajo sin confirmar de forma permanente. Ejecuta git status primero para confirmar lo que estás a punto de perder.

Para demostrarlo, primero crea algunos cambios pendientes:

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

Se ha creado y preparado un nuevo archivo llamado test_file, y el contenido de edited_file ha sido modificado en el directorio de trabajo. Verifiquemos el estado del repositorio con el comando git status:

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

Ahora hay cambios pendientes en dos árboles: el índice de preparación contiene el nuevo test_file, y el directorio de trabajo contiene las modificaciones a edited_file. Veamos el estado del índice de preparación:

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

El test_file ha sido añadido al índice. El edited_file ha sido actualizado, pero el SHA del índice de preparación (d7d77c1b04b5edd5acfc85de0b592449e5303770) permanece igual. Estos cambios están en el directorio de trabajo. No se promueven al índice de preparación porque no hemos ejecutado el comando git add. Ahora ejecutamos git reset --hard e inspeccionamos el nuevo estado del repositorio:

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 "restablecimiento duro". Git indica que HEAD apunta al commit reciente ab23324. Luego, el estado del repositorio se verifica con git status. Git indica que no hay cambios pendientes. En cuanto al estado del índice de preparación, ha sido restablecido a un punto anterior a la adición de test_file. Los cambios de edited_file y la adición de test_file han sido eliminados. Esta pérdida no puede deshacerse.

--mixed

--mixed es el modo predeterminado. Mueve los punteros de referencia y restablece el índice de preparación al commit de destino, pero deja el directorio de trabajo sin modificar. Los cambios que se retiran del índice reaparecen como modificaciones en el directorio de trabajo, por lo que no se pierde nada — simplemente tienes la oportunidad de volver a prepararlos.

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, test_file fue añadido y el contenido de edited_file fue modificado, y ambos cambios se aplicaron al índice de preparación con git add. Con este estado del repositorio, es momento de invocar git reset:

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. El git status muestra que hay cambios en edited_file y que test_file es un archivo sin rastrear. Este es exactamente el comportamiento de --mixed. El índice de preparación ha sido restablecido y los cambios pendientes se han movido al directorio de trabajo.

--soft

El argumento --soft mueve los punteros de referencia y se detiene ahí. No toca el índice de preparación ni el directorio de trabajo, por lo que todo lo que estaba confirmado permanece preparado y listo para ser vuelto a confirmar. Este es el modo que quieres cuando combinas varios commits en uno.

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 restablecimiento soft mueve solo el historial de commits. Por defecto apunta a HEAD. Creemos un nuevo commit y luego probemos un restablecimiento --soft con un commit de destino distinto de HEAD:

git commit -m "add changes to edited_file"

Ahora nuestro repositorio tiene tres commits. Para encontrar el primero, verificamos su ID en la salida de git log:

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

La entrada inferior es el commit inicial; usaremos su ID como destino para el restablecimiento soft. Primero, verifica el estado actual del repositorio:

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

Ahora podemos hacer un restablecimiento soft al primer commit:

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, realizamos un restablecimiento soft e invocamos la combinación de comandos git status y git ls-files, que muestra el estado del repositorio. El comando git status muestra que hay algunos cambios en edited_file, resaltándolos como cambios preparados para el próximo commit. La salida de git ls-files muestra que el índice de preparación no ha cambiado y conserva el SHA 32a252710639e5da6b515416fd779d0741e4561a. Examinemos el historial de commits después del restablecimiento soft con git log:

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

La salida ahora muestra un solo commit en el historial. Como ocurre con todas las invocaciones de git reset, --soft primero restablece el árbol de commits. A diferencia de los ejemplos anteriores de --hard y --mixed que apuntaban a HEAD, este restablecimiento soft movió el árbol de commits hacia atrás en el tiempo hasta un commit más antiguo — mientras el trabajo en sí permaneció de forma segura en el índice de preparación.

La diferencia entre los comandos reset y revert

git revert es generalmente una forma más segura de deshacer cambios que git reset, porque git reset puede perder trabajo. Un git reset no elimina inmediatamente un commit, pero puede dejar el commit huérfano — ninguna rama ni etiqueta apunta a él, por lo que no hay una forma directa de alcanzarlo. Git finalmente elimina los objetos huérfanos cuando ejecuta su recolector de basura (git gc), que por defecto elimina los objetos inalcanzables de más de 90 días (las entradas del reflog caducan después de 90 días, o 30 días para las inalcanzables). Hasta entonces, generalmente puedes recuperar un commit huérfano con el comando git reflog.

La otra diferencia clave: git revert está diseñado para deshacer commits públicos, ya compartidos añadiendo un nuevo commit que los revierte, mientras que git reset está diseñado para deshacer cambios locales en el directorio de trabajo y el índice de preparación.

Nunca restablezca el historial publicado

No ejecutes git reset <commit> cuando existan instantáneas posteriores a <commit> que ya hayan sido enviadas a un repositorio compartido. Una vez que publicas un commit, otros desarrolladores dependen de él. Reescribir o eliminar commits que tus compañeros ya han descargado causará historiales divergentes y conflictos de fusión dolorosos. Usa git reset solo en commits que existen únicamente en tu repositorio local. Para deshacer cambios públicos, usa el comando git revert en su lugar.

Ejemplos

Elimina un archivo específico del área de preparación sin cambiar el directorio de trabajo — retira el archivo del área de preparación manteniendo tus ediciones:

git reset <file>

Restablece todo el área de preparación para que coincida con el último commit, dejando el directorio de trabajo sin cambios. Esto retira todos los archivos del área de preparación sin sobrescribir los cambios, para que puedas reconstruir la instantánea preparada desde cero:

git reset

Restablece tanto el área de preparación como el directorio de trabajo para que coincidan con el último commit. Esto descarta todos los cambios sin confirmar en el directorio de trabajo también:

git reset --hard

Mueve la punta de la rama hacia atrás hasta un commit dado y restablece el área de preparación para que coincida, pero deja el directorio de trabajo sin modificar:

git reset <commit>

Mueve la punta de la rama actual hacia atrás hasta un commit dado y restablece tanto el área de preparación como el directorio de trabajo para que coincidan con él:

git reset --hard <commit>

Eliminar commits locales

Como se mostró anteriormente, puedes usar git reset para eliminar commits en tu repositorio local. En el ejemplo a continuación, git reset --hard HEAD~2 mueve la rama actual hacia atrás dos commits, eliminando las dos instantáneas más recientes del historial del proyecto:

# 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

Retirar archivos del área de preparación

Un uso muy común de git reset es ajustar qué archivos van en el próximo commit. En el ejemplo a continuación tenemos dos archivos, task.txt e index.txt, que fueron ambos preparados. git reset nos permite retirar del área de preparación los cambios que no pertenecen al próximo commit, para que cada archivo pueda confirmarse por separado:

# 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"

Práctica

Práctica
¿Cuáles son las características y opciones del comando 'git reset' en Git?
¿Cuáles son las características y opciones del comando 'git reset' en Git?
Was this page helpful?