4 puntos por GN⁺ 2025-01-18 | 1 comentarios | Compartir por WhatsApp
  • En Git, si no conoces el nombre de una situación de error, es difícil buscar cómo resolverla, así que este material organiza los comandos por escenarios comunes de recuperación para poder encontrarlos y usarlos de inmediato
  • Si dañaste gravemente el repositorio o quieres deshacer errores de borrado o merge, puedes revisar el historial de trabajo con git reflog y volver a un estado anterior con git reset HEAD@{index}
  • Para pequeños cambios o para modificar el mensaje del último commit, git commit --amend permite resolverlo rápido, pero no debe usarse en commits que ya fueron enviados a una rama pública/compartida
  • Si hiciste commit en la rama equivocada, puedes moverlo con el flujo reset, stash, checkout, stash pop o con cherry-pick, pero el método seguro cambia según si ya hiciste push o no
  • reset --hard y git clean -d --force, que fuerzan la recuperación al estado del repositorio remoto, también pueden borrar archivos no rastreados, así que hay que verificar antes de ejecutarlos

Por qué es difícil recuperarse de errores en Git

  • Es fácil cometer errores en Git, y muchas veces para buscar cómo corregir un problema ya necesitas conocer el nombre del concepto involucrado
  • Este material organiza situaciones problemáticas comunes en Git con inglés sencillo y ejemplos de comandos
  • No es una referencia completa, y puede haber otras formas de realizar la misma tarea

Volver el repositorio a un estado anterior

  • Si dañaste gravemente el repositorio o quieres recuperar algo que borraste, revisa el historial de trabajo con git reflog
git reflog


# puedes ver una lista de operaciones de Git en todas las ramas


# cada entrada tiene un índice con la forma HEAD@{index}
git reset HEAD@{index}
  • reflog puede usarse en estas situaciones
    • recuperar contenido borrado por accidente
    • eliminar un intento que dañó el repositorio
    • recuperarse después de un merge problemático
    • volver a un punto donde realmente funcionaba

Cómo corregir el último commit

  • Si necesitas agregar un pequeño cambio justo después del último commit, usa git commit --amend --no-edit
# después de hacer cambios
git add . # o agregar archivos individuales
git commit --amend --no-edit
  • Este comando incluye los cambios nuevos en el último commit
  • También podrías crear un nuevo commit y luego combinarlo con rebase -i, pero amend es mucho más rápido
  • No debes usar amend en commits que ya fueron enviados a una rama pública/compartida
  • Si solo quieres cambiar el mensaje del último commit, usa el siguiente comando
git commit --amend


# cambia el mensaje del commit siguiendo las indicaciones

Cuando hiciste commit en la rama equivocada

  • Si hiciste commit por error en master pero debía estar en una rama nueva, crea una nueva rama desde el estado actual y elimina el último commit de master
git branch some-new-branch-name
git reset HEAD~ --hard
git checkout some-new-branch-name
  • Este método no es adecuado para commits que ya fueron enviados a una rama pública/compartida
  • Si antes intentaste otras cosas, puede que necesites usar HEAD@{number-of-commits-back} en lugar de HEAD~
  • Si hiciste commit en una rama completamente equivocada, puedes moverlo con el flujo reset --soft, stash, checkout, stash pop
git reset HEAD~ --soft
git stash
git checkout name-of-the-correct-branch
git stash pop
git add . # o agregar archivos individuales
git commit -m "your message here"
  • También existe la opción de usar cherry-pick en la misma situación
git checkout name-of-the-correct-branch
git cherry-pick master
git checkout master
git reset HEAD~ --hard

Revertir cambios y commits anteriores

  • Si modificaste archivos pero git diff aparece vacío, puede que ya los hayas puesto en staging, así que necesitas la bandera --staged
git diff --staged
  • Para revertir cambios de hace varios commits, busca el hash con git log y ejecuta git revert
git log
git revert [saved hash]
  • git revert crea un nuevo commit que deshace ese commit
  • Si no quieres revertir un commit completo sino solo un archivo específico, usa el flujo de llevar a índice la versión anterior del archivo y luego hacer commit
git log
git checkout [saved hash] -- path/to/file
git commit -m "Wow, you don't have to copy-paste to undo"

Forzar la recuperación al estado del repositorio remoto

  • En lugar de borrar el repositorio y clonarlo de nuevo, puedes usar comandos de Git para alinearlo con el estado del repositorio remoto
git fetch origin
git checkout master
git reset --hard origin/master
git clean -d --force
  • git reset --hard origin/master ajusta la rama al estado de master en el remoto
  • git clean -d --force elimina archivos y directorios no rastreados
  • Debes repetir checkout, reset y clean en cada rama dañada
  • Estas operaciones son destructivas e irreversibles, así que debes tener cuidado antes de ejecutarlas

Alcance y contribuciones

  • Este material no es una referencia completa de Git, sino una colección de procedimientos prácticos de recuperación encontrados mediante prueba y error
  • Si quieres ayudar agregando traducciones, puedes enviar un PR en GitHub

1 comentarios

 
GN⁺ 2025-01-18
Opiniones de Hacker News
  • Hay algunas cosas que cambiaría: siempre usaría git switch en lugar de git checkout, y evitaría reset --hard tanto como sea posible.
    Por ejemplo, si “por error hiciste en master un commit que debería estar en una rama nueva”, se puede resolver con git branch some-new-branch-name, git switch -d HEAD~, git switch -C master, git switch some-new-branch-name, en ese orden.
    Si “hiciste commit en la rama equivocada”, también lo resolvería haciendo git switch a la rama correcta y luego git cherry-pick master, git switch -d master~, git switch -C master.
    Para “tirar todo y volver al estado remoto”, lo armaría como git fetch origin, git restore -WS ., git clean -d --force, git switch -d origin/master, git switch -C master.

    • El modelo interno de Git es hermoso, con blobs, árboles de commits y punteros a commits, pero la brecha con la interfaz de línea de comandos es demasiado grande.
      Aunque entiendas bien el modelo de Git, estas recetas no son intuitivas y además hay que conocer los comportamientos peculiares de cada comando.
      Por ejemplo, git switch -c some-new-branch-name ya existe, pero creo que sería más intuitivo un comando como git move-branch master HEAD~, que solo mueva una rama a otro commit.
    • No es que me oponga a la dirección de usar git switch y git restore en lugar de git checkout, pero estos comandos se introdujeron en Git v2.23, y esa versión salió hace unos 5 años.
      La página de ayuda todavía incluye la advertencia THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
      Claro, esa advertencia estuvo ahí prácticamente desde que aparecieron los comandos, y después de 5 años parece que ya sería momento de dejar de llamarlos experimentales.
      Aun así, git checkout tiene mayor compatibilidad hacia atrás, y puede verse como reflejo del uso de Git de la época en que se creó originalmente este sitio web.
      https://github.com/git/git/blob/757161efcca150a9a96b312d9e78...
      https://github.com/git/git/releases/tag/v2.23.0
      https://github.com/git/git/commit/4e43b7ff1ea4b6f16b93a432b6...
    • No me gusta que se enseñe git add . como opción por defecto para subir cambios al área de staging.
      Agregar archivos específicos de forma explícita deja menos margen para decir después “ay, la regué”.
    • Me da curiosidad cuál es el fundamento para recomendar este enfoque.
      Quisiera saber por qué switch es mejor que checkout y por qué no debería usarse reset --hard.
    • No entiendo qué significa “evitar reset --hard”.
      Me da curiosidad por qué en la práctica no sería suficiente, y lo uso bastante seguido junto con alias git-restore-file='git restore --source=HEAD --'; parece funcionar bien.
  • Al aprender Git, deberíamos empezar a recomendar clientes GUI como ruta predeterminada.
    Con eso se resolvería un tercio de estos problemas, y otro tercio ni siquiera aparecería.
    Si más adelante uno decide que la línea de comandos es más rápida, que la use entonces; primero la gente necesita ver visualmente cómo interactúa con los árboles.
    En lo personal me gusta fork.dev, pero hoy en día los clientes son bastante parecidos en general.

    • Estoy de acuerdo en que muchas veces una GUI le da a Git una mejor experiencia de usuario.
      Con Magit, acciones como “abortar cherry-pick” se pueden descubrir dentro de la interfaz y usan el mismo atajo que otras operaciones de “abortar X”.
      Si hubiera tenido que usar solo la línea de comandos de Git, no habría tenido idea de por dónde empezar.
      También me pasó que borré un commit que no debía borrar durante un rebase interactivo, pero en cada punto de inicio del rebase se crea una instantánea accesible desde el reflog, así que es seguro deshacer un rebase incorrecto.
      La UI de reflog de Magit es igual que la UI de log, así que no me perdí al verla por primera vez; con la CLI de Git probablemente no habría entendido qué estaba pasando.
    • Empecé a recomendar jj como alternativa compatible con Git.
      Me gusta que, al ejecutar jj sin argumentos, muestra la parte relevante del árbol de commits y la ubicación actual.
      De un vistazo puedes ver el commit en el que estás trabajando, las ramas conectadas y el historial, otras ramas activas dentro del repositorio, el mensaje de cada commit y si tiene cambios; es excelente para reorientarse.
    • A los principiantes de Git siempre les recomiendo encontrar una UI que les acomode y usarla en vez de la línea de comandos.
      Pero casi nadie hace caso, y terminan sufriendo con la CLI, rompiendo ramas, quedándose atrás de main y teniéndole miedo a rebase.
      Muchas cosas que en una UI se resolverían arrastrando y soltando, o de una forma parecida, parecen una especie de autoflagelación.
    • Si hay que ver visualmente cómo interactuar con los árboles, hay un tutorial interactivo que les ayudó a mis colegas: https://learngitbranching.js.org/
    • Por mi experiencia anterior usando frontends GUI de control de versiones, cuando todo funciona bien están bien, pero si algo se enreda y hay que ordenarlo, era difícil que alguien pudiera ayudar.
      Aunque la interfaz fuera horrible, era más fácil comunicarse usando la misma interfaz que el resto del equipo, y los recursos en línea para resolver problemas también suelen asumir la “forma normal”, así que resultaban más útiles.
  • Que en abril de 2025 Git cumpla 20 años se siente bastante raro
    Estuve ahí en ese momento, y recuerdo haber dudado sobre qué aprender entre git, darcs y bazaar para reemplazar SVN o CVS
    No sé si también probé Mercurial
    Me pregunto si el “efecto GitHub” prácticamente mató la necesidad de nuevos entrantes en el campo de los sistemas de control de versiones
    Tal vez en algún momento ya se había terminado de afeitar bastante al yak

    • El efecto GitHub, como todos los efectos de red, existe de verdad, pero eso no significa que no se pueda mejorar
      Me pasé por completo a jj, y como tiene compatibilidad con Git, no hace falta esperar a que otros también cambien
      Cada vez me frustran más varios aspectos de GitHub, así que espero que algún día todos también lo superen, pero en ese frente el camino hacia adelante no parece tan simple
    • SVN siempre funcionó bien
      SVN es intuitivo y funciona lo suficientemente bien en el 99% de los casos, así que no hace falta “enseñárselo” a la gente por separado
      Ojalá todos dejáramos de fingir ser hackers 1337 y admitiéramos que Git es una herramienta excesiva para la mayoría
    • Cualquier mejora tendría que ser, como mínimo, compatible con Git; jj, por ejemplo, lo es
  • Creo que tengo un modelo mental bastante bueno de lo que hace Git, pero en cuanto un comando se vuelve apenas un poco complejo, no recuerdo los argumentos
    Los comandos no son fáciles de descubrir ni de memorizar
    No sé si el problema es que la UI de texto es mala, o si describir en texto la manipulación de árboles ya es difícil de por sí

  • Me alegra ver resultados de búsqueda para hg y Mercurial en este hilo
    Todavía no puedo creer que Mercurial haya perdido la guerra de los sistemas de control de versiones distribuidos; me parece una herramienta mejor

    • Incluso con los criterios actuales, Mercurial es sin duda una herramienta mejor
      Es usable, estable, probada en producción y cuenta con soporte activo
      Se acerca a eso de “sé el cambio que quieres ver”, así que simplemente úsalo
      Decir que perdió es muy contraproducente, y la inhibición implícita que genera ese tipo de frase mantiene baja la adopción de Mercurial
      Se puede alojar al menos en Sourcehut y heptapod.host
      También opero una instancia privada de Heptapod, que es un fork de Gitlab con soporte directo para Mercurial, y simplemente funciona bien
    • Suena interesante, pero me pregunto si tiene enlace de GitHub
    • Mercurial es una herramienta más lenta
    • Si necesitas hosting para Mercurial, puedes usar este: https://hg.reactionary.software/
  • Git fue una de esas tecnologías que nunca terminé de meterme en la cabeza porque en muchos aspectos no sigue la intuición
    Si no llevas mucho tiempo usándolo, es muy probable que tengas que buscar en Google o mirar la página man para casi cualquier operación

    • Empecé con SVN y luego pasé a Git; tuve que aprender las diferencias, pero en muchos aspectos sentí que se volvió más fácil
      Sobre todo después de vivir el terror de intentar manejar una base de código grande en SVN con muchas ramas y luego fusionarlas de vuelta, se ve que Git es absurdamente mucho mejor en esa parte
      Para alguien que entra directamente con Git, la curva de aprendizaje puede ser mucho mayor, pero todavía a veces me cuesta entender dónde se traba la gente
    • Ayer intenté eliminar un archivo del historial
      Si usas la forma integrada, filter-branch, aparece una advertencia, tras una larga demora, de que presiones Control+C y descargues un script de Python de terceros llamado filter-repo
  • No es algo de lo que esté orgulloso, pero mi operación de Git de “la cagué” más frecuente es borrar el repositorio local, clonarlo de nuevo y volver a aplicar los cambios
    Funciona muy bien el 95% de las veces; para el resto le pido ayuda a la persona de DevOps

    • He usado Git durante casi 15 años, e incluso hice dos veces programas/productos que usan Git internamente para producir determinados resultados, pero todavía a veces hago algo como cp -R .git ../git-backup antes de hacer algo medio riesgoso en Git
      Porque si lo rompo demasiado, puedo reemplazar todo el directorio .git por la copia anterior
      Es mucho más rápido que encontrar la forma correcta de revertir una operación específica o un conjunto de operaciones
    • No estás solo: https://xkcd.com/1597/
  • Lo que me gustaría que la gente realmente interiorizara sobre Git es que Git es un almacén de datos solo de anexado
    Si quieres meter tu trabajo en el repositorio, haz commit; y una vez que el trabajo entra, no se puede eliminar con comandos de Git
    El reflog también funciona con este principio
    Hagas lo que hagas, puedes volver al estado anterior de la rama y restaurar el trabajo desde el almacén de datos
    Pero si no haces commit, Git no puede ayudarte, así que simplemente tienes que hacer commit
    Haz commit siempre, y si eso se siente demasiado pesado o se te olvida seguido, hay que arreglar la herramienta
    Si escribes código todo el día, en general deberías hacer commit al menos una vez por hora

  • Es una tontería, pero cada vez que veo git reflog, lo leo como re-flog
    Tal vez por eso me cuesta memorizar este comando, y parece que inconscientemente no lo pienso como “reference log”
    En cambio, se siente como si le estuviera pidiendo a Git que vuelva a vender algo barato

  • No soy usuario de Git, pero al ver cosas así se me refuerza la idea de que git commit en sí no tiene sentido, y que lo importante es solo el momento en que el commit se hace push o se mergea
    Es parecido a guardar un archivo de texto
    Como no escribes un mensaje corto cada vez que guardas un archivo, simplemente puedes dejarlo pasar

    • Si es un cambio que surgió después de una difícil jornada de depuración y existe la posibilidad de que otras personas lo consulten más adelante, escribo un mensaje
      No necesariamente corto; cuanto más pequeño es el cambio, a veces más larga es la explicación
      Alguna vez escribí 2 o 3 párrafos para un cambio de 2 o 3 líneas con mucho impacto
    • Parece demasiado claro que los commits locales y los commits remotos tienen propósitos distintos
      Un commit local es una forma de crear un punto de control en un momento en que todo compila y funciona
      Así puedes hacer stash de los cambios o volver a un estado que funcionaba si arruinas algo
      Antes de hacer push, puedes hacer reset y dividirlo en commits adecuados, manteniendo limpio el historial remoto y evitando que se vuelva un montón de wip
      O también puedes hacer squash merge del PR y obtener lo mejor de ambos mundos
    • Un commit es muy importante porque es la acción que hace que Git cree internamente una copia de los archivos
      Pero no hace falta ingresar mensajes sin sentido como more fixes
      Normalmente creo un commit inicial como (WIP) nombre de la funcionalidad, repito commits con amend hasta quedar satisfecho y al final quito (WIP) del mensaje
    • Se compara con guardar un archivo de texto, pero también hay muchas personas que escriben un breve texto sobre su día cada vez que se van a dormir
      Veo el registro de revisiones de Git como algo parecido a ese diario
      Está más ordenado que un documento TODO separado y te incentiva a explicar de forma legible lo que ya hiciste
      Cuando decides hasta dónde revertir, puede ser útil saber qué estabas pensando en aquel commit, y si el enfoque estaba equivocado, también ayuda para el análisis posterior
      Si el trabajo que no se ha mergeado ni pusheado está organizado como cambios separados con un objetivo, cuando algunas partes son buenas y otras malas es mucho más fácil conservar solo las partes buenas
    • Hasta antes de hacer push, todo es local, así que puedes hacer lo que quieras
      Aun así, técnicamente los commits no carecen de sentido
      Si no hay commits, tampoco hay nada a lo que hacer push o mergear
      Incluso en un sentido no técnico, registrar en texto plano por qué se cambia algo es muy útil
      Si terminas escribiendo ese tipo de explicación con demasiada frecuencia, deberías acotar más la unidad de “cambio” o seleccionar mejor qué cosas tocas al mismo tiempo
      Si no usas Git, me da curiosidad saber con qué haces control de versiones y gestión de cambios