El módulo anterior terminó con una lista de desastres anunciados: el reset --hard sobre tres días de trabajo, el commit en la rama equivocada, el pull sobre una rama divergida, la rama borrada que sí importaba, el .git/index corrupto. Este módulo es el manual para cuando ocurren.

Y ocurren siempre en el peor momento. Son las siete de la tarde de un viernes, Carla tiene que entregar GT-241 antes del cierre, ejecuta un comando que no acaba de entender y la terminal responde algo que no había visto nunca. Lo primero que hace la mayoría de la gente en ese momento —probar comandos al azar sacados de una respuesta de foro— es exactamente lo que convierte un problema pequeño en uno grande.

Esta lección es dos cosas. Primero, un protocolo de calma: qué hacer antes de tocar nada. Y después, el catálogo de urgencias: una tabla grande de síntoma → causa probable → dónde se resuelve, que funciona como índice del resto del módulo. Los casos triviales se resuelven aquí mismo; los que tienen sustancia se remiten a su lección.

Empecemos por la idea que sostiene todo lo demás, porque es la que permite trabajar con calma.

Contenido

  1. La idea que lo sostiene todo: en Git casi nada se pierde
  2. El protocolo de calma: cuatro comandos antes de tocar nada
  3. La copia de seguridad de diez segundos
  4. El catálogo de urgencias
  5. Los casos triviales, resueltos aquí
  6. Cómo leer un mensaje de error de Git
  7. Qué NO hacer nunca en estado de pánico

  1. La idea que lo sostiene todo: en Git casi nada se pierde

Recupera el modelo de datos de la lección 01-04. Un commit es un objeto inmutable en .git/objects, identificado por el hash de su contenido. Una rama es un fichero de 41 bytes que contiene un hash (lección 03-01). Y la relación entre ambas cosas es la clave:

Los commits no pertenecen a las ramas. Las ramas simplemente apuntan a commits.

De ahí se deduce lo que hace habitable este módulo: cuando borras una rama, no borras ningún commit. Cuando haces reset --hard, no borras ningún commit. Cuando un rebase sale mal, los commits originales siguen ahí. Lo único que ha cambiado es que ninguna referencia los apunta: se han vuelto inalcanzables.

flowchart LR
    subgraph antes["Antes del reset --hard HEAD~2"]
        A1["A"] --> B1["B"] --> C1["C"] --> D1["D"]
        R1(["main"]) -.-> D1
    end
    subgraph despues["Después"]
        A2["A"] --> B2["B"] --> C2["C (inalcanzable)"] --> D2["D (inalcanzable)"]
        R2(["main"]) -.-> B2
    end
    antes ==> despues

C y D siguen existiendo, byte a byte, en .git/objects. Nadie los ha tocado. Simplemente main ya no los alcanza, y por eso git log no los muestra. Recuperarlos es cuestión de volver a apuntar algo a ellos, y para eso existe el reflog, que es el tema de la lección 09-04.

Los objetos inalcanzables sí acaban desapareciendo, pero no de inmediato: git gc (lección 08-06) los elimina cuando llevan más de dos semanas sin referencias, y las entradas del reflog caducan a los 90 días (30 para lo inalcanzable). En la práctica, tienes semanas para recuperar cualquier commit.

La excepción, y es la importante

Todo lo anterior se apoya en una condición: que el trabajo haya llegado a ser un commit. Lo que nunca se confirmó no está en la base de datos de objetos y, por tanto, no hay nada que recuperar.

Estado del trabajo ¿Está en .git/objects? ¿Recuperable?
Confirmado (aunque sin publicar) , siempre, mientras gc no lo purgue
Preparado con git add (sin confirmar) , como blob suelto Sí, con git fsck (09-04)
En git stash , como commits ocultos Sí, incluso tras stash drop (09-04)
Modificado en el directorio de trabajo No No. Solo tu editor puede salvarte
Fichero sin seguimiento No No

Esa tabla es el mapa mental de todo el módulo. Fíjate en el detalle de la segunda fila: git add ya guarda el contenido en la base de datos. Un fichero que preparaste y luego destruiste con git checkout es recuperable, aunque nunca lo confirmaras.

De aquí sale el consejo más rentable del módulo, y no es un comando:

Confirma pronto y a menudo. Un commit feo con el mensaje "wip" te protege de todo lo que viene en las cinco lecciones siguientes. Ya lo limpiarás con rebase -i (lección 05-02) antes de publicar.

  1. El protocolo de calma: cuatro comandos antes de tocar nada

Cuando algo va mal, la tentación es actuar. Resístela. Los cuatro comandos siguientes no modifican nada, tardan tres segundos y, en la mayoría de los casos, el diagnóstico ya está hecho cuando terminas de leerlos.

Paso 1: git status

git status

Es el comando más infravalorado de Git. No solo dice qué ficheros han cambiado: dice en qué estado está el repositorio y, muy a menudo, te sugiere literalmente la salida.

interactive rebase in progress; onto 7d3a8f4
Last command done (2 commands done):
   pick e91d4a8 Extraer la creación del elemento de tarea
   squash b52c9d1 Corregir el contador
Next commands to do (3 remaining commands):
   pick 4f8a2e6 Añadir el filtro de pendientes
   pick 9c4e7b2 Documentar el filtro en el README
  (use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'GT-241' onto '7d3a8f4'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Ahí está todo: qué operación está a medias, por dónde va, y las tres formas de salir. Mucha gente en pánico no ejecuta git status precisamente cuando más lo necesita.

Los estados que puede anunciar:

Lo que dice git status Qué significa Salida inmediata
HEAD detached at 4f8a2e6 Estás fuera de cualquier rama (03-02) git switch - o git switch -c rama-nueva
You have unmerged paths Conflicto de fusión a medias (03-05) Resolver, o git merge --abort
interactive rebase in progress Rebase interactivo a medias (05-02) git rebase --continue / --abort
You are currently cherry-picking Cherry-pick a medias (05-03) git cherry-pick --continue / --abort
You are currently reverting Revert a medias (05-06) git revert --continue / --abort
You are currently bisecting Bisect en marcha (06-02) git bisect reset
Your branch and 'origin/x' have diverged Divergencia con el remoto Lección 09-03
nothing to commit, working tree clean No hay nada sin guardar Lo perdido, si acaso, está en commits

Paso 2: git log --oneline --graph -20

git log --oneline --graph --decorate -20

La forma del historial visible desde donde estás ahora. Sirve para dos cosas: confirmar que estás donde crees que estás, y ver si el commit que buscas sigue alcanzable.

* 9c4e7b2 (HEAD -> GT-241) Añadir el filtro de pendientes
* b52c9d1 Extraer la creación del elemento de tarea
*   4f8a2e6 (origin/main, main) Fusionar GT-238
|\
| * 7d3a8f4 Corregir el contador de tareas
|/
* e91d4a8 Documentar la instalación en el README

Y una variante que conviene tener a mano cuando sospechas que te falta algo:

# TODO lo alcanzable desde cualquier referencia, incluidas las remotas
git log --oneline --graph --decorate --all -30

Si el commit que buscas aparece en --all pero no en el log normal, no está perdido: está en otra rama. Ese es el caso más frecuente de todos, y no requiere recuperación de nada.

Paso 3: git reflog -20

git reflog -20

El registro de por dónde ha pasado HEAD. Es la caja negra del repositorio y contesta a la pregunta "¿qué demonios acabo de hacer?".

9c4e7b2 HEAD@{0}: reset: moving to HEAD~2
b52c9d1 HEAD@{1}: commit: Añadir el filtro de pendientes
4f8a2e6 HEAD@{2}: commit: Extraer la creación del elemento de tarea
7d3a8f4 HEAD@{3}: checkout: moving from main to GT-241
e91d4a8 HEAD@{4}: pull: Fast-forward

Se lee de abajo arriba en orden cronológico. La entrada HEAD@{0} es siempre lo último que pasó, y aquí dice claramente que hubo un reset que dejó atrás dos commits. Los hashes de esos commits siguen escritos ahí, y eso es exactamente lo que hace posible recuperarlos.

El reflog es el tema completo de la lección 09-04. Aquí basta con ejecutarlo y leerlo: en el 80 % de los sustos, la respuesta está en esas veinte líneas.

Paso 4: git fsck --no-progress, solo si lo anterior no cuadra

git fsck --no-progress

Comprueba la integridad de la base de objetos. No lo ejecutes por rutina: es lento en repositorios grandes y su salida asusta más de lo que debería (los objetos dangling son normales e inofensivos). Reserva este paso para cuando Git se niegue a hacer cosas básicas o dé errores sobre objetos. Su interpretación completa está en la lección 09-05.

El protocolo, en un alias

Merece la pena tenerlo como alias (lección 06-04):

git config --global alias.panico '!f() {
  echo "=== STATUS ===";  git status;
  echo; echo "=== LOG ==="; git log --oneline --graph --decorate -20;
  echo; echo "=== REFLOG ==="; git reflog -20;
}; f'
git panico

Tres segundos, cero modificaciones, y casi siempre el diagnóstico completo.

  1. La copia de seguridad de diez segundos

Si después del protocolo de calma la situación sigue sin estar clara, haz una copia antes de intentar nada. Es la diferencia entre "tengo un problema" y "tengo un problema y además he empeorado las cosas".

cp -a ../gestor-tareas ../gestor-tareas-COPIA-$(date +%Y%m%d-%H%M)

En Windows (PowerShell):

Copy-Item -Recurse ..\gestor-tareas ..\gestor-tareas-COPIA

Tres detalles importantes:

  • cp -a (o -R preservando enlaces) copia el directorio entero, .git incluido. Eso es lo que hace que sea una copia real y no un clon parcial.
  • No uses git clone para esto. Un clon no se lleva el reflog, ni los stashes, ni las ramas no publicadas, ni los cambios sin confirmar. Precisamente lo que necesitas conservar.
  • Hazlo antes de ejecutar cualquier comando que empiece por git reset, git clean, git rebase o git push --force.

Cuando el problema esté resuelto, borra la copia. Cuando no lo esté, agradecerás tenerla.

Para copias de seguridad de verdad, con formato y no con cp, está git bundle, que se ve en la lección 09-05.

  1. El catálogo de urgencias

Esta es la tabla central de la lección. Búscate en la columna de la izquierda.

Síntoma Causa probable Dónde se resuelve
He hecho cambios en la rama equivocada (sin confirmar todavía) git switch conserva los cambios sin confirmar, pero se te olvidó cambiar de rama antes de empezar Aquí, apartado 5.1
He confirmado en la rama equivocada Lo mismo, pero ya has hecho commit Aquí, apartado 5.2
El último commit tiene el autor equivocado user.email mal configurado en este repositorio (01-05) Aquí, apartado 5.3
Varios commits tienen el autor equivocado Configuración mal puesta desde hace tiempo 09-02 (rebase -i) o filter-repo (08-05)
Me he olvidado un fichero en el último commit Un git add incompleto Aquí, apartado 5.4
fatal: Unable to create '.git/index.lock': File exists Un proceso Git murió sin limpiar, o hay otro Git corriendo Aquí, apartado 5.5
detected dubious ownership in repository El directorio pertenece a otro usuario (típico en WSL, Docker, discos externos) Aquí, apartado 5.6
! [rejected] main -> main (non-fast-forward) El remoto tiene commits que tú no tienes 09-03
Your branch and 'origin/main' have diverged Ambos lados han avanzado desde el ancestro común 09-03
hint: You have divergent branches and need to specify how to reconcile them pull sin política configurada 09-03
fatal: refusing to merge unrelated histories Dos historiales sin ancestro común 09-03
He hecho push --force y he borrado el trabajo de un compañero Reescritura de historial publicado (regla de oro, 05-01) 09-03 y 09-04
You are in 'detached HEAD' state HEAD apunta a un commit y no a una rama (03-02) Aquí, apartado 5.7
He hecho git reset --hard y he perdido commits La rama se movió; los commits siguen ahí 09-02 (concepto) y 09-04 (rescate)
He hecho git reset --hard y he perdido cambios sin confirmar Nunca llegaron a la base de datos 09-02. Malas noticias: casi nunca es recuperable
He borrado una rama con -D y la necesitaba La referencia se borró; los commits no 09-04
Un rebase ha salido mal y no sé cómo estaba antes Reescritura local 09-04 (ORIG_HEAD y reflog)
He hecho stash drop de algo que necesitaba La referencia del stash se borró 09-04
Un fichero que borré reaparece en cada pull Sigue en el historial del remoto: alguien lo vuelve a traer 09-02 y 09-03
Un fichero está en .gitignore y Git lo sigue viendo Ya tenía seguimiento: .gitignore solo afecta a lo no seguido (08-03) 09-06 (check-ignore -v)
Permission denied (publickey) Clave SSH ausente, no cargada o no registrada (04-03) Aquí, apartado 5.8, y 09-06 para trazarlo
Estoy a medias de un conflicto y no sé cómo salir Fusión, rebase o cherry-pick interrumpido 03-05; salida rápida en el apartado 5.9
Todo el fichero aparece como modificado y solo cambié una línea Finales de línea CRLF/LF (08-04) 09-06 (ls-files --eol, check-attr)
error: object file ... is empty o fatal: bad object Corrupción real de la base de objetos 09-05
Git se niega a hacer nada y git status falla .git/index corrupto, o HEAD roto 09-05
git status tarda varios segundos No es un fallo: es rendimiento (08-06) 08-06
No entiendo por qué Git hace lo que hace Configuración inesperada, atributos, hooks 09-06

Un consejo de uso: antes de buscar en internet, busca en esta tabla. Las respuestas de foro suelen estar descontextualizadas y muchas empiezan por git reset --hard, que es exactamente lo que no debes ejecutar sin entenderlo.

  1. Los casos triviales, resueltos aquí

Estos no necesitan lección propia. Son problemas de un minuto, siempre que sepas cuál es el comando.

5.1. Cambios sin confirmar en la rama equivocada

Ana lleva media hora tocando app.js y descubre que está en main, no en GT-241.

No has perdido nada. Los cambios sin confirmar no pertenecen a ninguna rama: viven en el directorio de trabajo y en el índice. Cambiar de rama se los lleva contigo.

git status              # confirma que solo hay modificaciones, sin commits nuevos
git switch -c GT-241    # crea la rama aquí y se lleva los cambios
git status
Switched to a new branch 'GT-241'
On branch GT-241
Changes not staged for commit:
	modified:   app.js

Los cambios están intactos, ahora en la rama correcta, y main no se ha movido.

Si la rama ya existe:

git switch GT-241

Git se lleva los cambios sin más, salvo que alguno de los ficheros modificados difiera entre las dos ramas. En ese caso avisa:

error: Your local changes to the following files would be overwritten by checkout:
	app.js
Please commit your changes or stash them before you switch branches.

La salida es el stash de la lección 05-04:

git stash push -m "GT-241 a medias"
git switch GT-241
git stash pop

5.2. Ya has confirmado en la rama equivocada

Bruno ha hecho dos commits de GT-247 directamente sobre main, y main aún no se ha publicado con ellos.

No has perdido nada. Los commits existen; lo único mal puesto es a qué apunta cada rama. La receta es: marcar aquí, y devolver la rama a su sitio.

# 1. Comprobar qué hay
git log --oneline -3
b52c9d1 (HEAD -> main) GT-247 mostrar el número de tareas pendientes
4f8a2e6 GT-247 añadir el contador al DOM
e91d4a8 (origin/main) Documentar la instalación en el README
# 2. Crear la rama correcta AQUÍ (sin cambiarse: solo pone un marcador)
git branch GT-247

# 3. Devolver main a donde estaba el remoto
git reset --hard origin/main

# 4. Irse a la rama buena
git switch GT-247
git log --oneline -3
b52c9d1 (HEAD -> GT-247) GT-247 mostrar el número de tareas pendientes
4f8a2e6 GT-247 añadir el contador al DOM
e91d4a8 (origin/main, main) Documentar la instalación en el README

El orden importa: crea primero la rama, y solo después mueve main. Así los commits nunca dejan de estar alcanzables y el reset --hard es completamente seguro. (Si te equivocas de orden, tampoco pasa nada: para eso está el reflog de la 09-04. Pero hazlo bien.)

Si los commits ya estaban publicados en main, la respuesta no es esta sino git revert (lección 05-06), porque estarías reescribiendo historial público.

Variante: si son algunos commits sueltos y no los últimos, la herramienta es git cherry-pick (lección 05-03).

5.3. El commit tiene el autor equivocado

Carla ha configurado el portátil nuevo y ha confirmado como [email protected].

Para el último commit, sin publicar:

# Primero, arregla la causa, o volverá a pasar
git config user.name "Carla Vidal"
git config user.email "[email protected]"

# Y ahora el commit
git commit --amend --author="Carla Vidal <[email protected]>" --no-edit
git log -1 --format='%an <%ae>  |  %cn <%ce>'
Carla Vidal <[email protected]>  |  Carla Vidal <[email protected]>

Un matiz que confunde a mucha gente: Git guarda dos identidades por commit.

Campo Quién es Cuándo cambia
Author (%an/%ae) Quien escribió el cambio Se conserva en rebase y cherry-pick
Committer (%cn/%ce) Quien creó este objeto commit Se actualiza en cada reescritura

--author solo toca el primero. Para el segundo, Git usa user.email en el momento de crear el commit, así que basta con haber corregido la configuración antes del --amend. Si necesitas forzarlo:

GIT_COMMITTER_NAME="Carla Vidal" GIT_COMMITTER_EMAIL="[email protected]" \
  git commit --amend --no-edit

Y la prevención, que vale más que la cura (lección 01-05):

# Que Git se niegue a confirmar si no hay identidad explícita en el repositorio
git config --global user.useConfigOnly true

Con eso, un repositorio sin user.email propio da error en lugar de inventarse una identidad a partir del nombre de la máquina. Es la configuración que evita este problema para siempre.

Si son varios commits, hace falta reescribir: git rebase -i con exec (lección 05-02) para unos pocos, o git filter-repo --mailmap (lección 08-05) para un historial entero. Y siempre con la regla de oro delante: solo si no está publicado.

5.4. Me he olvidado un fichero en el último commit

git add estilos.css
git commit --amend --no-edit

--no-edit conserva el mensaje tal cual. El commit anterior se sustituye por uno nuevo con el mismo mensaje y el fichero incluido.

El hash cambia. Si ya lo habías publicado, esto reescribe historial: no lo hagas, y añade un commit nuevo en su lugar. Es exactamente la situación de la lección 08-02, donde la solución elegante es git commit --fixup + rebase --autosquash antes de publicar.

Comprobación previa útil, para no incluir algo de más:

git status --short           # ¿qué hay preparado ahora mismo?
git diff --cached --stat     # ¿qué entraría exactamente en el amend?

5.5. .git/index.lock: File exists

fatal: Unable to create '/home/ana/gestor-tareas/.git/index.lock': File exists.

Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. Please make sure all processes
are terminated then try again.

Qué es. Git crea .git/index.lock antes de modificar el índice y lo borra al terminar. Es un cerrojo para que dos procesos no escriban a la vez. Si el proceso murió —lo cerraste con Ctrl+C, se colgó el editor, el IDE lo mató—, el fichero se queda huérfano.

El procedimiento correcto, en este orden:

# 1. ¿Hay realmente otro Git ejecutándose? (muy frecuente: el IDE)
ps aux | grep -i "[g]it"

# 2. Si no hay ninguno, mirar el cerrojo: si es de hace rato, está huérfano
ls -l .git/index.lock

# 3. Borrarlo
rm -f .git/index.lock

# 4. Comprobar que todo está bien
git status

La causa más habitual con diferencia es un IDE (VS Code, IntelliJ, Eclipse) ejecutando git status en segundo plano justo cuando tú lanzas un comando. Si te pasa a menudo, no es un problema de Git: es una carrera entre dos clientes.

No borres .git/index.lock sin comprobar el paso 1. Si de verdad hay otro proceso escribiendo, quitarle el cerrojo puede corromper el índice —que es justo lo que trata la lección 09-05—. Con la comprobación hecha, borrarlo es completamente inofensivo.

Hay otros cerrojos con la misma lógica y el mismo tratamiento: .git/HEAD.lock, .git/refs/heads/main.lock, .git/shallow.lock.

5.6. detected dubious ownership in repository

fatal: detected dubious ownership in repository at '/home/ana/gestor-tareas'
To add an exception for this directory, call:

	git config --global --add safe.directory /home/ana/gestor-tareas

Qué es. Desde Git 2.35, Git se niega a operar en un repositorio cuyo propietario en el sistema de ficheros no eres tú. Es una medida de seguridad, no un fallo: un repositorio ajeno puede contener hooks (lección 06-01) o configuración que se ejecutan al lanzar comandos.

Los escenarios típicos: WSL accediendo a un disco de Windows, un contenedor Docker con un volumen montado, un disco externo formateado en otro sistema, o un repositorio clonado con sudo.

# Ver de quién es realmente
ls -ld .

# La excepción, para un repositorio concreto
git config --global --add safe.directory /home/ana/gestor-tareas

# Para todo un árbol (Git 2.36+)
git config --global --add safe.directory '/mnt/proyectos/*'

Y lo que no debes hacer, aunque lo encuentres en internet:

# NO: desactiva la comprobación en TODAS partes
git config --global --add safe.directory '*'

Antes de añadir la excepción, plantéate si la propiedad es correcta. Si el repositorio es tuyo y aparece como de root, probablemente clonaste con sudo y la solución real es:

sudo chown -R "$USER:$USER" /home/ana/gestor-tareas

5.7. "You are in 'detached HEAD' state"

Note: switching to '4f8a2e6'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

No es un error. Es Git informando de que HEAD apunta directamente a un commit y no a una rama (lección 03-02). Se llega ahí con git checkout <sha>, con git switch --detach, al inspeccionar una etiqueta, durante un bisect o en medio de un rebase.

Dos casos:

A. No has hecho commits estando ahí. Vuelve y ya está:

git switch -          # a donde estabas
git switch main       # o a donde quieras

B. Has hecho commits estando ahí. Este es el caso que hay que saber, porque esos commits no están en ninguna rama y desaparecerán de la vista en cuanto te muevas:

git log --oneline -3          # anota el hash de tu último commit
git switch -c rescate-demo    # crea una rama AQUÍ mismo
Switched to a new branch 'rescate-demo'

Y si ya te habías movido y los has perdido de vista: no pasa nada, el reflog los tiene (lección 09-04).

5.8. Permission denied (publickey)

git push
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.

Diagnóstico en tres comandos, de menos a más (lección 04-03):

# 1. ¿El servidor me reconoce?
ssh -T [email protected]
Hi ana-ferrer! You've successfully authenticated, but git.ejemplo.es does not provide shell access.

Si eso funciona y el push no, el problema es la URL del remoto, no la clave:

git remote -v      # ¿es una URL SSH o HTTPS?

Si no funciona:

# 2. ¿Hay claves cargadas en el agente?
ssh-add -l
The agent has no identities.
# 3. Cargarla
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Y si sigue fallando, la traza detallada, que es terreno de la lección 09-06:

GIT_SSH_COMMAND="ssh -v" git push 2>&1 | head -40

Causas frecuentes en orden de probabilidad: la clave pública no está registrada en el servidor; el agente no está corriendo (típico tras reiniciar); permisos incorrectos en ~/.ssh (debe ser 700, y las claves privadas 600); o hay varias claves y SSH ofrece la que no es (se arregla con un bloque Host en ~/.ssh/config).

5.9. Estoy a medias de un conflicto y quiero salir

La mecánica de resolución está completa en la lección 03-05. Lo único que hace falta aquí es la salida de emergencia, porque en pánico no se recuerda:

git status            # dice qué operación está en marcha

git merge --abort         # si es una fusión
git rebase --abort        # si es un rebase
git cherry-pick --abort   # si es un cherry-pick
git revert --abort        # si es un revert

--abort devuelve el repositorio exactamente al estado previo a la operación. Es seguro y no pierde nada de lo que había antes de empezar (sí pierde el trabajo de resolución que hubieras hecho durante el conflicto).

Si --abort falla porque tenías cambios sin confirmar mezclados, el reflog y ORIG_HEAD son la salida, y eso es la lección 09-04.

  1. Cómo leer un mensaje de error de Git

Git tiene fama de mensajes crípticos y en parte es merecida, pero han mejorado muchísimo. Merece la pena saber leerlos, porque casi siempre contienen la solución.

La anatomía típica:

error: Your local changes to the following files would be overwritten by merge:
	app.js
Please commit your changes or stash them before you merge.
Aborting
Parte Qué aporta
fatal: Git no ha hecho nada. El estado es el mismo de antes
error: Algo ha fallado; puede haber cambios parciales. Lee el resto
warning: Ha funcionado, pero hay algo que deberías saber
hint: La sugerencia de Git. Léela: suele ser literalmente la solución
Aborting Confirmación explícita de que no se ha tocado nada

Tres reglas prácticas:

  1. fatal: es tranquilizador. Significa que Git se ha negado a actuar. Nada se ha roto.
  2. Las líneas hint: son la documentación en el sitio exacto. Se pueden silenciar con advice.*, y mucha gente lo hace sin darse cuenta al copiar configuraciones ajenas. Compruébalo con git config --get-regexp '^advice\.'.
  3. El mensaje nombra los ficheros implicados. Casi siempre acota el problema al instante.

Y un ajuste que ayuda a leer, si trabajas en inglés forzado por defecto:

git config --global advice.detachedHead true    # que vuelva a avisar si lo silenciaste
LANG=en_US.UTF-8 git status                     # forzar el idioma para buscar el error

Buscar el mensaje en inglés da muchísimos más resultados útiles que buscarlo traducido.

  1. Qué NO hacer nunca en estado de pánico

Cierra la lección la lista negra. Todo lo que sigue ha convertido problemas pequeños en desastres reales.

1. Copiar y pegar comandos de internet sin entenderlos. Especialmente si empiezan por git reset --hard, git push --force, git clean -fdx o rm -rf .git. La respuesta que te sirve a ti depende de un contexto que el foro no conoce.

2. git push --force. Nunca en pánico, y casi nunca fuera de él. Si tiene que ser, --force-with-lease (lecciones 04-05 y 09-03), y solo sobre una rama que sea tuya.

3. git clean -fdx "para dejarlo limpio". Borra de verdad, y sin red de seguridad: los ficheros sin seguimiento no están en la base de datos. Ejecuta siempre git clean -n antes (lección 09-02).

4. Borrar .git. Es el repositorio entero: todo el historial, todas las ramas, el reflog. No es "la configuración".

5. Volver a clonar encima del directorio con problemas. A veces reclonar es la respuesta correcta (lección 09-05), pero primero se copia y se rescata lo local: ramas sin publicar, stashes, cambios en curso.

6. Encadenar comandos sin mirar el resultado de cada uno. Después de cada paso: git status y git log --oneline -5.

7. Ejecutar git gc --prune=now o git reflog expire --expire=now. Es lo único de esta lista que destruye la red de seguridad de todo el módulo (lección 08-06). Si hay algo que recuperar, esos comandos lo hacen imposible.

8. Callarse. Si has forzado algo sobre una rama compartida, o has borrado algo del servidor, avisa al equipo en el momento. Cinco minutos después es un aviso; cinco horas después es un rompecabezas para todos.

Errores Comunes y Consejos

Error 1: actuar antes de diagnosticar. El protocolo de calma —status, log --graph, reflog— cuesta tres segundos y resuelve la mayoría de los casos sin tocar nada.

Error 2: no leer lo que dice git status. Es el comando que más información da y el primero que se olvida en pánico. A menudo contiene literalmente el comando que necesitas.

Error 3: creer que un reset --hard o un branch -D han destruido commits. No lo han hecho. Los commits siguen en .git/objects y el reflog sabe dónde están (09-04).

Error 4: confiar en que todo es recuperable, incluido lo sin confirmar. No lo es. Lo que nunca fue un commit (ni un add, ni un stash) no está en ninguna parte.

Error 5: usar git clone como copia de seguridad de emergencia. No se lleva el reflog, ni los stashes, ni las ramas locales, ni lo sin confirmar. Usa cp -a del directorio completo.

Error 6: borrar .git/index.lock sin comprobar si hay otro proceso. Con la comprobación es inofensivo; sin ella puede corromper el índice.

Error 7: silenciar los hint: de Git. Son la mejor documentación contextual que existe, escrita justo cuando la necesitas.

Error 8: safe.directory '*'. Desactiva una protección real de seguridad en todas partes. Añade la excepción concreta, o arregla la propiedad del directorio.

Consejo 1: confirma pronto y a menudo. Un commit "wip" cada media hora hace que las cinco lecciones siguientes casi nunca te hagan falta. El historial se limpia luego con rebase -i.

Consejo 2: define el alias git panico. El día que lo necesites no vas a recordar los tres comandos.

Consejo 3: cp -a antes de nada serio. Diez segundos que compran tranquilidad para experimentar.

Consejo 4: user.useConfigOnly true en la configuración global. Elimina para siempre el commit con el autor equivocado.

Consejo 5: busca los mensajes de error en inglés. Diez veces más resultados útiles.

Consejo 6: cuando algo raro pase con una rama compartida, avisa inmediatamente. El coste social de avisar tarde es mucho mayor que el de avisar.

Ejercicios

Ejercicio 1: el commit en la rama equivocada

  1. Crea un repositorio con app.js y tres commits en main. Simula el remoto con un clon --bare local y publica main.
  2. Sin cambiar de rama, haz dos commits más que deberían haber ido a GT-247.
  3. Aplica el procedimiento del apartado 5.2 y deja main como en el remoto y GT-247 con los dos commits.
  4. Demuestra con git log --oneline --all --graph que ningún commit se ha perdido.
  5. Comprueba en el reflog qué movimientos han quedado registrados.

Ejercicio 2: identidad y ficheros olvidados

  1. En un repositorio nuevo, configura deliberadamente user.email mal (nadie@localhost) y haz un commit que incluya solo index.html, olvidando estilos.css.
  2. Comprueba el autor con git log -1 --format='%an <%ae>'.
  3. Corrige la configuración, añade el fichero olvidado y arregla el autor en un solo --amend.
  4. Comprueba que el hash del commit ha cambiado y explica por qué eso lo haría inaceptable si ya estuviera publicado.
  5. Activa user.useConfigOnly true y comprueba qué pasa al intentar confirmar en un repositorio sin identidad configurada.

Ejercicio 3: el protocolo de calma sobre un desastre simulado

  1. Crea un repositorio con seis commits y una rama GT-241 con dos commits propios.
  2. Estando en GT-241, ejecuta git reset --hard HEAD~2.
  3. Sin recuperar nada todavía, ejecuta los cuatro pasos del protocolo de calma y escribe qué te dice cada uno.
  4. Identifica en el reflog el hash exacto del commit que era la punta de GT-241 antes del reset.
  5. Crea una rama rescate sobre ese hash y comprueba que el trabajo está intacto.
  6. Repite el experimento, pero esta vez con un fichero modificado y sin confirmar en el momento del reset --hard. Explica la diferencia.

Soluciones

Solución 1:

mkdir -p /tmp/p9-01 && cd /tmp/p9-01
git init -b main gestor-tareas
cd gestor-tareas
git config user.name "Ana Ferrer"; git config user.email "[email protected]"

echo "console.log('gestor-tareas');" > app.js
git add . && git commit -q -m "chore: commit inicial"
echo "// listado" >> app.js && git commit -q -am "feat: listado de tareas"
echo "// contador" >> app.js && git commit -q -am "feat: contador"

# El "servidor"
git init -q --bare /tmp/p9-01/remoto.git
git remote add origin /tmp/p9-01/remoto.git
git push -q -u origin main
# 2. Dos commits que iban a GT-247, pero en main
echo "// filtro 1" >> app.js && git commit -q -am "GT-247 base del filtro"
echo "// filtro 2" >> app.js && git commit -q -am "GT-247 filtro de pendientes"
git log --oneline -3
7d3a8f4 (HEAD -> main) GT-247 filtro de pendientes
b52c9d1 GT-247 base del filtro
4f8a2e6 (origin/main) feat: contador
# 3. El procedimiento: marcar aquí, devolver main
git branch GT-247
git reset --hard origin/main
git switch GT-247
# 4. Nada perdido
git log --oneline --all --graph --decorate
* 7d3a8f4 (HEAD -> GT-247) GT-247 filtro de pendientes
* b52c9d1 GT-247 base del filtro
* 4f8a2e6 (origin/main, main) feat: contador
* e91d4a8 feat: listado de tareas
* 9c4e7b2 chore: commit inicial

main está donde el remoto, GT-247 tiene sus dos commits, y los cinco commits siguen existiendo. El reset --hard fue seguro porque git branch GT-247 ya los mantenía alcanzables.

# 5. El reflog
git reflog -6
7d3a8f4 HEAD@{0}: checkout: moving from main to GT-247
4f8a2e6 HEAD@{1}: reset: moving to origin/main
7d3a8f4 HEAD@{2}: commit: GT-247 filtro de pendientes
b52c9d1 HEAD@{3}: commit: GT-247 base del filtro
4f8a2e6 HEAD@{4}: commit: feat: contador

Fíjate en HEAD@{1}: el reflog registra el reset y el hash desde el que se salió. Aunque hubieras olvidado el paso 2, 7d3a8f4 está ahí escrito.

Solución 2:

mkdir /tmp/p9-01-b && cd /tmp/p9-01-b && git init -q -b main
git config user.name "Carla Vidal"
git config user.email "nadie@localhost"       # mal a propósito

echo "<h1>Gestor de tareas</h1>" > index.html
echo "body { margin: 0; }" > estilos.css
git add index.html                             # olvidamos estilos.css
git commit -q -m "feat: estructura inicial de la página"
# 2. El autor
git log -1 --format='%an <%ae>'
Carla Vidal <nadie@localhost>
# 3. Todo en un solo amend
git config user.email "[email protected]"
git add estilos.css
git commit --amend --author="Carla Vidal <[email protected]>" --no-edit

git log -1 --format='%h  autor: %an <%ae>  |  committer: %cn <%ce>'
git show --stat --oneline HEAD
3f8a1d6  autor: Carla Vidal <[email protected]>  |  committer: Carla Vidal <[email protected]>
3f8a1d6 feat: estructura inicial de la página
 estilos.css | 1 +
 index.html  | 1 +
 2 files changed, 2 insertions(+)

El committer también se ha corregido porque el --amend creó el objeto después de arreglar user.email.

# 4. El hash ha cambiado
git reflog -2
3f8a1d6 HEAD@{0}: commit (amend): feat: estructura inicial de la página
8f4c2a9 HEAD@{1}: commit (initial): feat: estructura inicial de la página

8f4c2a93f8a1d6. Un commit es inmutable (lección 01-04): --amend no lo modifica, crea uno nuevo y mueve la rama. Si el original estuviera publicado, quien lo tuviera descargado seguiría teniéndolo, la rama habría divergido y el push sería rechazado: exactamente el escenario de la lección 09-03.

# 5. useConfigOnly
git config --global user.useConfigOnly true
mkdir /tmp/p9-01-c && cd /tmp/p9-01-c && git init -q -b main
echo x > f.txt && git add . && git commit -m "prueba"
fatal: no email was given and auto-detection is disabled

Git se niega en lugar de inventarse usuario@nombre-de-la-maquina. El problema del apartado 5.3 deja de ser posible.

Solución 3:

mkdir /tmp/p9-01-d && cd /tmp/p9-01-d && git init -q -b main
git config user.name "Ana Ferrer"; git config user.email "[email protected]"
for i in 1 2 3 4 5 6; do echo "linea $i" >> app.js; git add . ; git commit -q -m "commit $i"; done

git switch -q -c GT-241
echo "// filtro" >> app.js && git commit -q -am "GT-241 base del filtro"
echo "// filtro ui" >> estilos.css && git add . && git commit -q -m "GT-241 estilos del filtro"
git log --oneline -3
b52c9d1 (HEAD -> GT-241) GT-241 estilos del filtro
4f8a2e6 GT-241 base del filtro
7d3a8f4 (main) commit 6
# 2. El desastre
git reset --hard HEAD~2
git log --oneline -1
7d3a8f4 (HEAD -> GT-241, main) commit 6
# 3. El protocolo
git status
On branch GT-241
nothing to commit, working tree clean

Lectura: no hay ninguna operación a medias y no hay nada sin guardar. Eso es buena noticia: todo lo que había estaba confirmado, y por tanto está en la base de datos.

git log --oneline --graph --decorate -20
* 7d3a8f4 (HEAD -> GT-241, main) commit 6
* e91d4a8 commit 5
...

Lectura: desde aquí, los dos commits de GT-241 ya no son alcanzables. git log no los ve. Esto es lo que provoca el pánico, y es solo un problema de referencias.

git reflog -6
7d3a8f4 HEAD@{0}: reset: moving to HEAD~2
b52c9d1 HEAD@{1}: commit: GT-241 estilos del filtro
4f8a2e6 HEAD@{2}: commit: GT-241 base del filtro
7d3a8f4 HEAD@{3}: checkout: moving from main to GT-241

Lectura: ahí está todo. HEAD@{1} es la punta que había antes del reset.

git fsck --no-progress
dangling commit b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b

Lectura: Git confirma que el commit existe y que nadie lo apunta. Dangling aquí es exactamente lo que esperamos (lección 09-05).

# 4 y 5. El rescate
git branch rescate b52c9d1
git log --oneline rescate -3
git diff GT-241 rescate --stat
b52c9d1 (rescate) GT-241 estilos del filtro
4f8a2e6 GT-241 base del filtro
7d3a8f4 (HEAD -> GT-241, main) commit 6
 app.js     | 1 +
 estilos.css | 1 +
 2 files changed, 2 insertions(+)

Trabajo intacto. Crear una rama es más seguro que hacer reset, porque no mueve nada de lo que ya está bien: solo añade un puntero. Es el patrón que se generaliza en la lección 09-04.

# 6. La diferencia: cambios SIN confirmar
git switch -q GT-241
echo "// trabajo de tres horas sin confirmar" >> app.js
git status --short
 M app.js
git reset --hard HEAD
git status --short          # (sin salida)
grep -c "tres horas" app.js
0
git reflog -2
git fsck --lost-found | grep -c blob
7d3a8f4 HEAD@{0}: reset: moving to HEAD
0

Esta línea no está en ninguna parte. No hay entrada de reflog que la registre, no hay objeto colgante que la contenga, git fsck no encuentra nada. Nunca llegó a la base de datos de objetos.

Y la variante que cambia el resultado por completo:

echo "// trabajo de tres horas" >> app.js
git add app.js              # <-- la única diferencia
git reset --hard HEAD
git fsck --lost-found
dangling blob 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b
git cat-file -p 6f2b9d4 | tail -1
// trabajo de tres horas

Con un simple git add, el contenido es recuperable. Esa es la frontera exacta entre lo que Git puede salvar y lo que no, y por eso el consejo de confirmar (o al menos preparar) a menudo no es una manía de estilo: es la red de seguridad.

Conclusión

Esta lección es el índice del módulo y, sobre todo, el cambio de actitud que hace que el resto funcione.

  • En Git casi nada se pierde de verdad. Los commits son objetos inmutables que sobreviven aunque ninguna referencia los apunte; borrar una rama o mover un puntero con reset no destruye nada. Lo verdaderamente frágil es lo que nunca llegó a la base de datos: los cambios sin confirmar y los ficheros sin seguimiento.
  • git add ya cuenta. El contenido preparado se guarda como blob y se puede rescatar. Confirmar es mejor, pero preparar ya es una red.
  • El protocolo de calmagit status, git log --oneline --graph -20, git reflog -20 y, solo si hace falta, git fsck— no modifica nada, tarda tres segundos y resuelve la mayoría de los sustos antes de tocar un solo comando destructivo.
  • cp -a del directorio completo es la copia de seguridad de emergencia. Un clone no sirve: no se lleva el reflog, ni los stashes, ni lo local.
  • Los mensajes de Git son mejores de lo que su fama sugiere. fatal: significa que no se ha hecho nada; las líneas hint: suelen contener literalmente la solución.
  • Los casos triviales se resuelven en un comando: git switch -c para los cambios en la rama equivocada, git branch + reset --hard origin/x para los commits mal colocados, commit --amend --author para la identidad, --amend --no-edit para el fichero olvidado, borrar index.lock tras comprobar los procesos, y safe.directory para la propiedad dudosa.
  • Y la lista negra: nada de --force, clean -fdx, rm -rf .git ni gc --prune=now en estado de pánico.

El resto del módulo desarrolla los casos que no caben en una fila de tabla. Empezamos por el más frecuente y el que cierra una promesa pendiente desde la lección 05-06: el mecanismo de git reset, sus tres modos, qué toca cada uno exactamente, y cómo elegir la herramienta correcta según si lo que quieres deshacer está sin confirmar, confirmado sin publicar o ya publicado.

Continúa en la lección 09-02: Deshaciendo Cambios.

Dominando Git: De Principiante a Avanzado

Módulo 1: Introducción a Git

Módulo 2: Operaciones Básicas de Git

Módulo 3: Ramas y Fusión

Módulo 4: Trabajando con Repositorios Remotos

Módulo 5: Operaciones Avanzadas de Git

Módulo 6: Herramientas y Técnicas de Git

Módulo 7: Estrategias de Colaboración y Flujo de Trabajo

Módulo 8: Mejores Prácticas y Consejos de Git

Módulo 9: Solución de Problemas y Depuración

Módulo 10: Git en el Mundo Real

© Copyright 2026. Todos los derechos reservados