SincroDev Logo SincroDev

El Viaje de un Commit Git: Del staging al repositorio remoto


Modificas un archivo, ejecutas git add, luego git commit, y finalmente git push. Tu código ahora está en GitHub. Pero, ¿qué pasó realmente? Git no es magia: es un sistema de archivos direccionado por contenido con un modelo de datos elegante. Veamos el viaje.

Capa 1: El Archivo Modificado

Empiezas editando un archivo:

echo "console.log('Hola Mundo');" > app.js

Git detecta el cambio en tu Working Directory (directorio de trabajo):

$ git status
Changes not staged for commit:
  modified:   app.js

En este punto, Git sabe que algo cambió, pero no ha guardado nada. El archivo solo existe en tu sistema de archivos.

┌─────────────────────────────────────────────────────────┐
│                  WORKING DIRECTORY                       │
│                                                         │
│   app.js (modificado)                                   │
│   └── "console.log('Hola Mundo');"                      │
│                                                         │
│   Git detecta: "Este archivo es diferente al último     │
│                 commit"                                 │
│                                                         │
└─────────────────────────────────────────────────────────┘

Capa 2: git add - El Staging Area

Ejecutas git add app.js. Aquí empieza la magia:

Git crea un Blob

Git comprime el contenido del archivo y calcula su SHA-1 (hash de 40 caracteres):

Contenido: "console.log('Hola Mundo');\n"

     ▼ zlib compress + SHA-1

Hash: 8ab686eafeb1f44702738c8b0f24f2567c36da6d

Este blob (binary large object) se guarda en .git/objects/:

.git/objects/
    └── 8a/
        └── b686eafeb1f44702738c8b0f24f2567c36da6d

Nota: Git divide el hash: primeros 2 caracteres = directorio, resto = nombre de archivo. Esto evita tener millones de archivos en un solo directorio.

Se actualiza el Index

El Index (staging area) es un archivo binario en .git/index. Guarda:

  • Ruta del archivo (app.js)
  • Hash del blob (8ab686e...)
  • Metadatos (permisos, timestamps)
┌─────────────────────────────────────────────────────────┐
│                    STAGING AREA                          │
│                    (.git/index)                          │
│                                                         │
│   ┌─────────────────────────────────────────────────┐   │
│   │ app.js → 8ab686eafeb1f44702738c8b0f24f2567c36da │   │
│   │ mode: 100644 (archivo normal)                   │   │
│   └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

.git/objects/
    └── 8a/b686... (blob con el contenido)

Capa 3: git commit - Creando el Snapshot

Ejecutas git commit -m "Add hello world". Git crea tres tipos de objetos:

1. Tree (Árbol)

Un tree representa un directorio. Lista archivos y subdirectorios con sus hashes:

tree 9f4d...
├── 100644 blob 8ab686... app.js
├── 100644 blob 3c4e21... README.md
└── 040000 tree a1b2c3... src/
                          ├── 100644 blob d4e5f6... index.js
                          └── 100644 blob e5f6a7... utils.js

2. Commit

El commit es un objeto que apunta a:

  • El tree raíz del proyecto
  • El commit padre (el anterior)
  • Autor y committer (pueden ser diferentes)
  • Mensaje del commit
  • Timestamp
commit 7c8f9a...
├── tree 9f4d...
├── parent 1a2b3c... (commit anterior)
├── author Walter <walter@ejemplo.com> 1706612400 +0100
├── committer Walter <walter@ejemplo.com> 1706612400 +0100
└── message "Add hello world"

El grafo de objetos

┌─────────────────────────────────────────────────────────┐
│                     .git/objects/                        │
│                                                         │
│   ┌──────────┐         ┌──────────┐                     │
│   │ commit   │────────→│  tree    │                     │
│   │ 7c8f9a.. │         │ 9f4d...  │                     │
│   └────┬─────┘         └────┬─────┘                     │
│        │                    │                           │
│        │ parent             ├── app.js → blob 8ab6...   │
│        ▼                    ├── README → blob 3c4e...   │
│   ┌──────────┐              └── src/ → tree a1b2...     │
│   │ commit   │                              │           │
│   │ 1a2b3c.. │                              ├── blob    │
│   │ (previo) │                              └── blob    │
│   └──────────┘                                          │
│                                                         │
└─────────────────────────────────────────────────────────┘

Actualización de referencias

Git actualiza .git/refs/heads/main (o tu rama actual):

$ cat .git/refs/heads/main
7c8f9a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f

La rama es simplemente un puntero a un commit.

┌─────────────────────────────────────────────────────────┐
│                      REFERENCIAS                         │
│                                                         │
│   .git/HEAD                                             │
│   └── ref: refs/heads/main                              │
│                                                         │
│   .git/refs/heads/main                                  │
│   └── 7c8f9a... (nuevo commit)                          │
│                                                         │
│   .git/refs/heads/feature                               │
│   └── 5d6e7f... (otro commit)                           │
│                                                         │
└─────────────────────────────────────────────────────────┘

Capa 4: git push - Preparando la Transferencia

Ejecutas git push origin main. Git necesita:

  1. Determinar qué enviar: ¿Qué commits tiene el remoto? ¿Cuáles me faltan?
  2. Empaquetar objetos: Crear un packfile eficiente
  3. Enviar por red: Usando el protocolo Git

Negociación

Git “negocia” con el servidor:

Cliente                              Servidor (GitHub)
   │                                      │
   │ ── "Quiero pushear a main" ─────────→│
   │                                      │
   │ ←── "Mi main está en abc123..." ─────│
   │                                      │
   │     (Cliente calcula diferencia)     │
   │     "Debo enviar commits 7c8f9a,     │
   │      6b7c8d, 5a6b7c"                 │
   │                                      │

Packfile

Git crea un packfile: un archivo comprimido con todos los objetos necesarios.

┌─────────────────────────────────────────────────────────┐
│                      PACKFILE                            │
│                                                         │
│   Header: PACK + version + num_objects                  │
│                                                         │
│   ┌─────────────────────────────────────────────────┐   │
│   │ Object 1: commit 7c8f9a (deltified)             │   │
│   │ Object 2: tree 9f4d... (deltified)              │   │
│   │ Object 3: blob 8ab6... (base)                   │   │
│   │ Object 4: blob 3c4e... (delta de otro blob)     │   │
│   │ ...                                             │   │
│   └─────────────────────────────────────────────────┘   │
│                                                         │
│   Checksum SHA-1 del packfile                           │
│                                                         │
└─────────────────────────────────────────────────────────┘

Deltificación: Git almacena objetos similares como “deltas” (diferencias). Un archivo con un pequeño cambio se guarda como “el blob X más estos bytes diferentes”.

Capa 5: El Protocolo de Red

Git puede usar varios protocolos:

ProtocoloURLPuertoUso
HTTPShttps://github.com/...443Más común, requiere auth
SSHgit@github.com:...22Usa llaves SSH
Gitgit://github.com/...9418Solo lectura, sin auth

Flujo HTTPS (simplificado)

Cliente                              GitHub
   │                                    │
   │ ── POST /repo.git/git-receive-pack │
   │    Headers: Authorization          │
   │    Body: capabilities + refs       │
   │                                    │
   │ ←── 200 OK                         │
   │     "Listo para recibir"           │
   │                                    │
   │ ── POST (packfile binario) ───────→│
   │                                    │
   │ ←── 200 OK                         │
   │     "unpack ok"                    │
   │     "refs/heads/main: ok"          │
   │                                    │

El packfile viaja

┌─────────┐         HTTPS/SSH          ┌─────────┐
│ Cliente │ ─── [PACKFILE binario] ───→│ GitHub  │
│         │         ~50KB              │         │
└─────────┘      (comprimido)          └─────────┘

Capa 6: El Servidor Recibe

GitHub (o tu servidor Git) procesa el push:

1. Verificación

┌─────────────────────────────────────────────────────────┐
│                    GITHUB SERVER                         │
│                                                         │
│   1. ¿Usuario autenticado? ✓                            │
│   2. ¿Tiene permisos de push? ✓                         │
│   3. ¿Es fast-forward? (no reescribe historia)          │
│      - Si main remoto es ancestro del nuevo → ✓         │
│      - Si no → rechazar (o --force)                     │
│                                                         │
└─────────────────────────────────────────────────────────┘

2. Hooks (pre-receive)

Antes de aceptar, ejecuta hooks:

# hooks/pre-receive (en el servidor)
# Puede rechazar el push por políticas:
# - No permitir push a main directo
# - Verificar que commits estén firmados
# - Validar formato de mensajes

3. Desempaquetar

Packfile recibido


┌─────────────────────────────────────────────────────────┐
│   Descomprimir objetos                                  │
│       │                                                 │
│       ▼                                                 │
│   Verificar integridad (SHA-1 de cada objeto)           │
│       │                                                 │
│       ▼                                                 │
│   Almacenar en .git/objects/                            │
│       │                                                 │
│       ▼                                                 │
│   Actualizar refs/heads/main                            │
└─────────────────────────────────────────────────────────┘

4. Hooks (post-receive)

Después de aceptar:

# hooks/post-receive
# - Notificar a CI/CD (GitHub Actions)
# - Enviar webhooks
# - Actualizar caches
# - Trigger deploys

Capa 7: Respuesta al Cliente

GitHub                              Cliente
   │                                   │
   │ ── "ok refs/heads/main" ─────────→│
   │                                   │
   │                    Actualiza remote tracking:
   │                    origin/main = 7c8f9a...
   │                                   │
   │                    $ git push
   │                    To github.com:user/repo.git
   │                       abc123..7c8f9a main → main
   │                                   │

El Diagrama Completo

┌─────────────────────────────────────────────────────────────────────┐
│                        TU COMPUTADORA                                │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                   WORKING DIRECTORY                          │   │
│   │                                                             │   │
│   │   app.js (modificado)                                       │   │
│   └─────────────────────────────┬───────────────────────────────┘   │
│                                 │ git add                           │
│                                 ▼                                   │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                    STAGING AREA                              │   │
│   │                    (.git/index)                              │   │
│   │                                                             │   │
│   │   app.js → blob 8ab6...                                     │   │
│   └─────────────────────────────┬───────────────────────────────┘   │
│                                 │ git commit                        │
│                                 ▼                                   │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                  LOCAL REPOSITORY                            │   │
│   │                    (.git/objects)                            │   │
│   │                                                             │   │
│   │   commit 7c8f → tree 9f4d → blob 8ab6 (app.js)              │   │
│   │      │                    → blob 3c4e (README)               │   │
│   │      │ parent                                                │   │
│   │      ▼                                                       │   │
│   │   commit 1a2b (anterior)                                     │   │
│   │                                                             │   │
│   │   refs/heads/main → 7c8f...                                 │   │
│   └─────────────────────────────┬───────────────────────────────┘   │
│                                 │ git push                          │
│                                 ▼                                   │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                    PACK & SEND                               │   │
│   │                                                             │   │
│   │   1. Negociar con remoto (¿qué objetos faltan?)             │   │
│   │   2. Crear packfile (commits + trees + blobs)               │   │
│   │   3. Comprimir (zlib + deltificación)                       │   │
│   │   4. Enviar por HTTPS/SSH                                   │   │
│   └─────────────────────────────┬───────────────────────────────┘   │
│                                 │                                   │
└─────────────────────────────────┼───────────────────────────────────┘
                                  │ HTTPS/SSH

┌─────────────────────────────────────────────────────────────────────┐
│                           GITHUB                                     │
│                                                                     │
│   1. Autenticar usuario                                             │
│   2. Verificar permisos                                             │
│   3. Ejecutar pre-receive hooks                                     │
│   4. Desempaquetar objetos                                          │
│   5. Verificar integridad (SHA-1)                                   │
│   6. Actualizar refs/heads/main                                     │
│   7. Ejecutar post-receive hooks                                    │
│      └── Trigger GitHub Actions, webhooks, etc.                     │
│                                                                     │
│   refs/heads/main → 7c8f...                                        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

¿Cuánto tiempo toma cada paso?

PasoTiempo típico
git add (archivo pequeño)< 10ms
git add (muchos archivos)100ms - 1s
git commit10-50ms
Crear packfile50-500ms
Transferencia red (1MB)100ms - 2s
Servidor procesa100-500ms
Total push típico500ms - 5s

Repositorios grandes (Linux kernel: 1GB+) pueden tomar minutos en el primer clone, pero pushes incrementales siguen siendo rápidos.

Herramientas para inspeccionar

# Ver el contenido de un objeto
git cat-file -p 7c8f9a2

# Ver tipo de objeto
git cat-file -t 7c8f9a2

# Ver todos los objetos
find .git/objects -type f

# Ver el log con hashes completos
git log --format="%H %s"

# Ver qué se enviaría en un push
git push --dry-run -v origin main

# Ver el contenido del index
git ls-files --stage

# Ver diferencia de objetos con remoto
git rev-list origin/main..main

# Inspeccionar un packfile
git verify-pack -v .git/objects/pack/*.idx

Conclusión

Un simple git commit && git push:

  1. Staging: Git hashea el contenido y crea blobs
  2. Index: Actualiza el mapeo ruta → hash
  3. Commit: Crea tree + commit object, actualiza refs
  4. Pack: Empaqueta objetos nuevos con compresión delta
  5. Transfer: Negocia con el servidor, envía el packfile
  6. Server: Verifica, desempaqueta, actualiza refs, ejecuta hooks
  7. Done: Tu código está en el remoto

Git es fundamentalmente un sistema de archivos direccionado por contenido. Cada archivo, directorio y commit es un objeto identificado por su hash SHA-1. Esto hace que sea imposible modificar historia sin cambiar los hashes, y permite verificación de integridad en cada paso.


Este post es parte de la serie “El Viaje de la Información”, donde exploramos qué sucede realmente cuando interactuamos con nuestros sistemas.