Todo lo que hemos visto en el módulo daba por supuesta una cosa: que la base de datos de objetos estaba sana. El reflog encontraba el hash, git branch creaba el puntero, git show leía el commit. Los objetos estaban donde tenían que estar y su contenido era el correcto.

Esta lección se ocupa de cuando no lo está.

error: object file .git/objects/4f/8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a is empty
fatal: loose object 4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a (stored in .git/objects/4f/8a2e6...) is corrupt

Es el mensaje que más asusta de todo el curso, y hay que empezar por dos cosas.

La primera es que casi nunca es corrupción de verdad. La mayoría de los mensajes alarmantes tienen causas mundanas: un disco lleno, un proceso interrumpido, un antivirus que bloqueó un fichero. Antes de "reparar" nada, hay que descartar eso.

La segunda es la que hace habitable esta lección: en un sistema distribuido, cada clon es una copia de seguridad casi completa. El repositorio de Bruno tiene los mismos objetos que el tuyo. El servidor también. Ese es, casi siempre, el camino de recuperación más rápido, y muchas veces el correcto es directamente volver a clonar en lugar de intentar arreglar nada.

Vamos a ver cómo distinguir corrupción real de otra cosa, cómo leer git fsck sin asustarse, cómo reparar los casos frecuentes ordenados de menor a mayor gravedad, y —lo más importante— cuándo dejar de intentarlo.

Contenido

  1. Cuándo sospechar corrupción y cuándo es otra cosa
  2. Las causas reales de la corrupción
  3. git fsck como herramienta de diagnóstico
  4. Reparaciones, de menor a mayor gravedad
  5. La red de seguridad del modelo distribuido
  6. Reconstruir un repositorio conservando tu trabajo
  7. Prevención: mantenimiento y copias de seguridad de verdad
  8. Cuándo dejar de reparar y volver a clonar

  1. Cuándo sospechar corrupción y cuándo es otra cosa

Antes de nada, la tabla de descarte. Muchos errores que parecen corrupción no lo son.

Mensaje / síntoma ¿Es corrupción? Causa real y solución
fatal: Unable to create '.git/index.lock': File exists No Cerrojo huérfano (09-01, apartado 5.5)
error: insufficient permission for adding an object No Permisos: se usó sudo alguna vez. chown -R
fatal: detected dubious ownership No Propiedad del directorio (09-01, apartado 5.6)
error: cannot open .git/objects/...: No space left on device No aún, pero lo será Disco lleno. Libera espacio ANTES de seguir
fatal: bad object HEAD Quizá HEAD roto (apartado 4.5) o repositorio vacío
warning: ignoring dangling symref No Referencia simbólica a algo inexistente. Inofensivo
dangling commit / blob / tree en fsck No Completamente normal (09-04)
error: object file ... is empty Fichero de objeto de 0 bytes. Apartado 4.4
fatal: loose object ... is corrupt El contenido no coincide con su hash. Apartado 4.4
error: refs/heads/x does not point to a valid object! Referencia rota. Apartado 4.3
missing blob / broken link from en fsck Falta un objeto necesario. Apartado 3
fatal: index file smaller than expected Sí, pero leve Índice corrupto (apartado 4.1): es un caché
error: bad signature 0x00000000 Sí, leve Lo mismo: índice corrupto
fatal: packed object ... cannot be read , grave Packfile dañado. Apartado 4.4
Todo va lento pero funciona No Rendimiento (08-06)

El descarte previo, en tres comandos

Antes de tocar nada de .git:

# 1. ¿Hay espacio en disco? Es la causa número uno de escrituras a medias
df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       234G  234G     0 100% /home

Si ves Use% 100%, ese es el problema. Libera espacio antes de seguir; cualquier reparación va a escribir objetos y fallará igual.

# 2. ¿Es mío el repositorio? ¿Tengo permisos?
ls -ld .git .git/objects
find .git -not -user "$(id -un)" | head
# 3. ¿Dónde vive el repositorio?
df -T . | tail -1
/dev/sda2 ext4 ...

Si en lugar de ext4/apfs/ntfs local ves nfs, cifs, fuse.sshfs o una ruta dentro de Dropbox, OneDrive, iCloud o Google Drive, ya tienes la explicación, y es el apartado 2.

  1. Las causas reales de la corrupción

Git es notablemente robusto. Los objetos son inmutables, se escriben una vez, y cada uno lleva su propia suma de verificación: el nombre del objeto es el hash de su contenido, así que cualquier alteración se detecta al leerlo (lección 01-04). La corrupción, cuando ocurre, casi siempre viene de fuera.

Causa Cómo ocurre Frecuencia
Repositorio en carpeta sincronizada con la nube Dropbox/OneDrive/iCloud/Drive sincronizan ficheros de .git en pleno commit, o resuelven "conflictos" duplicando ficheros La más común con diferencia
Disco lleno Git escribe un objeto a medias y no puede terminarlo Muy común
Apagón o cierre forzado durante un commit/gc Escritura interrumpida Común en portátiles
.git en un volumen de red (NFS, SMB, sshfs) Bloqueos y mtime poco fiables, escrituras reordenadas Común en entornos corporativos
Antivirus Bloquea o pone en cuarentena ficheros de .git/objects Común en Windows
Matar procesos de Git a lo bruto kill -9 durante gc o repack Ocasional
Disco con sectores defectuosos Hardware que falla Rara, pero grave
Editar ficheros de .git a mano "Voy a arreglar esto con un editor de texto" Autoinfligida

El caso de la nube, que merece énfasis

No pongas un repositorio Git dentro de una carpeta sincronizada por la nube.

Es la causa clásica, y el razonamiento es sencillo: Git escribe muchos ficheros pequeños en un orden concreto (objeto, luego índice, luego referencia). Un sincronizador los sube según su propio criterio y velocidad. Si trabajas desde dos máquinas, el sincronizador puede combinar un .git/index de una con las referencias de la otra, o crear ficheros del tipo main (conflicted copy 2026-07-28).lock.

El resultado es un repositorio en un estado que Git nunca podría haber producido.

Y lo irónico: no hace ninguna falta. Git ya es un sistema de sincronización distribuido. Para tener el proyecto en dos máquinas, la respuesta es un remoto, no Dropbox.

# Si te encuentras en esta situación: saca el repositorio de la carpeta sincronizada
mv ~/Dropbox/gestor-tareas ~/proyectos/gestor-tareas
cd ~/proyectos/gestor-tareas
git fsck --full          # comprobar los daños

Si de verdad necesitas que un repositorio viva ahí, la única forma segura es que sea un repositorio desnudo (--bare) usado como remoto, al que nunca se escribe desde dos sitios a la vez. Aun así, git bundle (apartado 7) es mejor idea.

La comprobación de antivirus en Windows

Si Carla trabaja en Windows 11 y sufre errores intermitentes:

Excluir de la protección en tiempo real:
  C:\Users\carla\proyectos\           (o al menos las carpetas .git)

Y en Git for Windows, un ajuste que reduce problemas con antivirus y con sistemas de ficheros lentos:

git config --global core.fscache true

  1. git fsck como herramienta de diagnóstico

git fsck (file system check) recorre la base de objetos, verifica que el contenido de cada objeto coincide con su hash, y comprueba que todas las referencias entre objetos se resuelven.

git fsck --full --no-progress

Opciones útiles:

Opción Qué hace
--full Comprueba también los objetos dentro de packfiles (por defecto solo los sueltos)
--no-progress Sin barra de progreso: mejor para redirigir a un fichero
--lost-found Crea enlaces en .git/lost-found/ (09-04)
--unreachable Lista también lo inalcanzable contando el reflog
--connectivity-only Rápido: solo comprueba enlaces, no verifica hashes
--strict Más severo: avisa de cosas que Git tolera
--dangling / --no-dangling Mostrar u ocultar los colgantes

Leer la salida: lo normal frente a lo grave

Esta es la tabla que evita el pánico innecesario.

Línea de fsck Gravedad Qué significa Qué hacer
dangling commit <sha> Ninguna Commit que ninguna referencia alcanza Nada. Es normal tras reset, rebase, --amend
dangling blob <sha> Ninguna Contenido de un git add sin confirmar Nada (o recuperarlo, 09-04)
dangling tree <sha> Ninguna Directorio sin commit que lo use Nada
unreachable <tipo> <sha> Ninguna Igual, contando el reflog Nada
notice: HEAD points to an unborn branch Ninguna Repositorio recién creado sin commits Nada
warning: ... has zero-padded file modes Muy baja Repositorio antiguo o importado Nada, o fsck.zeroPaddedFilemode ignore
warning: ... missingSpaceBeforeDate Muy baja Metadatos malformados de una importación Nada
error: <sha>: object corrupt or missing ALTA El objeto no se puede leer Apartado 4.4
missing blob <sha> ALTA Un árbol referencia un blob que no existe Apartado 4.4
missing tree <sha> ALTA Un commit referencia un árbol inexistente Apartado 4.4
broken link from <sha> to <sha> ALTA Un objeto apunta a otro que falta Apartado 4.4
error: refs/heads/x does not point to a valid object! ALTA Referencia rota Apartado 4.3
dangling commit en cantidades enormes Media Puede indicar un gc interrumpido git gc

La regla de oro para leer fsck:

# Ocultar el ruido normal y quedarse SOLO con lo importante
git fsck --full --no-progress 2>&1 | grep -v "^dangling" | grep -v "^notice"

Si eso no devuelve nada, tu repositorio está sano. Punto. Todo lo que hayas visto era ruido.

# Y para separar por gravedad
git fsck --full --no-progress 2>&1 | grep -E "^(error|missing|broken)"

Un ejemplo de salida sana

git fsck --full --no-progress
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18432/18432), done.
dangling commit 9c4e7b2e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b
dangling blob 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b
dangling commit 3e7f1a8b6d2e9a5f1c7b3d8e4a6f2c9b7d3a8f4a

Tres líneas alarmantes en apariencia, cero problemas. Son los restos del reset --hard de la semana pasada y de un git add que no llegó a confirmarse. Un repositorio activo siempre tiene esto.

Un ejemplo de salida grave

Checking object directories: 100% (256/256), done.
error: refs/heads/GT-241 does not point to a valid object!
error: object file .git/objects/4f/8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a is empty
fatal: loose object 4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a is corrupt
missing blob 8a1f6c3d5e2b9c7f4a6d1e8b3c5f7a9d2e4b6c8a
broken link from  tree 2f8c6e1a4b7d9c3e5f2a8b6d1c9e4f7a3b5d2c8e
              to  blob 8a1f6c3d5e2b9c7f4a6d1e8b3c5f7a9d2e4b6c8a

Aquí sí hay trabajo. Una referencia rota y un objeto ilegible, con el árbol que lo necesita identificado. Es el apartado 4.

Comprobar un objeto concreto

git cat-file -t 4f8a2e6      # tipo
git cat-file -s 4f8a2e6      # tamaño
git cat-file -p 4f8a2e6      # contenido
fatal: Not a valid object name 4f8a2e6

Ese mensaje, para un hash que sabes que existía, es la confirmación de que el objeto ha desaparecido.

  1. Reparaciones, de menor a mayor gravedad

4.1. .git/index corrupto (el caso más frecuente y más leve)

error: bad index file sha1 signature
fatal: index file corrupt

o

fatal: index file smaller than expected

No has perdido nada. Y la razón es conceptualmente importante:

El índice es un caché derivado. No contiene información única: es una foto de qué debería haber en el próximo commit, reconstruible enteramente a partir de HEAD y del directorio de trabajo.

Por eso la reparación es de las más agradecidas de Git:

# 1. Quitar el índice roto
rm -f .git/index

# 2. Reconstruirlo desde HEAD
git reset

# 3. Comprobar
git status
git status --short
 M app.js
 M estilos.css
?? borrador.txt

Lo único que se pierde es qué estaba preparado con git add: tras la reconstrucción, todas tus modificaciones aparecen como no preparadas. Los cambios en sí están intactos, porque viven en los ficheros del disco, no en el índice.

Un matiz importante: usa git reset (--mixed), nunca git reset --hard. El primero reconstruye el índice y deja el directorio de trabajo en paz; el segundo se llevaría por delante todas tus modificaciones (lección 09-02).

Si además tenías un conflicto de fusión a medias, la información de las etapas del índice (:1:, :2:, :3: de la lección 03-05) sí se pierde, y hay que rehacer la fusión:

git merge --abort 2>/dev/null || true
rm -f .git/index
git reset
git merge <rama>          # volver a empezar

4.2. Cerrojos huérfanos

Ya visto en la lección 09-01, aquí en su versión completa. Los cerrojos son ficheros .lock que Git crea antes de escribir y borra al terminar:

find .git -name "*.lock"
.git/index.lock
.git/refs/heads/GT-241.lock
.git/config.lock

El procedimiento:

# 1. SIEMPRE primero: ¿hay algún Git ejecutándose?
ps aux | grep "[g]it "

# 2. ¿De cuándo son?
ls -l $(find .git -name "*.lock")

# 3. Si no hay procesos y son antiguos, borrarlos
find .git -name "*.lock" -delete

# 4. Comprobar
git status

Un caso especial que merece cuidado: .git/config.lock. Si Git murió escribiendo la configuración, el fichero .git/config puede haber quedado truncado. Compruébalo antes de borrar el cerrojo:

git config --list --local
cat .git/config

Si está roto, la configuración local es lo más fácil de rehacer a mano de todo el repositorio.

4.3. Referencias rotas

error: refs/heads/GT-241 does not point to a valid object!

Qué es. El fichero refs/heads/GT-241 contiene un hash que no corresponde a ningún objeto existente. Recuerda que una rama son 41 bytes de texto (lección 03-01):

cat .git/refs/heads/GT-241
git cat-file -t "$(cat .git/refs/heads/GT-241)"
b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b
fatal: Not a valid object name b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b

Confirmado: la referencia apunta al vacío.

Reparación A: el reflog de la rama. Es la mejor opción, porque conserva historia:

git reflog show GT-241
b52c9d1 GT-241@{0}: commit: GT-241 estilos del indicador
7d3a8f4 GT-241@{1}: commit: GT-241 calcular el contador
4f8a2e6 GT-241@{2}: branch: Created from main

Prueba las entradas de arriba abajo hasta encontrar una válida:

git cat-file -t 7d3a8f4      # commit → esta sí existe
git update-ref refs/heads/GT-241 7d3a8f4
git log --oneline GT-241 -3

Has perdido el último commit, pero recuperas la rama. Mucho mejor que nada.

Reparación B: packed-refs. Las referencias también viven consolidadas en un fichero (lección 08-06):

grep GT-241 .git/packed-refs
7d3a8f4a2c6e9b1d5f3a8c6e2b9d4f7a1c5e8b3d refs/heads/GT-241

A veces la versión empaquetada es válida aunque la suelta esté rota. Borrando la suelta, Git usa la empaquetada:

rm .git/refs/heads/GT-241
git log --oneline GT-241 -3

Reparación C: el remoto.

git fetch origin
git update-ref refs/heads/GT-241 origin/GT-241

Reparación D: si la rama no importa, borrarla.

git update-ref -d refs/heads/GT-241

Y la limpieza masiva, cuando hay muchas referencias rotas:

# Ver todas las referencias y su validez
git for-each-ref --format='%(refname) %(objectname)' | while read -r ref sha; do
  git cat-file -e "$sha" 2>/dev/null || echo "ROTA: $ref -> $sha"
done

4.4. Objetos sueltos ilegibles o ausentes

Este es el caso serio.

error: object file .git/objects/4f/8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a is empty
fatal: loose object 4f8a2e6... is corrupt

Primer paso, el diagnóstico exacto:

ls -l .git/objects/4f/8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a
-r--r--r-- 1 ana ana 0 jul 28 16:04 .git/objects/4f/8a2e6c9b...

Cero bytes: escritura interrumpida. Es el patrón típico de disco lleno o apagón.

# ¿Qué se supone que era? (a menudo se puede deducir del contexto de fsck)
git fsck --full --no-progress 2>&1 | grep -A2 "4f8a2e6"
broken link from  tree 2f8c6e1a4b7d9c3e5f2a8b6d1c9e4f7a3b5d2c8e
              to  blob 4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a

La reparación que casi siempre funciona: traerlo de otro clon.

Esta es la idea central de la lección y el apartado 5 la desarrolla. Los objetos de Git son idénticos en todas partes: el mismo contenido produce el mismo hash y el mismo fichero comprimido. Si Bruno tiene ese commit, su objeto sirve exactamente igual.

# 1. Quitar el objeto roto (¡de cero bytes: no hay nada que perder!)
rm .git/objects/4f/8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a

# 2. Traerlo del remoto: la forma sencilla
git fetch origin --force '+refs/heads/*:refs/remotes/origin/*'

# 3. O copiarlo directamente de otro clon
cp /ruta/al/clon/de/bruno/.git/objects/4f/8a2e6c9b1d5e3a... \
   .git/objects/4f/

# 4. Comprobar
git fsck --full --no-progress 2>&1 | grep -E "^(error|missing|broken)"

Si el otro clon lo tiene empaquetado en lugar de suelto, hay que extraerlo:

# En el clon sano:
cd /ruta/al/clon/de/bruno
git cat-file -p 4f8a2e6 > /tmp/objeto-recuperado

# En el repositorio roto:
cd ~/proyectos/gestor-tareas
git hash-object -w -t blob /tmp/objeto-recuperado
4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a

Si el hash resultante coincide con el que faltaba, la reparación es exacta. Y no puede no coincidir: el hash se calcula del contenido, así que si sale otro, el contenido no era el mismo (lección 01-04).

Para un objeto de tipo tree o commit, el mismo procedimiento con -t tree / -t commit y el contenido exacto que da cat-file -p en el clon sano.

Si el objeto era un packfile dañado:

fatal: packed object 4f8a2e6... (stored in .git/objects/pack/pack-a1b2c3.pack) is corrupt

Un packfile roto es peor, porque contiene miles de objetos y las cadenas de delta dependen unas de otras (lección 08-06). Se puede intentar extraer lo salvable:

# Mover el pack roto fuera y ver qué sobrevive
mkdir -p /tmp/packs-rotos
mv .git/objects/pack/pack-a1b2c3.* /tmp/packs-rotos/
git fsck --full --no-progress

# Intentar recuperar objetos individuales del pack roto
git verify-pack -v /tmp/packs-rotos/pack-a1b2c3.idx 2>/dev/null | head

Pero seamos claros: con un packfile dañado, la respuesta correcta casi siempre es reclonar (apartado 8). Extraer objetos de un pack roto es un ejercicio de arqueología que rara vez compensa cuando hay un clon sano a un git clone de distancia.

4.5. HEAD roto

fatal: bad object HEAD
fatal: not a git repository (or any of the parent directories)

HEAD es un fichero de texto de una línea (lección 03-01):

cat .git/HEAD

Lo que debería contener:

ref: refs/heads/main

Lo que puede haber pasado:

Contenido de .git/HEAD Problema Reparación
ref: refs/heads/rama-borrada La rama no existe git symbolic-ref HEAD refs/heads/main
Vacío o basura binaria Escritura interrumpida Reescribirlo
Un hash suelto (sin ref:) Detached HEAD: no es un error git switch main
Un hash de un objeto inexistente Corrupción real Apuntar a una rama válida
# Ver qué ramas hay realmente
ls .git/refs/heads/
cat .git/packed-refs 2>/dev/null | grep refs/heads

# Reparar apuntando a una rama existente (forma correcta, no editar a mano)
git symbolic-ref HEAD refs/heads/main

# Comprobar
git status
git log --oneline -3

Si no queda ninguna rama válida, el reflog de HEAD sigue siendo texto plano y legible aunque Git no funcione:

tail -5 .git/logs/HEAD
... 7d3a8f4a2c6e9b1d ... commit: GT-241 calcular el contador
git update-ref refs/heads/rescate 7d3a8f4
git symbolic-ref HEAD refs/heads/rescate

4.6. .git/config corrupto

fatal: bad config line 12 in file .git/config

Es el fichero más fácil de reparar, porque su contenido es trivial de rehacer:

cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
[remote "origin"]
	url = git.ejemplo.es:equipo/gestor-tareas.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
	remote = origin
	merge = refs/heads/main

Si está truncado, edítalo (aquí sí, con un editor de texto: config es texto plano por diseño y Git no lo verifica con hashes) o reconstruye lo esencial:

git remote add origin git.ejemplo.es:equipo/gestor-tareas.git
git branch --set-upstream-to=origin/main main
git config user.name "Ana Ferrer"
git config user.email "[email protected]"

Tabla resumen de reparaciones

Problema Gravedad Reparación ¿Se pierde algo?
.git/index corrupto Baja rm .git/index && git reset Solo qué estaba preparado
Cerrojo .lock huérfano Baja Comprobar procesos y borrar Nada
.git/config roto Baja Editar o rehacer con git config Configuración local
Referencia rota Media Reflog, packed-refs, o el remoto Quizá los últimos commits de esa rama
HEAD roto Media git symbolic-ref HEAD refs/heads/main Nada
Objeto suelto ilegible Alta Traerlo de otro clon Nada, si alguien lo tiene
Packfile dañado Muy alta Reclonar Solo lo que fuera exclusivamente local
Corrupción extendida Muy alta Reclonar Lo mismo

  1. La red de seguridad del modelo distribuido

Aquí está la idea que convierte esta lección en algo manejable, y que cierra el círculo del módulo 1.

En un sistema centralizado, el servidor es un punto único de fallo: si se corrompe, se ha corrompido el proyecto. En Git, cada clon contiene todo el historial. Ana, Bruno, Carla, Diego y el servidor tienen cinco copias completas de la base de objetos.

flowchart TD
    S["git.ejemplo.es<br/>historia completa"]
    A["Ana (Ubuntu)<br/>historia completa<br/>+ GT-241 sin publicar"]
    B["Bruno (macOS)<br/>historia completa"]
    C["Carla (Windows)<br/>historia completa"]
    D["Diego (fork)<br/>historia completa"]
    S <--> A
    S <--> B
    S <--> C
    S <--> D

Consecuencia práctica: cuando tu repositorio se corrompe, la pregunta correcta no es "¿cómo lo reparo?" sino "¿qué tengo yo que no tenga nadie más?".

# 1. ¿Qué ramas tengo sin publicar?
git for-each-ref --format='%(refname:short)  upstream=[%(upstream:short)]' refs/heads
main       upstream=[origin/main]
GT-241     upstream=[]
GT-238     upstream=[origin/GT-238]

GT-241 no tiene upstream: existe solo en esta máquina. Es lo único que hay que salvar.

# 2. ¿Qué commits tengo por delante del remoto?
git log --oneline origin/main..main
git log --oneline --branches --not --remotes

# 3. ¿Hay stashes?
git stash list

# 4. ¿Hay cambios sin confirmar?
git status --short

Con esas cuatro respuestas ya sabes exactamente cuánto vale la reparación. Si la respuesta es "nada, todo está publicado", reclonar tarda dos minutos y es la solución perfecta.

Y si el problema es del servidor, la dirección se invierte: cualquier clon del equipo puede reconstruirlo.

# Reconstruir el remoto desde un clon sano
cd ~/proyectos/gestor-tareas
git push --all origin
git push --tags origin

# O crear un espejo completo desde cero
git clone --mirror ~/proyectos/gestor-tareas /tmp/gestor-tareas-restaurado.git

--mirror copia todas las referencias tal cual: ramas, etiquetas, notas. Es la forma correcta de duplicar un repositorio desnudo.

  1. Reconstruir un repositorio conservando tu trabajo

El procedimiento completo, cuando has decidido reclonar pero tienes cosas locales que salvar.

Paso 1: la copia de seguridad, siempre

cd ~/proyectos
cp -a gestor-tareas gestor-tareas-ROTO-$(date +%Y%m%d-%H%M)

Aunque vayas a reclonar. Cuesta diez segundos y es lo único que impide que un error durante el rescate sea definitivo.

Paso 2: inventario de lo que solo tienes tú

cd ~/proyectos/gestor-tareas

# Ramas sin publicar
git for-each-ref --format='%(refname:short) %(upstream)' refs/heads | awk '$2==""{print $1}'

# Commits locales por delante del remoto, de todas las ramas
git log --oneline --branches --not --remotes

# Stashes
git stash list

# Cambios sin confirmar
git status --short

# Etiquetas locales sin publicar
git tag --no-merged origin/main 2>/dev/null

Paso 3: extraer lo local a un bundle

git bundle empaqueta commits en un solo fichero que funciona como un remoto. Es la herramienta correcta para esto, y funciona incluso si el repositorio está parcialmente dañado (mientras los objetos implicados estén sanos).

# Todo lo que tengo y no está en el remoto
git bundle create /tmp/rescate.bundle --branches --not --remotes

# O solo una rama concreta
git bundle create /tmp/GT-241.bundle GT-241

# Comprobar que el bundle es válido
git bundle verify /tmp/rescate.bundle
The bundle contains these 2 refs:
7d3a8f4a2c6e9b1d5f3a8c6e2b9d4f7a1c5e8b3d refs/heads/GT-241
b52c9d1e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b refs/heads/GT-238
The bundle requires these 1 ref:
4f8a2e6c9b1d5e3a7f2c8b6d9e4a1f5c3b7d2e9a refs/heads/main
/tmp/rescate.bundle is okay

Si además tienes cambios sin confirmar, sácalos aparte:

git diff > /tmp/cambios-sin-confirmar.patch
git diff --cached > /tmp/cambios-preparados.patch
# Y los ficheros sin seguimiento, a mano:
cp -a borrador.txt notas.md /tmp/rescate-ficheros/

Si git bundle falla porque el repositorio está demasiado dañado, la vía bruta también sirve:

# Copiar los ficheros del proyecto (sin .git) y tratarlos como cambios nuevos
mkdir /tmp/rescate-trabajo
rsync -a --exclude='.git' ~/proyectos/gestor-tareas/ /tmp/rescate-trabajo/

Se pierde el historial de las ramas locales, pero se conserva el contenido, que suele ser lo que importaba.

Paso 4: clonar de nuevo

cd ~/proyectos
mv gestor-tareas gestor-tareas-roto
git clone git.ejemplo.es:equipo/gestor-tareas.git
cd gestor-tareas

Paso 5: reincorporar lo rescatado

# Traer el bundle como si fuera un remoto
git fetch /tmp/rescate.bundle 'refs/heads/*:refs/heads/*'
git branch
  GT-238
  GT-241
* main

Las ramas locales han vuelto, con todos sus commits.

# Los cambios sin confirmar
git apply /tmp/cambios-sin-confirmar.patch

# Los que estaban preparados
git apply --cached /tmp/cambios-preparados.patch

# Los ficheros sin seguimiento
cp -a /tmp/rescate-ficheros/* .

Paso 6: verificar y limpiar

git fsck --full --no-progress 2>&1 | grep -E "^(error|missing|broken)"
git log --oneline --all --graph -15
git status

Si no hay errores y todo tu trabajo está ahí:

rm -rf ~/proyectos/gestor-tareas-roto
rm /tmp/rescate.bundle /tmp/*.patch

No borres la copia hasta haber verificado. Es la única regla que importa de este apartado.

El procedimiento en una tabla

Paso Comando Para qué
1 cp -a <repo> <repo>-ROTO-<fecha> Red de seguridad
2 git for-each-ref + git log --branches --not --remotes Saber qué es exclusivamente tuyo
3 git bundle create /tmp/rescate.bundle --branches --not --remotes Empaquetar lo local
4 git clone <url> Repositorio limpio
5 git fetch /tmp/rescate.bundle 'refs/heads/*:refs/heads/*' Devolver lo local
6 git fsck --full + comprobar Verificar antes de borrar nada

  1. Prevención: mantenimiento y copias de seguridad de verdad

git maintenance y git fsck periódicos

Retomando la lección 08-06:

# Mantenimiento programado en segundo plano
git maintenance start

Además de mejorar el rendimiento, mantiene la base de objetos ordenada y reduce el número de ficheros sueltos expuestos a escrituras interrumpidas.

Y una comprobación periódica, que en un repositorio normal tarda segundos:

# Rápida: solo enlaces, no verifica hashes
git fsck --connectivity-only --no-progress

# Completa: verifica cada objeto contra su hash. Mensual está bien
git fsck --full --no-progress 2>&1 | grep -vE "^(dangling|notice)"

Y una configuración que hace que Git verifique los objetos al recibirlos, detectando la corrupción en el momento en lugar de meses después:

git config --global transfer.fsckObjects true
git config --global fetch.fsckObjects true
git config --global receive.fsckObjects true

Cuesta un poco de tiempo en cada fetch y a cambio impide que un objeto malformado entre en tu repositorio. En el servidor, receive.fsckObjects es directamente obligatorio.

git bundle: la copia de seguridad de verdad

Un bundle es un único fichero con todo lo que le pidas, que funciona como un remoto. Es portátil, verificable y no depende de ningún servicio.

# Copia completa del repositorio
git bundle create ~/copias/gestor-tareas-$(date +%Y%m%d).bundle --all

# Comprobar
git bundle verify ~/copias/gestor-tareas-20260731.bundle

# Restaurar: se clona como si fuera una URL
git clone ~/copias/gestor-tareas-20260731.bundle gestor-tareas-restaurado

Y copias incrementales, si el repositorio es grande:

# Solo lo posterior a una etiqueta
git bundle create ~/copias/desde-v1.5.bundle v1.5.0..main

Un guion de copia semanal:

#!/usr/bin/env bash
# copia-git.sh — copia de seguridad de los repositorios de trabajo
DESTINO=~/copias/git
mkdir -p "$DESTINO"
for repo in ~/proyectos/*/; do
  [ -d "$repo/.git" ] || continue
  nombre=$(basename "$repo")
  echo "=== $nombre ==="
  git -C "$repo" bundle create "$DESTINO/$nombre-$(date +%Y%m%d).bundle" --all \
    && git -C "$repo" bundle verify "$DESTINO/$nombre-$(date +%Y%m%d).bundle" > /dev/null \
    && echo "  OK" || echo "  FALLÓ"
done
# Conservar solo las 8 copias más recientes de cada repositorio
find "$DESTINO" -name "*.bundle" -mtime +56 -delete

Por qué un bundle es mejor que copiar .git con cp:

cp -a .git git bundle
Coherencia si hay un Git escribiendo Puede copiar un estado a medias Coherente: Git lo genera
Verificable No : git bundle verify
Tamaño Todo, incluidos objetos huérfanos Solo lo alcanzable, comprimido
Restauración Copiar de vuelta git clone <fichero>
Portátil (correo, USB) Miles de ficheros Un fichero

La lista de prevención

1. Nunca un repositorio en una carpeta sincronizada por la nube. La causa número uno. Usa un remoto.

2. Nunca .git en un volumen de red si puedes evitarlo. Clona en local y publica.

3. Excluye tus carpetas de proyectos del antivirus en Windows.

4. Vigila el espacio en disco. Un df -h de vez en cuando.

5. git maintenance start en los repositorios de trabajo.

6. transfer.fsckObjects true para detectar la corrupción cuando entra.

7. Publica tus ramas. Una rama publicada está en dos máquinas. Es la mejor copia de seguridad y la más barata.

8. git bundle semanal de lo que solo tienes tú.

9. No mates procesos de Git con kill -9, especialmente durante gc o repack.

10. No edites ficheros de .git a mano (salvo config, que es texto por diseño). Usa git update-ref, git symbolic-ref, git config.

  1. Cuándo dejar de reparar y volver a clonar

La decisión más importante de la lección, y la que más tiempo ahorra.

Reclonar es la respuesta correcta cuando:

  • El repositorio está completamente publicado (nada local sin enviar). Entonces reclonar no tiene ningún coste.
  • Hay un packfile dañado. Extraer objetos de un pack roto rara vez compensa.
  • git fsck devuelve más de un puñado de errores.
  • La corrupción reaparece tras repararla: hay una causa de fondo (disco, nube, antivirus) que no has resuelto.
  • Llevas más de treinta minutos intentando repararlo. El coste de un git clone es de minutos; el de una tarde de arqueología, no.

Merece la pena intentar la reparación cuando:

  • El problema es el índice, un cerrojo, HEAD o config. Son minutos y no se pierde nada.
  • Es una referencia rota y el reflog o packed-refs la resuelven.
  • Falta un objeto y sabes de dónde traerlo.
  • Tienes trabajo local importante sin publicar y necesitas extraerlo antes de reclonar (apartado 6).

En forma de árbol:

flowchart TD
    Q1{"¿Tengo trabajo local<br/>sin publicar?"}
    Q1 -->|No| R1["RECLONAR.<br/>Es la respuesta correcta<br/>y tarda dos minutos"]
    Q1 -->|Sí| Q2{"¿El daño es índice, cerrojo,<br/>HEAD o una referencia?"}
    Q2 -->|Sí| R2["Reparar: apartado 4.<br/>Minutos, sin pérdida"]
    Q2 -->|No: objetos o packfiles| Q3{"¿Puedo extraer lo local<br/>con git bundle?"}
    Q3 -->|Sí| R3["Bundle + reclonar +<br/>reincorporar (apartado 6)"]
    Q3 -->|No| R4["rsync del contenido sin .git,<br/>reclonar, y reaplicar como cambios"]

Y la frase que resume la lección:

Un repositorio Git dañado no es un problema de datos: es un problema de logística. Los datos casi siempre están en otro sitio. Tu trabajo consiste en identificar qué es exclusivamente tuyo, salvarlo, y traer el resto de donde esté sano.

Errores Comunes y Consejos

Error 1: asustarse con los dangling de git fsck. Son completamente normales en cualquier repositorio activo. Filtra con grep -v "^dangling" y mira si queda algo.

Error 2: dar por corrupto lo que es un disco lleno. df -h es el primer comando. Reparar con el disco al 100 % no puede funcionar.

Error 3: tener el repositorio en Dropbox, OneDrive o iCloud. Es la causa número uno de corrupción real, y no hace ninguna falta: Git ya sincroniza.

Error 4: git reset --hard para reparar un índice corrupto. rm .git/index && git reset reconstruye sin tocar tus ficheros; con --hard los perderías todos.

Error 5: editar ficheros de .git con un editor de texto. Salvo config, usa los comandos: git update-ref, git symbolic-ref, git config.

Error 6: reparar sin haber hecho una copia. cp -a cuesta diez segundos y evita que un error durante el rescate sea definitivo.

Error 7: pasar horas reparando un repositorio que está íntegramente publicado. Comprueba primero qué tienes que no tenga nadie más. Si la respuesta es "nada", reclona.

Error 8: usar cp -a .git como copia de seguridad "oficial". Vale para una emergencia, pero puede copiar un estado a medias. git bundle es coherente y verificable.

Error 9: borrar el repositorio roto antes de verificar el nuevo. Verifica con git fsck y comprueba que todo tu trabajo está ahí; luego borra.

Consejo 1: aprende el filtro de fsck. git fsck --full --no-progress 2>&1 | grep -vE "^(dangling|notice)". Si no devuelve nada, estás sano.

Consejo 2: git bundle create ... --all semanal. Un fichero, verificable, restaurable con git clone.

Consejo 3: transfer.fsckObjects true en la configuración global. Detecta la corrupción cuando entra, no meses después.

Consejo 4: publica tus ramas. Es la copia de seguridad más barata y la que más veces salva.

Consejo 5: el índice es un caché. Perderlo no tiene importancia; recordarlo evita mucho pánico innecesario.

Consejo 6: pon un límite de tiempo. Treinta minutos de reparación y, si sigue roto, reclona. El clon tarda dos.

Ejercicios

Ejercicio 1: leer git fsck sin asustarse

  1. Crea un repositorio con diez commits, una rama con tres commits propios y un git add sin confirmar.
  2. Provoca objetos colgantes: un reset --hard HEAD~3, un commit --amend y un branch -D.
  3. Ejecuta git fsck --full y cuenta cuántas líneas devuelve.
  4. Aplica el filtro del consejo 1 y comprueba que no queda nada. Explica por qué el repositorio está sano pese a las líneas anteriores.
  5. Identifica, entre los colgantes, cuál corresponde al git add sin confirmar y recupéralo con git cat-file -p.

Ejercicio 2: romper y reparar

Sobre un repositorio de pruebas (nunca sobre uno real):

  1. Corrompe el índice: printf 'basura' > .git/index. Ejecuta git status y anota el error. Repáralo y explica por qué no se pierde nada.
  2. Rompe una referencia: echo "0000000000000000000000000000000000000000" > .git/refs/heads/GT-241. Ejecuta git fsck y repárala con el reflog.
  3. Rompe HEAD: echo "ref: refs/heads/inexistente" > .git/HEAD. Diagnostica y repara con git symbolic-ref.
  4. Vacía un objeto suelto: localiza uno con find .git/objects -type f y trúncalo con : > <fichero>. Ejecuta git fsck --full y observa el error.
  5. Repara el punto 4 desde un clon sano que habrás hecho antes de romper nada.
  6. Después de cada reparación, ejecuta git fsck --full filtrado y confirma que está limpio.

Ejercicio 3: rescate completo con bundle

  1. Monta un remoto local con git init --bare y clónalo.
  2. En el clon, publica dos commits en main y crea dos ramas locales sin publicar con tres commits cada una. Añade un stash y algún cambio sin confirmar.
  3. Haz el inventario del apartado 5: qué ramas no tienen upstream, qué commits no están en el remoto.
  4. Crea un bundle con todo lo local y verifícalo.
  5. Borra el clon entero (simulando corrupción irreparable) y vuelve a clonar.
  6. Reincorpora las dos ramas desde el bundle y comprueba que los seis commits están.
  7. Explica qué se ha perdido y qué no, y cómo lo habrías evitado publicando las ramas.

Soluciones

Solución 1:

rm -rf /tmp/p9-05 && mkdir /tmp/p9-05 && cd /tmp/p9-05 && git init -q -b main
git config user.name "Ana Ferrer"; git config user.email "[email protected]"
for i in $(seq 1 10); do echo "linea $i" >> app.js; git add .; git commit -q -m "commit $i"; done

git switch -q -c GT-241
for i in 1 2 3; do echo "filtro $i" >> filtro.js; git add .; git commit -q -m "GT-241 parte $i"; done
echo "PREPARADO SIN CONFIRMAR" > estilos.css && git add estilos.css
# 2. Los desastres
git reset -q --hard HEAD~3
git commit -q --amend -m "commit 10 (mensaje cambiado)" 2>/dev/null || true
git switch -q main
git branch -D GT-241
Deleted branch GT-241 (was 8f4c2a9).
# 3. La salida completa
git fsck --full --no-progress 2>&1 | wc -l
git fsck --full --no-progress 2>&1 | head
9
Checking object directories: 100% (256/256), done.
Checking objects: 100% (43/43), done.
dangling commit 9c4e7b2e4a7f3c8b6d2e9a5f1c7b3d8e4a6f2c9b
dangling blob 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b
dangling commit 3e7f1a8b6d2e9a5f1c7b3d8e4a6f2c9b7d3a8f4a
dangling tree 8a1f6c3d5e2b9c7f4a6d1e8b3c5f7a9d2e4b6c8a
dangling commit 8f4c2a9b6d2e9a5f1c7b3d8e4a6f2c9b7d3a8f4a

Nueve líneas de aspecto preocupante.

# 4. El filtro
git fsck --full --no-progress 2>&1 | grep -vE "^(dangling|notice|Checking)"
(sin salida)

Cero errores. El repositorio está perfectamente sano.

La razón: dangling significa "este objeto existe y es legible, pero ninguna referencia lo alcanza". Es exactamente el resultado esperado de un reset, un --amend y un branch -D: los commits siguen ahí (por eso son recuperables, lección 09-04) pero ya no cuelgan de ninguna rama. Es lo contrario de un problema: es la prueba de que la red de seguridad funciona.

Los errores de verdad empiezan por error:, missing o broken link, y aquí no hay ninguno.

# 5. El blob del add sin confirmar
for b in $(git fsck --lost-found --no-progress 2>/dev/null | awk '/dangling blob/ {print $3}'); do
  echo "--- $b ---"; git cat-file -p "$b" | head -2
done
--- 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b ---
PREPARADO SIN CONFIRMAR
git cat-file -p 6f2b9d4 > estilos.css && cat estilos.css
PREPARADO SIN CONFIRMAR

Solución 2:

rm -rf /tmp/p9-05b && mkdir /tmp/p9-05b && cd /tmp/p9-05b && git init -q -b main
git config user.name "Bruno Salas"; git config user.email "[email protected]"
for i in 1 2 3 4 5; do echo "l $i" >> app.js; git add .; git commit -q -m "c$i"; done
git switch -q -c GT-241
echo "filtro" > filtro.js && git add . && git commit -q -m "GT-241 filtro"
git switch -q main

# El clon sano, ANTES de romper nada (paso 5)
git clone -q /tmp/p9-05b /tmp/p9-05b-sano
# 1. Índice corrupto
echo "TRABAJO SIN CONFIRMAR" >> app.js
printf 'basura' > .git/index
git status
error: bad index file sha1 signature
fatal: index file corrupt
rm -f .git/index
git reset
git status --short
tail -1 app.js
 M app.js
TRABAJO SIN CONFIRMAR

Nada perdido. La modificación seguía en el fichero del disco; el índice solo era la foto de qué iba a entrar en el próximo commit, y se reconstruye desde HEAD más el directorio de trabajo. Es un caché.

# 2. Referencia rota
git rev-parse GT-241 > /tmp/hash-bueno.txt
echo "0000000000000000000000000000000000000000" > .git/refs/heads/GT-241
git fsck --full --no-progress 2>&1 | grep -E "^(error|missing|broken)"
error: refs/heads/GT-241 does not point to a valid object!
git log --oneline GT-241 -1
fatal: bad object GT-241
git reflog show GT-241
b52c9d1 GT-241@{0}: commit: GT-241 filtro
7d3a8f4 GT-241@{1}: branch: Created from HEAD
git cat-file -t b52c9d1        # comprobar que el objeto existe
git update-ref refs/heads/GT-241 b52c9d1
git log --oneline GT-241 -2
commit
b52c9d1 (GT-241) GT-241 filtro
7d3a8f4 (HEAD -> main) c5

Reparada, y con su commit. El reflog conservaba el hash correcto porque el fichero de reflog es independiente del fichero de referencia.

# 3. HEAD roto
echo "ref: refs/heads/inexistente" > .git/HEAD
git status
On branch inexistente

No commits yet

Git no falla, pero cree estar en una rama que no existe: git log no muestra nada y un commit crearía una rama nueva.

ls .git/refs/heads/
git symbolic-ref HEAD refs/heads/main
git status -sb | head -1
git log --oneline -1
GT-241  main
## main
7d3a8f4 c5
# 4. Objeto suelto vaciado
git gc -q --prune=now 2>/dev/null    # empaquetamos para dejar pocos sueltos
echo "nuevo contenido" > nuevo.txt && git add . && git commit -q -m "c6"
OBJ=$(find .git/objects -type f -path "*/??/*" | head -1)
echo "Objeto elegido: $OBJ"
cp "$OBJ" /tmp/objeto-original     # por si acaso
: > "$OBJ"
ls -l "$OBJ"
git fsck --full --no-progress 2>&1 | grep -vE "^(dangling|Checking)"
-rw-r--r-- 1 bruno bruno 0 jul 31 12:44 .git/objects/6f/2b9d4a8c1e5f3b...
error: object file .git/objects/6f/2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b is empty
error: unable to mmap .git/objects/6f/2b9d4...: No such device
fatal: loose object 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b is corrupt
git log --oneline -1
fatal: loose object 6f2b9d4... is corrupt

Git se niega a operar: no puede leer un objeto que necesita.

# 5. Reparar desde el clon sano
SHA=$(basename $(dirname "$OBJ"))$(basename "$OBJ")
echo "Falta: $SHA"
rm "$OBJ"

# ¿Lo tiene el clon sano?
git -C /tmp/p9-05b-sano cat-file -t "$SHA"
Falta: 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b
blob
# Extraerlo y volver a escribirlo
git -C /tmp/p9-05b-sano cat-file -p "$SHA" > /tmp/contenido
NUEVO=$(git hash-object -w -t blob /tmp/contenido)
echo "Escrito: $NUEVO"
[ "$NUEVO" = "$SHA" ] && echo "COINCIDE: reparación exacta"
Escrito: 6f2b9d4a8c1e5f3b7d9a2c6e4f8b1d3a5c7e9f2b
COINCIDE: reparación exacta

El hash coincide, así que la reparación es demostrablemente exacta. No puede ser de otra manera: el nombre del objeto es el hash de su contenido (lección 01-04), de modo que si el hash sale igual, el contenido es idéntico byte a byte. Esta propiedad es la que hace que reparar un repositorio Git desde otro clon sea seguro y verificable.

# 6. Verificación final
git fsck --full --no-progress 2>&1 | grep -vE "^(dangling|notice|Checking)"
git log --oneline --all -3
(sin salida)
2f8c6e1 (HEAD -> main) c6
7d3a8f4 c5
b52c9d1 (GT-241) GT-241 filtro

Solución 3:

rm -rf /tmp/p9-05c && mkdir /tmp/p9-05c && cd /tmp/p9-05c
git init -q --bare servidor.git
git clone -q servidor.git trabajo && cd trabajo
git config user.name "Carla Vidal"; git config user.email "[email protected]"

# 2. Lo publicado
echo "// gestor-tareas" > app.js && git add . && git commit -q -m "chore: inicio"
echo "// listado" >> app.js && git commit -q -am "feat: listado"
git push -q -u origin main

# Dos ramas locales SIN publicar
for r in GT-241 GT-247; do
  git switch -q -c "$r" main
  for i in 1 2 3; do echo "$r parte $i" >> "$r.js"; git add .; git commit -q -m "$r parte $i"; done
done
git switch -q main

# Un stash y cambios sin confirmar
echo "experimento" >> app.js && git stash push -q -m "experimento a medias"
echo "trabajo en curso" >> app.js
echo "borrador" > borrador.txt
# 3. Inventario
echo "--- Ramas sin upstream ---"
git for-each-ref --format='%(refname:short) %(upstream)' refs/heads | awk '$2==""{print "  "$1}'
echo "--- Commits que no están en ningún remoto ---"
git log --oneline --branches --not --remotes
echo "--- Stashes ---"
git stash list
echo "--- Sin confirmar ---"
git status --short
--- Ramas sin upstream ---
  GT-241
  GT-247
--- Commits que no están en ningún remoto ---
2f8c6e1 GT-247 parte 3
7a4d9b3 GT-247 parte 2
5c1e8f2 GT-247 parte 1
9c4e7b2 GT-241 parte 3
3e7f1a8 GT-241 parte 2
8a1f6c3 GT-241 parte 1
--- Stashes ---
stash@{0}: On main: experimento a medias
--- Sin confirmar ---
 M app.js
?? borrador.txt

Seis commits, dos ramas, un stash y dos cambios existen únicamente en esta máquina. Ese es exactamente el valor de la reparación.

# 4. El bundle
git bundle create /tmp/rescate.bundle --branches --not --remotes
git bundle verify /tmp/rescate.bundle
The bundle contains these 2 refs:
9c4e7b2... refs/heads/GT-241
2f8c6e1... refs/heads/GT-247
The bundle requires these 1 ref:
b52c9d1...
/tmp/rescate.bundle is okay
# El stash y lo sin confirmar, aparte
git stash show -p stash@{0} > /tmp/stash.patch
git diff > /tmp/sin-confirmar.patch
mkdir -p /tmp/ficheros-nuevos && cp borrador.txt /tmp/ficheros-nuevos/
# 5. La catástrofe
cd /tmp/p9-05c && rm -rf trabajo
git clone -q servidor.git trabajo && cd trabajo
git log --oneline --all
b52c9d1 (HEAD -> main, origin/main) feat: listado
1e6f2c8 chore: inicio

Solo lo publicado.

# 6. Reincorporar
git fetch -q /tmp/rescate.bundle 'refs/heads/*:refs/heads/*'
git branch
git log --oneline GT-241 -3
git log --oneline GT-247 -3
  GT-241
  GT-247
* main
9c4e7b2 (GT-241) GT-241 parte 3
3e7f1a8 GT-241 parte 2
8a1f6c3 GT-241 parte 1
2f8c6e1 (GT-247) GT-247 parte 3
7a4d9b3 GT-247 parte 2
5c1e8f2 GT-247 parte 1

Los seis commits y las dos ramas, con sus hashes originales.

git apply /tmp/sin-confirmar.patch
cp /tmp/ficheros-nuevos/borrador.txt .
git stash apply --index 2>/dev/null || git apply /tmp/stash.patch
git status --short
 M app.js
?? borrador.txt
git fsck --full --no-progress 2>&1 | grep -vE "^(dangling|notice|Checking)"
(sin salida)
# 7. Qué se ha perdido
git stash list
git reflog | wc -l
(sin salida)
3

Lo que se ha perdido:

  • La pila de stashes como tal. El contenido se ha reaplicado desde el parche, pero la entrada stash@{0} no existe. (Un bundle con --all habría incluido refs/stash.)
  • Todo el reflog, que es local y no viaja (lección 09-04). El clon nuevo tiene tres entradas.
  • Las ramas de seguimiento antiguas y la configuración local del repositorio.

Lo que no se ha perdido: ni un solo commit, ni un solo cambio.

Cómo se habría evitado: publicando las ramas.

git push -q -u origin GT-241 GT-247

Con las dos ramas publicadas, el inventario del paso 3 habría salido vacío, y toda esta lección se habría reducido a rm -rf trabajo && git clone. Dos segundos de push frente a veinte minutos de rescate.

Conclusión

La corrupción de un repositorio Git asusta mucho más de lo que cuesta.

  • La mayoría de los mensajes alarmantes no son corrupción: un cerrojo huérfano, un disco lleno, un problema de permisos o de propiedad. Descártalo primero con df -h y ls -ld .git.
  • Las causas reales vienen casi siempre de fuera de Git: un repositorio en una carpeta sincronizada con la nube (la número uno), un disco lleno, un apagón, un volumen de red, un antivirus. Los objetos de Git son inmutables y llevan su propia verificación; Git rara vez se rompe solo.
  • git fsck se lee filtrando el ruido. Los objetos dangling son completamente normales —son los restos de cada reset, rebase y --amend— y su presencia es la prueba de que la red de seguridad de la lección 09-04 funciona. Lo grave empieza por error:, missing o broken link.
  • Las reparaciones, de menor a mayor: el índice es un caché derivado y se reconstruye con rm .git/index && git reset sin perder nada; los cerrojos se borran tras comprobar procesos; una referencia rota se repara con el reflog, packed-refs o el remoto; HEAD con git symbolic-ref; un objeto ilegible trayéndolo de otro clon, con la garantía de que si el hash coincide, la reparación es exacta.
  • El modelo distribuido es la red de seguridad. Cada clon del equipo es una copia casi completa. La pregunta correcta ante un repositorio dañado no es "¿cómo lo reparo?" sino "¿qué tengo yo que no tenga nadie más?", y se responde con git for-each-ref y git log --branches --not --remotes.
  • El rescate se hace con git bundle: empaquetar lo local en un fichero, reclonar, y reincorporarlo con git fetch <bundle>. Y siempre con un cp -a previo y una verificación posterior antes de borrar nada.
  • Prevención: nada de repositorios en la nube ni en volúmenes de red, git maintenance start, transfer.fsckObjects true, git bundle periódico y —lo que más rinde— publicar las ramas.
  • Y el criterio: si todo está publicado, reclona. Tarda dos minutos y es la respuesta correcta. Reserva la reparación para el índice, los cerrojos, HEAD y las referencias, o para extraer trabajo local antes de reclonar.

Con esto, el módulo ha cubierto los cuatro problemas que anunciaba el cierre del módulo 8: el reset --hard desafortunado, el enredo con el remoto, la rama borrada y el repositorio que se niega a funcionar.

Queda lo transversal. Porque muchas veces el problema no es que Git esté roto ni que hayas perdido algo, sino que Git está haciendo algo que no entiendes: un fichero que se ignora cuando no debería, una configuración que viene de un sitio que no sabías que existía, un push que falla por razones opacas, un cambio de comportamiento que nadie recuerda haber introducido. Para eso hay una caja de herramientas propia —trazas, fontanería, check-ignore, check-attr, ls-files— y, sobre todo, un método.

Continúa en la lección 09-06: Técnicas Avanzadas de Depuración.

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