╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 🎨 GIT ATTRIBUTES: CONTROL MICROSCÓPICO DEL REPO ║ ║ La Alquimia de la Configuración Granular ║ ║ ║ ║ "No solo QUÉ trackear, sino CÓMO trackear" ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════════════╝
═══════════════════════════════════════════════════════════════════════════════ 🧠 CAPÍTULO 1: LA METAFÍSICA DE LOS ATRIBUTOS ═══════════════════════════════════════════════════════════════════════════════
🔷 CONCEPTO FUNDAMENTAL: MÁS ALLÁ DEL TRACKING BINARIO ────────────────────────────────────────────────────────────────────────────
La mayoría de los developers conocen .gitignore: "trackear" vs "no trackear". Pero Git permite un nivel de control MUCHO más profundo.
PREGUNTA EVOLUCIONARIA: ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ¿Y si no solo pudieras decidir SI trackear un archivo, │ │ sino también CÓMO Git debe tratarlo? │ │ │ │ • ¿Cómo hacer diff? │ │ • ¿Cómo resolver merges? │ │ • ¿Cómo normalizar line endings? │ │ • ¿Qué lenguaje de programación es? │ │ • ¿Aplicar transformaciones al commit/checkout? │ │ │ │ Esto es .gitattributes. │ │ │ └─────────────────────────────────────────────────────────────────────────┘
🎭 EVOLUCIÓN DEL PENSAMIENTO ────────────────────────────────────────────────────────────────────────────
NIVEL 1: EXISTENCIA BINARIA
────────────────────────────
│
¿Trackear?
│
┌───────┴───────┐
│ │
SÍ NO
│ │
Git tracks .gitignore
│
│
▼
.gitignore
opera aquí
│
│
▼
NIVEL 2: TRATAMIENTO DIFERENCIADO
──────────────────────────────────
│
¿CÓMO trackear?
│
┌───────────────┼───────────────┐
│ │ │
Como TEXTO Como BINARIO Con FILTROS
│ │ │
Diff línea "Binary files Transformar
por línea differ" en commit
│ │ │
├─ Con merge ├─ No diff ├─ Encriptar
│ strategy │ textual │ Comprimir
│ específica │ │ Expandir
│ │ │
▼ ▼ ▼
.gitattributes
opera aquí
│
│
▼
NIVEL 3: TRANSFORMACIÓN CONTEXTUAL
───────────────────────────────────
│
¿Transformar durante operaciones?
│
┌───────────────┼───────────────┐
│ │ │
CHECKOUT COMMIT MERGE
│ │ │
Aplicar smudge Aplicar clean Estrategia
filter filter custom
│ │ │
Descifrar → ← Cifrar Union/Ours/
Expandir → ← Comprimir Theirs/Custom
Descargar → ← Apuntar LFS
│ │ │
└───────────────┴───────────────┘
│
.gitattributes +
.gitconfig
orquestan esto
EL PARADIGMA DE ATRIBUTOS ────────────────────────────────────────────────────────────────────────────
Git attributes sigue un patrón simple pero poderoso:
╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 📐 ARQUITECTURA DE ATRIBUTOS ║ ║ ║ ║ PATRÓN → ATRIBUTO → COMPORTAMIENTO ║ ║ ──────── ───────── ──────────────── ║ ║ ║ ║ *.cpp → text → Diff char-by-char ║ ║ *.wav → binary → "Binary files differ" ║ ║ *.md → linguist → Count as docs ║ ║ *.yml → merge=union → Union merge strategy ║ ║ *.env → filter=crypt → Encrypt on commit ║ ║ ║ ║ ↑ ↑ ↑ ║ ║ │ │ │ ║ ║ Glob match Directiva Git behavior ║ ║ de archivos que aplica resultante ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
SINTAXIS FUNDAMENTAL: ────────────────────────────────────────────────────────────────────────────
pattern attribute=value
EJEMPLOS: *.txt text *.jpg binary *.cpp text eol=lf secret.env filter=encrypt CHANGELOG merge=union
COMPOSICIÓN DE ATRIBUTOS: ────────────────────────────────────────────────────────────────────────────
Un archivo puede tener MÚLTIPLES atributos simultáneos:
*.cpp text eol=lf diff=cpp linguist-language=cpp
INTERPRETACIÓN: ├─ text → Tratar como archivo de texto ├─ eol=lf → Line endings siempre LF ├─ diff=cpp → Usar driver de diff específico para C++ └─ linguist-language=cpp → Contar como código C++ en stats
🔷 LOS CINCO DOMINIOS DE CONTROL ────────────────────────────────────────────────────────────────────────────
.gitattributes controla cinco áreas fundamentales del comportamiento de Git:
🗺️ TERRITORIAL MAP OF ATTRIBUTES ────────────────────────────────────────────────────────────────────────────
┌─────────────────────┐
│ GIT ATTRIBUTES │
│ CONTROL DOMAINS │
└──────────┬──────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────────▼────────┐ ┌─────▼──────┐ ┌───────▼────────┐
│ 📝 ENCODING & │ │ 🔍 DIFF │ │ 🔀 MERGE │
│ LINE ENDINGS │ │ BEHAVIOR │ │ STRATEGIES │
└────────┬────────┘ └─────┬──────┘ └───────┬────────┘
│ │ │
│ │ │
┌────────▼────────┐ ┌─────▼──────┐
│ 🎨 LANGUAGE │ │ 🔄 FILTERS │
│ DETECTION │ │ & XFORMS │
└─────────────────┘ └────────────┘
DOMINIO 1: 📝 ENCODING & LINE ENDINGS ────────────────────────────────────────────────────────────────────────────
PROBLEMA: ├─ Windows usa CRLF (\r\n) ├─ Unix usa LF (\n) ├─ Repos mixed = diff chaos └─ Falsos cambios en todo el archivo
CONSECUENCIA: • Git diff muestra TODO el archivo modificado • Merge conflicts artificiales • History noise • Code reviews imposibles
SOLUCIÓN: text=auto → Git detecta y normaliza eol=lf → Fuerza LF eol=crlf → Fuerza CRLF -text → Desactiva conversión (binario)
DOMINIO 2: 🔍 DIFF BEHAVIOR ────────────────────────────────────────────────────────────────────────────
PROBLEMA: ├─ Binary diffs: "Binary files differ" (inútil) ├─ Structured files: diff sin contexto ├─ Images: no metadata visible └─ Word docs: solo bytes
CONSECUENCIA: • No puedes ver qué cambió en imágenes • Metadata changes invisibles • Office docs son black boxes
SOLUCIÓN: diff=word → Word-by-word diff diff=exif → Extract EXIF de imágenes diff=custom → Tu script custom
DOMINIO 3: 🔀 MERGE STRATEGIES ────────────────────────────────────────────────────────────────────────────
PROBLEMA: ├─ Merge conflicts en CHANGELOGs ├─ Lockfiles siempre conflicto ├─ Config files: tu version vs theirs └─ Resolución manual repetitiva
CONSECUENCIA: • Tedioso resolver mismos conflictos • Errores al resolver manualmente • Workflow interrumpido constantemente
SOLUCIÓN: merge=ours → Mantener nuestra versión merge=theirs → Tomar su versión merge=union → Combinar ambas merge=custom → Driver personalizado
DOMINIO 4: 🎨 LANGUAGE DETECTION ────────────────────────────────────────────────────────────────────────────
PROBLEMA: ├─ node_modules contamina estadísticas ├─ Generated code infla números ├─ "Tu repo es 95% JavaScript" (falso) └─ Detección incorrecta de lenguaje
CONSECUENCIA: • GitHub language bar incorrecto • Search engines malinterpretan repo • Stats no reflejan trabajo real
SOLUCIÓN: linguist-vendored → Excluir de stats linguist-generated → Marcar auto-generated linguist-documentation → Contar como docs linguist-language → Override detección
DOMINIO 5: 🔄 FILTERS & TRANSFORMATIONS ────────────────────────────────────────────────────────────────────────────
PROBLEMA: ├─ Archivos grandes hinchan repo ├─ Secretos en código fuente ├─ Metadata que cambia constantemente └─ Formatos necesitan conversión
CONSECUENCIA: • Repo gigante, clones lentos • Secretos expuestos en historia • Diffs ruidosos por metadata • Incompatibilidad entre plataformas
SOLUCIÓN: filter=lfs → Git Large File Storage filter=crypt → Encriptar secretos filter=custom → Tu transformación
═══════════════════════════════════════════════════════════════════════════════ 🎯 CAPÍTULO 2: LINE ENDINGS - LA GUERRA SILENCIOSA ═══════════════════════════════════════════════════════════════════════════════
🔷 LA GRAN DIVERGENCIA ────────────────────────────────────────────────────────────────────────────
Este es uno de los problemas más sutiles y frustrantes en Git. Invisible a simple vista, pero devastador en impacto.
🌍 GÉNESIS DEL CONFLICTO ────────────────────────────────────────────────────────────────────────────
📜 ERA DE LAS MÁQUINAS DE ESCRIBIR
─────────────────────────────────
│
Dos acciones físicas:
│
┌───────────┴───────────┐
│ │
CARRIAGE RETURN LINE FEED
─────────────── ─────────
Cursor vuelve Papel sube
al inicio una línea
de la línea
│ │
└───────────┬───────────┘
│
Ambos necesarios
mecánicamente
│
│
▼
💻 ERA DIGITAL: DIVERGENCIA
──────────────────────────
│
┌───────────┼───────────┐
│ │ │
┌──────▼─────┐ ┌──▼──────┐ ┌─▼──────────┐
│ 🪟 WINDOWS │ │🐧 UNIX/ │ │🍎 CLASSIC │
│ /DOS │ │ LINUX │ │ MAC │
└──────┬─────┘ └──┬──────┘ └─┬──────────┘
│ │ │
CRLF \r\n LF \n CR \r
───────── ───── ─────
Dos chars Un char Un char
(histórico) (lógico) (cambió
a LF en
OS X)
│ │ │
└───────────┼───────────┘
│
▼
🔥 PROBLEMA EN GIT
─────────────────
│
┌───────────────┼───────────────┐
│ │ │
Windows dev Linux dev Mac dev
commits CRLF commits LF commits LF
│ │ │
└───────────────┴───────────────┘
│
▼
Git diff shows:
EVERYTHING changed
(false positive)
EL PROBLEMA VISUALIZADO ────────────────────────────────────────────────────────────────────────────
ARCHIVO REAL (mismo contenido):
Windows:
#include
Linux:
#include
GIT DIFF SIN NORMALIZACIÓN:
- #include
¡Todas las líneas "cambiaron"! Pero el contenido es IDÉNTICO.
CONSECUENCIAS EN EL WORKFLOW ────────────────────────────────────────────────────────────────────────────
❌ SIN NORMALIZACIÓN: │ ├─ Developer A (Windows) commitea archivo │ └─ Con CRLF │ ├─ Developer B (Linux) hace checkout │ └─ Git mantiene CRLF (archivo no modificado para Git) │ ├─ Developer B abre en editor Linux │ └─ Editor convierte CRLF → LF automáticamente │ ├─ Developer B guarda archivo │ └─ Ahora tiene LF │ ├─ Git status │ └─ "modified" (todo el archivo) │ ├─ Developer B hace commit │ └─ Con LF │ ├─ Developer A pull │ └─ Merge conflict artificial │ └─ Mismo contenido, diferentes line endings │ └─ 😱 FRUSTRACIÓN TOTAL
✅ CON NORMALIZACIÓN: │ ├─ Developer A (Windows) commitea │ ├─ Working dir: CRLF (Windows native) │ ├─ Git auto-convierte: CRLF → LF │ └─ Repo almacena: LF │ ├─ Developer B (Linux) checkout │ ├─ Repo tiene: LF │ ├─ Git mantiene: LF (Linux native) │ └─ Working dir: LF │ ├─ Developer C (Windows) checkout │ ├─ Repo tiene: LF │ ├─ Git auto-convierte: LF → CRLF │ └─ Working dir: CRLF (Windows native) │ └─ 🎯 RESULTADO: ├─ Repo: siempre LF (consistente) ├─ Working: adaptado al OS (conveniente) └─ Diffs: limpios (sin ruido)
TAXONOMÍA DE ESTRATEGIAS ────────────────────────────────────────────────────────────────────────────
╔═══════════════════════════════════════════════════════════════════════════╗ ║ LINE ENDING STRATEGIES ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ ESTRATEGIA │ REPO │ CHECKOUT │ CUÁNDO USAR ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ text=auto │ LF │ Platform │ DEFAULT ║ ║ │ (norm) │ native │ 90% de casos ║ ║ │ │ │ Deja Git decidir ║ ║ ║ ║ text eol=lf │ LF │ LF │ Shell scripts ║ ║ │ │ (forzado) │ Configs Unix ║ ║ │ │ │ Dockerfiles ║ ║ │ │ │ DEBE ser LF siempre ║ ║ ║ ║ text eol=crlf │ LF │ CRLF │ .bat files ║ ║ │ (norm) │ (forzado) │ Windows scripts ║ ║ │ │ │ DEBE ser CRLF siempre ║ ║ ║ ║ binary │ Sin │ Sin │ Imágenes ║ ║ │ conv │ conversión │ Binarios compilados ║ ║ │ │ │ Audio/video ║ ║ │ │ │ Archivos comprimidos ║ ║ ║ ║ -text │ Sin │ Sin │ Force binary ║ ║ │ conv │ conversión │ Override detección ║ ║ │ │ │ False positive prevention ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
DETALLE: text=auto ────────────────────────────────────────────────────────────────────────────
La estrategia más común y recomendada.
COMPORTAMIENTO: ├─ Git DETECTA si archivo es texto o binario ├─ Si es TEXTO: │ ├─ Normaliza a LF en commit │ └─ Convierte a OS-native en checkout │ └─ Si es BINARIO: └─ No toca nada
HEURÍSTICA DE DETECCIÓN: Git considera archivo como TEXTO si: ├─ No tiene bytes NULL ├─ No tiene secuencias binarias obvias └─ Tiene encoding reconocible
Pero puede equivocarse → por eso declaras explícitamente
FLUJO DE NORMALIZACIÓN ────────────────────────────────────────────────────────────────────────────
🔄 LIFECYCLE DE LINE ENDINGS ────────────────────────────────────────────────────────────────────────────
┌──────────────────────────────────────────────────────────────────────────┐ │ │ │ 📝 DEVELOPER EDITA (Windows) │ │ ──────────────────────────── │ │ │ │ │ └─ archivo.cpp en editor Windows │ │ └─ Tiene CRLF (native Windows) │ │ │ │ ↓ │ │ │ │ 💾 GIT ADD (Working → Staging) │ │ ───────────────────────────── │ │ │ │ │ ├─ Git lee .gitattributes │ │ ├─ Encuentra: *.cpp text=auto │ │ ├─ Detecta: es archivo de texto │ │ ├─ TRANSFORMA: CRLF → LF │ │ └─ Staging area: archivo tiene LF │ │ │ │ ↓ │ │ │ │ 📦 GIT COMMIT (Staging → Repository) │ │ ──────────────────────────────────── │ │ │ │ │ └─ Repository almacena: LF │ │ └─ Historia normalizada │ │ │ │ ↓ │ │ │ │ ☁️ GIT PUSH (Local → Remote) │ │ ──────────────────────────── │ │ │ │ │ └─ Remote recibe: LF │ │ └─ Consistencia global │ │ │ │ ↓ │ │ │ │ 📥 OTRO DEV PULL (Remote → Local) │ │ ──────────────────────────────────── │ │ │ │ │ ├─ Repository local: LF │ │ │ │ │ └─ Git checkout (Repo → Working) │ │ │ │ │ ├─ Si developer en LINUX: │ │ │ └─ Mantiene LF (native) │ │ │ │ │ └─ Si developer en WINDOWS: │ │ ├─ Lee .gitattributes │ │ ├─ Encuentra: text=auto │ │ ├─ TRANSFORMA: LF → CRLF │ │ └─ Working dir: CRLF (native Windows) │ │ │ └──────────────────────────────────────────────────────────────────────────┘
RESULTADO FINAL: • Repository: SIEMPRE LF (consistencia) • Working directory: Adaptado al OS (conveniencia) • Diffs: Limpios, solo cambios reales • Merges: Sin conflictos artificiales
CASOS ESPECIALES: FORZAR LINE ENDING ────────────────────────────────────────────────────────────────────────────
Hay archivos que DEBEN tener un line ending específico, sin importar OS.
EJEMPLO 1: Shell scripts (DEBEN ser LF) ────────────────────────────────────────────────────────────────────────────
PROBLEMA: • Script bash con CRLF NO ejecuta en Linux • Error: "/bin/bash^M: bad interpreter" • ^M = carriage return visible
SOLUCIÓN: *.sh text eol=lf
COMPORTAMIENTO: ├─ Repository: LF ├─ Working dir (Linux): LF └─ Working dir (Windows): LF (¡NO CRLF!) └─ Forzado, ignora OS preference
EJEMPLO 2: Windows batch files (DEBEN ser CRLF) ────────────────────────────────────────────────────────────────────────────
PROBLEMA: • Algunos parsers de Windows esperan CRLF • Comportamiento inconsistente con LF
SOLUCIÓN: *.bat text eol=crlf *.cmd text eol=crlf
COMPORTAMIENTO: ├─ Repository: LF (normalizado) ├─ Working dir (Windows): CRLF └─ Working dir (Linux): CRLF (forzado)
EJEMPLO 3: Binary files (SIN conversión) ────────────────────────────────────────────────────────────────────────────
PROBLEMA: • Git puede detectar mal un binario como texto • Conversión CORROMPE el archivo • Ejecutable/imagen inutilizable
SOLUCIÓN: *.exe binary *.dll binary *.png binary *.wav binary
COMPORTAMIENTO: ├─ Sin detección automática ├─ Sin conversión nunca └─ Almacenado exactamente como está
ESTRATEGIA RECOMENDADA PARA REPOS ────────────────────────────────────────────────────────────────────────────
.gitattributes completo para proyecto típico:
════════════════════════════════════════¶
DEFAULT BEHAVIOR¶
════════════════════════════════════════¶
- text=auto
════════════════════════════════════════¶
SOURCE CODE (always LF)¶
════════════════════════════════════════¶
*.cpp text eol=lf *.h text eol=lf *.c text eol=lf *.hpp text eol=lf *.py text eol=lf *.js text eol=lf *.ts text eol=lf *.java text eol=lf *.rs text eol=lf
════════════════════════════════════════¶
SCRIPTS (platform-specific)¶
════════════════════════════════════════¶
*.sh text eol=lf *.bash text eol=lf *.fish text eol=lf Makefile text eol=lf *.bat text eol=crlf *.cmd text eol=crlf *.ps1 text eol=crlf
════════════════════════════════════════¶
CONFIGS (always LF)¶
════════════════════════════════════════¶
*.yml text eol=lf *.yaml text eol=lf *.json text eol=lf *.toml text eol=lf *.xml text eol=lf .gitignore text eol=lf .gitattributes text eol=lf Dockerfile text eol=lf
════════════════════════════════════════¶
BINARIES (never convert)¶
════════════════════════════════════════¶
*.png binary *.jpg binary *.jpeg binary *.gif binary *.ico binary *.mov binary *.mp4 binary *.mp3 binary *.wav binary *.aiff binary *.flac binary *.exe binary *.dll binary *.so binary *.dylib binary *.a binary *.lib binary *.zip binary *.7z binary *.gz binary *.tar binary *.pdf binary
═══════════════════════════════════════════════════════════════════════════════ 🔍 CAPÍTULO 3: DIFF DRIVERS - VER LO INVISIBLE ═══════════════════════════════════════════════════════════════════════════════
🔷 MÁS ALLÁ DEL DIFF TEXTUAL ────────────────────────────────────────────────────────────────────────────
Git diff es increíblemente poderoso para código fuente. Pero muchos archivos en un repo NO son texto plano.
PREGUNTA: ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ ¿Qué pasa cuando modificas una imagen? │ │ ¿O un PDF? │ │ ¿O un archivo de Word? │ │ ¿O un archivo comprimido? │ │ │ │ Git dice: "Binary files differ" │ │ │ │ Eso es... inútil. │ │ │ └─────────────────────────────────────────────────────────────────────────┘
🎨 SPECTRUM DE DIFFABILIDAD ────────────────────────────────────────────────────────────────────────────
No todos los archivos son igualmente "diffables".
MÁXIMO MÍNIMO
ÚTIL ÚTIL
│ │
▼ ▼
┌────────┬──────────┬──────────┬──────────┬─────────┐
│ │ │ │ │ │
📝 TEXTO │ 🗂️ STRUCT│ 📊 BINARY│🗜️ COMPRES│💾 PURE │ PLANO │ TEXT │ +METADATA│ SED │ BINARY │ │ │ │ │ │ │ │ │ │ │ │ │ ✅✅✅✅✅ ✅✅✅✅ ✅✅✅ ✅✅ ✅ │ │ │ │ │ │ │ │ │ │ │ │ │ .cpp │ XML │ Images │ .zip │ .exe │ .md │ YAML │ +EXIF │ .docx │ .dll │ .json │ +parser │ PDFs │ (extract)│ .so │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ │ Char-by Structure Metadata Decompress "Binary │ char aware extract required files │ diff diff diff then diff differ" │ │ │ │ │ │ │ └────────┴──────────┴──────────┴──────────┴─────────┘
PROBLEMA DEFAULT: TODO ES "BINARY FILES DIFFER" ────────────────────────────────────────────────────────────────────────────
EJEMPLO: Modificaste una foto
ANTES: photo.jpg: • Camera: Canon EOS 5D • ISO: 400 • Aperture: f/2.8 • Date: 2024-01-15
DESPUÉS: photo.jpg: • Camera: Canon EOS 5D • ISO: 800 ← CAMBIÓ • Aperture: f/4 ← CAMBIÓ • Date: 2024-01-16 ← CAMBIÓ
GIT DIFF DEFAULT: Binary files a/photo.jpg and b/photo.jpg differ
¿Qué cambió? No idea. Totalmente opaco.
SOLUCIÓN: CUSTOM DIFF DRIVERS ────────────────────────────────────────────────────────────────────────────
Git permite definir CÓMO hacer diff de ciertos archivos.
CONCEPTO: En vez de comparar BYTES, Comparar CONTENIDO EXTRAÍDO.
ARQUITECTURA DE UN DIFF DRIVER ────────────────────────────────────────────────────────────────────────────
🏗️ DIFF DRIVER ANATOMY ────────────────────────────────────────────────────────────────────────────
┌──────────────────┐
│ .gitattributes │
│ │
│ *.jpg diff=exif │
└────────┬─────────┘
│
│ Declara: "archivos .jpg usan driver exif"
│
▼
┌──────────────────┐
│ .gitconfig │
│ │
│ [diff "exif"] │
│ textconv=exiftool│
└────────┬─────────┘
│
│ Define: "driver exif ejecuta exiftool"
│
▼
┌──────────────────────────────────┐
│ GIT DIFF EXECUTION FLOW │
└──────────────────────────────────┘
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ OLD VERSION │ │ NEW VERSION │
│ photo.jpg │ │ photo.jpg │
└───────┬───────┘ └───────┬───────┘
│ │
│ exiftool photo.jpg │ exiftool photo.jpg
▼ ▼
┌───────────────┐ ┌───────────────┐
│ METADATA │ │ METADATA │
│ EXTRACTED │ │ EXTRACTED │
│ │ │ │
│ Camera: Canon │ │ Camera: Canon │
│ ISO: 400 │ │ ISO: 800 │
│ Aperture: f/2.8│ │ Aperture: f/4 │
│ Date: 01-15 │ │ Date: 01-16 │
└───────┬───────┘ └───────┬───────┘
│ │
└───────────────┬───────────────┘
│
▼
┌──────────────────┐
│ TEXTUAL DIFF │
│ │
│ -ISO: 400 │
│ +ISO: 800 │
│ -Aperture: f/2.8│
│ +Aperture: f/4 │
│ -Date: 01-15 │
│ +Date: 01-16 │
└──────────────────┘
│
▼
HUMAN-READABLE DIFF!
DIFF DRIVERS COMUNES ────────────────────────────────────────────────────────────────────────────
╔═══════════════════════════════════════════════════════════════════════════╗ ║ USEFUL DIFF DRIVERS ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ DRIVER │ ARCHIVOS │ TOOL │ OUTPUT ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ exif │ Images │ exiftool │ Camera metadata ║ ║ │ .jpg, .png │ │ ISO, aperture, date ║ ║ │ │ │ GPS coordinates ║ ║ ║ ║ word │ Text │ Git built-in │ Word-by-word diff ║ ║ │ Any text │ │ vs char-by-char ║ ║ │ │ │ Better for prose ║ ║ ║ ║ odt │ Office docs │ odt2txt │ Text content ║ ║ │ .odt │ │ Ignora formatting ║ ║ ║ ║ docx │ Word docs │ docx2txt │ Text from .docx ║ ║ │ .docx │ pandoc │ Sin binary noise ║ ║ ║ ║ pdf │ PDFs │ pdftotext │ Extracted text ║ ║ │ .pdf │ │ vs binary blob ║ ║ ║ ║ xlsx │ Excel │ xlsx2csv │ CSV representation ║ ║ │ .xlsx │ │ Diffable ║ ║ ║ ║ sqlite │ Databases │ sqlite3 │ Schema dump ║ ║ │ .db, .sqlite │ .dump │ SQL commands ║ ║ ║ ║ zip │ Archives │ unzip -l │ File listing ║ ║ │ .zip │ │ Contents inventory ║ ║ ║ ║ json │ JSON │ jq │ Pretty-printed ║ ║ │ .json │ │ Normalized format ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
EJEMPLO COMPLETO: EXIF DRIVER PARA IMÁGENES ────────────────────────────────────────────────────────────────────────────
SETUP:
1️⃣ .gitattributes: *.jpg diff=exif *.png diff=exif *.jpeg diff=exif
2️⃣ .gitconfig: [diff "exif"] textconv = exiftool
3️⃣ Install exiftool: (herramienta externa)
RESULTADO:
ANTES: git diff photo.jpg Binary files differ
DESPUÉS: git diff photo.jpg
--- a/photo.jpg
+++ b/photo.jpg
@@ -1,10 +1,10 @@
Camera Model Name: Canon EOS 5D Mark IV
-ISO: 400
+ISO: 800
-Aperture: f/2.8
+Aperture: f/4
-Shutter Speed: 1/100
+Shutter Speed: 1/200
-Date/Time Original: 2024:01:15 14:30:00
+Date/Time Original: 2024:01:16 10:15:00
Lens Model: EF 24-70mm f/2.8L
¡MUCHO mejor! Ahora VES qué cambió.
CUSTOM DIFF DRIVERS: ESCRIBE TU PROPIO ────────────────────────────────────────────────────────────────────────────
Puedes crear drivers completamente custom.
CONCEPTO: Driver = cualquier programa que: • Recibe archivo como input (stdin o arg) • Output texto a stdout • Git hace diff de los outputs
EJEMPLO: Audio file metadata
SCRIPT: audio-metadata.sh ────────────────────────────────────────────────────────────────────────────
!/bin/bash¶
Extract metadata from audio files¶
ffprobe -v quiet \ -print_format json \ -show_format \ -show_streams \ "$1" | jq -r ' .format | "Duration: (.duration)s", "Sample Rate: (.tags.sample_rate // "Unknown")", "Channels: (.tags.channels // "Unknown")", "Codec: (.format_name)" '
.gitattributes: *.wav diff=audio *.aiff diff=audio
.gitconfig: [diff "audio"] textconv = ./scripts/audio-metadata.sh
RESULTADO: git diff sample.wav
--- a/sample.wav
+++ b/sample.wav
@@ -1,4 +1,4 @@
-Duration: 10.5s
+Duration: 12.3s
Sample Rate: 48000
-Channels: 2
+Channels: 4
Codec: wav
═══════════════════════════════════════════════════════════════════════════════ 🔀 CAPÍTULO 4: MERGE STRATEGIES - CONFLICTOS INTELIGENTES ═══════════════════════════════════════════════════════════════════════════════
🔷 LA GEOMETRÍA DEL MERGE ────────────────────────────────────────────────────────────────────────────
Merges son donde Git realmente brilla... o donde todo explota. Default merge behavior es inteligente, pero no omnisciente.
ANATOMÍA DE UN MERGE ────────────────────────────────────────────────────────────────────────────
BASE COMÚN
○
│
┌───────┴───────┐
│ │
Branch A Branch B
● ●
│ │
Cambios A Cambios B
│ │
└───────┬───────┘
│
MERGE POINT
◎
│
¿Cómo combinar?
TIPOS DE CAMBIOS Y SU RESOLUCIÓN ────────────────────────────────────────────────────────────────────────────
╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ESCENARIO │ RESOLUCIÓN DEFAULT ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ Solo A modificó línea │ ✅ Automático: Tomar cambio de A ║ ║ ║ ║ Solo B modificó línea │ ✅ Automático: Tomar cambio de B ║ ║ ║ ║ A y B modificaron │ ❌ CONFLICTO: Resolución manual ║ ║ MISMA línea │ ║ ║ ║ ║ A modificó líneas 10-15 │ ✅ Automático: Combinar ambos ║ ║ B modificó líneas 20-25 │ (no overlap) ║ ║ ║ ║ A añadió líneas │ ✅ Automático: Incluir ambos ║ ║ B añadió líneas diferentes │ ║ ║ ║ ║ A borró líneas │ ✅ Automático: Aplicar ambos deletes ║ ║ B borró líneas diferentes │ ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
PROBLEMA: CONFLICTOS PREDECIBLES Y REPETITIVOS ────────────────────────────────────────────────────────────────────────────
Ciertos archivos SIEMPRE tienen conflictos en merge: ├─ CHANGELOG.md (ambos branches añaden entradas) ├─ package-lock.json (lockfiles cambian constantemente) ├─ database/migrations/ (migraciones concurrentes) └─ config/local.yml (configs personales)
Estos conflictos: • Son PREDECIBLES • Tienen resolución OBVIA • Son TEDIOSOS de resolver manualmente • Interrumpen workflow
SOLUCIÓN: CUSTOM MERGE DRIVERS ────────────────────────────────────────────────────────────────────────────
Git permite definir CÓMO mergear ciertos archivos.
🎭 TIPOS DE MERGE STRATEGIES ────────────────────────────────────────────────────────────────────────────
┌────────────────────┐
│ MERGE STRATEGIES │
└──────────┬─────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌───▼────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ OURS │ │ THEIRS │ │ UNION │
└───┬────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
"Mantener "Tomar su "Combinar
nuestra versión" AMBAS"
versión"
│ │ │
│ │ │
┌───▼────┐ ┌─────▼─────┐ ┌─────▼─────┐
│RECURSIVE│ │ CUSTOM │ │ DEFAULT │
└───┬────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
"Inteligente "Tu script "Git decide
3-way merge" personalizado" automático"
STRATEGY 1: OURS ────────────────────────────────────────────────────────────────────────────
CONCEPTO: En conflicto: SIEMPRE mantener nuestra versión. Ignorar cambios de ellos.
CASO DE USO: Config files personales
ESCENARIO: .env.local contiene configuración personal • DB host: localhost (tú) • DB host: production.server (main branch)
NUNCA quieres sobrescribir tu localhost.
SETUP: .gitattributes: .env.local merge=ours config/*.local merge=ours
.gitconfig:
[merge "ours"]
driver = true
# "true" command = exitcode 0 (success), no hace nada
RESULTADO: Merge automático, siempre mantiene tu versión local.
VISUALIZACIÓN: ────────────────────────────────────────────────────────────────────────────
BASE: DB_HOST=localhost
Branch A (tú): DB_HOST=localhost DB_PORT=5432
Branch B (main): DB_HOST=production.server DB_PORT=5432
MERGE con ours: DB_HOST=localhost ← Tu versión preservada DB_PORT=5432 ← Cambio de B aplicado (no conflicto)
STRATEGY 2: THEIRS ────────────────────────────────────────────────────────────────────────────
CONCEPTO: En conflicto: SIEMPRE tomar su versión. Ignorar nuestros cambios.
CASO DE USO: Lockfiles regenerables
ESCENARIO: package-lock.json siempre tiene conflictos Pero es REGENERABLE (npm install lo reconstruye)
Estrategia: tomar su versión, luego regenerar.
SETUP: .gitattributes: package-lock.json merge=theirs yarn.lock merge=theirs Pipfile.lock merge=theirs
.gitconfig:
[merge "theirs"]
driver = git merge-file --theirs %O %A %B
WORKFLOW: 1. Merge automático (toma versión de main) 2. npm install (regenera lockfile correcto) 3. Commit si cambió
STRATEGY 3: UNION ────────────────────────────────────────────────────────────────────────────
CONCEPTO: En conflicto: COMBINAR ambas versiones. Sin conflict markers. Concatenar cambios.
CASO DE USO: CHANGELOG, CONTRIBUTORS, listas
ESCENARIO: CHANGELOG.md ────────────────────────────────────────────────────────────────────────────
BASE: # Changelog - v1.0.0: Initial release
Branch A añade: # Changelog - v1.1.0: Add reverb effect - v1.0.0: Initial release
Branch B añade: # Changelog - v1.1.0: Fix audio glitch - v1.0.0: Initial release
❌ SIN UNION MERGE: # Changelog <<<<<<< HEAD - v1.1.0: Add reverb effect ======= - v1.1.0: Fix audio glitch >>>>>>> branch-b - v1.0.0: Initial release
CONFLICTO! Resolver manualmente.
✅ CON UNION MERGE: # Changelog - v1.1.0: Add reverb effect - v1.1.0: Fix audio glitch - v1.0.0: Initial release
¡Automático! Ambas entradas preservadas.
SETUP: .gitattributes: CHANGELOG.md merge=union CONTRIBUTORS.md merge=union docs/releases/* merge=union
.gitconfig:
[merge "union"]
driver = git merge-file --union %O %A %B
NOTA: Union puede crear duplicados o orden incorrecto. Pero es MEJOR que conflicto manual cada vez. Post-process manual solo si necesario.
STRATEGY 4: CUSTOM DRIVER ────────────────────────────────────────────────────────────────────────────
CONCEPTO: Tu script completamente personalizado. Lógica domain-specific.
EJEMPLO: Merging database migrations
PROBLEMA: • Branch A añade migration 005_add_users_table.sql • Branch B añade migration 005_add_products_table.sql • Mismo número! Conflicto.
SCRIPT: migrations-merge.sh ────────────────────────────────────────────────────────────────────────────
!/bin/bash¶
Custom merge for database migrations¶
Renumerar automáticamente¶
$1 = base, $2 = ours, $3 = theirs¶
Lógica custom:¶
1. Detectar numero de migration¶
2. Si duplicado, re-numerar el más nuevo¶
3. Combinar en orden correcto¶
(Pseudo-code conceptual)¶
.gitattributes: db/migrations/* merge=migrations
.gitconfig: [merge "migrations"] driver = ./scripts/migrations-merge.sh %O %A %B
CASOS DE USO VISUALIZADOS ────────────────────────────────────────────────────────────────────────────
╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 📋 CASO 1: CHANGELOG.md (UNION) ║ ║ ════════════════════════════ ║ ║ ║ ║ Branch A: Branch B: ║ ║ - Fix bug #123 - Add feature X ║ ║ ║ ║ ❌ DEFAULT: Conflict ║ ║ ✅ UNION: Combine both ║ ║ ║ ║ Result: ║ ║ - Add feature X ║ ║ - Fix bug #123 ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 📋 CASO 2: .env.local (OURS) ║ ║ ═══════════════════════ ║ ║ ║ ║ Yours: Main: ║ ║ DB_HOST=localhost DB_HOST=prod.server ║ ║ API_KEY=dev_key API_KEY=prod_key ║ ║ ║ ║ ❌ DEFAULT: Conflict (no quieres prod keys!) ║ ║ ✅ OURS: Keep your local config ║ ║ ║ ║ Result: ║ ║ DB_HOST=localhost ← Protected ║ ║ API_KEY=dev_key ← Protected ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 📋 CASO 3: package-lock.json (THEIRS + regenerate) ║ ║ ═══════════════════════════════════════════ ║ ║ ║ ║ Yours: Main: ║ ║ lodash: 4.17.20 lodash: 4.17.21 ║ ║ (100+ lines diff) (different dependency tree) ║ ║ ║ ║ ❌ DEFAULT: Massive conflict (tedious to resolve) ║ ║ ✅ THEIRS: Take main's version ║ ║ THEN: npm install (regenerates correctly) ║ ║ ║ ║ Result: ║ ║ Lockfile reflects actual dependencies ║ ║ No manual conflict resolution ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
═══════════════════════════════════════════════════════════════════════════════ 🎨 CAPÍTULO 5: LINGUIST - ESTADÍSTICAS HONESTAS ═══════════════════════════════════════════════════════════════════════════════
🔷 EL PROBLEMA DE LA CLASIFICACIÓN ────────────────────────────────────────────────────────────────────────────
GitHub (y otros platforms) muestran "Language Stats" en cada repo.
EJEMPLO: ██████████████████████████ JavaScript 95% ██ C++ 4% █ Other 1%
Esto se usa para: ├─ Búsqueda de repos por lenguaje ├─ Trending repos por lenguaje ├─ Indicar qué tipo de proyecto es └─ SEO y discoverability
PERO... la detección automática es INGENUA.
📊 REPOSITORY LANGUAGE DETECTION NAIVE ────────────────────────────────────────────────────────────────────────────
ALGORITMO SIMPLE: 1. Contar bytes por extensión 2. Agrupar por lenguaje 3. Calcular porcentajes
PROBLEMA:
REPO REAL: src/ main.cpp 10 KB audio_engine.cpp 50 KB dsp.cpp 30 KB node_modules/ (1000 packages) 50 MB build/ generated.cpp 5 MB
STATS REPORTADOS: JavaScript: 95% ← node_modules! C++: 4% Other: 1%
ESTO ES FALSO. El proyecto ES un proyecto C++. Pero stats dicen JavaScript porque node_modules contamina.
CONSECUENCIAS: ────────────────────────────────────────────────────────────────────────────
❌ Repo aparece en búsquedas incorrectas ❌ Developers buscan "C++ audio" no lo encuentran ❌ GitHub language bar misleading ❌ Trending lists incorrectos ❌ Percepción del proyecto incorrecta
SOLUCIÓN: LINGUIST ATTRIBUTES ────────────────────────────────────────────────────────────────────────────
GitHub usa librería llamada "Linguist" para detección. Linguist RESPETA .gitattributes.
ATRIBUTOS LINGUIST ────────────────────────────────────────────────────────────────────────────
╔═══════════════════════════════════════════════════════════════════════════╗
║ LINGUIST ATTRIBUTES ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ║
║ ATRIBUTO │ EFECTO │ CASO DE USO ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ║
║ linguist-vendored │ Excluir de stats │ node_modules/ ║
║ │ Colapsar en UI │ vendor/ ║
║ │ │ third_party/ ║
║ ║
║ linguist-generated │ Excluir de stats │ Protobuf builds ║
║ │ Marcar auto-gen │ Parser outputs ║
║ │ │ build/ ║
║ ║
║ linguist-documentation │ Contar como docs │ README.md ║
║ │ No como código │ docs/ ║
║ │ │ Wikis ║
║ ║
║ linguist-language=
TRANSFORMACIÓN VISUAL ────────────────────────────────────────────────────────────────────────────
📊 ANTES DE LINGUIST ATTRIBUTES ────────────────────────────────────────────────────────────────────────────
Repo structure: src/.cpp 100 KB (tu código) node_modules/**/.js 50 MB (dependencias) docs/.md 500 KB (documentación) build/.cpp 5 MB (generado)
GitHub muestra: ████████████████████████ JavaScript 89.5% ███ C++ (generated) 8.9% ██ Markdown 0.9% █ C++ (real) 0.7%
HORRIBLE. Completamente misleading.
📊 DESPUÉS DE LINGUIST ATTRIBUTES ────────────────────────────────────────────────────────────────────────────
.gitattributes: node_modules/** linguist-vendored build/** linguist-generated docs/** linguist-documentation *.md linguist-documentation
GitHub muestra: ████████████████████████ C++ 89% ████ CMake 8% ██ Shell 2% █ Python 1%
¡CORRECTO! Refleja el código REAL del proyecto.
EJEMPLO COMPLETO: AUDIOLAB LINGUIST CONFIG ────────────────────────────────────────────────────────────────────────────
.gitattributes:
════════════════════════════════════════¶
VENDORED CODE (3rd party)¶
════════════════════════════════════════¶
node_modules/** linguist-vendored vcpkg/** linguist-vendored third_party/** linguist-vendored vendor/** linguist-vendored external/** linguist-vendored
════════════════════════════════════════¶
GENERATED CODE¶
════════════════════════════════════════¶
build/** linguist-generated .pb.h linguist-generated *.pb.cc linguist-generated *_generated. linguist-generated cmake-build-/* linguist-generated
════════════════════════════════════════¶
DOCUMENTATION¶
════════════════════════════════════════¶
docs/** linguist-documentation *.md linguist-documentation *.rst linguist-documentation *.txt linguist-documentation
════════════════════════════════════════¶
LANGUAGE OVERRIDES¶
════════════════════════════════════════¶
CMake files should count¶
CMakeLists.txt linguist-language=CMake *.cmake linguist-language=CMake
Shader files¶
*.glsl linguist-language=GLSL *.vert linguist-language=GLSL *.frag linguist-language=GLSL
RESULTADO: Accurate representation de tu proyecto!
═══════════════════════════════════════════════════════════════════════════════ 🔄 CAPÍTULO 6: FILTERS - TRANSFORMACIÓN EN TIEMPO REAL ═══════════════════════════════════════════════════════════════════════════════
🔷 CONCEPTO DE FILTER PIPELINE ────────────────────────────────────────────────────────────────────────────
Filters son la feature MÁS PODEROSA de .gitattributes. Permiten TRANSFORMAR archivos durante commit/checkout.
🎭 SMUDGE & CLEAN FILTERS ────────────────────────────────────────────────────────────────────────────
┌─────────────────────┐
│ WORKING DIRECTORY │
│ (Developer's view) │
└──────────┬──────────┘
│
SMUDGE FILTER
(Checkout)
│
▼
┌─────────────────────┐
│ GIT REPOSITORY │
│ (Stored form) │
└──────────┬──────────┘
│
CLEAN FILTER
(Commit)
│
▼
┌─────────────────────┐
│ WORKING DIRECTORY │
│ (Developer's view) │
└─────────────────────┘
NOMENCLATURA: CLEAN = "Limpiar" para almacenamiento Working → Repo Commit direction
SMUDGE = "Ensuciar" para trabajo
Repo → Working
Checkout direction
EJEMPLO CONCEPTUAL: ENCRYPTION FILTER ────────────────────────────────────────────────────────────────────────────
ARCHIVO: secrets.env
WORKING DIRECTORY (lo que developer ve): API_KEY=abc123xyz DB_PASSWORD=supersecret JWT_SECRET=verysecure
CLEAN FILTER (durante commit): Input: API_KEY=abc123xyz... Process: GPG encrypt Output: -----BEGIN PGP MESSAGE----- hQEMA... (encrypted blob) -----END PGP MESSAGE-----
GIT REPOSITORY (almacenado): -----BEGIN PGP MESSAGE----- hQEMA... -----END PGP MESSAGE-----
SMUDGE FILTER (durante checkout): Input: -----BEGIN PGP MESSAGE-----... Process: GPG decrypt (requiere key) Output: API_KEY=abc123xyz DB_PASSWORD=supersecret
RESULTADO: • Developer ve archivo PLAIN • Repo almacena ENCRYPTED • History pública no expone secretos • Transparente en workflow
ARQUITECTURA CONCEPTUAL ────────────────────────────────────────────────────────────────────────────
🏗️ FILTER SYSTEM DESIGN ────────────────────────────────────────────────────────────────────────────
┌──────────────────────────────────────────────────────────────────────────┐ │ │ │ 📋 STEP 1: DECLARATION │ │ ─────────────────────── │ │ │ │ .gitattributes: │ │ secrets.env filter=encrypt │ │ │ │ Asocia archivo con filter llamado "encrypt" │ │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ ⚙️ STEP 2: CONFIGURATION │ │ ────────────────────── │ │ │ │ .gitconfig (local o global): │ │ [filter "encrypt"] │ │ clean = gpg -e -r you@email.com │ │ smudge = gpg -d │ │ required = true │ │ │ │ Define comandos para transformación │ │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ 🔄 STEP 3: BIDIRECTIONAL FLOW │ │ ─────────────────────────── │ │ │ │ Ambos filtros reciben input via STDIN, output via STDOUT │ │ │ │ CLEAN: │ │ plaintext → [gpg -e] → ciphertext │ │ │ │ SMUDGE: │ │ ciphertext → [gpg -d] → plaintext │ │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ │ │ 🎯 STEP 4: TRANSPARENCY │ │ ──────────────────── │ │ │ │ Developer workflow NO cambia: │ │ • Edita archivo normal │ │ • git add (clean automático) │ │ • git commit │ │ • git push │ │ │ │ Filter ejecuta INVISIBLE en background │ │ │ └──────────────────────────────────────────────────────────────────────────┘
CASOS DE USO REALES ────────────────────────────────────────────────────────────────────────────
╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 🔐 CASO 1: GIT LFS (LARGE FILE STORAGE) ║ ║ ════════════════════════════════════════ ║ ║ ║ ║ PROBLEMA: ║ ║ • Audio samples de 100+ MB ║ ║ • Repo se vuelve gigante ║ ║ • Clone takes forever ║ ║ • History bloated ║ ║ ║ ║ SOLUCIÓN: Git LFS Filter ║ ║ ║ ║ CLEAN FILTER (commit): ║ ║ ┌────────────────────────────────────┐ ║ ║ │ Input: sample.wav (100 MB) │ ║ ║ │ Process: │ ║ ║ │ 1. Upload file to LFS server │ ║ ║ │ 2. Generate pointer file │ ║ ║ │ Output: Pointer (200 bytes) │ ║ ║ │ │ ║ ║ │ version https://git-lfs... │ ║ ║ │ oid sha256:abc123... │ ║ ║ │ size 104857600 │ ║ ║ └────────────────────────────────────┘ ║ ║ ║ ║ SMUDGE FILTER (checkout): ║ ║ ┌────────────────────────────────────┐ ║ ║ │ Input: Pointer file │ ║ ║ │ Process: │ ║ ║ │ 1. Read oid from pointer │ ║ ║ │ 2. Download from LFS server │ ║ ║ │ 3. Verify checksum │ ║ ║ │ Output: sample.wav (100 MB) │ ║ ║ └────────────────────────────────────┘ ║ ║ ║ ║ RESULTADO: ║ ║ • Repo stays small (only pointers) ║ ║ • Clone fast (download files on demand) ║ ║ • Large files versioned separately ║ ║ ║ ║ SETUP: ║ ║ .gitattributes: ║ ║ .wav filter=lfs diff=lfs merge=lfs -text ║ ║ *.aiff filter=lfs diff=lfs merge=lfs -text ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 🔒 CASO 2: ENCRYPTION FILTER ║ ║ ═══════════════════════ ║ ║ ║ ║ PROBLEMA: ║ ║ • Necesitas commitear secretos ║ ║ • Repo puede ser público eventualmente ║ ║ • No quieres secretos en history ║ ║ ║ ║ SOLUCIÓN: GPG Encryption Filter ║ ║ ║ ║ CLEAN FILTER: ║ ║ ┌────────────────────────────────────┐ ║ ║ │ Input: │ ║ ║ │ API_KEY=secret123 │ ║ ║ │ DB_PASS=password │ ║ ║ │ │ ║ ║ │ Process: gpg -e │ ║ ║ │ │ ║ ║ │ Output: │ ║ ║ │ -----BEGIN PGP MESSAGE----- │ ║ ║ │ hQEMA... [encrypted blob] │ ║ ║ │ -----END PGP MESSAGE----- │ ║ ║ └────────────────────────────────────┘ ║ ║ ║ ║ SMUDGE FILTER: ║ ║ ┌────────────────────────────────────┐ ║ ║ │ Input: [encrypted blob] │ ║ ║ │ Process: gpg -d (requires key!) │ ║ ║ │ Output: Plain secrets │ ║ ║ └────────────────────────────────────┘ ║ ║ ║ ║ SETUP: ║ ║ .gitattributes: ║ ║ .env.production filter=encrypt ║ ║ secrets/* filter=encrypt ║ ║ ║ ║ .gitconfig: ║ ║ [filter "encrypt"] ║ ║ clean = gpg -e -r team@company.com ║ ║ smudge = gpg -d ║ ║ required = true ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 📝 CASO 3: KEYWORD EXPANSION ║ ║ ═══════════════════════ ║ ║ ║ ║ PROBLEMA: ║ ║ • Quieres metadata en archivos ║ ║ • Commit hash, date, version, etc. ║ ║ • Debe actualizarse automático ║ ║ ║ ║ SOLUCIÓN: Keyword Expansion Filter ║ ║ ║ ║ CLEAN FILTER (strip dynamic content): ║ ║ ┌────────────────────────────────────┐ ║ ║ │ Input: │ ║ ║ │ // $Id: abc123def... $ │ ║ ║ │ // $Date: 2024-01-15 $ │ ║ ║ │ │ ║ ║ │ Output: │ ║ ║ │ // \(Id\) │ ║ ║ │ // \(Date\) │ ║ ║ └────────────────────────────────────┘ ║ ║ ║ ║ SMUDGE FILTER (expand with current info): ║ ║ ┌────────────────────────────────────┐ ║ ║ │ Input: // \(Id\) │ ║ ║ │ │ ║ ║ │ Process: │ ║ ║ │ Get current commit hash │ ║ ║ │ Get current date │ ║ ║ │ │ ║ ║ │ Output: │ ║ ║ │ // $Id: [current hash] $ │ ║ ║ │ // $Date: [current date] $ │ ║ ║ └────────────────────────────────────┘ ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
CONSIDERACIONES IMPORTANTES ────────────────────────────────────────────────────────────────────────────
⚠️ PERFORMANCE: • Filters ejecutan en CADA checkout/commit • Filters lentos = workflow lento • Evita operaciones pesadas si posible
⚠️ PORTABILITY: • Filters requieren tools instalados • Otros developers necesitan setup • Documenta requerimientos
⚠️ REQUIRED FLAG: [filter "name"] required = true
• Si true: falla si filter no disponible
• Si false: Git continúa sin filter (peligroso!)
⚠️ REVERSIBILITY: • CLEAN y SMUDGE deben ser INVERSOS • clean(smudge(X)) = X • smudge(clean(X)) = X • Si no: corrupción de datos
═══════════════════════════════════════════════════════════════════════════════ 🎯 CAPÍTULO 7: AUDIOLAB ATTRIBUTES STRATEGY ═══════════════════════════════════════════════════════════════════════════════
🔷 CONFIGURACIÓN COMPLETA RECOMENDADA ────────────────────────────────────────────────────────────────────────────
Basado en todos los conceptos anteriores, aquí está la configuración COMPLETA recomendada para AudioLab.
╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 📋 AUDIOLAB .gitattributes MASTER CONFIG ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
══════════════════════════════════════════════════════════════════════════¶
DEFAULT BEHAVIOR¶
══════════════════════════════════════════════════════════════════════════¶
Auto detect text files and normalize line endings to LF in repo¶
- text=auto
══════════════════════════════════════════════════════════════════════════¶
SOURCE CODE: ALWAYS LF¶
══════════════════════════════════════════════════════════════════════════¶
C/C++ source¶
*.c text eol=lf *.cpp text eol=lf *.cxx text eol=lf *.cc text eol=lf *.h text eol=lf *.hpp text eol=lf *.hxx text eol=lf
CMake¶
*.cmake text eol=lf CMakeLists.txt text eol=lf
Python¶
*.py text eol=lf
JavaScript/TypeScript¶
*.js text eol=lf *.ts text eol=lf *.jsx text eol=lf *.tsx text eol=lf
Rust¶
*.rs text eol=lf
Assembly¶
*.asm text eol=lf *.s text eol=lf
══════════════════════════════════════════════════════════════════════════¶
SCRIPTS: PLATFORM-SPECIFIC LF/CRLF¶
══════════════════════════════════════════════════════════════════════════¶
Unix scripts (MUST be LF)¶
*.sh text eol=lf *.bash text eol=lf *.fish text eol=lf Makefile text eol=lf makefile text eol=lf
Windows scripts (MUST be CRLF)¶
*.bat text eol=crlf *.cmd text eol=crlf *.ps1 text eol=crlf
══════════════════════════════════════════════════════════════════════════¶
CONFIG FILES: ALWAYS LF¶
══════════════════════════════════════════════════════════════════════════¶
*.yml text eol=lf *.yaml text eol=lf *.json text eol=lf *.toml text eol=lf *.ini text eol=lf *.xml text eol=lf *.conf text eol=lf *.config text eol=lf
Git config files¶
.gitignore text eol=lf .gitattributes text eol=lf .gitmodules text eol=lf
Docker¶
Dockerfile text eol=lf *.dockerfile text eol=lf docker-compose.yml text eol=lf
══════════════════════════════════════════════════════════════════════════¶
DOCUMENTATION: ALWAYS LF¶
══════════════════════════════════════════════════════════════════════════¶
*.md text eol=lf *.markdown text eol=lf *.rst text eol=lf *.txt text eol=lf *.adoc text eol=lf LICENSE text eol=lf README text eol=lf
══════════════════════════════════════════════════════════════════════════¶
BINARY FILES: NO CONVERSION¶
══════════════════════════════════════════════════════════════════════════¶
Audio files¶
*.wav binary *.aiff binary *.aif binary *.flac binary *.mp3 binary *.ogg binary *.m4a binary *.wma binary *.opus binary
Images¶
*.png binary *.jpg binary *.jpeg binary *.gif binary *.bmp binary *.ico binary *.svg binary *.webp binary *.tiff binary *.tif binary
Video¶
*.mp4 binary *.mov binary *.avi binary *.mkv binary *.webm binary
Archives¶
*.zip binary *.tar binary *.gz binary *.tgz binary *.7z binary *.rar binary *.bz2 binary
Compiled binaries¶
*.exe binary *.dll binary *.so binary *.dylib binary *.a binary *.lib binary *.o binary *.obj binary
Fonts¶
*.ttf binary *.otf binary *.woff binary *.woff2 binary *.eot binary
Other binary formats¶
*.pdf binary *.dat binary *.db binary *.sqlite binary *.pack binary
══════════════════════════════════════════════════════════════════════════¶
GIT LFS TRACKING¶
══════════════════════════════════════════════════════════════════════════¶
Large audio samples¶
samples//*.wav filter=lfs diff=lfs merge=lfs -text samples//.aiff filter=lfs diff=lfs merge=lfs -text references/**/.wav filter=lfs diff=lfs merge=lfs -text references/**/*.aiff filter=lfs diff=lfs merge=lfs -text
Large design/asset files¶
assets//*.psd filter=lfs diff=lfs merge=lfs -text assets//.ai filter=lfs diff=lfs merge=lfs -text assets/**/.sketch filter=lfs diff=lfs merge=lfs -text
Video files¶
docs//*.mp4 filter=lfs diff=lfs merge=lfs -text demos//*.mp4 filter=lfs diff=lfs merge=lfs -text
══════════════════════════════════════════════════════════════════════════¶
LINGUIST CONFIGURATION¶
══════════════════════════════════════════════════════════════════════════¶
Vendored code (exclude from language stats)¶
node_modules/** linguist-vendored vcpkg/** linguist-vendored vcpkg_installed/** linguist-vendored third_party/** linguist-vendored vendor/** linguist-vendored external/** linguist-vendored libs/** linguist-vendored
Generated code¶
build/** linguist-generated cmake-build-/* linguist-generated out/** linguist-generated dist/** linguist-generated .pb.h linguist-generated *.pb.cc linguist-generated *_generated. linguist-generated *.g.cpp linguist-generated
Documentation (count as docs, not code)¶
docs/** linguist-documentation *.md linguist-documentation *.rst linguist-documentation *.txt linguist-documentation
Language overrides¶
CMakeLists.txt linguist-language=CMake *.cmake linguist-language=CMake
Shader files (if applicable)¶
*.glsl linguist-language=GLSL *.vert linguist-language=GLSL *.frag linguist-language=GLSL *.shader linguist-language=GLSL
══════════════════════════════════════════════════════════════════════════¶
MERGE STRATEGIES¶
══════════════════════════════════════════════════════════════════════════¶
Union merge for lists (combine both versions)¶
CHANGELOG.md merge=union CONTRIBUTORS.md merge=union AUTHORS merge=union docs/releases/* merge=union
Keep ours for local configs¶
.local merge=ours *.local. merge=ours .env.local merge=ours config/*.local merge=ours
══════════════════════════════════════════════════════════════════════════¶
DIFF DRIVERS¶
══════════════════════════════════════════════════════════════════════════¶
Image metadata (if exiftool configured)¶
*.jpg diff=exif *.jpeg diff=exif *.png diff=exif
══════════════════════════════════════════════════════════════════════════¶
SPECIAL CASES¶
══════════════════════════════════════════════════════════════════════════¶
Ensure these are always treated as binary¶
*.pdb binary *.ilk binary *.pch binary *.cache binary
CONFIGURACIÓN ADICIONAL: .gitconfig ────────────────────────────────────────────────────────────────────────────
Para que algunos atributos funcionen, necesitas .gitconfig:
══════════════════════════════════════════════════════════════════════════¶
GIT CONFIG FOR AUDIOLAB¶
══════════════════════════════════════════════════════════════════════════¶
[diff "exif"] textconv = exiftool
[merge "ours"] driver = true
[merge "union"] driver = git merge-file --union %O %A %B
Git LFS (if using)¶
[filter "lfs"] clean = git-lfs clean -- %f smudge = git-lfs smudge -- %f process = git-lfs filter-process required = true
═══════════════════════════════════════════════════════════════════════════════ 💡 CAPÍTULO 8: PRINCIPIOS DE DISEÑO ═══════════════════════════════════════════════════════════════════════════════
🔷 LAS REGLAS DE ORO ────────────────────────────────────────────────────────────────────────────
Al diseñar tu .gitattributes, sigue estos principios:
╔═══════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 🎯 PRINCIPIOS DE DISEÑO ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 1️⃣ EXPLÍCITO > IMPLÍCITO ║ ║ ─────────────────────────── ║ ║ ║ ║ ❌ Malo: ║ ║ * text=auto ║ ║ (confiar solo en auto-detección) ║ ║ ║ ║ ✅ Bueno: ║ ║ * text=auto ║ ║ .cpp text eol=lf ║ ║ *.sh text eol=lf ║ ║ *.wav binary ║ ║ (declarar explícitamente tipos conocidos) ║ ║ ║ ║ RATIONALE: ║ ║ • Auto-detección puede equivocarse ║ ║ • Explícito previene sorpresas ║ ║ • Documentación clara de intenciones ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 2️⃣ CONSISTENCIA INTERNA ║ ║ ─────────────────────── ║ ║ ║ ║ ❌ Malo: ║ ║ src/.cpp text eol=lf ║ ║ tests/.cpp text eol=crlf (¿Por qué diferente?) ║ ║ ║ ║ ✅ Bueno: ║ ║ *.cpp text eol=lf ║ ║ (todos los C++ igual) ║ ║ ║ ║ RATIONALE: ║ ║ • Reduce cognitive load ║ ║ • Previene bugs sutiles ║ ║ • Fácil de entender y mantener ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 3️⃣ DOCUMENTAR DECISIONES ║ ║ ──────────────────────── ║ ║ ║ ║ ❌ Malo: ║ ║ *.pb.cc linguist-generated ║ ║ ║ ║ ✅ Bueno: ║ ║ # Protobuf generated files ║ ║ # Exclude from language stats ║ ║ *.pb.cc linguist-generated ║ ║ *.pb.h linguist-generated ║ ║ ║ ║ RATIONALE: ║ ║ • Future you/teammates necesitan contexto ║ ║ • Explica POR QUÉ, no solo QUÉ ║ ║ • Reduce confusión y re-decisión ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 4️⃣ PERFORMANCE AWARENESS ║ ║ ──────────────────────── ║ ║ ║ ║ ⚠️ Cuidado: ║ ║ *. filter=expensive-filter ║ ║ (filtering TODOS los archivos = lento) ║ ║ ║ ║ ✅ Better: ║ ║ secrets/*.env filter=encrypt ║ ║ (solo archivos que realmente necesitan) ║ ║ ║ ║ RATIONALE: ║ ║ • Filters ejecutan en cada checkout/commit ║ ║ • Slow filters = slow workflow ║ ║ • Solo filter lo que necesitas ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 5️⃣ FAIL GRACEFULLY ║ ║ ────────────────── ║ ║ ║ ║ ❌ Malo: ║ ║ [filter "custom"] ║ ║ clean = obscure-tool ║ ║ required = true ║ ║ (si obscure-tool no está, ¡todo explota!) ║ ║ ║ ║ ✅ Bueno: ║ ║ Documentar dependencies claramente ║ ║ README explains setup ║ ║ O usar required = false con warning ║ ║ ║ ║ RATIONALE: ║ ║ • Otros developers pueden no tener tools ║ ║ • Fresh clone debe ser smooth ║ ║ • Mensajes de error claros > fallo críptico ║ ║ ║ ╠═══════════════════════════════════════════════════════════════════════════╣ ║ ║ ║ 6️⃣ ORGANIZACIÓN CLARA ║ ║ ───────────────────── ║ ║ ║ ║ ❌ Malo: ║ ║ *.cpp text ║ ║ *.md linguist-documentation ║ ║ *.wav binary ║ ║ *.sh text ║ ║ (orden caótico) ║ ║ ║ ║ ✅ Bueno: ║ ║ # ══════ LINE ENDINGS ══════ ║ ║ *.cpp text eol=lf ║ ║ *.sh text eol=lf ║ ║ ║ ║ # ══════ BINARY FILES ══════ ║ ║ *.wav binary ║ ║ ║ ║ # ══════ LINGUIST ══════ ║ ║ *.md linguist-documentation ║ ║ ║ ║ RATIONALE: ║ ║ • Fácil de navegar ║ ║ • Fácil de modificar ║ ║ • Secciones claras por propósito ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝
ANTI-PATTERNS A EVITAR ────────────────────────────────────────────────────────────────────────────
❌ ANTI-PATTERN 1: Wildcards demasiado amplios ────────────────────────────────────────────────────────────────────────────
MALO: * binary
PROBLEMA: • Marca TODO como binario • Sobrescribe otras reglas • Pérdida de diff en archivos texto
❌ ANTI-PATTERN 2: Configuración contradictoria ────────────────────────────────────────────────────────────────────────────
MALO: .cpp text eol=lf src/.cpp text eol=crlf
PROBLEMA: • Confusión sobre qué aplica • Comportamiento inconsistente • Última regla gana (no obvio)
❌ ANTI-PATTERN 3: Olvidar -text en LFS ────────────────────────────────────────────────────────────────────────────
MALO: *.wav filter=lfs diff=lfs merge=lfs
CORRECTO: *.wav filter=lfs diff=lfs merge=lfs -text
PROBLEMA: • Sin -text, Git puede intentar conversión line ending • Corrompe binarios • LFS SIEMPRE necesita -text
❌ ANTI-PATTERN 4: No version .gitattributes ────────────────────────────────────────────────────────────────────────────
MALO: .gitattributes en .gitignore
CORRECTO: .gitattributes commiteado en repo
PROBLEMA: • Attributes son PARTE del proyecto • Deben compartirse entre developers • Sin ellos, comportamiento inconsistente
═══════════════════════════════════════════════════════════════════════════════ 🎓 CONCLUSIÓN: CONTROL MICROSCÓPICO DEL REPOSITORIO ═══════════════════════════════════════════════════════════════════════════════
🎯 SÍNTESIS CONCEPTUAL ────────────────────────────────────────────────────────────────────────────
┌──────────────────┐
│ .gitignore │
│ │
│ "¿QUÉ trackear?"│
└────────┬─────────┘
│
Decisión BINARIA
│
┌──────┴──────┐
│ │
SÍ NO
│ │
Track Ignore
│
│
▼
┌──────────────────┐
│ .gitattributes │
│ │
│ "¿CÓMO trackear?"│
└────────┬─────────┘
│
Decisión ESPECTRO
│
┌────────────────────┼────────────────────┐
│ │ │
Como TEXTO Como BINARIO Con TRANSFORM
│ │ │
┌───┴───┐ ┌────┴────┐ ┌────┴────┐
│ │ │ │ │ │
LF CRLF No diff Metadata Encrypt LFS │ │ │ driver │ │ Merge Union Ours Theirs Clean Smudge driver driver │ │ Transform Transform
JUNTOS: CONTROL TOTAL ────────────────────────────────────────────────────────────────────────────
.gitignore + .gitattributes = Repo profesional
├─ .gitignore: │ ├─ Qué NO trackear │ ├─ Build artifacts out │ ├─ Secrets out │ └─ Temp files out │ └─ .gitattributes: ├─ CÓMO trackear lo trackeado ├─ Line endings normalizados ├─ Diffs útiles en binarios ├─ Merges automáticos inteligentes ├─ Stats honestas de lenguajes └─ Transformaciones transparentes
BENEFICIOS FINALES ────────────────────────────────────────────────────────────────────────────
✅ COLABORACIÓN SIN FRICCIÓN • Windows + Linux + Mac developers en armonía • No más "todo el archivo cambió" • No más merge conflicts artificiales
✅ REPO PROFESIONAL • Stats correctas en GitHub • Diffs útiles para todos tipos de archivo • Large files manejados eficientemente
✅ WORKFLOW INTELIGENTE • Merges resuelven automáticamente • Configs locales protegidos • CHANGELOGs sin conflictos
✅ SEGURIDAD TRANSPARENTE • Secretos encriptados automáticamente • Large files en LFS sin pensar • Filters invisibles al developer
FILOSOFÍA FINAL ────────────────────────────────────────────────────────────────────────────
┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ .gitattributes es INVERSIÓN, no overhead. │ │ │ │ 10 minutos configurando atributos │ │ = HORAS ahorradas en conflictos, confusión, y frustración │ │ = DÍAS ahorrados en carrera del proyecto │ │ │ │ Un repo bien configurado es PLACER de usar. │ │ Un repo mal configurado es DOLOR constante. │ │ │ │ Invierte el tiempo ahora. Tu future self agradecerá. │ │ │ └─────────────────────────────────────────────────────────────────────────┘
EL VIAJE DE MADUREZ ────────────────────────────────────────────────────────────────────────────
NIVEL 1: Sin .gitattributes ├─ Conflictos misteriosos ├─ Diffs ruidosos ├─ Stats incorrectas └─ "Git es difícil"
NIVEL 2: Básico (solo line endings) ├─ * text=auto ├─ Menos conflictos └─ "Git es... mejor"
NIVEL 3: Intermedio (+ binarios + linguist) ├─ Binarios declarados ├─ Vendored code excluido ├─ Stats correctas └─ "Git tiene sentido"
NIVEL 4: Avanzado (+ merge strategies) ├─ Merges automáticos ├─ Custom drivers ├─ Workflow smooth └─ "Git es poderoso"
NIVEL 5: Maestría (+ filters) ├─ LFS configurado ├─ Encryption transparente ├─ Custom transformations └─ "Git es extensión de mi pensamiento"
Tu objetivo: Llegar a NIVEL 4 mínimo. NIVEL 5 para proyectos enterprise.
═══════════════════════════════════════════════════════════════════════════════ FIN DEL DOCUMENTO ═══════════════════════════════════════════════════════════════════════════════
"Control microscópico es control total. .gitattributes es el microscopio." 🔬