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
- La idea que lo sostiene todo: en Git casi nada se pierde
- El protocolo de calma: cuatro comandos antes de tocar nada
- La copia de seguridad de diez segundos
- El catálogo de urgencias
- Los casos triviales, resueltos aquí
- Cómo leer un mensaje de error de Git
- Qué NO hacer nunca en estado de pánico
- 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) | Sí | Sí, siempre, mientras gc no lo purgue |
Preparado con git add (sin confirmar) |
Sí, como blob suelto | Sí, con git fsck (09-04) |
En git stash |
Sí, 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.
- 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
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
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 -30Si 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
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-forwardSe 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
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'Tres segundos, cero modificaciones, y casi siempre el diagnóstico completo.
- 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".
En Windows (PowerShell):
Tres detalles importantes:
cp -a(o-Rpreservando enlaces) copia el directorio entero,.gitincluido. Eso es lo que hace que sea una copia real y no un clon parcial.- No uses
git clonepara 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 rebaseogit 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.
- 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.
- 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 statusLos cambios están intactos, ahora en la rama correcta, y main no se ha movido.
Si la rama ya existe:
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:
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.
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 -3b52c9d1 (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-editCarla 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-editY 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 trueCon 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
--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 statusLa 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.locksin 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:
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:
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á:
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Í mismoY 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)
[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]Si eso funciona y el push no, el problema es la URL del remoto, no la clave:
Si no funciona:
Y si sigue fallando, la traza detallada, que es terreno de la lección 09-06:
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.
- 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:
fatal:es tranquilizador. Significa que Git se ha negado a actuar. Nada se ha roto.- Las líneas
hint:son la documentación en el sitio exacto. Se pueden silenciar conadvice.*, y mucha gente lo hace sin darse cuenta al copiar configuraciones ajenas. Compruébalo congit config --get-regexp '^advice\.'. - 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 errorBuscar el mensaje en inglés da muchísimos más resultados útiles que buscarlo traducido.
- 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
- Crea un repositorio con
app.jsy tres commits enmain. Simula el remoto con un clon--barelocal y publicamain. - Sin cambiar de rama, haz dos commits más que deberían haber ido a
GT-247. - Aplica el procedimiento del apartado 5.2 y deja
maincomo en el remoto yGT-247con los dos commits. - Demuestra con
git log --oneline --all --graphque ningún commit se ha perdido. - Comprueba en el reflog qué movimientos han quedado registrados.
Ejercicio 2: identidad y ficheros olvidados
- En un repositorio nuevo, configura deliberadamente
user.emailmal (nadie@localhost) y haz un commit que incluya soloindex.html, olvidandoestilos.css. - Comprueba el autor con
git log -1 --format='%an <%ae>'. - Corrige la configuración, añade el fichero olvidado y arregla el autor en un solo
--amend. - Comprueba que el hash del commit ha cambiado y explica por qué eso lo haría inaceptable si ya estuviera publicado.
- Activa
user.useConfigOnly truey comprueba qué pasa al intentar confirmar en un repositorio sin identidad configurada.
Ejercicio 3: el protocolo de calma sobre un desastre simulado
- Crea un repositorio con seis commits y una rama
GT-241con dos commits propios. - Estando en
GT-241, ejecutagit reset --hard HEAD~2. - Sin recuperar nada todavía, ejecuta los cuatro pasos del protocolo de calma y escribe qué te dice cada uno.
- Identifica en el reflog el hash exacto del commit que era la punta de
GT-241antes delreset. - Crea una rama
rescatesobre ese hash y comprueba que el trabajo está intacto. - 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 -37d3a8f4 (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* 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.
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: contadorFí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"# 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 HEAD3f8a1d6 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.
3f8a1d6 HEAD@{0}: commit (amend): feat: estructura inicial de la página
8f4c2a9 HEAD@{1}: commit (initial): feat: estructura inicial de la página8f4c2a9 → 3f8a1d6. 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"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 -3b52c9d1 (HEAD -> GT-241) GT-241 estilos del filtro 4f8a2e6 GT-241 base del filtro 7d3a8f4 (main) commit 6
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.
Lectura: desde aquí, los dos commits de
GT-241ya no son alcanzables.git logno los ve. Esto es lo que provoca el pánico, y es solo un problema de referencias.
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-241Lectura: ahí está todo.
HEAD@{1}es la punta que había antes del reset.
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 --statb52c9d1 (rescate) GT-241 estilos del filtro 4f8a2e6 GT-241 base del filtro 7d3a8f4 (HEAD -> GT-241, main) commit 6
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 --shortEsta 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-foundCon 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
resetno 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 addya cuenta. El contenido preparado se guarda como blob y se puede rescatar. Confirmar es mejor, pero preparar ya es una red.- El protocolo de calma —
git status,git log --oneline --graph -20,git reflog -20y, 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 -adel directorio completo es la copia de seguridad de emergencia. Uncloneno 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íneashint:suelen contener literalmente la solución. - Los casos triviales se resuelven en un comando:
git switch -cpara los cambios en la rama equivocada,git branch+reset --hard origin/xpara los commits mal colocados,commit --amend --authorpara la identidad,--amend --no-editpara el fichero olvidado, borrarindex.locktras comprobar los procesos, ysafe.directorypara la propiedad dudosa. - Y la lista negra: nada de
--force,clean -fdx,rm -rf .gitnigc --prune=nowen 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
- ¿Qué es Git?
- Instalando Git
- Terminología Básica de Git
- El Modelo de Datos de Git
- Configurando Git
- Configuración Inicial
Módulo 2: Operaciones Básicas de Git
- Creando un Repositorio
- Clonando un Repositorio
- Flujo de Trabajo Básico de Git
- Preparando y Confirmando Cambios
- Inspeccionando Cambios con git diff
- Visualizando el Historial de Confirmaciones
Módulo 3: Ramas y Fusión
- Entendiendo las Ramas
- Creando y Cambiando Ramas
- Fusionando Ramas
- Estrategias de Fusión
- Resolviendo Conflictos de Fusión
- Gestión de Ramas
Módulo 4: Trabajando con Repositorios Remotos
- Entendiendo los Repositorios Remotos
- Agregando un Repositorio Remoto
- Autenticación con Repositorios Remotos
- Obteniendo y Extrayendo Cambios
- Enviando Cambios
- Rastreando Ramas
Módulo 5: Operaciones Avanzadas de Git
- Rebase
- Rebase Interactivo
- Cherry-Picking de Confirmaciones
- Guardando Cambios Temporales
- Etiquetando Confirmaciones
- Revirtiendo Confirmaciones
Módulo 6: Herramientas y Técnicas de Git
- Usando Git Hooks
- Git Bisect
- Git Blame
- Git Log y Alias
- Submódulos de Git
- Múltiples Copias de Trabajo con git worktree
Módulo 7: Estrategias de Colaboración y Flujo de Trabajo
- Forks y Pull Requests
- Revisiones de Código con Git
- Flujo de Trabajo Git Flow
- GitHub Flow
- Trunk Based Development
- Integración Continua con Git
Módulo 8: Mejores Prácticas y Consejos de Git
- Escribiendo Buenos Mensajes de Confirmación
- Manteniendo un Historial Limpio
- Ignorando Archivos con .gitignore
- Atributos de Fichero con .gitattributes
- Mejores Prácticas de Seguridad
- Consejos de Rendimiento
Módulo 9: Solución de Problemas y Depuración
- Problemas Comunes de Git
- Deshaciendo Cambios
- Resolviendo Divergencias con el Remoto
- Recuperando Confirmaciones Perdidas
- Tratando con Repositorios Corruptos
- Técnicas Avanzadas de Depuración
