Introducción
Los objetos son inmutables y nombrados por hash. Las referencias son los nombres mutables y legibles que apuntan a ellos. Cada branch, tag, branch de seguimiento remoto y HEAD es una ref.
Dónde viven las refs
Las refs son archivos (o entradas en el archivo packed-refs) bajo .git/refs/:
.git/refs/heads/<name>: branches locales..git/refs/tags/<name>: tags..git/refs/remotes/<remote>/<name>: branches de seguimiento remoto..git/HEAD: la ref actual.
Cada ref no simbólica es un archivo de una línea que contiene un hash:
cat .git/refs/heads/main
# a1b2c3d4...
Listando refs
git for-each-ref
git for-each-ref refs/tags/
git for-each-ref --format='%(refname:short) %(objectname:short) %(subject)'
git show-ref
Refs simbólicas
Una ref simbólica apunta a otra ref en lugar de a un hash. HEAD es el ejemplo canónico:
cat .git/HEAD
# ref: refs/heads/main
git symbolic-ref HEAD
git symbolic-ref refs/remotes/origin/HEAD
Leyendo refs de forma segura
Siempre pasa por plumbing en lugar de leer archivos:
git rev-parse HEAD
git rev-parse main
git rev-parse origin/main
git rev-parse v1.0.0^{commit}
El plumbing maneja packed refs, refs simbólicas y búsquedas de refs uniformemente.
Actualizando refs
git update-ref refs/heads/feature/login <sha>
git update-ref -d refs/heads/feature/login # eliminar
git update-ref refs/heads/feature/login <new> <old> # CAS
La forma de tres argumentos es compare-and-set; rechaza si el valor actual difiere.
Packed refs
Por rendimiento, Git puede empaquetar muchas refs en un solo archivo .git/packed-refs. git pack-refs --all hace esto; las escrituras futuras siguen yendo a archivos sueltos hasta el próximo pack.
Sintaxis de refspec
Los remotos usan refspecs para mapear entre refs locales y remotas:
+refs/heads/*:refs/remotes/origin/*
El + inicial significa "force update". Esta única línea es lo que hace que git fetch popule refs/remotes/origin/.
El backend reftable
Git moderno (2.45+) incluye un formato alternativo de almacenamiento de refs llamado reftable, originalmente desarrollado para JGit. Reftable almacena millones de refs eficientemente en un formato binario log-estructurado y soporta transacciones atómicas a través de muchas refs a la vez. Habilítalo en un nuevo repo:
git init --ref-format=reftable
git rev-parse --show-ref-format
Para la mayoría de usuarios el formato clásico loose+packed sigue siendo válido. Reftable brilla en servidores y en monorepos enormes con cientos de miles de refs (p.ej., una ref por pull request).
Jerarquías de refs personalizadas
Las refs no tienen que vivir bajo los cuatro namespaces estándar. Las herramientas que necesitan sus propios nombres usan jerarquías personalizadas bajo refs/:
refs/notes/*:git notes.refs/stash:git stash.refs/replace/*:git replace.refs/bisect/*: estado degit bisect.refs/pull/*/headyrefs/merge-requests/*/head: proveedores de hosting.
git for-each-ref refs/notes/
git for-each-ref refs/replace/
Puedes crear las tuyas propias mientras el nombre sea una ref válida (sin puntos dobles, sin espacios, sin guión inicial).
Errores comunes
Editar archivos de ref a mano y terminar con un problema de salto de línea final o un archivo escrito a medias. Usa git update-ref. Confundir refs/heads/main, main y refs/main; git rev-parse --symbolic-full-name resuelve cualquier nombre corto a su forma canónica. Nombrar un branch y un tag de forma idéntica; las herramientas entonces prefieren el tag, llevando a confusión sutil. Finalmente, eliminar .git/packed-refs para "limpiar"; elimina las refs que no estaban también sueltas. Siempre pasa por Git.