LFS_CONCEPTUAL_ARCHITECTURE.md¶
Git LFS: La Arquitectura de Dos Mundos¶
Filosofía y Pensamiento Detrás del Large File Storage¶
🎯 PROPÓSITO DE ESTE DOCUMENTO¶
Este documento explora la arquitectura conceptual de Git LFS, no como una herramienta técnica, sino como una solución filosófica a un problema ontológico: el choque entre la naturaleza de Git (diseñado para texto delta-comprimible) y la realidad de archivos grandes binarios (incompresibles, inmutables, voluminosos).
NO encontrarás aquí: - Comandos de instalación - Sintaxis de configuración - Procedimientos paso a paso
SÍ encontrarás: - La naturaleza del problema que LFS resuelve - La arquitectura conceptual de la solución de dos mundos - Patrones de pensamiento para clasificar assets - El ciclo de vida completo de un archivo LFS - Trade-offs y decisiones estratégicas - La estrategia específica para AudioLab
📖 ÍNDICE¶
CAPÍTULO 1: El Problema Ontológico del Tamaño¶
1.1 El Modelo Mental de Git: Todo es Delta 1.2 La Paradoja del Archivo Binario Grande 1.3 Taxonomía de Archivos: ¿Qué es "Grande"? 1.4 Síntomas de un Repositorio Enfermo
CAPÍTULO 2: Arquitectura de Dos Mundos¶
2.1 El Concepto del Pointer File 2.2 Content-Addressable Storage (CAS) Externo 2.3 El Triángulo de Actores: Local/Repo/Storage 2.4 Flujo de Datos en Push/Pull
CAPÍTULO 3: Patrones de Tracking¶
3.1 .gitattributes como Constitución 2.2 Matriz de Decisión: ¿Qué Trackear con LFS? 3.3 Patrones por Tipo de Proyecto 3.4 El Anti-Pattern del "Track Everything"
CAPÍTULO 4: Ciclo de Vida de un Asset¶
4.1 Las 7 Fases del Viaje 4.2 Visualización de Estados 4.3 Momento de Decisión: Commit vs Upload 4.4 Garbage Collection y Prune
CAPÍTULO 5: Trade-offs y Decisiones¶
5.1 Beneficios: Qué Ganas 5.2 Limitaciones: Qué Pierdes 5.3 Matriz de Costos 5.4 Cuándo NO Usar LFS
CAPÍTULO 6: Estrategia AudioLab¶
6.1 Clasificación de Assets en AudioLab 6.2 Umbrales y Políticas 6.3 Workflow de Audio Samples 6.4 Híbridos: Artifacts de Build
CAPÍTULO 7: Alternativas y Complementos¶
7.1 Git Annex: El Hermano Mayor 7.2 DVC: Data Version Control 7.3 Submodules: La Estrategia de Separación 7.4 Paisaje del Ecosistema
CAPÍTULO 1: El Problema Ontológico del Tamaño¶
1.1 El Modelo Mental de Git: Todo es Delta¶
Git fue diseñado con un supuesto fundamental sobre la naturaleza de los archivos que versiona:
┌─────────────────────────────────────────────────────────────┐
│ SUPUESTO FUNDAMENTAL DE GIT │
├─────────────────────────────────────────────────────────────┤
│ │
│ Los archivos son MAYORMENTE TEXTO. │
│ El texto cambia por LÍNEAS. │
│ Las líneas cambian INCREMENTALMENTE. │
│ Los deltas son COMPRIMIBLES. │
│ │
│ Por lo tanto: │
│ Almacenar N versiones ≈ Almacenar 1 versión + deltas │
│ │
└─────────────────────────────────────────────────────────────┘
Esto funciona BRILLANTEMENTE para código fuente:
Versión 1 del archivo "compressor.cpp":
┌──────────────────────────────┐
│ float compress(float input) {│ → 1.2 KB
│ return input * 0.5; │
│ } │
└──────────────────────────────┘
Versión 2 del archivo (cambio menor):
┌──────────────────────────────┐
│ float compress(float input) {│ → 1.3 KB original
│ float ratio = 0.5; │ → 0.2 KB delta
│ return input * ratio; │
│ } │
└──────────────────────────────┘
Almacenamiento total: 1.2 KB + 0.2 KB = 1.4 KB
El modelo mental de Git:
HISTORIA = SNAPSHOT INICIAL + CADENA DE TRANSFORMACIONES
v1 v2 v3 v4
●────────────●───────────────●───────────────●
[full] [+delta] [+delta] [+delta]
1.2 KB +0.2 KB +0.1 KB +0.3 KB
Total stored: 1.8 KB para 4 versiones completas
Compression ratio: ~3x
Esta arquitectura asume que: - Los cambios son locales (algunas líneas) - Los deltas son pequeños comparados con el archivo completo - La compresión es efectiva (texto se comprime bien) - El historial es navegable (puedes reconstruir cualquier versión)
1.2 La Paradoja del Archivo Binario Grande¶
Ahora introduce un archivo binario grande en este modelo:
piano_sample.wav (50 MB)
┌─────────────────────────────────────────────────────────────┐
│ 01001110 10101010 11001100 01010101 10101010 11110000 ... │
│ [50 millones de bytes de datos de audio PCM] │
└─────────────────────────────────────────────────────────────┘
Características del archivo:
✗ NO es texto
✗ NO tiene "líneas"
✗ Cambios NO son incrementales
✗ Datos NO son comprimibles (ya está en formato eficiente)
¿Qué pasa cuando haces un cambio "menor" al archivo?
CAMBIO: Ajustar el volumen del sample en 1 dB
Versión 1: Versión 2:
piano_sample.wav (50 MB) piano_sample.wav (50 MB)
┌──────────────────────┐ ┌──────────────────────┐
│ [muestra 1]: 0.532 │ │ [muestra 1]: 0.597 │
│ [muestra 2]: 0.127 │ │ [muestra 2]: 0.142 │
│ [muestra 3]: -0.231 │ → │ [muestra 3]: -0.259 │
│ ... │ │ ... │
│ [muestra N]: 0.891 │ │ [muestra N]: 1.000 │
└──────────────────────┘ └──────────────────────┘
CADA BYTE del archivo cambió (multiplicación por constante).
Delta size: ~50 MB
Original size: 50 MB
Compression ratio: 1x (ninguna compresión)
La paradoja:
┌─────────────────────────────────────────────────────────────┐
│ PARADOJA DEL ARCHIVO BINARIO │
├─────────────────────────────────────────────────────────────┤
│ │
│ Cambio semántico: MINÚSCULO (+1 dB de volumen) │
│ Cambio binario: MASIVO (50 MB completos) │
│ │
│ Para Git, es como si hubieras reescrito el archivo │
│ completo desde cero. │
│ │
└─────────────────────────────────────────────────────────────┘
Resultado en el repositorio:
Commit 1: piano_sample.wav v1 → +50 MB
Commit 2: piano_sample.wav v2 → +50 MB
Commit 3: piano_sample.wav v3 → +50 MB
Commit 4: piano_sample.wav v4 → +50 MB
────────
Total repo size: 200 MB para 4 versiones de un solo archivo
Git no puede aplicar su modelo de deltas porque: 1. No hay estructura de líneas que referenciar 2. Los cambios son globales (afectan todo el archivo) 3. No hay compresión efectiva (datos ya optimizados) 4. Cada versión es opaca (binario incomprensible)
1.3 Taxonomía de Archivos: ¿Qué es "Grande"?¶
No todos los archivos son iguales. Necesitamos una taxonomía para clasificar:
┌─────────────────────────────────────────────────────────────────────┐
│ TAXONOMÍA DE ARCHIVOS POR NATURALEZA │
└─────────────────────────────────────────────────────────────────────┘
DIMENSIÓN 1: Tamaño
─────────────────────────────────────────────────────────────
Tiny Small Medium Large Huge
< 10 KB < 100 KB < 1 MB < 100 MB > 100 MB
●───────────●───────────●───────────●───────────●
.cpp .h/.json .png/.wav .mov/.psd datasets
DIMENSIÓN 2: Formato
─────────────────────────────────────────────────────────────
Texto Plano Texto Estructurado Binario Comprimido Binario Raw
●──────────────●─────────────────────●─────────────────────●
.cpp/.txt .json/.xml/.md .zip/.png/.mp3 .wav/.raw/.dng
DIMENSIÓN 3: Mutabilidad
─────────────────────────────────────────────────────────────
Inmutable Semi-mutable Altamente mutable
●─────────────────────●──────────────────────●
Assets finales Trabajo en progreso Código fuente
(renders, releases) (WIP mockups) (.cpp, .h)
DIMENSIÓN 4: Comprimibilidad
─────────────────────────────────────────────────────────────
Alta (>70%) Media (30-70%) Baja (<30%)
●─────────────────────●──────────────────────●
Texto repetitivo Código fuente Audio/Video comprimido
logs/XML .cpp/.h .mp3/.h264
La regla de oro para decidir si un archivo es "problemático" en Git:
┌─────────────────────────────────────────────────────────────┐
│ ALGORITMO DE DECISIÓN: ¿Es este archivo problemático? │
├─────────────────────────────────────────────────────────────┤
│ │
│ PREGUNTA 1: ¿Es binario? (no texto plano) │
│ NO → Git nativo es perfecto │
│ SÍ → Continúa... │
│ │
│ PREGUNTA 2: ¿Es > 1 MB? │
│ NO → Probablemente OK en Git nativo │
│ SÍ → Continúa... │
│ │
│ PREGUNTA 3: ¿Cambiará frecuentemente? │
│ NO → Borderline (depende de cantidad) │
│ SÍ → CANDIDATO PARA LFS │
│ │
│ PREGUNTA 4: ¿Necesitas versionar TODAS las versiones? │
│ NO → Tal vez no pertenece al repo │
│ SÍ → DEFINITIVAMENTE LFS │
│ │
└─────────────────────────────────────────────────────────────┘
Ejemplos clasificados:
ARCHIVO Binario Tamaño Mutable Comprim DECISIÓN
────────────────────────────────────────────────────────────────────────
main.cpp NO 5 KB Alta Alta GIT NATIVO
config.json NO 2 KB Media Alta GIT NATIVO
logo.png SÍ 30 KB Baja N/A GIT NATIVO
piano_sample.wav SÍ 50 MB Media Baja → LFS
video_tutorial.mp4 SÍ 200 MB Baja Baja → LFS
dataset_training.zip SÍ 2 GB Baja N/A → LFS o externo
build/app.exe SÍ 15 MB N/A Media NO VERSIONAR
────────────────────────────────────────────────────────────────────────
1.4 Síntomas de un Repositorio Enfermo¶
¿Cómo saber si tu repositorio tiene "obesidad mórbida" por archivos grandes?
┌─────────────────────────────────────────────────────────────┐
│ LOS 7 SÍNTOMAS DEL REPOSITORIO ENFERMO │
└─────────────────────────────────────────────────────────────┘
SÍNTOMA 1: Clone Lento
─────────────────────────────────────────────────────────────
$ git clone https://github.com/team/project.git
Cloning into 'project'...
remote: Counting objects: 1234, done.
remote: Compressing objects: 100% (890/890), done.
Receiving objects: 12% (148/1234), 2.3 GB | 1.2 MB/s ← ☠️
NORMAL: < 1 minuto para repo de código
ENFERMO: > 10 minutos, varios GB de descarga
SÍNTOMA 2: Fetch Pesado
─────────────────────────────────────────────────────────────
$ git fetch origin
remote: Counting objects: 45, done.
remote: Compressing objects: 100% (45/45), done.
remote: Total 45 (delta 12), reused 0 (delta 0)
Unpacking objects: 100% (45/45), 450 MB, done. ← ☠️
NORMAL: Fetch de cambios < 10 MB
ENFERMO: Cada fetch descarga cientos de MB
SÍNTOMA 3: .git/ Gigante
─────────────────────────────────────────────────────────────
$ du -sh .git/
4.2G .git/ ← ☠️
$ du -sh .
4.5G . ← ☠️
La carpeta .git/ es MAYOR que el proyecto actual.
NORMAL: .git/ es ~2-5x el tamaño del working tree
ENFERMO: .git/ es 10-100x el tamaño del código
SÍNTOMA 4: Operaciones Lentas
─────────────────────────────────────────────────────────────
$ git status
[espera 30 segundos...] ← ☠️
$ git log --oneline
[espera 15 segundos...] ← ☠️
NORMAL: Comandos instantáneos (< 1 segundo)
ENFERMO: Cada operación requiere paciencia
SÍNTOMA 5: Push/Pull Dolorosos
─────────────────────────────────────────────────────────────
$ git push origin main
Counting objects: 234, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (156/156), done.
Writing objects: 23% (54/234), 1.2 GB | 800 KB/s ← ☠️
Un simple push de un commit tarda 20 minutos.
SÍNTOMA 6: CI/CD Timeout
─────────────────────────────────────────────────────────────
GitHub Actions:
┌──────────────────────────────────────────┐
│ ✗ Build failed: Timeout after 60 minutes │
│ - Checkout: 45 minutes (!!!) │ ← ☠️
│ - Build: Not reached │
└──────────────────────────────────────────┘
El CI gasta TODO su tiempo clonando el repo.
SÍNTOMA 7: Historias de Horror del Equipo
─────────────────────────────────────────────────────────────
"Dejé el git clone corriendo durante la noche"
"No puedo trabajar desde el café, el WiFi no aguanta"
"El nuevo en el equipo tardó 3 días en poder compilar"
"Tuve que comprar un disco más grande solo para el repo"
La progresión de la enfermedad:
ETAPA 1: Asintomático
├─ Repo < 100 MB
├─ Clone en < 30 segundos
└─ Nadie nota nada
ETAPA 2: Molestias Menores
├─ Repo ~ 500 MB
├─ Clone en 2-3 minutos
└─ "El repo está un poco pesado, ¿no?"
ETAPA 3: Dolor Recurrente
├─ Repo ~ 2 GB
├─ Clone en 10-15 minutos
└─ Quejas frecuentes, workarounds
ETAPA 4: Disfuncional
├─ Repo > 5 GB
├─ Clone > 30 minutos
└─ Productividad severamente afectada
ETAPA 5: Terminal
├─ Repo > 20 GB
├─ Clone medido en horas
└─ Considerando reescribir historia o migrar
Ejemplo real de AudioLab (antes de LFS):
Situación en Marzo 2024:
┌──────────────────────────────────────────────────────────┐
│ audio-lab/.git/objects/ │
├──────────────────────────────────────────────────────────┤
│ │
│ test_samples/ 1.2 GB │
│ ├─ piano_C4_v1.wav (50 MB) × 8 versiones │
│ ├─ drum_kit_v1.wav (30 MB) × 12 versiones │
│ └─ reverb_IR_v1.wav (100 MB) × 6 versiones │
│ │
│ docs/tutorial_videos/ 800 MB │
│ └─ mixing_tutorial.mp4 (200 MB) × 4 versiones │
│ │
│ benchmarks/golden_outputs/ 500 MB │
│ └─ reference_mixes/ (varios archivos WAV grandes) │
│ │
│ Total .git/ size: 2.8 GB │
│ Total working tree: 120 MB (código + configs) │
│ │
│ Ratio: .git/ es 23x más grande que el código actual │
│ │
└──────────────────────────────────────────────────────────┘
Impacto:
- Clone inicial: 18 minutos en red universitaria
- Fetch diario: 2-5 minutos (aunque no haya cambios grandes)
- CI checkout: 12 minutos (60% del tiempo total de build)
- New developer onboarding: "¿Por qué tarda tanto?"
Este es el dolor que LFS viene a resolver.
CAPÍTULO 2: Arquitectura de Dos Mundos¶
2.1 El Concepto del Pointer File¶
La idea central de LFS es separación de concerns:
┌─────────────────────────────────────────────────────────────┐
│ FILOSOFÍA CORE DE GIT LFS │
├─────────────────────────────────────────────────────────────┤
│ │
│ Git es excelente para METADATA y REFERENCIAS. │
│ Git es terrible para BINARY BLOBS grandes. │
│ │
│ Solución: │
│ - Git versiona un POINTER (metadata, 100 bytes) │
│ - LFS almacena el CONTENIDO (blob, GB) │
│ - El usuario ve el archivo completo (transparencia) │
│ │
└─────────────────────────────────────────────────────────────┘
El pointer file es un archivo de texto pequeño que vive en el repositorio Git:
ARCHIVO REAL EN DISCO:
piano_sample.wav (50 MB de audio PCM)
ARCHIVO EN EL REPO GIT:
piano_sample.wav (130 bytes de texto)
┌──────────────────────────────────────────────────┐
│ version https://git-lfs.github.com/spec/v1 │
│ oid sha256:4d7a8f9b2c1e...a3f7c9d8e6b5a2c1 │
│ size 52428800 │
└──────────────────────────────────────────────────┘
Este texto es lo que Git versiona, hace diff, comprime.
El contenido real (50 MB) vive en un storage separado.
Anatomía del pointer:
┌─────────────────────────────────────────────────────────────┐
│ ESTRUCTURA DEL POINTER FILE │
├─────────────────────────────────────────────────────────────┤
│ │
│ version https://git-lfs.github.com/spec/v1 │
│ └─ Versión del spec de LFS (v1 es la actual) │
│ │
│ oid sha256:4d7a8f9b2c1e3a5f7b9d1c3e5a7f9b1d... │
│ └─ Hash criptográfico del contenido │
│ (SHA-256, 64 caracteres hexadecimales) │
│ Sirve como: │
│ - Identificador único del blob │
│ - Verificación de integridad │
│ - Key en el content-addressable storage │
│ │
│ size 52428800 │
│ └─ Tamaño en bytes del archivo real │
│ (útil para pre-allocación, verificación) │
│ │
└─────────────────────────────────────────────────────────────┘
Ventajas del pointer:
- Deltas son baratos: Si el archivo no cambia, el pointer tampoco cambia. Delta = 0 bytes.
- Metadata es liviano: 130 bytes vs 50 MB en cada commit.
- Deduplicación automática: Mismo hash = mismo contenido = almacenar una sola vez.
- Verificación de integridad: El hash garantiza que el contenido es el correcto.
2.2 Content-Addressable Storage (CAS) Externo¶
El contenido real vive en un Content-Addressable Storage:
┌─────────────────────────────────────────────────────────────┐
│ CONTENT-ADDRESSABLE STORAGE (CAS) │
└─────────────────────────────────────────────────────────────┘
CONCEPTO:
En lugar de nombrar archivos por su PATH (ej: /samples/piano.wav),
los nombras por su CONTENIDO (el hash SHA-256).
ESTRUCTURA:
.git/lfs/objects/
├─ 4d/
│ └─ 7a/
│ └─ 4d7a8f9b2c1e3a5f7b9d1c3e5a7f9b1d... (50 MB)
├─ a3/
│ └─ f7/
│ └─ a3f7c9d8e6b5a2c1f4e8d7b6a5c4f3e2... (30 MB)
└─ ...
PROPIEDADES:
1. DEDUPLICACIÓN: Mismo contenido = mismo hash = un solo archivo
2. INMUTABILIDAD: El hash cambia si el contenido cambia
3. VERIFICACIÓN: Puedes verificar integridad recalculando el hash
4. DISTRIBUCIÓN: Fácil de distribuir/cachear/replicar
Comparación con el modelo tradicional:
MODELO TRADICIONAL (Path-based):
─────────────────────────────────────────────────────────────
filesystem/
├─ samples/
│ ├─ piano.wav ← Nombre arbitrario
│ └─ drum.wav ← Podría estar corrupto sin saberlo
└─ tests/
└─ piano_test.wav ← Podría ser duplicado de piano.wav
Problemas:
- Duplicados no detectados
- Corrupción no detectada
- Nombres cambian, contenido puede ser el mismo
MODELO CAS (Content-based):
─────────────────────────────────────────────────────────────
.git/lfs/objects/
├─ 4d7a.../ ← Hash del contenido de piano.wav
└─ a3f7.../ ← Hash del contenido de drum.wav
Si piano_test.wav tiene el mismo contenido que piano.wav:
→ Mismo hash
→ NO se almacena dos veces
→ Deduplicación automática
Si un archivo se corrompe:
→ Hash no coincide
→ Detectado instantáneamente
Visualización del flujo:
WRITE (git add piano.wav):
1. Calcular hash del contenido
piano.wav (50 MB) → SHA256 → 4d7a8f9b2c1e...
2. Almacenar en CAS
.git/lfs/objects/4d/7a/4d7a8f9b2c1e... ← 50 MB guardado
3. Crear pointer file
piano.wav (en staging) ← 130 bytes de metadata
READ (git checkout branch-with-piano):
1. Leer pointer file
piano.wav → version v1, oid 4d7a8f9b2c1e..., size 50MB
2. Buscar en CAS local
.git/lfs/objects/4d/7a/4d7a8f9b2c1e... ← ¿Existe?
3. Si existe: Copiar a working directory
Si NO existe: Descargar desde LFS server
4. Usuario ve el archivo completo
piano.wav (50 MB en working directory)
2.3 El Triángulo de Actores: Local/Repo/Storage¶
LFS introduce un tercer actor en el modelo Git:
┌─────────────────────────────────────────────────────────────┐
│ EL TRIÁNGULO DE GIT LFS │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────┐
│ LFS SERVER │
│ (Blob Storage) │
│ │
│ • GitHub LFS │
│ • GitLab LFS │
│ • Self-hosted │
│ • S3/Azure/GCS │
└─────────────────────┘
▲ ▲
│ │
PUT │ │ GET
(upload) (download)
│ │
│ └──────────────────┐
│ │
┌───────┴────────┐ ┌────────┴────────┐
│ GIT REPO │ │ LOCAL CACHE │
│ (Pointers) │◄────►│ (.git/lfs/) │
│ │ │ │
│ • GitHub │ PULL │ • Objects/ │
│ • GitLab │ PUSH │ • Tmp/ │
│ • Bitbucket │ │ │
└────────────────┘ └─────────────────┘
▲ ▲
│ │
metadata full content
(pointers) (blobs)
│ │
└────────┬───────────────┘
│
┌────────┴────────┐
│ WORKING DIR │
│ (Your files) │
│ │
│ piano.wav │
│ drum.wav │
└─────────────────┘
Roles de cada actor:
1. WORKING DIRECTORY (Tu espacio de trabajo)
─────────────────────────────────────────────────────────────
Rol: Donde trabajas día a día
Contiene: Archivos completos (50 MB piano.wav)
Ignorante de: Si el archivo es LFS o no
Desde tu perspectiva:
- Editas piano.wav en tu DAW
- Guardas cambios
- git add piano.wav
- Todo parece normal
2. GIT REPOSITORY (Control de versiones)
─────────────────────────────────────────────────────────────
Rol: Versionar la ESTRUCTURA y METADATA
Contiene: Pointer files (130 bytes)
Almacena: Historia de QUÉ archivos, CUÁNDO, POR QUIÉN
En cada commit:
commit abc123
piano.wav → pointer (oid: 4d7a..., size: 50MB)
drum.wav → pointer (oid: a3f7..., size: 30MB)
Permanece liviano, rápido, clonable.
3. LFS SERVER (Almacenamiento de blobs)
─────────────────────────────────────────────────────────────
Rol: Almacenar CONTENIDO binario grande
Contiene: Los archivos reales (50 MB, 30 MB, etc.)
Organización: Por hash (content-addressable)
Estructura:
4d7a8f9b2c1e... → [50 MB de datos de piano]
a3f7c9d8e6b5... → [30 MB de datos de drum]
Puede estar en:
- GitHub LFS (incluido en plan)
- GitLab LFS (incluido)
- AWS S3 (self-hosted)
- Azure Blob Storage
- Google Cloud Storage
4. LOCAL LFS CACHE (.git/lfs/objects/)
─────────────────────────────────────────────────────────────
Rol: Cache local de blobs descargados
Contiene: Copias de blobs del LFS server
Propósito: Evitar re-descargar, acelerar checkouts
Es como un proxy local:
- Primer checkout: Descarga desde LFS server
- Siguientes checkouts: Copia desde cache local
2.4 Flujo de Datos en Push/Pull¶
El flujo completo de datos en operaciones Git:
┌─────────────────────────────────────────────────────────────┐
│ FLUJO: GIT PUSH CON LFS │
└─────────────────────────────────────────────────────────────┘
1. USUARIO HACE CAMBIOS
┌────────────────┐
│ Working Dir │
│ piano.wav │ ← Editaste el archivo (ahora 51 MB)
└────────────────┘
2. GIT ADD
┌────────────────┐
│ $ git add piano.wav │
└────────────────┘
LFS Hook intercepta:
✓ Detecta que piano.wav está en .gitattributes
✓ Calcula nuevo hash: 9f3e2a...
✓ Almacena blob en .git/lfs/objects/9f/3e/9f3e2a...
✓ Crea nuevo pointer file en staging area
Estado:
┌────────────────┐
│ Staging Area │
│ piano.wav │ ← Pointer (oid: 9f3e2a..., size: 51MB)
└────────────────┘
┌────────────────┐
│ Local LFS │
│ 9f3e2a... │ ← Blob de 51 MB
└────────────────┘
3. GIT COMMIT
┌────────────────┐
│ $ git commit -m "Increase piano volume" │
└────────────────┘
Git guarda:
commit def456
parent: abc123
piano.wav → pointer (oid: 9f3e2a...)
Solo el pointer entra en el commit (130 bytes).
4. GIT PUSH
┌────────────────┐
│ $ git push origin main │
└────────────────┘
FASE 1: Push Git metadata
→ Git server recibe commits
→ Git server recibe pointer files
→ Rápido (solo pointers)
FASE 2: Push LFS blobs
→ LFS client escanea commits pusheados
→ Encuentra oid: 9f3e2a... (no existe en server)
→ Sube blob a LFS server
Flujo de datos:
Local LFS → Internet → LFS Server
9f3e2a... (51 MB) → [████] → 9f3e2a... (51 MB)
Progreso:
Uploading LFS objects: 100% (1/1), 51 MB | 2.3 MB/s
┌─────────────────────────────────────────────────────────────┐
│ FLUJO: GIT PULL CON LFS │
└─────────────────────────────────────────────────────────────┘
1. GIT PULL
┌────────────────┐
│ $ git pull origin main │
└────────────────┘
FASE 1: Pull Git metadata
→ Git client descarga nuevos commits
→ Git client descarga nuevos pointer files
→ Rápido (solo metadata)
Estado en .git/:
commit def456
piano.wav → pointer (oid: 9f3e2a..., size: 51MB)
2. GIT CHECKOUT (AUTOMÁTICO TRAS PULL)
Git LFS Hook intercepta:
✓ Lee pointer file de piano.wav
✓ Extrae oid: 9f3e2a...
✓ Busca en local cache (.git/lfs/objects/)
CASO A: Blob existe en cache local
─────────────────────────────────────────────────────────
✓ Copia de cache a working directory
✓ Instantáneo, sin red
.git/lfs/objects/9f/3e/9f3e2a... → piano.wav (51 MB)
CASO B: Blob NO existe en cache local
─────────────────────────────────────────────────────────
✓ Descarga desde LFS server
✓ Guarda en cache local
✓ Copia a working directory
LFS Server → .git/lfs/objects/ → piano.wav
51 MB 51 MB 51 MB
Progreso:
Downloading LFS objects: 100% (1/1), 51 MB | 3.1 MB/s
3. RESULTADO
┌────────────────┐
│ Working Dir │
│ piano.wav │ ← 51 MB, listo para usar
└────────────────┘
Usuario ve archivo completo, transparentemente.
Optimización: Shallow Clone
LFS permite clonar sin TODO el historial de blobs:
CLONE NORMAL:
$ git clone https://github.com/team/audio-lab.git
Descarga:
- Todo el historial de commits (metadata): 50 MB
- TODOS los blobs LFS de TODAS las versiones: 5 GB
Total download: 5.05 GB
Tiempo: 30 minutos
CLONE CON LFS SHALLOW:
$ git lfs install
$ git config lfs.fetchinclude "main"
$ git clone https://github.com/team/audio-lab.git
Descarga:
- Todo el historial de commits (metadata): 50 MB
- SOLO blobs de la rama actual, última versión: 200 MB
Total download: 250 MB
Tiempo: 3 minutos
Los blobs antiguos se descargan on-demand si checkouteas
commits viejos.
CAPÍTULO 3: Patrones de Tracking¶
3.1 .gitattributes como Constitución¶
El archivo .gitattributes define QUÉ archivos son trackeados con LFS:
┌─────────────────────────────────────────────────────────────┐
│ .gitattributes: LA CONSTITUCIÓN DEL REPOSITORIO │
└─────────────────────────────────────────────────────────────┘
CONCEPTO:
.gitattributes es un archivo de configuración que vive en el repo.
Define REGLAS sobre cómo Git debe tratar ciertos archivos.
Para LFS, define: "Estos patrones de archivos usan LFS".
SINTAXIS:
pattern filter=lfs diff=lfs merge=lfs -text
pattern: Glob pattern (*.wav, *.mp4, assets/*.psd)
filter=lfs: Usa el filtro LFS para clean/smudge
diff=lfs: Usa diff especial de LFS
merge=lfs: Usa merge especial de LFS
-text: Marca explícitamente como binario (no texto)
Ejemplo de .gitattributes para AudioLab:
# .gitattributes para audio-lab
# ======================================
# AUDIO SAMPLES (siempre LFS)
# ======================================
*.wav filter=lfs diff=lfs merge=lfs -text
*.aiff filter=lfs diff=lfs merge=lfs -text
*.flac filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
# ======================================
# IMPULSE RESPONSES (LFS)
# ======================================
**/impulse_responses/*.wav filter=lfs diff=lfs merge=lfs -text
**/IR/*.wav filter=lfs diff=lfs merge=lfs -text
# ======================================
# VIDEO TUTORIALS (LFS)
# ======================================
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.avi filter=lfs diff=lfs merge=lfs -text
# ======================================
# IMÁGENES GRANDES (solo si > 1MB)
# ======================================
# PNGs pequeños (icons) → Git nativo
# PNGs grandes (mockups) → LFS
docs/images/*.png filter=lfs diff=lfs merge=lfs -text
docs/mockups/*.psd filter=lfs diff=lfs merge=lfs -text
# ======================================
# DATASETS DE PRUEBA (LFS)
# ======================================
tests/golden_outputs/*.wav filter=lfs diff=lfs merge=lfs -text
benchmarks/reference_data/*.bin filter=lfs diff=lfs merge=lfs -text
# ======================================
# BINARIOS COMPILADOS (NO LFS, .gitignore)
# ======================================
# Estos NO deben versionarse en absoluto
# Están en .gitignore, no aquí
# ======================================
# ARCHIVOS DE TEXTO (Git nativo)
# ======================================
# Por defecto, todo lo demás usa Git nativo:
# *.cpp, *.h, *.json, *.md, etc.
Filosofía del archivo:
┌─────────────────────────────────────────────────────────────┐
│ PRINCIPIOS PARA .gitattributes │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. EXPLÍCITO SOBRE IMPLÍCITO │
│ - Define TODOS los tipos de archivos grandes │
│ - No dejes que Git "adivine" │
│ │
│ 2. GRANULARIDAD APROPIADA │
│ - Wildcards para tipos comunes: *.wav │
│ - Paths específicos para casos especiales │
│ │
│ 3. COMENTARIOS GENEROSOS │
│ - Explica POR QUÉ cada patrón está aquí │
│ - Ejemplos de qué archivos matchean │
│ │
│ 4. CONSERVADOR EN EXPANSIÓN │
│ - Añade tipos gradualmente │
│ - No hagas *.* filter=lfs │
│ │
│ 5. VERSIONADO │
│ - .gitattributes está en Git │
│ - Cambios son versionados y compartidos │
│ - Todo el equipo usa las mismas reglas │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 Matriz de Decisión: ¿Qué Trackear con LFS?¶
No todo archivo grande debe ir a LFS. Matriz de decisión:
┌─────────────────────────────────────────────────────────────────────┐
│ MATRIZ DE DECISIÓN: GIT NATIVO vs LFS vs NO VERSIONAR │
└─────────────────────────────────────────────────────────────────────┘
TIPO DE ARCHIVO Tamaño Muta Binario DECISIÓN RAZÓN
──────────────────────────────────────────────────────────────────────
Código fuente (.cpp) < 100KB Alta NO GIT NATIVO Diseñado para esto
Configs (.json) < 10KB Media NO GIT NATIVO Texto, pequeño
Headers (.h) < 50KB Alta NO GIT NATIVO Parte del código
Markdown docs (.md) < 500KB Media NO GIT NATIVO Texto, comprimible
──────────────────────────────────────────────────────────────────────
Audio samples (.wav) > 10MB Baja SÍ LFS Binario grande
Video tutorials (.mp4) > 50MB Baja SÍ LFS Muy grande
Impulse responses (.wav)> 1MB Baja SÍ LFS Audio de referencia
Photoshop mockups (.psd)> 5MB Media SÍ LFS Diseño versionado
──────────────────────────────────────────────────────────────────────
Binarios build (.exe) > 10MB N/A SÍ NO VERSIONAR Reproducibles
Dependencies (.dll) > 1MB N/A SÍ NO VERSIONAR Gestionados por pkg mgr
Temp files (.tmp) Var N/A Var NO VERSIONAR Temporales
Logs (.log) Var N/A NO NO VERSIONAR Efímeros
──────────────────────────────────────────────────────────────────────
Test data pequeños < 1MB Baja SÍ GIT NATIVO Aceptable en repo
Test data grandes > 10MB Baja SÍ LFS Demasiado grandes
Golden outputs > 5MB Baja SÍ LFS Referencias estables
──────────────────────────────────────────────────────────────────────
Icons (.png) < 50KB Baja SÍ GIT NATIVO Pequeños, útiles
Screenshots grandes > 2MB Baja SÍ LFS Documentación visual
Datasets ML > 100MB Baja SÍ EXTERNO Demasiado grandes para repo
──────────────────────────────────────────────────────────────────────
Árbol de decisión visual:
¿Debe estar en el repo?
│
┌───────────┴───────────┐
NO SÍ
│ │
.gitignore ¿Es archivo de texto?
│
┌───────────┴───────────┐
SÍ NO
│ │
GIT NATIVO ¿Es > 1 MB?
│
┌───────────┴───────────┐
NO SÍ
│ │
GIT NATIVO ¿Cambia frecuentemente?
(binario pequeño) │
┌───────────┴───────────┐
NO/Rara vez SÍ/A veces
│ │
LFS ¿Es > 100 MB?
(asset estable) │
┌───────────┴───────────┐
NO SÍ
│ │
LFS Considerar EXTERNO
(asset grande) (dataset, storage)
3.3 Patrones por Tipo de Proyecto¶
Diferentes proyectos necesitan diferentes configuraciones LFS:
┌─────────────────────────────────────────────────────────────┐
│ PATRÓN 1: PROYECTO DE AUDIO/MÚSICA (como AudioLab) │
└─────────────────────────────────────────────────────────────┘
Características:
- Audio samples de referencia (WAV, AIFF)
- Impulse responses para reverb
- Golden outputs de tests
- Documentación con videos
.gitattributes:
*.wav filter=lfs diff=lfs merge=lfs -text
*.aiff filter=lfs diff=lfs merge=lfs -text
*.flac filter=lfs diff=lfs merge=lfs -text
**/impulse_responses/** filter=lfs diff=lfs merge=lfs -text
tests/golden_outputs/*.wav filter=lfs diff=lfs merge=lfs -text
docs/videos/*.mp4 filter=lfs diff=lfs merge=lfs -text
Tamaño típico:
- Código: 50-100 MB
- LFS assets: 500 MB - 2 GB
- Ratio: 10:1
┌─────────────────────────────────────────────────────────────┐
│ PATRÓN 2: GAME DEVELOPMENT │
└─────────────────────────────────────────────────────────────┘
Características:
- Texturas (PNG, DDS, PSD)
- Modelos 3D (FBX, OBJ)
- Audio (WAV, OGG)
- Videos de cutscenes
.gitattributes:
*.psd filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.dds filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.obj filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
Tamaño típico:
- Código: 100-500 MB
- LFS assets: 5-50 GB
- Ratio: 50:1
┌─────────────────────────────────────────────────────────────┐
│ PATRÓN 3: MACHINE LEARNING │
└─────────────────────────────────────────────────────────────┘
Características:
- Modelos entrenados (H5, ONNX)
- Datasets pequeños de test
- Notebooks con outputs
- (Datasets grandes → storage externo)
.gitattributes:
*.h5 filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
*.pkl filter=lfs diff=lfs merge=lfs -text
tests/data/*.npy filter=lfs diff=lfs merge=lfs -text
docs/plots/*.png filter=lfs diff=lfs merge=lfs -text
Tamaño típico:
- Código: 10-50 MB
- LFS assets: 100-500 MB
- Datasets grandes: EXTERNO (S3, DVC)
┌─────────────────────────────────────────────────────────────┐
│ PATRÓN 4: DISEÑO WEB/MOBILE │
└─────────────────────────────────────────────────────────────┘
Características:
- Mockups de diseño (PSD, Sketch)
- Assets gráficos (PNG, SVG)
- Fuentes (TTF, WOFF)
- Videos de demos
.gitattributes:
*.psd filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
design/mockups/*.png filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.woff filter=lfs diff=lfs merge=lfs -text
demos/*.mp4 filter=lfs diff=lfs merge=lfs -text
Tamaño típico:
- Código: 20-100 MB
- LFS assets: 200-800 MB
- Ratio: 5:1
3.4 El Anti-Pattern del "Track Everything"¶
Error común: Poner demasiado en LFS.
┌─────────────────────────────────────────────────────────────┐
│ ⚠️ ANTI-PATTERN: TRACK EVERYTHING │
└─────────────────────────────────────────────────────────────┘
.gitattributes MALO:
* filter=lfs diff=lfs merge=lfs -text
"Pongamos TODO en LFS para estar seguros"
CONSECUENCIAS:
✗ Archivos de texto (código) van a LFS
✗ Pierdes diffs legibles de código
✗ Pierdes merge inteligente
✗ Incrementas costo de LFS storage innecesariamente
✗ Operaciones se vuelven más lentas
✗ No puedes hacer grep en el historial de código
EJEMPLO REAL:
Proyecto que puso *.json en LFS
→ Configs pequeños (2-5 KB) fueron a LFS
→ Diffs mostraban "pointer changed" en lugar del JSON
→ Debugging de cambios de config se volvió imposible
→ Tuvieron que remover de LFS y reescribir historia
Regla de oro:
┌─────────────────────────────────────────────────────────────┐
│ REGLA DE ORO │
├─────────────────────────────────────────────────────────────┤
│ │
│ Usa LFS SOLO cuando: │
│ 1. El archivo es binario (no texto plano) │
│ 2. El archivo es > 1 MB (o va a crecer) │
│ 3. El archivo DEBE estar versionado (no build artifact) │
│ │
│ Si cumple las 3 condiciones → LFS │
│ Si falta alguna → Re-evaluar │
│ │
└─────────────────────────────────────────────────────────────┘
Migración gradual:
Si ya tienes un repo con archivos grandes en Git nativo:
ESTRATEGIA DE MIGRACIÓN:
PASO 1: Identificar archivos problemáticos
$ git rev-list --objects --all | \
git cat-file --batch-check='%(objectsize) %(objectname) %(rest)' | \
sort -rn | head -20
Encuentra los 20 archivos más grandes en la historia.
PASO 2: Decidir qué migrar
- Archivos > 10 MB → Candidatos fuertes
- Archivos 1-10 MB → Evaluar caso por caso
- Archivos < 1 MB → Dejar en Git nativo
PASO 3: Migrar gradualmente
- NO reescribas toda la historia de una vez
- Migra hacia adelante: nuevos archivos grandes → LFS
- Opcionalmente, BFG Repo-Cleaner para limpiar historia
PASO 4: Actualizar .gitattributes
- Define patrones para futuros archivos
- Comunica al equipo el cambio
CAPÍTULO 4: Ciclo de Vida de un Asset¶
4.1 Las 7 Fases del Viaje¶
Un archivo LFS pasa por múltiples fases desde creación hasta eventual eliminación:
┌─────────────────────────────────────────────────────────────┐
│ LAS 7 FASES DEL CICLO DE VIDA LFS │
└─────────────────────────────────────────────────────────────┘
FASE 1: CREACIÓN
Usuario crea el archivo en working directory
FASE 2: STAGING
git add → Archivo entra en staging area como pointer
FASE 3: COMMIT
git commit → Pointer se guarda en Git, blob en cache local
FASE 4: PUSH
git push → Blob se sube a LFS server
FASE 5: DISTRIBUCIÓN
Otros clones descargan pointer (Git) y blob (LFS) on-demand
FASE 6: ACTUALIZACIÓN
Archivo se modifica → Nuevo blob, nuevo pointer
FASE 7: GARBAGE COLLECTION
Blobs antiguos se limpian del cache local (opcional)
Visualización detallada:
FASE 1: CREACIÓN
═══════════════════════════════════════════════════════════════
User Action:
- Graba un nuevo piano sample en Pro Tools
- Exporta como piano_C4.wav (50 MB)
- Lo guarda en samples/
Estado del sistema:
Working Dir:
samples/piano_C4.wav (50 MB, sin trackear)
Git:
(no sabe nada aún)
LFS:
(no sabe nada aún)
FASE 2: STAGING (git add samples/piano_C4.wav)
═══════════════════════════════════════════════════════════════
LFS Filter (Clean):
1. Lee piano_C4.wav (50 MB)
2. Calcula SHA-256: 4d7a8f9b2c1e3a5f...
3. Almacena blob en .git/lfs/objects/4d/7a/4d7a8f9b2c1e...
4. Genera pointer file:
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a8f9b2c1e3a5f...
size 52428800
5. Pasa pointer a Git staging
Estado del sistema:
Working Dir:
samples/piano_C4.wav (50 MB)
Staging Area:
samples/piano_C4.wav (130 bytes, pointer)
.git/lfs/objects/:
4d/7a/4d7a8f9b2c1e... (50 MB)
FASE 3: COMMIT (git commit -m "Add piano C4 sample")
═══════════════════════════════════════════════════════════════
Git guarda el pointer en el commit tree.
El blob permanece en .git/lfs/objects/ (cache local).
Estado del sistema:
Commit abc123:
samples/piano_C4.wav → pointer (oid: 4d7a...)
.git/lfs/objects/:
4d/7a/4d7a8f9b2c1e... (50 MB)
LFS Server:
(aún no tiene el blob)
FASE 4: PUSH (git push origin main)
═══════════════════════════════════════════════════════════════
Proceso en 2 pasos:
PASO 1: Git push (metadata)
→ Sube commit abc123 a Git server
→ Sube pointer file
→ Rápido (pocos KB)
PASO 2: LFS push (blobs)
→ LFS client identifica oid: 4d7a... no existe en server
→ Sube blob de 50 MB a LFS server
→ Progreso: Uploading LFS objects: 100% (1/1), 50 MB
Estado del sistema:
Git Server:
commit abc123
samples/piano_C4.wav → pointer (oid: 4d7a...)
LFS Server:
4d7a8f9b2c1e... → [50 MB blob]
Local:
.git/lfs/objects/4d/7a/4d7a8f9b2c1e... (50 MB)
FASE 5: DISTRIBUCIÓN (Otro developer: git clone)
═══════════════════════════════════════════════════════════════
Developer B clona el repositorio:
PASO 1: Git clone (metadata)
→ Descarga todo el historial Git
→ Descarga pointer files
PASO 2: LFS checkout (blobs)
→ LFS client lee pointers de la rama actual
→ Descarga blobs desde LFS server
→ Almacena en cache local
→ Copia a working directory
Estado en máquina de Developer B:
Working Dir:
samples/piano_C4.wav (50 MB)
.git/:
commit abc123 (con pointer)
.git/lfs/objects/:
4d/7a/4d7a8f9b2c1e... (50 MB)
FASE 6: ACTUALIZACIÓN (Developer B edita el archivo)
═══════════════════════════════════════════════════════════════
Developer B ajusta el volumen del piano:
- Abre piano_C4.wav en DAW
- Aumenta volumen en 2 dB
- Guarda (ahora 50.1 MB por metadata)
git add samples/piano_C4.wav:
→ Nuevo hash: 9f3e2a1b4c5d...
→ Nuevo blob en .git/lfs/objects/
→ Nuevo pointer
git commit -m "Increase piano volume":
→ Commit def456
→ piano_C4.wav → pointer (oid: 9f3e...)
Estado:
.git/lfs/objects/:
4d/7a/4d7a8f9b2c1e... (50 MB, versión antigua)
9f/3e/9f3e2a1b4c5d... (50.1 MB, versión nueva)
Ambos blobs coexisten en cache local.
FASE 7: GARBAGE COLLECTION (git lfs prune)
═══════════════════════════════════════════════════════════════
Con el tiempo, acumulas muchos blobs en .git/lfs/objects/.
Puedes limpiar blobs que:
- No están en la rama actual
- No están en commits recientes
- Ya están en LFS server
$ git lfs prune
→ Escanea referencias
→ Identifica blob 4d7a... no está en HEAD
→ Verifica que está en LFS server
→ ELIMINA .git/lfs/objects/4d/7a/4d7a8f9b2c1e...
Estado después de prune:
.git/lfs/objects/:
9f/3e/9f3e2a1b4c5d... (50.1 MB)
El blob antiguo se eliminó del cache local.
PERO sigue en LFS server (recuperable).
4.2 Visualización de Estados¶
Diagrama de estado completo:
┌─────────────────────────────────────────────────────────────┐
│ DIAGRAMA DE ESTADOS DE UN ARCHIVO LFS │
└─────────────────────────────────────────────────────────────┘
┌──────────────┐
│ UNTRACKED │ Archivo existe en working dir
│ (new file) │ Git no lo conoce
└──────┬───────┘
│ git add
▼
┌──────────────┐
│ STAGED │ Pointer en staging area
│ (to commit) │ Blob en .git/lfs/objects/
└──────┬───────┘
│ git commit
▼
┌──────────────┐
│ COMMITTED │ Pointer en Git tree
│ (local) │ Blob en local cache
└──────┬───────┘
│ git push
▼
┌──────────────┐
│ PUBLISHED │ Pointer en Git server
│ (remote) │ Blob en LFS server
└──────┬───────┘
│
├─────────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ MODIFIED │ │ DELETED │
│ │ │ │
└────┬─────┘ └────┬─────┘
│ │
│ git add │ git rm
▼ ▼
┌──────────┐ ┌──────────┐
│ STAGED │ │ STAGED │
│ (update) │ │ (delete) │
└──────────┘ └──────────┘
│ │
│ commit │ commit
▼ ▼
[COMMITTED] [POINTER REMOVED]
│
│ (blob permanece en LFS server)
▼
[ORPHANED BLOB]
│
│ (eventual garbage collection)
▼
[DELETED FROM SERVER]
Estados del blob en diferentes locations:
┌─────────────────────────────────────────────────────────────┐
│ MATRIZ DE PRESENCIA DEL BLOB │
└─────────────────────────────────────────────────────────────┘
Working Local Git LFS
Dir LFS Cache Server Server
────────────────────────────────────────────────────────────────
UNTRACKED ✓ ✗ ✗ ✗
STAGED ✓ ✓ ✗ ✗
COMMITTED ✓ ✓ pointer ✗
PUSHED ✓ ✓ pointer ✓
AFTER PRUNE ✓ ✗ pointer ✓
OTHER CLONE ✗→✓ ✗→✓ pointer ✓
(after checkout)
────────────────────────────────────────────────────────────────
LEYENDA:
✓ = Blob completo presente
✗ = No presente
pointer = Solo el pointer file (metadata)
✗→✓ = No presente inicialmente, se descarga on-demand
4.3 Momento de Decisión: Commit vs Upload¶
Pregunta filosófica: ¿Cuándo se "compromete" realmente el archivo?
┌─────────────────────────────────────────────────────────────┐
│ COMMIT vs UPLOAD: DOS MOMENTOS DIFERENTES │
└─────────────────────────────────────────────────────────────┘
GIT TRADICIONAL:
git commit → Archivo está "versionado"
git push → Archivo está "compartido"
Un solo concepto: el archivo completo.
GIT LFS:
git commit → POINTER está versionado
BLOB está en cache local
git push → POINTER está compartido (Git server)
BLOB está compartido (LFS server)
Dos conceptos separados: metadata vs contenido.
IMPLICACIONES:
1. PUEDES TENER commits sin blobs en el server
→ Commit está en Git server
→ Blob solo en tu máquina
→ Otros no pueden checkout ese commit
2. PUEDES TENER blobs sin commits (huérfanos)
→ Blob está en LFS server
→ Ningún commit lo referencia (borrado de historia)
→ Ocupa espacio pero no es accesible
3. ROLLBACK tiene dos dimensiones
→ Git rollback: revertir pointer
→ LFS: blob antiguo sigue en LFS server
Escenario problemático:
PROBLEMA: Commit sin Push de LFS
─────────────────────────────────────────────────────────────
Developer A:
$ git add huge_sample.wav
$ git commit -m "Add sample"
$ git push origin main
Pero... su red se cae antes de que LFS termine de subir el blob.
Estado:
Git Server: ✓ Tiene el commit con pointer
LFS Server: ✗ NO tiene el blob
Developer B:
$ git pull origin main
$ git checkout main
ERROR: smudge filter lfs failed
Error downloading object: huge_sample.wav (oid abc123...)
Developer B no puede trabajar. El repo está "roto".
SOLUCIÓN:
Developer A debe re-intentar el push:
$ git lfs push origin main --all
Esto sube los blobs faltantes al LFS server.
4.4 Garbage Collection y Prune¶
El cache local crece con el tiempo:
┌─────────────────────────────────────────────────────────────┐
│ CRECIMIENTO DEL CACHE LOCAL │
└─────────────────────────────────────────────────────────────┘
Día 1:
.git/lfs/objects/: 200 MB (versiones actuales)
Día 30:
.git/lfs/objects/: 450 MB (versiones actuales + antiguas)
Día 90:
.git/lfs/objects/: 1.2 GB (muchas versiones acumuladas)
El cache incluye:
- Blobs de la rama actual
- Blobs de ramas antiguas que exploraste
- Blobs de commits que revertiste
- Blobs de archivos que ya no existen
git lfs prune: Limpieza inteligente
$ git lfs prune
QUÉ HACE:
1. Escanea todos los commits locales recientes (últimos 10 días por defecto)
2. Identifica qué blobs son referenciados
3. Elimina blobs que:
- NO están en commits recientes
- SÍ están en LFS server (no se pierde nada)
4. Mantiene blobs que podrías necesitar pronto
OPCIONES:
$ git lfs prune --dry-run
→ Muestra qué se eliminaría sin hacerlo
$ git lfs prune --verify-remote
→ Verifica que blobs existan en server antes de borrar
$ git lfs prune --recent
→ Solo elimina blobs muy antiguos (> 10 días)
EJEMPLO:
$ git lfs prune --dry-run --verbose
prune: 5 local objects, 2 retained, 3 verified with remote
prune: Deleting objects: 100% (3/3), done.
4d7a8f9b2c1e3a5f... (50 MB) ← piano_C4.wav versión antigua
a3f7c9d8e6b5a2c1... (30 MB) ← drum_kit versión antigua
b2c4d6e8f0a1c3e5... (100 MB) ← reverb_IR versión antigua
Total reclaimed: 180 MB
Políticas de prune:
ESTRATEGIA 1: Prune agresivo (desarrollador individual)
$ git lfs prune --recent
Beneficio: Minimiza uso de disco
Riesgo: Podrías necesitar re-descargar blobs si checkouteas ramas viejas
ESTRATEGIA 2: Prune conservador (laptop con buen disco)
$ git lfs prune
Beneficio: Balance entre espacio y conveniencia
ESTRATEGIA 3: Nunca prune (CI/CD con cache)
No ejecutar prune
Beneficio: Builds rápidos (cache completo)
Costo: Más espacio en disco del runner
ESTRATEGIA 4: Prune con fetch (mantener solo rama actual)
$ git lfs prune
$ git lfs fetch --recent
Mantiene solo lo relevante de los últimos días.
CAPÍTULO 5: Trade-offs y Decisiones¶
5.1 Beneficios: Qué Ganas¶
LFS resuelve problemas reales:
┌─────────────────────────────────────────────────────────────┐
│ BENEFICIOS DE GIT LFS │
└─────────────────────────────────────────────────────────────┘
BENEFICIO 1: Repositorio Liviano
─────────────────────────────────────────────────────────────
SIN LFS:
.git/ size: 5.2 GB (código + binarios)
git clone: 25 minutos
CON LFS:
.git/ size: 150 MB (código + pointers)
git clone: 2 minutos
Mejora: 17x más pequeño, 12x más rápido
BENEFICIO 2: Clone Selectivo
─────────────────────────────────────────────────────────────
Puedes clonar sin descargar todos los blobs históricos:
$ git lfs install
$ git clone --branch main repo.git
Descarga SOLO los blobs de la rama main, versión actual.
Blobs de otras ramas: on-demand.
BENEFICIO 3: Operaciones Git Rápidas
─────────────────────────────────────────────────────────────
git status, git log, git diff: Instantáneos
No tienen que procesar GBs de blobs binarios.
BENEFICIO 4: CI/CD Eficiente
─────────────────────────────────────────────────────────────
GitHub Actions checkout: 2 minutos (antes: 20 minutos)
Más tiempo para compilar y testear, menos para clonar.
BENEFICIO 5: Deduplicación Automática
─────────────────────────────────────────────────────────────
Si piano_C4.wav (50 MB) existe en 3 ramas:
SIN LFS: 3 × 50 MB = 150 MB en .git/
CON LFS: 1 × 50 MB = 50 MB en LFS server (mismo hash)
BENEFICIO 6: Ancho de Banda Reducido
─────────────────────────────────────────────────────────────
git fetch solo descarga pointers nuevos (KB).
Blobs se descargan solo si checkouteas esa rama.
BENEFICIO 7: Integridad Verificable
─────────────────────────────────────────────────────────────
Cada blob tiene un hash SHA-256.
Si el blob se corrompe, el hash no coincide → detectado.
BENEFICIO 8: Escalabilidad
─────────────────────────────────────────────────────────────
Repo puede crecer a 100s de GB de assets sin ralentizar Git.
Cuantificación para AudioLab:
┌─────────────────────────────────────────────────────────────┐
│ AUDIOLAB: ANTES Y DESPUÉS DE LFS │
└─────────────────────────────────────────────────────────────┘
MÉTRICA ANTES (Git Nativo) DESPUÉS (LFS)
──────────────────────────────────────────────────────────────
Repo size (.git/) 2.8 GB 120 MB
Clone time (100 Mbps) 18 min 3 min
CI checkout time 12 min 2 min
git status 8 sec 0.3 sec
git log --oneline 5 sec 0.1 sec
Disk usage (developer) 3.5 GB 400 MB*
(* con solo branch actual)
IMPACTO EN WORKFLOW:
✓ New developer onboarding: 18 min → 3 min
✓ CI builds: 60% más rápidos
✓ git operations: 10-20x más rápidas
✓ Bandwidth usage: 80% reducción
5.2 Limitaciones: Qué Pierdes¶
LFS no es perfecto. Trade-offs:
┌─────────────────────────────────────────────────────────────┐
│ LIMITACIONES DE GIT LFS │
└─────────────────────────────────────────────────────────────┘
LIMITACIÓN 1: Diff No Funciona en Binarios
─────────────────────────────────────────────────────────────
$ git diff piano_C4.wav
SIN LFS (binario en Git):
Binary files differ
(no muy útil, pero al menos intentaba)
CON LFS:
diff --git a/piano_C4.wav b/piano_C4.wav
@@ -1,3 +1,3 @@
-oid sha256:4d7a8f9b...
+oid sha256:9f3e2a1b...
Solo ves que el hash cambió, no CÓMO cambió.
(De todas formas, diff de WAV no es útil.)
LIMITACIÓN 2: Merge Conflicts Más Complejos
─────────────────────────────────────────────────────────────
Si dos personas editan el mismo archivo LFS:
Git detecta conflicto en el pointer:
<<<<<<< HEAD
oid sha256:abc123...
=======
oid sha256:def456...
>>>>>>> feature-branch
¿Cuál versión es correcta? No puedes hacer "merge semántico".
Tienes que elegir una versión completa u otra.
LIMITACIÓN 3: Costo de Storage
─────────────────────────────────────────────────────────────
LFS server tiene límites:
GitHub:
- Incluido: 1 GB storage, 1 GB bandwidth/mes
- Adicional: $5/mes por 50 GB storage + bandwidth
GitLab:
- Incluido: 10 GB storage (plan gratuito)
- Adicional: Según plan
Para proyectos grandes, esto puede ser caro.
LIMITACIÓN 4: Dependencia del Server
─────────────────────────────────────────────────────────────
Si LFS server cae o es lento:
- No puedes clonar el repo (blobs faltantes)
- No puedes checkout commits que necesiten blobs
- Workflow se detiene
Con Git nativo, todo está en el repo (autonomía).
LIMITACIÓN 5: Complejidad Operacional
─────────────────────────────────────────────────────────────
Equipo necesita entender:
- Qué es LFS
- Cómo configurar .gitattributes
- Cómo diagnosticar problemas ("smudge filter failed")
- Cuándo hacer prune
Curva de aprendizaje adicional.
LIMITACIÓN 6: Compatibilidad con Tools
─────────────────────────────────────────────────────────────
Algunos tools asumen que "git clone" te da todo:
- Scripts de build antiguos
- IDEs sin soporte LFS
- CI/CD pipelines que no configuran LFS
Necesitas asegurar que todas las herramientas funcionen.
LIMITACIÓN 7: No Puedes "Grep" el Contenido
─────────────────────────────────────────────────────────────
$ git grep "piano" *.wav
No funciona (archivos son pointers, no contenido real).
Solo puedes grep en archivos checkoueados localmente.
LIMITACIÓN 8: Historial No es Realmente "Todo Contenido"
─────────────────────────────────────────────────────────────
Si LFS server borra blobs antiguos (garbage collection):
- Los commits existen en Git
- Pero no puedes obtener el contenido real
- Historia está "hueca"
Con Git nativo, todo está preservado mientras exista el repo.
5.3 Matriz de Costos¶
Análisis de costos real:
┌─────────────────────────────────────────────────────────────────────┐
│ MATRIZ DE COSTOS: GIT LFS EN DIFERENTES PLATAFORMAS │
└─────────────────────────────────────────────────────────────────────┘
GITHUB:
─────────────────────────────────────────────────────────────────────
Plan Gratuito:
Storage: 1 GB
Bandwidth: 1 GB/mes
Costo adicional:
$5/mes por cada "data pack" de:
- 50 GB storage
- 50 GB bandwidth/mes
Ejemplo (AudioLab con 3 GB de assets):
Necesitas: 1 paquete base + 1 data pack
Costo: $5/mes = $60/año
GITLAB:
─────────────────────────────────────────────────────────────────────
Plan Gratuito:
Storage: 10 GB (mucho más generoso)
Plan Premium:
Storage: Ilimitado
Costo: $19/usuario/mes
Ejemplo (AudioLab):
Con 3 GB de assets: Plan gratuito suficiente
Costo: $0/año
BITBUCKET:
─────────────────────────────────────────────────────────────────────
Plan Gratuito:
LFS: 1 GB storage
Plan Standard:
LFS: Soft limit 100 GB
Costo: $3/usuario/mes
Ejemplo (AudioLab):
Con 3 GB: Plan Standard
Costo: $3/mes = $36/año
SELF-HOSTED (AWS S3):
─────────────────────────────────────────────────────────────────────
S3 Pricing:
Storage: $0.023/GB/mes
Transfer out: $0.09/GB
Ejemplo (AudioLab con 3 GB, 50 clones/mes):
Storage: 3 GB × $0.023 = $0.07/mes
Transfer: 3 GB × 50 × $0.09 = $13.50/mes
Total: $13.57/mes = $163/año
(Pero tienes control total, sin límites arbitrarios)
Comparación de costo total:
ESCENARIO: AudioLab (3 GB assets, 5 developers, 100 clones/mes)
┌──────────────┬───────────────┬────────────────┬─────────────┐
│ Plataforma │ Plan │ Costo/mes │ Costo/año │
├──────────────┼───────────────┼────────────────┼─────────────┤
│ GitHub │ Free + 1 pack │ $5 │ $60 │
│ GitLab │ Free │ $0 │ $0 │
│ Bitbucket │ Standard (5u) │ $15 │ $180 │
│ Self S3 │ Custom │ ~$14 │ ~$168 │
└──────────────┴───────────────┴────────────────┴─────────────┘
GANADOR: GitLab (plan gratuito cubre 10 GB)
MEJOR VALOR: GitHub ($60/año por simplicity + integración)
5.4 Cuándo NO Usar LFS¶
LFS no es siempre la respuesta:
┌─────────────────────────────────────────────────────────────┐
│ CASOS DONDE NO DEBERÍAS USAR LFS │
└─────────────────────────────────────────────────────────────┘
CASO 1: Archivos de Build (Artifacts)
─────────────────────────────────────────────────────────────
✗ build/app.exe
✗ dist/bundle.js
✗ target/release/binary
RAZÓN: Son reproducibles desde el código fuente.
MEJOR: .gitignore + CI/CD artifacts storage
CASO 2: Datasets Masivos (> 10 GB)
─────────────────────────────────────────────────────────────
✗ training_data.zip (50 GB)
✗ video_corpus/ (200 GB)
RAZÓN: Demasiado grandes incluso para LFS.
MEJOR: DVC (Data Version Control), S3 directo, institutional storage
CASO 3: Archivos Temporales
─────────────────────────────────────────────────────────────
✗ cache/
✗ logs/
✗ .DS_Store
RAZÓN: No necesitan versionarse.
MEJOR: .gitignore
CASO 4: Dependencies Binarias
─────────────────────────────────────────────────────────────
✗ node_modules/
✗ vendor/
✗ lib/*.dll
RAZÓN: Gestionadas por package managers.
MEJOR: package.json + npm install / vcpkg / etc.
CASO 5: Archivos de Configuración Privados
─────────────────────────────────────────────────────────────
✗ .env (secrets)
✗ credentials.json
RAZÓN: No deben estar en el repo en absoluto.
MEJOR: .gitignore + documentar en README cómo crear
CASO 6: Archivos Pequeños Binarios (< 100 KB)
─────────────────────────────────────────────────────────────
✗ icon.png (30 KB)
✗ logo.svg (5 KB)
RAZÓN: Git nativo los maneja bien.
MEJOR: Git nativo (overhead de LFS no vale la pena)
CASO 7: Archivos Que Cambiarán TODO EL TIEMPO
─────────────────────────────────────────────────────────────
✗ work_in_progress.psd (cambia cada hora)
RAZÓN: LFS almacena cada versión (costo crece rápido).
MEJOR: No versionar hasta que esté "estable"
Árbol de decisión final:
¿Debe estar en control de versiones?
│
┌───────────┴───────────┐
NO SÍ
│ │
.gitignore ¿Es binario?
│
┌───────────┴───────────┐
NO SÍ
│ │
GIT NATIVO ¿Es > 1 MB?
│
┌───────────┴───────────┐
NO SÍ
│ │
GIT NATIVO ¿Es > 10 GB?
│
┌───────────┴───────────┐
NO SÍ
│ │
LFS Considerar
alternativa
(DVC, S3)
CAPÍTULO 6: Estrategia AudioLab¶
6.1 Clasificación de Assets en AudioLab¶
Política específica para el proyecto audio-lab:
┌─────────────────────────────────────────────────────────────┐
│ AUDIOLAB: CLASIFICACIÓN DE ARCHIVOS │
└─────────────────────────────────────────────────────────────┘
CATEGORÍA 1: CÓDIGO FUENTE (Git Nativo)
─────────────────────────────────────────────────────────────
✓ src/**/*.cpp
✓ include/**/*.h
✓ CMakeLists.txt
✓ scripts/*.py
Tamaño típico: 5-50 KB por archivo
Total: ~50 MB
Razón: Texto, cambia frecuentemente, diff/merge necesarios
CATEGORÍA 2: CONFIGURACIONES (Git Nativo)
─────────────────────────────────────────────────────────────
✓ config/*.json
✓ .clang-format
✓ .github/workflows/*.yml
✓ vcpkg.json
Tamaño típico: 1-10 KB
Total: ~2 MB
Razón: Texto, necesitamos ver diffs
CATEGORÍA 3: DOCUMENTACIÓN (Git Nativo)
─────────────────────────────────────────────────────────────
✓ docs/**/*.md
✓ README.md
✓ docs/diagrams/*.svg
Tamaño típico: 5-100 KB
Total: ~10 MB
Razón: Texto/SVG, cambios frecuentes
CATEGORÍA 4: AUDIO SAMPLES DE PRUEBA (LFS)
─────────────────────────────────────────────────────────────
→ tests/samples/*.wav
→ tests/samples/*.aiff
Tamaño típico: 10-50 MB por archivo
Total: ~500 MB
Razón: Binario grande, cambian raramente, usados en tests
CATEGORÍA 5: IMPULSE RESPONSES (LFS)
─────────────────────────────────────────────────────────────
→ tests/impulse_responses/*.wav
Tamaño típico: 50-200 MB por archivo
Total: ~800 MB
Razón: Binario muy grande, casi inmutables
CATEGORÍA 6: GOLDEN OUTPUTS (LFS)
─────────────────────────────────────────────────────────────
→ tests/golden_outputs/*.wav
Tamaño típico: 5-20 MB por archivo
Total: ~200 MB
Razón: Referencias para regression testing
CATEGORÍA 7: DOCUMENTACIÓN MULTIMEDIA (LFS)
─────────────────────────────────────────────────────────────
→ docs/videos/*.mp4
→ docs/screenshots_large/*.png (solo si > 2 MB)
Tamaño típico: 50-200 MB por video
Total: ~400 MB
Razón: Tutoriales, demos visuales
CATEGORÍA 8: BUILD ARTIFACTS (.gitignore)
─────────────────────────────────────────────────────────────
✗ build/
✗ out/
✗ *.exe, *.dll, *.so
Razón: Reproducibles, no deben versionarse
CATEGORÍA 9: DEPENDENCIES (.gitignore + vcpkg)
─────────────────────────────────────────────────────────────
✗ vcpkg_installed/
✗ third_party/
Razón: Gestionadas por vcpkg, no versionar binarios
6.2 Umbrales y Políticas¶
Reglas concretas:
┌─────────────────────────────────────────────────────────────┐
│ AUDIOLAB: POLÍTICAS DE VERSIONAMIENTO │
└─────────────────────────────────────────────────────────────┘
POLÍTICA 1: Umbral de Tamaño
─────────────────────────────────────────────────────────────
< 100 KB: Siempre Git nativo
100 KB - 1 MB: Git nativo (evaluación caso por caso)
1 MB - 100 MB: LFS (si es binario)
> 100 MB: LFS o considerar externo
POLÍTICA 2: Archivos de Audio
─────────────────────────────────────────────────────────────
REGLA: Todos los WAV/AIFF > 1 MB → LFS
EXCEPCIÓN: Samples de test muy pequeños (< 100 KB) → Git nativo
JUSTIFICACIÓN:
- Audio es casi siempre binario incomprimible
- Cambios son raros (samples de referencia)
- Necesitamos versionarlos para reproducibilidad
POLÍTICA 3: Impulse Responses
─────────────────────────────────────────────────────────────
REGLA: Todos los IR → LFS (sin umbral)
JUSTIFICACIÓN:
- IR son inmutables (mediciones físicas)
- Son grandes (50-200 MB)
- Críticos para calidad de reverb
POLÍTICA 4: Videos de Documentación
─────────────────────────────────────────────────────────────
REGLA: Videos > 10 MB → LFS
LÍMITE: Máximo 5 videos en el repo
- Si necesitas más: YouTube + links en docs
JUSTIFICACIÓN:
- Videos son útiles pero crecen rápido
- Balance entre accesibilidad y tamaño
POLÍTICA 5: Screenshots
─────────────────────────────────────────────────────────────
< 2 MB: Git nativo
> 2 MB: LFS
PREFERENCIA: Optimizar PNGs antes de commitear
- Usar herramientas como pngquant
- Objetivo: < 500 KB si es posible
POLÍTICA 6: Golden Outputs
─────────────────────────────────────────────────────────────
REGLA: Todos los golden outputs → LFS
MANTENIMIENTO:
- Revisar cada 6 meses
- Eliminar golden outputs obsoletos
- Mantener solo para tests activos
POLÍTICA 7: Migración
─────────────────────────────────────────────────────────────
Si un archivo pequeño crece:
- Cuando supera 1 MB → Migrar a LFS
- Comunicar al equipo
- Actualizar .gitattributes
6.3 Workflow de Audio Samples¶
Proceso específico para assets de audio:
┌─────────────────────────────────────────────────────────────┐
│ WORKFLOW: AÑADIR NUEVO AUDIO SAMPLE │
└─────────────────────────────────────────────────────────────┘
PASO 1: Grabar/Obtener el Sample
- Grabar en DAW (Pro Tools, Ableton, etc.)
- O descargar de biblioteca libre de royalties
PASO 2: Procesar y Normalizar
- Format: WAV, 48 kHz, 24-bit (estándar AudioLab)
- Normalizar a -1 dBFS peak
- Trim silence al inicio/final
- Nombrar descriptivamente: instrument_note_variant.wav
Ejemplo: piano_C4_soft.wav
PASO 3: Decidir Ubicación
tests/samples/ → Para unit tests
tests/impulse_responses/ → Para IRs de reverb
tests/golden_outputs/ → Para regression tests
benchmarks/ → Para performance benchmarks
PASO 4: Añadir al Repo
$ git add tests/samples/piano_C4_soft.wav
LFS detecta automáticamente (*.wav en .gitattributes)
y lo procesa como LFS.
PASO 5: Commit con Metadata
$ git commit -m "Add piano C4 soft sample
- Source: Yamaha C3 grand piano
- Recording: Studio A, condenser mic
- Format: 48kHz 24-bit WAV
- Use: tests/dsp/test_pitch_shift.cpp
- Size: 12.3 MB"
PASO 6: Push
$ git push origin main
Git push + LFS upload se hacen automáticamente.
PASO 7: Documentar
Añadir entrada en tests/samples/README.md:
## piano_C4_soft.wav
- **Instrument**: Yamaha C3 Grand Piano
- **Note**: C4 (Middle C)
- **Dynamics**: Soft (mp)
- **Duration**: 5 seconds
- **Use Cases**: Pitch shifting tests, time stretching
Mantenimiento de samples:
CADA 3 MESES: Revisión de Samples
1. Listar todos los samples:
$ git lfs ls-files tests/samples/
2. Verificar uso:
$ grep -r "piano_C4_soft.wav" tests/
Si no hay hits → Sample huérfano
3. Decisión:
- En uso → Mantener
- Huérfano → Considerar eliminar
- Duplicado → Deduplicar
4. Actualizar documentación:
README.md con lista actualizada
6.4 Híbridos: Artifacts de Build¶
Algunos archivos están en zona gris:
┌─────────────────────────────────────────────────────────────┐
│ CASO ESPECIAL: ARTIFACTS DE BUILD │
└─────────────────────────────────────────────────────────────┘
PROBLEMA:
Los builds de AudioLab generan:
- audiolab.dll (15 MB)
- test_runner.exe (8 MB)
- benchmark_suite.exe (12 MB)
¿Deben versionarse?
ANÁLISIS:
✓ Son reproducibles desde el código (git clone + build)
✗ Pero builds tardan 20 minutos en compilar
✓ Útil para QA testing sin compilar
✗ Cambian en cada commit (muchas versiones)
DECISIÓN PARA AUDIOLAB:
REGLA GENERAL: .gitignore (no versionar)
EXCEPCIÓN: Release builds
- Cuando hacemos un release (v1.0.0)
- Usamos GitHub Releases para almacenar binarios
- NO van en el repo Git, van en "Releases" de GitHub
WORKFLOW:
1. Tag release: git tag v1.0.0
2. Build release: cmake --build . --config Release
3. Crear GitHub Release: gh release create v1.0.0
4. Upload binarios: gh release upload v1.0.0 audiolab.dll
Resultado:
- Código versionado en Git
- Binarios disponibles en Releases
- No inflan el repo
Artifacts de CI/CD:
GITHUB ACTIONS: Artifacts Temporales
.github/workflows/ci.yml:
- name: Build
run: cmake --build .
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: audiolab-build-${{ github.sha }}
path: build/audiolab.dll
retention-days: 7
Estos artifacts:
- Se almacenan en GitHub (no en Git)
- Disponibles por 7 días
- Útiles para debugging de CI
- Automáticamente eliminados después
CAPÍTULO 7: Alternativas y Complementos¶
7.1 Git Annex: El Hermano Mayor¶
Git Annex es la alternativa más antigua y compleja a LFS:
┌─────────────────────────────────────────────────────────────┐
│ GIT ANNEX vs GIT LFS │
└─────────────────────────────────────────────────────────────┘
GIT ANNEX:
Creado: 2010 (antes de LFS)
Filosofía: Descentralizado, flexible, potente
Características:
✓ Totalmente descentralizado (no requiere server central)
✓ Múltiples backends de storage (S3, rsync, HTTP, etc.)
✓ Políticas de distribución sofisticadas ("2 copias en servers geográficamente separados")
✓ Soporte para archivos MUY grandes (TBs)
✓ Partial checkout muy granular
Desventajas:
✗ Complejidad alta (curva de aprendizaje empinada)
✗ Menos integración con plataformas (GitHub, GitLab)
✗ Comandos menos intuitivos
✗ Requiere más configuración manual
GIT LFS:
Creado: 2015 (por GitHub)
Filosofía: Simple, integrado, "just works"
Características:
✓ Súper simple de usar
✓ Integración nativa en GitHub, GitLab, Bitbucket
✓ Transparente (casi no notas que está ahí)
✓ Comunidad grande, bien documentado
Desventajas:
✗ Requiere server LFS centralizado
✗ Menos flexible que Annex
✗ Políticas de distribución básicas
Cuándo elegir Git Annex:
ESCENARIO 1: Datos Científicos Masivos
- Proyecto: 5 TB de datos de telescopio
- Necesidad: Distribución en múltiples instituciones
- Política: "Al menos 3 copias, 2 en continentes diferentes"
→ Git Annex (LFS no escala a este nivel)
ESCENARIO 2: Workflow Descentralizado Extremo
- Equipo: Investigadores en field work sin internet confiable
- Necesidad: Sincronización peer-to-peer cuando hay conectividad
→ Git Annex (soporta backends offline)
ESCENARIO 3: Control Total
- Organización: Gobierno/militar con requisitos de seguridad estrictos
- Necesidad: Zero dependencias en servicios de terceros
→ Git Annex (totalmente self-hosted)
Cuándo elegir Git LFS:
ESCENARIO 1: Proyecto Web/App Típico
- Assets: Imágenes, videos, fonts (< 10 GB total)
- Equipo: Developers con Git básico
→ Git LFS (simplicidad)
ESCENARIO 2: Integración con Plataformas
- Repo: Hosted en GitHub/GitLab
- CI/CD: GitHub Actions, GitLab CI
→ Git LFS (integración nativa)
ESCENARIO 3: AudioLab
- Assets: Audio samples, IR (< 5 GB)
- Equipo: Pequeño, necesita simplicidad
→ Git LFS ✓
7.2 DVC: Data Version Control¶
DVC es la solución moderna para ML/Data Science:
┌─────────────────────────────────────────────────────────────┐
│ DVC (Data Version Control) │
└─────────────────────────────────────────────────────────────┘
CONCEPTO:
"Git para datasets y modelos de ML"
No reemplaza a Git, lo complementa:
- Git: versiona código y metadata
- DVC: versiona datos y modelos (pointers en Git)
ARQUITECTURA:
Similar a LFS, pero optimizado para ML workflows.
dataset.dvc (en Git):
outs:
- md5: 4d7a8f9b2c1e3a5f...
size: 5000000000
path: data/training_set.zip
El archivo real (5 GB) vive en:
- S3, GCS, Azure Blob
- O storage local
VENTAJAS SOBRE LFS:
✓ Pipelines de ML versionados
✓ Métricas y experiments tracking
✓ Integración con ML tools (MLflow, etc.)
✓ Optimizado para datasets masivos
✓ Deduplicación más agresiva
DESVENTAJAS:
✗ Complejidad adicional
✗ Menos integración con plataformas Git
✗ Requiere aprender nueva tool
Cuándo usar DVC:
PROYECTO DE MACHINE LEARNING:
- Datasets: 50 GB de imágenes para training
- Modelos: 2 GB de pesos de red neuronal
- Workflow: Experimentación constante con diferentes datasets
Workflow con DVC:
$ dvc add data/training_set.zip
$ git add data/training_set.zip.dvc
$ git commit -m "Update training set v3"
$ dvc push # Sube a S3
Otro researcher:
$ git pull
$ dvc pull # Descarga dataset desde S3
$ python train.py # Entrena con datos correctos
AUDIOLAB: ¿Usar DVC?
RESPUESTA: No (por ahora)
RAZÓN:
- Nuestros "datasets" son pequeños (< 5 GB)
- No hacemos ML training intensivo
- LFS es más simple y suficiente
FUTURO:
Si AudioLab evoluciona a ML-based audio processing:
- Datasets de 100+ GB
- Modelos de deep learning
→ Entonces considerar DVC
7.3 Submodules: La Estrategia de Separación¶
Git submodules: referencia otro repo como directorio:
┌─────────────────────────────────────────────────────────────┐
│ GIT SUBMODULES │
└─────────────────────────────────────────────────────────────┘
CONCEPTO:
En lugar de almacenar archivos grandes en el repo principal,
los mueves a un repo separado y lo referencias.
ESTRUCTURA:
audio-lab/ (repo principal)
├─ src/
├─ tests/
└─ samples/ ───────────► audio-lab-samples/ (repo separado)
(submodule) ├─ piano/
├─ drums/
└─ impulse_responses/
WORKFLOW:
$ git submodule add https://github.com/team/audio-lab-samples.git samples
$ git commit -m "Add samples as submodule"
Otro developer:
$ git clone https://github.com/team/audio-lab.git
$ git submodule update --init --recursive
VENTAJAS:
✓ Repo principal se mantiene pequeño
✓ Puedes actualizar samples independientemente
✓ Diferentes branches de samples para diferentes propósitos
✓ No requiere LFS (si samples repo es pequeño)
DESVENTAJAS:
✗ Complejidad (dos repos que sincronizar)
✗ Developers olvidan actualizar submodules
✗ Commits en submodule deben pushearse antes que en main repo
✗ Curva de aprendizaje
Cuándo usar Submodules:
CASO 1: Assets Compartidos entre Proyectos
Situación:
- audio-lab-core
- audio-lab-plugins
- audio-lab-tools
Todos comparten los mismos test samples.
Solución:
- audio-lab-samples (repo separado)
- Cada proyecto lo referencia como submodule
Beneficio: Un solo lugar para assets, varias referencias.
CASO 2: Assets con Ciclo de Vida Diferente
Situación:
- Código cambia diariamente
- Samples cambian mensualmente
Solución:
- Código en repo principal
- Samples en submodule
Beneficio: Puedes actualizar samples sin tocar código.
AUDIOLAB: ¿Usar Submodules?
RESPUESTA: No (por ahora)
RAZÓN:
- Assets están acoplados con tests (cambios frecuentes juntos)
- No compartimos assets con otros proyectos
- LFS resuelve el problema de tamaño sin complejidad de submodules
FUTURO:
Si creamos familia de proyectos AudioLab:
→ Considerar samples compartidos como submodule
7.4 Paisaje del Ecosistema¶
Mapa completo de soluciones:
┌─────────────────────────────────────────────────────────────────────┐
│ ECOSISTEMA DE VERSIONAMIENTO DE ARCHIVOS GRANDES │
└─────────────────────────────────────────────────────────────────────┘
COMPLEJIDAD
▲
│
GIT │
ANNEX│ ┌─────────┐
│ │ Control │
│ │ Total │
│ │ Policies│
│ └─────────┘
│
│ ┌─────────┐
DVC│ │ ML │
│ │ Data │
│ └─────────┘
│
│
GIT LFS │───────┐
│ │ Sweet spot
│ │ para mayoría
│ │
GIT NATIVE │ │
│ │
│
Nothing │ ┌────────┐
│ │ Simple │
│ │Projects│
└──┴────────┴─────────────►
Small Large TAMAÑO DE ASSETS
(<1GB) (>10GB)
MATRIZ DE DECISIÓN:
───────────────────────────────────────────────────────────────────────
Simple Medium Complex Massive
(< 1 GB) (1-10 GB) (10-100 GB) (> 100 GB)
───────────────────────────────────────────────────────────────────────
Proyectos Web Native LFS LFS DVC/External
Game Dev Native LFS LFS+Sub Git Annex
ML/Data Science Native LFS DVC DVC
Scientific Data Native LFS Git Annex Git Annex
AudioLab Native LFS ✓ - -
───────────────────────────────────────────────────────────────────────
Comparación lado a lado:
┌──────────────┬──────────┬──────────┬──────────┬──────────┐
│ Feature │ Native │ LFS │ Annex │ DVC │
├──────────────┼──────────┼──────────┼──────────┼──────────┤
│ Simplicidad │ ★★★★★ │ ★★★★☆ │ ★★☆☆☆ │ ★★★☆☆ │
│ Integración │ ★★★★★ │ ★★★★★ │ ★★☆☆☆ │ ★★★☆☆ │
│ Escalabilidad│ ★☆☆☆☆ │ ★★★☆☆ │ ★★★★★ │ ★★★★☆ │
│ Flexibilidad │ ★★☆☆☆ │ ★★★☆☆ │ ★★★★★ │ ★★★★☆ │
│ Velocidad │ ★★★★☆ │ ★★★★☆ │ ★★★☆☆ │ ★★★☆☆ │
│ Costo │ FREE │ $-$$ │ FREE │ $-$$ │
└──────────────┴──────────┴──────────┴──────────┴──────────┘
🎯 CONCLUSIÓN: Decisión para AudioLab¶
┌─────────────────────────────────────────────────────────────┐
│ DECISIÓN FINAL PARA AUDIOLAB │
└─────────────────────────────────────────────────────────────┘
SOLUCIÓN ELEGIDA: GIT LFS
JUSTIFICACIÓN:
✓ Tamaño de assets: ~3 GB (sweet spot para LFS)
✓ Equipo pequeño: Simplicidad > Flexibilidad
✓ Integración: GitHub con LFS nativo
✓ Costo: $0-5/mes (asequible)
✓ Experiencia: Documentación excelente, comunidad activa
CONFIGURACIÓN:
.gitattributes:
*.wav filter=lfs diff=lfs merge=lfs -text
*.aiff filter=lfs diff=lfs merge=lfs -text
tests/impulse_responses/** filter=lfs diff=lfs merge=lfs -text
tests/golden_outputs/** filter=lfs diff=lfs merge=lfs -text
docs/videos/*.mp4 filter=lfs diff=lfs merge=lfs -text
HOSTING:
GitLab (plan gratuito, 10 GB LFS storage)
Si migramos a GitHub en el futuro:
→ 1 data pack ($5/mes) cubre necesidades
PLAN DE CONTINGENCIA:
Si assets crecen > 20 GB:
→ Re-evaluar (considerar DVC o Git Annex)
Si necesitamos ML workflows:
→ Añadir DVC como complemento
REVISIÓN:
Cada 6 meses: Revisar tamaño de assets y ajustar políticas
📚 EPÍLOGO: La Filosofía Detrás de LFS¶
Git LFS no es solo una herramienta técnica. Es una solución filosófica a un problema de incompatibilidad ontológica:
┌─────────────────────────────────────────────────────────────┐
│ LA DUALIDAD FUNDAMENTAL │
└─────────────────────────────────────────────────────────────┘
GIT fue diseñado para el mundo de:
- Texto
- Deltas
- Cambios granulares
- Merge inteligente
- Historia navegable línea por línea
LOS ARCHIVOS BINARIOS GRANDES pertenecen al mundo de:
- Bloques opacos
- Inmutabilidad
- Cambios atómicos
- Reemplazo completo
- Identificación por hash
Estos dos mundos NO son compatibles.
GIT LFS es la INTERFACE entre ambos:
- Git versiona la IDENTIDAD (hash)
- LFS almacena la SUSTANCIA (bytes)
- El usuario experimenta UNIDAD (transparencia)
La lección más profunda:
No todos los problemas se resuelven con la misma herramienta.
Git es perfecto para código. Git LFS es perfecto para assets binarios grandes. Juntos, forman un sistema completo para proyectos modernos que mezclan ambos mundos.
Para AudioLab, esto significa: - El algoritmo (código C++) vive en Git - El sonido (muestras WAV) vive en LFS - La referencia (pointer) los une en un repositorio coherente
Dos mundos, una arquitectura. Esa es la filosofía de Git LFS.
FIN DEL DOCUMENTO
Total: ~35 páginas de teoría conceptual y visual sobre Git LFS. Cero comandos técnicos de instalación. Todo el enfoque en ENTENDER el sistema, no en operarlo.