Skip to content

╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ 🎨 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 \r\n int main() {\r\n return 0;\r\n }\r\n

Linux: #include \n int main() {\n return 0;\n }\n

GIT DIFF SIN NORMALIZACIÓN: - #include \r\n + #include \n - int main() {\r\n + int main() {\n - return 0;\r\n + return 0;\n - }\r\n + }\n

¡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= │ Override detect │ Força tipo ║ ║ │ Forzar lenguaje │ Archivos ambiguos ║ ║ ║ ║ linguist-detectable=false │ Nunca en stats │ Configs ║ ║ │ Ignorar completo │ Build scripts ║ ║ ║ ║ linguist-detectable=true │ Forzar inclusión │ Override defaults ║ ║ │ Contar siempre │ Lenguajes raros ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════════════╝

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." 🔬