Saltar a contenido

Capítulo 9: Recuperando cambios y viajando en el tiempo con Git

Git no solo sirve para avanzar: también te permite retroceder, corregir y explorar versiones anteriores de tu proyecto sin perder el hilo de la historia. La propia documentación oficial describe reset, restore y revert como comandos complementarios para deshacer cambios en distintos niveles, mientras que las etiquetas (tags) permiten marcar versiones importantes del repositorio como puntos de referencia.

En este capítulo vas a trabajar sobre el mismo sitio web personal del libro y simular errores reales: archivos eliminados, modificaciones equivocadas, commits que no debieron existir. Aprenderás a resolver estos problemas usando Git como una auténtica máquina del tiempo.

9.1 Git como una máquina del tiempo

El historial de Git es una secuencia de commits que representan estados completos de tu proyecto en puntos específicos del tiempo. Cada commit registra qué cambió, cuándo y por quién, y está identificado por un hash único.

El concepto de historial

Cuando haces un commit, Git guarda un snapshot (una “foto”) del contenido del proyecto en ese momento. No guarda solo diferencias, sino una referencia completa al árbol de archivos, optimizada internamente para evitar duplicación innecesaria.

Ese conjunto de commits forma una línea temporal:

  • el commit inicial,
  • los commits intermedios,
  • el estado actual.

Git te permite moverte por esa línea temporal para ver cómo era el proyecto en cualquier punto anterior.

¿Por qué Git no pierde información fácilmente?

El diseño de Git busca preservar la historia:

  • cada commit crea un nuevo nodo en la línea temporal;
  • mientras los commits sigan referenciados por ramas, etiquetas o el propio HEAD, no se descartan;
  • incluso operaciones de reescritura generan nuevas referencias, manteniendo accesibles estados anteriores vía reflog (más allá de lo que veremos aquí).

Por eso, un archivo que “desaparece” del Working Directory no ha desaparecido por completo del repositorio: sigue existiendo en algún commit anterior y puede recuperarse.

Analogía de “viajar en el tiempo”

Imagina que tu proyecto es una película y cada commit es un fotograma clave. Git te permite:

  • pausar en el fotograma actual;
  • retroceder a fotogramas anteriores para ver cómo estaban las cosas;
  • crear nuevas ramas de la historia a partir de fotogramas intermedios.

Lo importante es saber cuándo quieres solo mirar el pasado (explorar commits) y cuándo quieres reescribir la historia (reset, revert, etc.).

flowchart LR
    A[Commit inicial] --> B[Commit 2]
    B --> C[Commit 3]
    C --> D[Commit actual]

Este diagrama representa la línea temporal básica de un repositorio: cada commit es un nodo que puedes visitar, analizar y utilizar como punto de partida para nuevas decisiones.

[Ilustración: línea de tiempo moderna representando varios puntos (commits) con fechas y breves descripciones, destacando que se puede retroceder para revisar versiones anteriores de un proyecto de software]

Nota

Git no se comporta como “guardar y sobrescribir” de un editor tradicional. Cada commit añade historia; deshacer cambios devuelve el proyecto a estados anteriores sin borrar automáticamente esa historia.

Breve conclusión: Git registra la evolución de tu proyecto como una línea temporal de commits. Gracias a ese historial, puedes viajar hacia atrás para recuperar archivos, deshacer cambios y revisar versiones anteriores.

9.2 Recuperando cambios antes del commit

Antes de tocar el historial de commits, muchas recuperaciones se pueden hacer directamente en el Working Directory y la Staging Area. El comando git restore fue introducido para aclarar este tipo de operaciones: deshacer cambios sin afectar el historial existente.

git restore

La documentación oficial de git restore explica que su función es devolver archivos del árbol de trabajo al contenido almacenado en un commit, una rama o una etiqueta específica.1 También se usa para descartar cambios locales y devolver archivos a su estado confirmado más reciente.

Casos típicos:

  • recuperas un archivo al último commit porque hiciste cambios equivocados;
  • descartas todas las modificaciones no confirmadas en un archivo;
  • restauras una versión específica usando --source.

Ejemplo básico:

git restore index.html

Este comando descarta cambios locales no confirmados en index.html y lo devuelve al estado del último commit (HEAD).

git restore --staged

La opción --staged de git restore deshace el efecto de git add: quita archivos de la Staging Area sin tocar sus modificaciones reales en el Working Directory.1

Ejemplos:

git restore --staged index.html
  • el archivo deja de estar preparado (sale de la Staging Area);
  • los cambios permanecen en el Working Directory.

Esto es equivalente conceptual a usar git reset HEAD index.html en el modo clásico, pero restore hace más explícita la intención de “solo desprepare, no modifiques el historial”.

Cuándo utilizar cada uno

Comando Qué hace Cuándo usarlo
git restore archivo Descarta cambios locales y devuelve al último commit. Cuando quieres “volver atrás” en un archivo sin haber hecho commit
git restore --staged archivo Quita el archivo de la Staging Area pero mantiene cambios. Cuando preparaste un archivo por error o quieres reorganizar el commit

Qué ocurre internamente

  • git restore sin --staged actualiza el contenido del archivo en el Working Directory usando la versión de referencia (por defecto, el último commit en HEAD).
  • git restore --staged actualiza la entrada del índice (Staging Area) sin tocar el archivo en el Working Directory.

Ejemplo aplicado al proyecto del libro

Supón que editas index.html para probar un cambio en el título, pero luego decides que no te gusta.

  1. Ves el archivo como modificado en git status.
  2. Ejecutas:
git restore index.html
  1. index.html regresa a la versión confirmada más reciente, como si el experimento nunca hubiera ocurrido.

En otro caso, preparas index.html y style.css, pero quieres que el commit incluya solo index.html.

  1. Ejecutas:
git restore --staged style.css
  1. style.css vuelve a la sección de cambios sin preparar, listo para otro commit.

Error común

Confundir git restore con operaciones sobre el historial. restore actúa sobre el Working Directory y la Staging Area, no sobre los commits ya registrados.

Breve conclusión: git restore y git restore --staged te permiten deshacer cambios antes del commit: restaurar archivos a su estado confirmado y reorganizar qué forma parte de cada commit sin tocar el historial.

9.3 Deshaciendo commits

Cuando el problema está en uno o varios commits ya creados, el comando central es git reset. La documentación oficial explica que reset puede mover HEAD a otro commit y, según el modo (--soft, --mixed, --hard), actualizar también la Staging Area y el Working Directory.

git reset --soft

git reset --soft mueve HEAD al commit indicado pero no cambia ni el índice ni el Working Directory.

Ejemplo:

git reset --soft HEAD~1

Este comando “deshace” el último commit en el sentido de que:

  • HEAD apunta ahora al commit anterior;
  • los cambios del commit deshecho quedan preparados en la Staging Area.

Esto permite, por ejemplo, combinar varios commits en uno solo: haces reset --soft y luego git commit con un nuevo mensaje.

git reset --mixed (modo por defecto)

git reset --mixed es el modo por defecto cuando no se especifica nada. Mueve HEAD y actualiza la Staging Area, pero deja el Working Directory intacto.

Ejemplo:

git reset HEAD~1
  • HEAD retrocede un commit;
  • el índice pasa a coincidir con el nuevo HEAD;
  • los cambios del commit deshecho quedan como modificaciones sin preparar en el Working Directory.

Es como decir: “devuélveme al commit anterior, pero deja los cambios como si aún no se hubieran confirmado”.

git reset --hard

git reset --hard es la opción más agresiva. La documentación oficial advierte que este modo sobrescribe todos los archivos y directorios con la versión del commit objetivo, y que también puede sobrescribir archivos sin seguimiento.2

Ejemplo:

git reset --hard HEAD~1
  • HEAD retrocede un commit;
  • el índice se actualiza;
  • el Working Directory se reemplaza completamente por el contenido del commit anterior.

Cualquier cambio local sin confirmar se pierde.

Ventajas, riesgos y casos de uso

Modo Ventajas Riesgos Uso típico
--soft Permite recombinar commits sin perder cambios Puede confundir si no se entiende que los cambios siguen preparados Reescribir historial local antes de compartir
--mixed Devuelve commits a cambios sin preparar Puede dejar muchos cambios en el Working Directory Ajustar commits recientes, reorganizar trabajo local
--hard Limpia por completo, deja el repo en un commit anterior Destruye cambios sin confirmar; peligroso si no se usa con cuidado Volver a un estado estable en local cuando los cambios no merecen conservarse

Advertencias importantes

La propia documentación de Git insiste en que reset --hard no debe usarse para reescribir historial ya compartido con otras personas. Si hiciste push de esos commits a GitHub y otros repositorios dependen de ellos, borrarlos de tu rama local puede crear problemas de sincronización.

Nota

Usa reset para historial que aún no has compartido. Una vez que los commits están en un remoto usado por otras personas, la herramienta más segura es git revert.

Breve conclusión: git reset permite deshacer commits moviendo HEAD y, según el modo, actualizando o sobrescribiendo la Staging Area y el Working Directory. Es poderoso y útil, pero debe usarse con cuidado, especialmente en equipos.

9.4 Revertir cambios de forma segura

Cuando el historial ya fue compartido —por ejemplo, hiciste push a GitHub y otros repositorios dependen de esos commits— la herramienta segura para “deshacer” es git revert. La documentación oficial de git revert lo describe como un comando que crea nuevos commits que revierten el efecto de commits anteriores.3

git revert

Ejemplo:

git revert HEAD
  • crea un nuevo commit que invierte el efecto del último commit;
  • el historial mantiene ambos: el commit original y el commit que lo revierte.

Cuándo utilizarlo

Debes usar git revert cuando:

  • el commit ya fue publicado en un remoto (GitHub);
  • otros clones pueden depender de él;
  • quieres deshacer el efecto sin reescribir el historial compartido.

En contextos de colaboración, revert es la herramienta recomendada para “deshacer” cambios.

Por qué es la mejor opción en historial compartido

reset borra referencia a commits, mientras que revert añade nuevos commits que restauran el estado anterior. La documentación oficial sugiere ver “Reset, restore and revert” para entender estos matices.

Comparación conceptual:

Comando Qué hace sobre historial Contexto recomendado
reset Mueve HEAD y puede descartar commits de la rama actual Historial local no compartido
revert Crea nuevos commits que invierten cambios anteriores Historial publicado/shared, proyectos colaborativos

Ejemplo aplicado al proyecto del libro

Supón que hiciste un commit que cambia el color de fondo del sitio a algo que no gusta y ya lo publicaste en GitHub.

En lugar de usar reset --hard después de push (lo que generaría historia divergente), haces:

git revert HEAD

Git crea un nuevo commit que restaura el color anterior. El historial cuenta la historia completa:

  • commit que cambia el color;
  • commit que lo revierte.

Buenas prácticas

Cuando no estás seguro de quién depende de un commit, git revert es la opción prudente. Registra la corrección sin borrar la historia original.

Breve conclusión: git revert deshace cambios compartidos creando nuevos commits inversos. Es la manera segura de corregir errores en repositorios donde el historial ya se publicó o se usa en equipo.

9.5 Recuperando versiones anteriores

Además de deshacer, a veces solo quieres explorar cómo era el proyecto en un commit anterior: revisar un diseño anterior, comparar versiones o tomar ideas sin modificar el historial principal.

Regresar temporalmente a commits anteriores

Con git checkout (o git switch y git restore combinados) puedes moverte a commits específicos.

Ejemplo clásico:

git checkout abc1234

Donde abc1234 es el hash de un commit anterior.

Al hacerlo:

  • tu Working Directory muestra el estado del proyecto en ese commit;
  • HEAD puede quedar “detached” (desprendido), lo que significa que no estás en una rama, sino directamente en un commit.

Puedes navegar archivos, probar el sitio y ver cómo estaba el proyecto en ese momento.

Diferencia entre explorar el historial y modificarlo

Explorar historial:

  • moverte a un commit anterior con checkout;
  • revisar archivos, probar código;
  • si sales y vuelves a tu rama (git checkout main), el historial de la rama no cambia.

Modificar historial:

  • usar reset para mover HEAD y cambiar la referencia de la rama;
  • usar revert para añadir commits inversos.

En el primer caso, solo viajas en el tiempo para mirar. En el segundo, cambias permanentemente la historia de una rama.

Ejemplo aplicado al proyecto del libro

Supón que quieres ver cómo era el sitio antes de que se agregara la página de recursos. Puedes:

  1. Buscar el commit anterior en git log.
  2. Hacer git checkout <hash-anterior>.
  3. Abrir el sitio y comprobar la diferencia.
  4. Cuando termines, regresar a main:
git checkout main

Consejo

Cuando trabajes en un commit en modo detached HEAD, evita hacer commits allí a menos que sepas cómo recuperar ese trabajo. Lo más seguro es usarlo solo para explorar.

Breve conclusión: puedes viajar a commits anteriores para revisar el proyecto sin alterar la historia de tus ramas. La clave está en distinguir entre explorar y modificar el historial.

9.6 Etiquetas (Tags)

Los tags en Git son referencias simbólicas que apuntan a commits específicos. La documentación y recursos sobre etiquetado muestran que son muy útiles para marcar versiones estables, lanzamientos y puntos importantes del proyecto.

Qué son y cuándo utilizarlos

Un tag es como un “marcador” permanente en la línea temporal. En lugar de recordar un hash largo, puedes recordar un nombre como v1.0 o capitulo-09-estable.

Usos típicos:

  • marcar versiones de producción;
  • marcar entregas importantes en el libro o proyecto;
  • referenciar puntos estables para despliegues.

Tipos de tags

La documentación distingue entre:

  • Tags ligeros: solo un nombre apuntando a un commit.
  • Tags anotados: incluyen autor, fecha y mensaje; se recomiendan para releases compartidos.

git tag

Para crear un tag ligero:

git tag v1.0

Esto apunta v1.0 al commit actual (HEAD).

Para listar tags:

git tag

git tag -a

Para crear un tag anotado:

git tag -a v1.0 -m "Versión 1.0 estable del sitio"

Este tag almacena mensaje, autor y fecha, y se recomienda para versiones importantes.

Puedes etiquetar commits pasados:

git tag -a v0.9 -m "Versión previa" abc1234

git push --tags

Por defecto, git push no envía tags. Si quieres compartirlos en GitHub:

  • para un tag específico:
git push origin v1.0
  • para todos los tags:
git push --tags

La documentación recuerda que es necesario empujar los tags explícitamente para que otros colaboradores los vean.

Ejemplo aplicado al proyecto del libro

Supón que finalizas el capítulo 9 y quieres marcar la versión del sitio que corresponde a ese punto del libro:

  1. Confirmas que main está estable.
  2. Creas un tag anotado:
git tag -a capitulo-09 -m "Estado del proyecto al finalizar el capítulo 9"
  1. Lo publicas en GitHub:
git push origin capitulo-09

Ahora siempre podrás volver a ese estado referenciando el tag.

flowchart LR
    A[Commits del proyecto] --> B[Tag v1.0]
    A --> C[Tag capitulo-09]

[Ilustración: línea de tiempo del proyecto con pequeños marcadores etiquetados como “v1.0”, “v1.1”, “capitulo-09”, destacando versiones estables y puntos clave del desarrollo]

Buenas prácticas

Usa tags anotados para versiones que compartirás con otros (producción, lanzamientos, entregas de curso). Los tags ligeros pueden reservarse para uso local.

Breve conclusión: las etiquetas permiten marcar commits importantes como versiones estables del proyecto. Son claves para organizar entregas, desplegar versiones y navegar por la historia de forma más humana.

9.7 Errores comunes y recuperación

En esta sección simularemos escenarios frecuentes y veremos cómo Git ayuda a recuperarse.

Eliminar un archivo por accidente

Escenario:

  • Estás en una rama de trabajo.
  • Borras contacto.html pensando que ya no lo necesitas.
  • Luego recuerdas que contenía información importante.

Si aún no hiciste commit:

  1. git status mostrará contacto.html como eliminado.
  2. Puedes restaurarlo:
git restore contacto.html

El archivo vuelve a su estado confirmado más reciente.

Si ya hiciste commit y quieres revertir la eliminación en historial compartido:

  1. Usa git revert sobre el commit que lo eliminó.
git revert <hash-del-commit-de-eliminacion>
  1. Esto creará un nuevo commit que reintroduce contacto.html.

Modificar archivos incorrectos

Escenario:

  • Querías editar recursos.html, pero terminaste cambiando index.html.
  • Preparaste ambos archivos (git add), pero solo recursos.html debería ir en el commit.

Solución:

  1. Quita index.html de la Staging Area:
git restore --staged index.html
  1. Haz el commit solo con recursos.html preparado.

Este flujo evita que cambios erróneos entren en commits importantes.

Realizar un commit equivocado

Escenario:

  • Hiciste un commit con mensaje incorrecto o combinaste cambios que deberían estar separados.
  • El commit aún no se ha compartido.

Soluciones:

  1. Si solo quieres corregir el mensaje, puedes usar git commit --amend (sin profundizar aquí).
  2. Si quieres reorganizar los cambios, puedes usar:
git reset --soft HEAD~1
  • HEAD retrocede;
  • los cambios vuelven a la Staging Area;
  • puedes crear commits nuevos más ordenados.

Querer regresar a una versión anterior

Escenario:

  • Probaste muchos cambios y el sitio se ve peor que antes.
  • Quieres volver a una versión anterior estable.

Si el historial no se ha compartido (solo local):

  • puedes usar git reset --hard <hash-estable> para volver completamente a ese estado;
  • ten cuidado: perderás cambios sin confirmar.

Si el historial ya está compartido:

  • usa git revert sobre los commits que introdujeron los cambios problemáticos;
  • el historial quedará con commits que corrigen la situación sin borrar los anteriores.

Error común

Usar reset --hard sobre historial compartido para “arreglar rápido” y provocar divergencias entre tu rama y el remoto. En colaboración, el camino seguro es revertir.

Breve conclusión: muchos errores cotidianos —eliminar archivos, preparar contenido incorrecto, hacer commits equivocados o querer volver a una versión anterior— tienen soluciones claras en Git usando restore, reset y revert según el contexto.

9.8 Buenas prácticas

Con todas las herramientas en la mesa, es momento de resumir cuándo conviene usar restore, reset, revert y tag.

Cuándo usar cada comando

Comando En qué nivel actúa Uso recomendado
restore Working Directory / Staging Area. Deshacer cambios antes del commit, restaurar archivos, quitar de Staging Area
reset HEAD, índice y Working Directory según modo. Reescribir historial local, reorganizar commits antes de compartir
revert Historial de commits (añadiendo nuevos). Deshacer cambios ya compartidos de forma segura
tag Referencias a commits específicos. Marcar versiones estables, lanzamientos, puntos clave del proyecto

Tipos de reset y su uso

Modo reset Índice Working Directory Caso típico
--soft sin cambios sin cambios Combinar commits, ajustar historial manteniendo cambios preparados.
--mixed coincide con commit objetivo mantiene cambios Deshacer commits y tratar cambios como modificaciones sin preparar.
--hard coincide con commit objetivo coincide con commit objetivo Regresar completamente a un estado conocido en local, descartando cambios

Tabla comparativa: restore vs reset vs revert

Acción ¿Modifica historial? ¿Modifica archivos? ¿Contexto ideal?
restore archivo No Sí, Working Directory Correcciones rápidas antes del commit
restore --staged archivo No Solo índice Reorganizar qué entra en cada commit
reset Sí (mueve HEAD) Sí, según modo Reescritura local antes de compartir
revert commit Sí (añade commits) Sí, aplicando cambios inversos Correcciones en historial ya publicado

Buenas prácticas

Piensa siempre en dos preguntas antes de deshacer: “¿Este historial ya fue compartido?” y “¿Quiero borrar historia o registrar la corrección?”. Tus respuestas te guiarán hacia reset o revert.

Breve conclusión: usar bien restore, reset, revert y tag depende de entender en qué nivel actúan (archivos vs historial) y en qué contexto (local vs compartido). Con ese criterio, Git se convierte en una máquina del tiempo controlada, no en un riesgo.

Práctica guiada

En esta práctica vas a aplicar todos los conceptos sobre el mismo proyecto del libro.

Paso 1: recuperar archivos eliminados

  1. En una rama de trabajo, elimina contacto.html.
  2. Antes de hacer commit, usa:
git restore contacto.html
  1. Verifica que el archivo vuelve al estado anterior.

Paso 2: deshacer modificaciones

  1. Modifica index.html y style.css con cambios de prueba.
  2. Usa:
git restore index.html

para descartar los cambios de index.html y mantener los de style.css.

  1. Observa cómo git status refleja la diferencia.

Paso 3: restaurar archivos preparados

  1. Prepara ambos archivos:
git add index.html style.css
  1. Luego decide que quieres un commit solo con index.html.
  2. Usa:
git restore --staged style.css
  1. Crea el commit con index.html como único archivo preparado.

Paso 4: deshacer un commit

  1. Realiza un commit de prueba con cambios menores.
  2. Usa git log para verlo.
  3. Aplica:
git reset --soft HEAD~1
  1. Comprueba que el commit desaparece de la historia pero los cambios siguen preparados.
  2. Reorganiza el commit como prefieras.

Paso 5: revertir un commit

  1. Haz un commit que cambie visiblemente el sitio (por ejemplo, el color de fondo).
  2. Haz push al remoto.
  3. Usa:
git revert HEAD
  1. Haz push nuevamente y observa cómo el commit de revert aparece en GitHub.

Paso 6: crear etiquetas

  1. Con main estable, crea un tag anotado:
git tag -a capitulo-09 -m "Estado del proyecto al finalizar el capítulo 9"
  1. Publica el tag:
git push origin capitulo-09
  1. Comprueba en GitHub que la etiqueta aparece en la lista de releases o tags.

Paso 7: consultar versiones anteriores

  1. Usa git log para identificar un commit clave anterior.
  2. Haz:
git checkout <hash>
  1. Explora el proyecto en ese estado.
  2. Regresa a main con:
git checkout main

Diagrama de la práctica completa

flowchart TD
    A[Eliminar/modificar archivos] --> B[Recuperar con git restore]
    B --> C[Reorganizar commits con git reset]
    C --> D[Deshacer cambios compartidos con git revert]
    D --> E[Marcar estado estable con tags]
    E --> F[Explorar versiones anteriores]

[Ilustración: línea de tiempo del proyecto mostrando acciones como “restore”, “reset”, “revert” y “tag” aplicadas sobre distintos puntos, con íconos que representan recuperación de archivos, deshacer commits y marcar versiones estables]

Consejo

Practica estas operaciones en una rama de prueba antes de aplicarlas sobre la rama principal. Ganar confianza en la “máquina del tiempo” de Git es clave para usarla sin miedo.

Breve conclusión: la práctica guiada te muestra que puedes recuperar archivos, deshacer commits, revertir cambios y marcar versiones estables de forma controlada usando Git. Tu proyecto deja de ser frágil: cualquier error tiene un camino de vuelta.

Cierre del capítulo

En este capítulo viste a Git como una verdadera máquina del tiempo: aprendiste a recuperar archivos y estados con git restore, a reescribir historial local con git reset, a revertir cambios ya compartidos con git revert y a marcar versiones estables mediante tags.

Aplicando estas técnicas al proyecto del libro, comprobaste que los errores cotidianos —eliminar archivos, hacer commits incorrectos, probar cambios arriesgados— dejan de ser motivo de pánico. Siempre que entiendas en qué nivel actúa cada comando y en qué contexto (local vs compartido), podrás moverte con seguridad por la línea temporal de tu proyecto.

Notas al pie

Para profundizar


  1. git-restore Documentation - Sitio oficial. https://git-scm.com/docs/git-restore 

  2. git-reset Documentation - Sitio oficial. https://git-scm.com/docs/git-reset 

  3. git-revert Documentation - Sitio oficial. https://git-scm.com/docs/git-revert