Ahorre 15% en todos los servicios de hosting

Pon a prueba tus habilidades y obtén Descuento<\/span> en cualquier plan de hosting

Usa el código: Skills Comenzar
Secciones
Linux Servidores virtuales

Estructura del Repositorio Git: Una Guía Técnica Completa

Git es un sistema de control de versiones distribuido que almacena el historial del proyecto como un grafo acíclico dirigido (DAG) de objetos de instantánea inmutables. Cada repositorio Git se construye a partir de tres zonas lógicas — el directorio de trabajo, el índice de preparación y el almacén de objetos dentro de .git/ — más un conjunto de punteros ligeros (ramas, etiquetas, remotos) que navegan por ese historial. Comprender cómo interactúan estas capas es la diferencia entre usar Git mecánicamente y usarlo con precisión quirúrgica.

Si alojas tus repositorios en un VPS, dominar esta estructura interna te permite recuperarte de desastres, diseñar pipelines de CI/CD eficientes y auditar cada byte del historial de tu proyecto sin depender de una plataforma de terceros.

El Modelo de Tres Zonas: Cómo Git Mueve los Datos

Antes de profundizar en los componentes individuales, interioriza el modelo de flujo de datos que gobierna cada operación de Git:

Working Directory  -->  Staging Area (Index)  -->  .git/ Object Store
     (edit)               (git add)                  (git commit)

Los cambios viajan de izquierda a derecha cuando construyes un commit, y de derecha a izquierda cuando restauras o restableces. Cada comando de Git es esencialmente una operación de lectura o escritura en una o más de estas zonas.

Directorio de Trabajo

El directorio de trabajo (también llamado árbol de trabajo) es la vista del sistema de archivos de tu proyecto en un estado de checkout específico. Cuando ejecutas git clone o git checkout, Git reconstruye los archivos a partir de objetos comprimidos en .git/objects/ y los escribe en este directorio.

Los archivos en el directorio de trabajo existen en uno de cuatro estados:

  • Sin seguimiento — Git nunca ha visto este archivo; solo existe en el disco.
  • Con seguimiento, sin modificar — el archivo coincide exactamente con la última instantánea confirmada.
  • Con seguimiento, modificado — el archivo difiere de la última instantánea confirmada pero no ha sido preparado.
  • Con seguimiento, eliminado — el archivo fue eliminado del disco pero la eliminación no ha sido preparada.

Un matiz crítico que confunde a muchos desarrolladores: el directorio de trabajo no es una simple copia del repositorio. Git lo reconstruye leyendo objetos de árbol y descomprimiendo objetos blob. Si .git/ está intacto, siempre puedes regenerar el directorio de trabajo desde cero — lo inverso no es cierto.

Sparse Checkout para Grandes Monorepos

En repositorios con decenas de miles de archivos (común en arquitecturas de monorepo), puedes limitar qué rutas Git materializa en el directorio de trabajo:

git sparse-checkout init --cone
git sparse-checkout set services/api services/auth

Esto es invaluable en un VPS con I/O de disco limitado, porque Git omite la descompresión de blobs para rutas fuera del cono.

Área de Preparación (Índice)

El área de preparación, llamada internamente el índice, es un archivo binario ubicado en .git/index. Actúa como el próximo commit propuesto — una instantánea mutable que se sitúa entre tu directorio de trabajo y el almacén de objetos permanente.

git add <file>          # Stage a specific file
git add -p              # Interactively stage hunks within a file
git add -u              # Stage all tracked modifications and deletions
git status              # Compare working directory and index against HEAD
git diff --cached       # Show diff between index and HEAD

Por Qué Existe el Índice

El índice resuelve un problema que las herramientas VCS más simples ignoran: los commits parciales. Puedes haber modificado cinco archivos pero solo querer tres de ellos en el próximo commit. El índice te permite componer exactamente la instantánea que pretendes registrar, independientemente de lo que tu editor tenga abierto.

Caso extremo — corrupción del índice: Si un fallo del sistema interrumpe un git add, el archivo de índice puede corromperse. Los síntomas incluyen que git status se cuelgue o reporte resultados extraños. Recuperación:

rm .git/index
git reset

Git reconstruye el índice desde HEAD sin tocar tu directorio de trabajo.

El Índice como Registro de Conflictos de Fusión

Durante un conflicto de fusión, el índice almacena tres versiones de cada archivo en conflicto simultáneamente (etapas 1, 2 y 3 — base, la nuestra, la de ellos). Por eso git diff --cached no muestra nada útil en medio de un conflicto; necesitas git diff --cc o una herramienta de fusión para inspeccionar las tres etapas.

El Directorio .git/: Anatomía del Almacén de Objetos

El directorio .git/ es el repositorio. Todo lo demás — el directorio de trabajo, los clones remotos — se deriva de él. Eliminar .git/ convierte un repositorio en un directorio simple sin historial.

.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│   ├── HEAD
│   └── refs/
├── objects/
│   ├── info/
│   └── pack/
└── refs/
    ├── heads/
    ├── remotes/
    └── tags/

HEAD es un archivo de texto plano que contiene una referencia simbólica (apuntando a una rama) o un hash SHA-1 sin procesar (estado HEAD desconectado).

cat .git/HEAD
# ref: refs/heads/main        <-- on a branch
# a3f1c9d...                  <-- detached HEAD

El HEAD desconectado no es un estado de error — es intencional cuando haces checkout de una etiqueta o un commit específico para inspección. El peligro es hacer commits en HEAD desconectado: esos commits solo son accesibles a través del reflog hasta que los adjuntes a una rama.

git checkout -b rescue-branch   # Attach detached commits to a new branch

config

El archivo de configuración del repositorio local. Anula la configuración global (~/.gitconfig) y del sistema (/etc/gitconfig). Entradas comunes:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
[remote "origin"]
    url = git@github.com:user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
    remote = origin
    merge = refs/heads/main

En un servidor autoalojado, frecuentemente editarás este archivo directamente cuando rotas URLs remotas o configuras uploadpack.allowReachableSHA1InWant para clones parciales.

refs/

El directorio refs/ contiene archivos de texto plano, cada uno con un único hash SHA-1. Son los punteros con nombre que hacen navegable el DAG de Git.

Tipo de RefRutaDescripción
Rama localrefs/heads/<name>Apunta al commit en la punta de una rama
Rama de seguimiento remotorefs/remotes/<remote>/<name>Caché local de la punta de una rama remota
Etiqueta ligerarefs/tags/<name>Apunta directamente a un objeto commit
Etiqueta anotadarefs/tags/<name>Apunta a un objeto etiqueta, que apunta a un commit
Stashrefs/stashApunta al commit del stash

Por rendimiento, Git empaqueta las refs en .git/packed-refs una vez que un repositorio acumula muchas. Siempre verifica ambas ubicaciones cuando escribas scripts que trabajen con refs.

Objetos Git: El Núcleo Inmutable

Todo lo almacenado en .git/objects/ está direccionado por contenido: el nombre del archivo es el hash SHA-1 (o SHA-256 en versiones más nuevas de Git) del contenido del objeto. Esto hace que Git sea inherentemente resistente a manipulaciones — cambiar cualquier byte cambia el hash, rompiendo la cadena.

Los Cuatro Tipos de Objetos

Tipo de ObjetoQué AlmacenaApunta A
BlobContenido de archivo sin procesar (sin nombre de archivo, sin permisos)Nada
TreeListado de directorio: nombres de archivo, permisos, SHAs de blob/treeBlobs y otros trees
CommitAutor, committer, marca de tiempo, mensaje, SHA(s) padreUn tree + cero o más commits padre
TagIdentidad del tagger, marca de tiempo, mensaje, firma GPGGeneralmente un commit

Inspeccionando Objetos Directamente

# Show the type of any object
git cat-file -t a3f1c9d

# Show the content of any object
git cat-file -p a3f1c9d

# Show the tree of the current HEAD commit
git ls-tree HEAD

# Show a specific blob's content
git show HEAD:src/main.py

Objetos Sueltos vs. Archivos Pack

Inicialmente, cada objeto se almacena como un archivo comprimido individual bajo .git/objects/<2-char-prefix>/<38-char-suffix>. Estos son objetos sueltos. Con el tiempo, Git ejecuta git gc (recolección de basura) para agrupar objetos sueltos en archivos pack (.git/objects/pack/*.pack) con un índice correspondiente (.pack.idx).

Los archivos pack usan compresión delta — almacenando la diferencia entre objetos similares en lugar de copias completas. Un repositorio con miles de archivos de texto similares puede reducirse drásticamente después del empaquetado. En un VPS con capacidad NVMe limitada, ejecutar git gc --aggressive en repositorios grandes antes de archivarlos es una práctica estándar.

git count-objects -vH    # Show loose object count and disk usage
git gc --aggressive      # Repack aggressively (CPU-intensive)
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20
# Find the 20 largest objects in the pack

Historial de Commits: El Grafo Acíclico Dirigido

Cada objeto commit contiene exactamente un puntero a un objeto tree (la instantánea del directorio raíz) y cero o más punteros a commits padre. Esto forma un DAG donde:

  • Cero padres = el commit inicial (commit raíz)
  • Un padre = un commit normal
  • Dos padres = un commit de fusión
  • Tres o más padres = una fusión octopus (rara, usada para integrar muchas ramas de características simultáneamente)
git log --oneline --graph --all    # Visualize the full DAG
git log --format="%H %P"           # Show each commit's SHA and parent SHA(s)

Inmutabilidad de Commits y Reescritura del Historial

Dado que el SHA de un commit se deriva de su contenido (incluyendo los SHAs padre), cualquier reescritura crea un nuevo commit con un nuevo SHA. Operaciones como git rebase, git commit --amend y git filter-repo no modifican el historial — crean un historial paralelo. Los commits antiguos permanecen en el almacén de objetos hasta que son recolectados por el recolector de basura.

Por eso hacer force-push de historial reescrito a una rama compartida es destructivo: las ramas locales de los colaboradores aún apuntan a la cadena de commits antigua.

Ramas: Punteros Ligeros

Una rama no es más que un archivo de 41 bytes que contiene un hash SHA-1. Crear una rama es instantáneo independientemente del tamaño del repositorio porque Git solo escribe un archivo pequeño.

git branch feature/auth           # Create branch at current HEAD
git checkout -b feature/auth      # Create and switch in one step
git switch -c feature/auth        # Modern equivalent (Git 2.23+)
git branch -d feature/auth        # Delete (safe: refuses if unmerged)
git branch -D feature/auth        # Delete (force: regardless of merge status)

Internos de las Ramas

cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0

Cuando haces un commit en una rama, Git escribe el nuevo SHA del commit en este archivo. Eso es todo lo que significa “avanzar un puntero de rama”.

Ramas de Seguimiento y Configuración Upstream

Una relación de seguimiento le dice a Git con qué rama remota debe comparar una rama local para el reporte de divergencia git status y el comportamiento de git pull.

git branch --set-upstream-to=origin/main main
git branch -vv    # Show tracking relationships and ahead/behind counts

Etiquetas: Marcadores Permanentes en el Historial

Las etiquetas marcan commits específicos como significativos — típicamente versiones de software. A diferencia de las ramas, las etiquetas no se mueven con los nuevos commits.

CaracterísticaEtiqueta LigeraEtiqueta Anotada
AlmacenamientoUn archivo ref apuntando a un commitUn objeto etiqueta en el almacén de objetos
MetadatosNingunoNombre del tagger, email, fecha, mensaje
Firma GPGNo es posibleCompatible mediante git tag -s
Recomendado para versionesNo
Transferencia con git push --tags
git tag v2.1.0                              # Lightweight tag at HEAD
git tag -a v2.1.0 -m "Release 2.1.0"       # Annotated tag
git tag -s v2.1.0 -m "Signed release"      # GPG-signed annotated tag
git push origin --tags                      # Push all tags to remote
git push origin v2.1.0                      # Push a specific tag

Error crítico: git push no envía etiquetas por defecto. Los equipos frecuentemente olvidan esto y publican notas de versión que hacen referencia a una etiqueta que no existe en el remoto.

Remotos: Colaboración Distribuida

Un remoto es una URL con nombre almacenada en .git/config. Las ramas de seguimiento remoto (bajo refs/remotes/) son instantáneas locales de solo lectura de las ramas del remoto, actualizadas solo cuando haces fetch explícitamente.

git remote add origin git@github.com:user/repo.git
git remote -v                          # List remotes with URLs
git remote set-url origin <new-url>    # Change a remote URL
git fetch origin                       # Update remote-tracking branches
git fetch --prune                      # Remove stale remote-tracking branches
git push origin main                   # Push local main to remote
git push -u origin feature/auth        # Push and set upstream tracking

Múltiples Remotos

Un único repositorio puede rastrear múltiples remotos — común cuando se mantiene un fork junto al upstream:

git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/main

Cuando alojas repositorios bare en un servidor dedicado para tu equipo, cada desarrollador agrega el servidor como remoto y usa autenticación por clave SSH para acceso de push.

Hooks: Automatización en Cada Evento de Git

Los hooks son scripts ejecutables en .git/hooks/. Git los llama en puntos definidos del flujo de trabajo. No se transfieren mediante git clone o git push — cada desarrollador (o servidor) debe instalarlos de forma independiente. Esta es una fuente frecuente de confusión en entornos de equipo.

Hooks del Lado del Cliente

HookDisparadorUso Común
pre-commitAntes del prompt del mensaje de commitLinting, escaneo de secretos, ejecución de pruebas
prepare-commit-msgDespués de crear el mensaje predeterminadoInyectar nombre de rama en el mensaje
commit-msgDespués de que el usuario escribe el mensajeAplicar formato de commit convencional
post-commitDespués de registrar el commitNotificaciones locales
pre-pushAntes de que se ejecute git pushEjecutar suite completa de pruebas
pre-rebaseAntes de que comience el rebasePrevenir rebase de ramas publicadas

Hooks del Lado del Servidor

HookDisparadorUso Común
pre-receiveAntes de que se actualicen las refsAplicar protección de ramas, rechazar force-push
updatePor ref durante la recepciónAplicación de políticas por rama
post-receiveDespués de actualizar todas las refsDisparar CI/CD, enviar notificaciones

Ejemplo: Hook Pre-commit para Detección de Secretos

#!/usr/bin/env bash
# .git/hooks/pre-commit

if git diff --cached --name-only | xargs grep -lE '(AKIA|passwords*=|api_keys*=)' 2>/dev/null; then
    echo "ERROR: Potential secret detected in staged files. Commit aborted."
    exit 1
fi
exit 0

Hazlo ejecutable:

chmod +x .git/hooks/pre-commit

Para la distribución de hooks en todo el equipo, usa una herramienta como Husky (proyectos Node.js) o almacena los hooks en un directorio hooks/ en la raíz del repositorio y crea enlaces simbólicos durante la configuración del proyecto.

Reflog: La Red de Seguridad

El reflog registra cada movimiento de HEAD y los punteros de rama, incluyendo operaciones que parecen destruir el historial (resets duros, rebases, commits enmendados). Se almacena en .git/logs/.

git reflog                          # Show HEAD movement history
git reflog show main                # Show movement history for a specific branch
git checkout HEAD@{3}               # Check out the state HEAD was in 3 moves ago
git branch recovered HEAD@{5}       # Recover commits by branching from a reflog entry

Las entradas del reflog expiran después de 90 días por defecto (gc.reflogExpire). En un servidor de producción, considera extender esto:

git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30

Repositorios Bare: Alojamiento del Lado del Servidor

Un repositorio bare no tiene directorio de trabajo. Contiene solo el contenido de .git/ en el nivel raíz. Los repositorios bare son el formato correcto para el alojamiento centralizado — aceptan pushes sin las complicaciones de una rama con checkout.

git init --bare /srv/repos/myproject.git

Cuando haces push a GitHub, GitLab o un servidor Git autoalojado, estás haciendo push a un repositorio bare. Si alojas tu propio servidor Git en un VPS con cPanel o un VPS Linux sin configuración adicional, los repositorios bare bajo /srv/repos/ con acceso SSH son la arquitectura estándar.

Inicializando un Repositorio Bare Compartido

# On the server
git init --bare --shared=group /srv/repos/project.git
chown -R git:developers /srv/repos/project.git

# On a developer's machine
git remote add origin git@yourserver.com:/srv/repos/project.git
git push -u origin main

Almacenamiento de Objetos Git: Tamaño, Integridad y Mantenimiento

Verificando la Salud del Repositorio

git fsck --full          # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found    # Write dangling objects to .git/lost-found/

Encontrando y Eliminando Objetos Grandes

Los archivos binarios grandes confirmados accidentalmente son una causa común de repositorios inflados. Identifícalos antes de usar git filter-repo para eliminarlos:

# Find the 10 largest objects by compressed size
git verify-pack -v .git/objects/pack/*.idx 
  | sort -k3 -rn 
  | head -10 
  | awk '{print $1}' 
  | xargs -I{} git cat-file -p {}
# Remove a file from all history (requires git-filter-repo)
git filter-repo --path path/to/large-file.bin --invert-paths

Después del filtrado, todos los colaboradores deben volver a clonar — sus repositorios locales hacen referencia a hashes SHA que ya no existen en el historial reescrito.

Comparación: Conceptos Clave del Repositorio Git

ConceptoTipoMutableAlmacenado EnTransferido por Push/Fetch
BlobObjetoNo.git/objects/Sí (cuando es accesible)
TreeObjetoNo.git/objects/Sí (cuando es accesible)
CommitObjetoNo.git/objects/Sí (cuando es accesible)
Etiqueta AnotadaObjetoNo.git/objects/Solo con --tags
RamaRef.git/refs/heads/
Rama de seguimiento remotoRefSí (en fetch).git/refs/remotes/No (caché local)
Etiqueta LigeraRefNo.git/refs/tags/Solo con --tags
HEADSymref/hash.git/HEADNo
ÍndiceArchivo binario.git/indexNo
HooksScripts.git/hooks/No
ReflogLogSí (expira automáticamente).git/logs/No

Matriz de Decisión Práctica y Conclusiones Clave

Usa esta lista de verificación al configurar o auditar un repositorio Git en tu infraestructura:

Inicialización del repositorio

  • Usa git init --bare --shared=group para cualquier repositorio que recibirá pushes de múltiples usuarios.
  • Almacena repositorios bare fuera de directorios accesibles desde la web (nunca bajo /var/www/).

Salud del almacén de objetos

  • Ejecuta git fsck --full después de cualquier incidente de almacenamiento o error del sistema de archivos.
  • Programa git gc periódicamente en repositorios de larga duración; automatízalo mediante cron en tu servidor.
  • Monitorea el tamaño de los archivos pack con git count-objects -vH; investiga si el recuento de objetos sueltos supera 1.000.

Higiene de ramas y refs

  • Elimina las ramas fusionadas rápidamente; las refs obsoletas se acumulan y ralentizan las operaciones de git fetch --prune.
  • Usa git fetch --prune en pipelines de CI para evitar actuar sobre ramas remotas eliminadas.

Despliegue de hooks

  • Nunca dependas de .git/hooks/ para políticas de todo el equipo — los hooks no se clonan. Usa hooks pre-receive del lado del servidor o una puerta de CI en su lugar.
  • Audita los hooks del lado del servidor después de cada actualización del servidor Git; las rutas del intérprete de hooks pueden cambiar.

Seguridad en servidores autoalojados

  • Restringe el acceso SSH al usuario git con comandos forzados (command= en authorized_keys).
  • Usa git-shell como shell de inicio de sesión para el usuario git para prevenir la ejecución de comandos arbitrarios.
  • Combina tu servidor de repositorios con un certificado SSL válido si expones alguna interfaz web (Gitea, GitLab, cgit).

Reescritura del historial

  • Nunca reescribas el historial en ramas compartidas con otros sin un plan de migración coordinado.
  • Después de git filter-repo, todos los colaboradores deben volver a clonar; actualiza las URLs remotas de CI/CD inmediatamente.

Recuperación ante desastres

  • Extiende la expiración del reflog en servidores de producción (gc.reflogExpire = 180).
  • Mantén un clon bare secundario en un host separado como respaldo; un simple git fetch desde el primario es suficiente.

Preguntas Frecuentes

¿Cuál es la diferencia entre un repositorio Git bare y uno no bare?

Un repositorio no bare tiene un directorio de trabajo donde se hace checkout de los archivos, más un subdirectorio .git/ que contiene el almacén de objetos. Un repositorio bare contiene solo el almacén de objetos en su raíz (sin directorio de trabajo) y es el formato correcto para un servidor compartido que recibe pushes.

¿Puedo recuperar commits después de ejecutar git reset --hard?

Sí, siempre que los commits no hayan sido recolectados por el recolector de basura. Ejecuta git reflog para encontrar el SHA del commit que deseas recuperar, luego git checkout -b recovery-branch <SHA> para adjuntarlo a una nueva rama. Las entradas del reflog se retienen durante 90 días por defecto.

¿Por qué git push no transfiere mis etiquetas?

Por diseño, git push solo transfiere commits accesibles desde las refs que explícitamente envías. Las etiquetas son refs separadas y deben enviarse con git push origin --tags (todas las etiquetas) o git push origin <tagname> (una etiqueta específica).

¿Qué sucede con el índice durante un conflicto de fusión?

El índice almacena las tres versiones de cada archivo en conflicto simultáneamente: etapa 1 (ancestro común/base), etapa 2 (tu versión) y etapa 3 (su versión). El git add normal solo escribe la etapa 0 (resuelta). Hasta que todos los conflictos estén resueltos y preparados, git commit se negará a continuar.

¿Cómo difieren los hooks de Git entre despliegues del lado del cliente y del servidor?

Los hooks del lado del cliente se ejecutan en la máquina del desarrollador y no se aplican de forma centralizada — cualquier desarrollador puede omitirlos eliminando el archivo del hook. Los hooks del lado del servidor (pre-receive, update, post-receive) se ejecutan en el servidor de alojamiento y no pueden ser omitidos por el cliente, lo que los convierte en el punto de aplicación correcto para políticas de protección de ramas, requisitos de revisión de código y disparadores de CI/CD.

Administración Linux
Administración Linux Servidores virtuales
Linux Seguridad