devAlice
← Multi-OS

Finales de línea en Git — acaba con el infierno de CRLF entre Mac/Linux y Windows

Un único .gitattributes garantiza los mismos finales de línea en todas partes. La trampa de core.autocrlf y la manera correcta de evitarla.

Si el mismo repositorio se comparte entre Mac/Linux y Windows, casi con certeza te tropezarás con esto: CRLF vs LF. Las diferencias en un PR explotan con «1000 líneas cambiadas cuando no cambié nada», los scripts de shell fallan con \r: command not found y Python lanza SyntaxError: invalid character.

Creo que los finales de línea son uno de los problemas más frustrantes precisamente porque son invisibles. No es que el código sea incorrecto — en lugar de un error claro, ves un diff gigante o un script que falla por una razón que no parece tener sentido. Por eso .gitattributes es la solución correcta: no depende de que cada desarrollador configure su entorno, porque está en el repositorio.

Esta guía muestra cómo garantizar los mismos finales de línea en todos los sistemas operativos mediante .gitattributes. Configúralo una vez y olvídate del problema.

TL;DR

  1. Coloca .gitattributes en la raíz del repositorio con * text=auto eol=lf
  2. Desactiva core.autocrlf.gitattributes lo sobreescribe de todos modos
  3. Fuerza LF en los scripts de shell de Windows (*.sh text eol=lf)
  4. Fuerza CRLF en los archivos batch de Windows (*.bat text eol=crlf)
  5. Tras unificar, ejecuta git add --renormalize . para realinear los archivos existentes

Requisitos previos

  • Git 2.10+ (compatible con eol= en .gitattributes)
  • Repositorio usado simultáneamente por usuarios de Mac y Windows

1. El problema — por qué CRLF no desaparece

1.1 Finales de línea por defecto según el SO

SOFinal de líneaHistoria
Unix · macOS · LinuxLF (\n, 0x0A)Unix 1970+
WindowsCRLF (\r\n, 0x0D 0x0A)DOS 1980+
Mac OS 9 clásicoCR (\r)1984–2001

1.2 La conversión automática de git — core.autocrlf

ValorAl hacer checkoutAl hacer commit
true (por defecto en Windows)LF → CRLFCRLF → LF
input (recomendado en Unix)ningunoCRLF → LF
falseningunoninguno

El problema: cada usuario tiene ajustes distintos, las máquinas nuevas varían y las conversiones silenciosas provocan los clásicos incidentes de «no cambié nada pero aparecen diferencias».

1.3 La solución correcta es .gitattributes

Colócalo dentro del repositorio para imponer las mismas reglas a todo el equipo. Tiene prioridad sobre core.autocrlf.


2. Línea base de .gitattributes — 5 min

.gitattributes en la raíz del repositorio:

# Por defecto — almacena todos los archivos de texto como LF; detección automática de texto
* text=auto eol=lf
 
# Texto explícito — fuerza LF
*.c          text eol=lf
*.cc         text eol=lf
*.cpp        text eol=lf
*.h          text eol=lf
*.hpp        text eol=lf
*.cs         text eol=lf
*.go         text eol=lf
*.java       text eol=lf
*.kt         text eol=lf
*.py         text eol=lf
*.rb         text eol=lf
*.rs         text eol=lf
*.swift      text eol=lf
*.ts         text eol=lf
*.tsx        text eol=lf
*.js         text eol=lf
*.jsx        text eol=lf
*.mjs        text eol=lf
*.cjs        text eol=lf
*.json       text eol=lf
*.jsonc      text eol=lf
*.yml        text eol=lf
*.yaml       text eol=lf
*.toml       text eol=lf
*.md         text eol=lf
*.mdx        text eol=lf
*.txt        text eol=lf
*.html       text eol=lf
*.css        text eol=lf
*.scss       text eol=lf
*.svg        text eol=lf
*.xml        text eol=lf
*.sql        text eol=lf
 
# Scripts de shell — LF (CRLF los rompe)
*.sh         text eol=lf
*.bash       text eol=lf
*.zsh        text eol=lf
*.fish       text eol=lf
Dockerfile*  text eol=lf
Makefile*    text eol=lf
 
# Solo para Windows — CRLF
*.bat        text eol=crlf
*.cmd        text eol=crlf
*.ps1        text eol=crlf
*.psm1       text eol=crlf
 
# Binarios — sin conversión
*.png        binary
*.jpg        binary
*.jpeg       binary
*.gif        binary
*.ico        binary
*.webp       binary
*.avif       binary
*.pdf        binary
*.zip        binary
*.gz         binary
*.tar        binary
*.7z         binary
*.exe        binary
*.dll        binary
*.so         binary
*.dylib      binary
*.woff       binary
*.woff2      binary
*.ttf        binary
*.otf        binary
*.mp3        binary
*.mp4        binary
*.mov        binary
 
# Diffs silenciosos — texto pero inútil comparar
*.lock       text eol=lf -diff
pnpm-lock.yaml text eol=lf -diff
package-lock.json text eol=lf -diff
yarn.lock    text eol=lf -diff
Cargo.lock   text eol=lf -diff

⚠️ Los archivos PowerShell (*.ps1) requieren CRLF. Windows PowerShell a veces rechaza los archivos .ps1 que solo tienen LF.


3. Aplicar a un repositorio existente — 5 min

Añade .gitattributes y haz commit; después realinea los finales de línea ya incorrectos.

3.1 renormalize

# En la raíz del repositorio
git add .gitattributes
git commit -m "feat: add .gitattributes baseline"
 
# Realinea todos los archivos según .gitattributes
git add --renormalize .
git status   # ver qué cambió
git commit -m "chore: renormalize line endings"

--renormalize deja el árbol de trabajo intacto; solo reescribe el índice. El resto de colaboradores se alineará automáticamente al hacer pull.

3.2 Repositorios grandes / colaboración activa

--renormalize genera un PR enorme. Coordina el equipo:

  • Abre una ventana de tiempo («parar los pushes a partir de X»)
  • Mergea el renormalize y pide a todos que hagan git pull una vez
  • O usa git rebase origin/main en las ramas de funcionalidades para absorber el cambio

3.3 Repositorios pequeños / trabajo en solitario

Basta con mergearlo en un PR.


4. Configuración global del usuario — Domar core.autocrlf — 1 min

.gitattributes hace irrelevante core.autocrlf en ese repositorio. Para repositorios heredados o externos sin .gitattributes, sí importa. Recomendación:

# macOS / Linux
git config --global core.autocrlf input
# Sin conversión al hacer checkout; CRLF → LF al hacer commit
 
# Windows
git config --global core.autocrlf input
# Incluso en Windows, prefiere input — tus archivos hacen commit como LF. El CRLF ajeno se normaliza.
 
# O confía solo en .gitattributes
git config --global core.autocrlf false

Por qué input incluso en Windows: tu salida no depende de los valores por defecto del SO; siempre será LF.

core.eol

git config --global core.eol lf

Final de línea por defecto al hacer checkout. La directiva eol= en .gitattributes tiene prioridad.


5. Ajustes del editor — Alinea tu IDE

VS Code (.vscode/settings.json o ajustes de usuario):

{
  "files.eol": "\n",
  "files.insertFinalNewline": true,
  "files.trimTrailingWhitespace": true
}

JetBrains: Settings → Editor → Code Style → Line separator → Unix and macOS (\n).

Vim:

" ~/.vimrc
set fileformats=unix,dos
set fileformat=unix

6. Verificación — ¿Son realmente LF?

6.1 Un solo archivo

# macOS/Linux
file scripts/build.sh
# ASCII text  ← OK
file scripts/build.sh | grep -q CRLF && echo has CRLF
 
# O también
od -c scripts/build.sh | head -3
# Las líneas terminan solo en \n. Si ves \r \n, hay CRLF.

6.2 Todo el repositorio

# Busca archivos con CRLF
git grep --cached -lI $'\r' -- ':!*.bat' ':!*.cmd' ':!*.ps1'

-I excluye binarios. Descarta los archivos con CRLF intencional (ps1, bat).

6.3 Añadir una comprobación en CI

GitHub Actions:

- name: Check no CRLF in LF files
  run: |
    crlf=$(git grep --cached -lI $'\r' -- ':!*.bat' ':!*.cmd' ':!*.ps1' || true)
    if [ -n "$crlf" ]; then
      echo "CRLF found in LF-expected files:"
      echo "$crlf"
      exit 1
    fi

7. Problemas habituales y soluciones

7.1 .sh falla en Windows con ^M: command not found

Script de shell con CRLF en el repositorio. Solución rápida (no permanente):

dos2unix scripts/build.sh
# O
sed -i 's/\r$//' scripts/build.sh

Solución definitiva: *.sh text eol=lf en .gitattributes + git add --renormalize.

7.2 Python SyntaxError: invalid character

Archivo .py con CRLF + BOM UTF-8. Fuerza LF en .gitattributes + elimina el BOM en tu editor.

sed -i '1s/^\xEF\xBB\xBF//' file.py

7.3 .gitattributes no se aplica realmente

  • .gitattributes no está en la raíz sino en un subdirectorio: solo se aplica allí
  • Los archivos no se renormalizaron: los finales existentes permanecen (hay que ejecutar renormalize una vez)

7.4 El IDE guarda de nuevo como CRLF

Añade .editorconfig:

# .editorconfig (en la raíz del repositorio)
root = true
 
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
 
[*.{bat,cmd,ps1}]
end_of_line = crlf

VS Code, JetBrains, Vim, Sublime y otros leen .editorconfig automáticamente.

7.5 El editor de Windows añade BOM automáticamente

PowerShell Out-File usa UTF-16 BOM por defecto. Fuerza UTF-8:

$content | Out-File "file.txt" -Encoding utf8 -NoNewline
# O PowerShell 7+
$content | Out-File "file.txt" -Encoding utf8NoBOM

8. Lista de verificación para arreglar un repositorio existente

  • Añadir la línea base de .gitattributes (§2)
  • Añadir .editorconfig (§7.4)
  • git add --renormalize . + commit
  • Añadir comprobación CRLF en CI (§6.3)
  • Notificar al equipo — «Después de hacer pull, confirma git config --global core.autocrlf input»
  • Una línea en el README — «Este repositorio unifica LF. Ver .gitattributes

Siguientes pasos

Referencias

Historial de cambios

  • 2026-05-12 — Borrador inicial (devAlice M2 seed expansion)