git reflog: cómo recuperar commits y ramas perdidos
git reflog es un registro local en el que Git anota cada movimiento de HEAD y de las ramas: commits, reset, checkout, rebase, merge. Busca en git reflog la entrada anterior al error, por ejemplo HEAD@{2}, y restaura ese estado con git reset --hard HEAD@{2} o crea una rama con git branch rescue HEAD@{2}.
git reflog es el registro de referencias (reference log): Git anota en él cada movimiento de HEAD y de las puntas de las ramas. Haces un commit, cambias de rama, ejecutas reset, rebase o merge, y en el reflog aparece una línea con el hash al que apuntaba la referencia tras la operación. Por eso incluso un commit al que ya no apunta ninguna rama se puede encontrar y recuperar.
Lo principal que hay que saber: el reflog es local. Solo existe en tu clon, no se envía al servidor ni llega con git clone. A cambio, en tu propio repositorio te salva de casi cualquier error con el historial.
Cómo leer la salida de git reflog
git reflog
3c4d5e6 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9a8b7c6 HEAD@{1}: commit: Añadir validación del e-mail
1f2e3d4 HEAD@{2}: commit: Mover la configuración a un archivo aparte
3c4d5e6 (HEAD -> main) HEAD@{3}: checkout: moving from feature to main
7b6a5c4 (feature) HEAD@{4}: commit: Borrador del formulario de inicio de sesión
Cada línea es un estado de HEAD, con el más reciente arriba:
- hash: el commit al que apuntaba HEAD tras la operación;
- HEAD@{n}: «dónde estaba HEAD hace n movimientos»; es una referencia completa que puedes pasar a cualquier comando;
- descripción: el tipo de operación (commit, reset, checkout, rebase, merge, pull, cherry-pick, commit (amend)) y los detalles.
En el ejemplo se ve que se hicieron dos commits, «Mover la configuración» y «Añadir validación», y que luego reset HEAD~2 hizo retroceder la rama. En git log ya no aparecen, pero en el reflog siguen ahí.
Reflog de una rama concreta
Cada rama tiene su propio registro. Es más corto y más útil cuando has cambiado mucho de rama:
git reflog show feature
git reflog show origin/main # cómo cambió la rama remota con cada fetch
Referencias por tiempo
En lugar de un número puedes indicar un momento y Git buscará a dónde apuntaba entonces la referencia:
git show main@{yesterday}
git diff main@{"2 hours ago"} main
git log -g --since="1 day ago" --oneline
La opción -g (--walk-reflogs) de git log recorre el reflog en lugar de los enlaces entre padres y permite usar los formatos y filtros habituales. Para mostrar fechas, resulta cómodo git reflog --date=iso.
Cómo recuperar commits después de git reset --hard
La situación más habitual: ejecutaste git reset --hard hacia el commit equivocado y el trabajo «ha desaparecido». En realidad los commits no se han ido a ninguna parte; simplemente ya no se ven desde las ramas.
- Abre el registro:
git reflog. - Busca la línea justo anterior a
reset: moving to ...: ese es el estado previo al error. En el ejemplo de arriba,HEAD@{1}con el hash9a8b7c6. - Comprueba que es el correcto:
git log --oneline -5 HEAD@{1}. - Restaura la rama:
git reset --hard HEAD@{1}.
git reset --hard HEAD@{1}
HEAD is now at 9a8b7c6 Añadir validación del e-mail
Atención: tras cada operación los números se desplazan. Lo que era HEAD@{1} pasará a ser HEAD@{2} después del reset. Por eso es más fiable copiar el hash que el número.
Una limitación importante: el reflog solo guarda lo que tiene commit. Los cambios sin confirmar que borró reset --hard o git checkout -- archivo no se pueden recuperar con el reflog. Queda algo de esperanza si los archivos estaban añadidos al índice; lo vemos más abajo.
Cómo recuperar una rama borrada
Borraste una rama con git branch -D feature y tenía commits sin subir. El registro de la propia rama se elimina con ella, pero el de HEAD se mantiene, y HEAD pasó por sus commits mientras trabajabas en ella.
git reflog | grep feature
7b6a5c4 HEAD@{4}: commit: Borrador del formulario de inicio de sesión
3c4d5e6 HEAD@{3}: checkout: moving from feature to main
# El último commit de la rama es aquel desde el que saliste de ella
git branch feature 7b6a5c4
Busca la línea checkout: moving from feature to ...: el hash de la entrada anterior (más abajo en la lista) es el último estado de la rama. Por cierto, al borrarla Git imprime el hash: Deleted branch feature (was 7b6a5c4).; si la terminal sigue abierta, con eso basta.
Cómo deshacer un rebase fallido
Rebase reescribe los commits, y si durante el proceso resolviste mal los conflictos, el resultado puede ser peor que el original. El reflog guarda el estado previo al inicio del rebase.
git reflog
e1f2a3b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e1f2a3b HEAD@{1}: rebase (pick): Borrador del formulario de inicio de sesión
c4d5e6f HEAD@{2}: rebase (start): checkout main
7b6a5c4 HEAD@{3}: commit: Borrador del formulario de inicio de sesión
git reset --hard HEAD@{3}
Necesitas la entrada anterior a rebase (start). También hay un atajo: antes de operaciones peligrosas (rebase, merge, reset) Git guarda la posición anterior en la referencia ORIG_HEAD, así que justo después de un rebase funciona git reset --hard ORIG_HEAD. Pero ORIG_HEAD se sobrescribe con la siguiente operación de ese tipo, mientras que el reflog guarda todo el historial.
Cómo recuperar el commit anterior a git commit --amend
--amend no modifica el commit, sino que crea uno nuevo en su lugar. El antiguo sigue en el reflog:
git reflog
5a6b7c8 HEAD@{0}: commit (amend): Añadir validación del e-mail
9a8b7c6 HEAD@{1}: commit: Añadir validación del e-mail
git diff HEAD@{1} HEAD # qué cambió el amend
git reset --soft HEAD@{1} # volver al commit antiguo; los cambios del amend quedan en el índice
Recuperación segura: una rama en lugar de reset
Si no estás seguro de qué commit necesitas, no hagas un reset --hard a ciegas. Crea una rama de rescate: no rompe nada y protege el commit encontrado de ser eliminado:
git branch rescue HEAD@{5}
git log --oneline rescue
git switch rescue
Después puedes llevarte los commits de rescue con cherry-pick o fusionarlos, y borrar la rama cuando todo vuelva a estar en su sitio.
Cuánto tiempo se conserva el reflog
Las entradas no son eternas. Las limpia git gc, que Git lanza por su cuenta de vez en cuando. Los plazos se definen con estos ajustes:
| Ajuste | Por defecto | Qué significa |
|---|---|---|
gc.reflogExpire | 90 días | Plazo para entradas con commits alcanzables desde las ramas actuales |
gc.reflogExpireUnreachable | 30 días | Plazo para entradas con commits no alcanzables desde ninguna rama, justo los «perdidos» |
gc.pruneExpire | 2 semanas | Cuándo se borran los propios objetos inalcanzables a los que ya no apunta nada |
Conclusión práctica: un commit perdido está garantizado en el reflog durante aproximadamente un mes. No aplaces la recuperación y no ejecutes a mano git gc --prune=now ni git reflog expire --expire=now --all hasta haber recuperado lo que necesitas.
Si el reflog no ayuda: git fsck
Hay casos en los que el commit no está en el reflog: por ejemplo, lo creó git stash y luego el stash se eliminó con git stash drop, o los cambios solo estaban en el índice. Entonces hay que buscar objetos «colgantes» (dangling):
git fsck --lost-found
dangling commit 2b3c4d5e6f...
dangling blob 8e9f0a1b2c...
git show 2b3c4d5e6f # ver el contenido
git branch recovered 2b3c4d5e6f
Los commits colgantes de un stash eliminado llevan el mensaje «WIP on ...». Los blobs colgantes son el contenido de archivos que en su día se añadieron con git add pero nunca llegaron a un commit. No tienen nombre de archivo y habrá que identificarlos por su contenido, pero así a veces se logra salvar el trabajo tras un reset --hard. Con la opción --lost-found, Git además los copia en .git/lost-found/.
Lo que el reflog no puede hacer
- No funciona entre máquinas. Si un compañero hizo force push y machacó tus commits en el servidor, búscalos en el reflog de quien los tuviera en local; por ejemplo,
git reflog show origin/mainen la máquina de cualquiera que hiciera fetch antes de la sobrescritura. - No ve lo que no tiene commit. Por eso el mejor seguro antes de una operación arriesgada es un commit o
git stash. - No guarda el historial para siempre. Están los plazos de arriba y, además, un clon nuevo empieza con el registro vacío.
En la práctica: un seguro antes de una operación peligrosa
El reflog te salva, pero recuperar con él exige atención: hay que dar con la entrada correcta entre decenas de líneas parecidas. Es mucho más sencillo dejar una marca de antemano. Antes de un rebase, de un reset grande o de experimentos con el historial, crea una rama o etiqueta temporal:
git branch backup/before-rebase
git rebase -i main
# algo salió mal
git reset --hard backup/before-rebase
# todo salió bien
git branch -D backup/before-rebase
Cuesta un comando y te ahorra buscar el hash. El reflog queda como plan B para cuando te acuerdas del seguro demasiado tarde.
Cómo encontrar rápido la entrada que buscas
En un registro largo ayuda filtrar. Busca por tipo de operación o por el texto del mensaje del commit y, si hay dudas, mira el contenido:
git reflog | grep -E "reset|rebase \(start\)"
git log -g --grep="validación" --oneline
git show --stat HEAD@{7}
El comando git log -g con --grep busca en los mensajes de los commits del reflog, y git show --stat muestra qué archivos se tocaron: con eso es fácil identificar el commit correcto aunque su mensaje fuera poco informativo, tipo «wip».
Conclusión
git reflog es la red de seguridad de Git: todo lo que se confirmó en tu repositorio durante las últimas semanas se puede encontrar y recuperar. Los pasos son siempre los mismos: abrir el registro, encontrar la entrada anterior al error, comprobarla con git log o git show y luego crear una rama o hacer reset --hard. El uso de restore, reset, revert y la recuperación tras errores se practican en el curso de Git.
Preguntas frecuentes
¿En qué se diferencia git reflog de git log?
git log recorre los enlaces entre commits padre e hijo y muestra el historial de la rama. git reflog muestra a dónde se han movido HEAD y las ramas en tu repositorio, incluidos commits que ya no están en el historial de ninguna rama.
¿El reflog se sube a GitHub o GitLab?
No, el reflog se guarda solo en la carpeta local .git/logs y no se transfiere con push ni con clone.
¿Se puede limpiar el reflog?
Sí, con git reflog expire --expire=now --all seguido de git gc --prune=now, pero después ya no podrás recuperar los commits perdidos.
¿Dónde se guarda físicamente el reflog?
En los archivos de texto .git/logs/HEAD y .git/logs/refs/heads/nombre-rama, con una línea por cada movimiento.
¿Funciona el reflog en un repositorio bare del servidor?
Por defecto, en los repositorios bare el reflog está desactivado (core.logAllRefUpdates), así que en el servidor no se puede contar con él.
Este tema se explica a fondo y con práctica en el curso «Git desde cero hasta desarrollador seguro».
Ir al curso