git status muestra todos los archivos modificados en WSL

Haces un merge o un checkout con Git para Windows, vuelves a la terminal de WSL2 y git status lista cientos de archivos como modificados, aunque no editaste ninguno:

$ git status --short
 M src/app.ts
 M src/lib/formato.ts
 M README.md
 ...

Si el repositorio usa git worktree y alguno se creó desde Windows, en WSL aparece además este error:

fatal: not a git repository: //wsl.localhost/Ubuntu/home/usuario/mi-app/.git/worktrees/rama-x

Y al intentar filtrar el ruido con git status --ignore-cr-at-eol, Git responde:

error: unknown option `ignore-cr-at-eol'
usage: git status [<options>] [--] [<pathspec>...]

Los tres síntomas tienen una causa común: dos instalaciones de Git (la de Windows y la de WSL) trabajan sobre el mismo árbol de archivos. Este artículo explica cómo confirmarlo, cómo limpiar el estado sin perder cambios reales y cómo evitar que se repita.

Por qué pasa

Git para Windows y Git dentro de WSL son instalaciones independientes, cada una con sus propios archivos de configuración. Es habitual que la de Windows tenga core.autocrlf=true (el libro Pro Git recomienda ese valor en Windows) y que la de WSL no tenga ninguna conversión configurada. Compruébalo ejecutando el mismo comando en ambos lados:

git config --show-origin --get-all core.autocrlf

En Windows puede devolver algo como file:C:/Program Files/Git/etc/gitconfig true; en WSL, ninguna línea.

Según la documentación de Git, core.autocrlf=true equivale a poner text=auto en todos los archivos y core.eol=crlf: al hacer checkout o merge, Git para Windows escribe los archivos con CRLF y los convierte a LF al guardarlos en el índice, que sigue teniendo LF. Git en WSL, sin conversión, compara los bytes del disco (CRLF) con los del índice (LF) y concluye que el archivo cambió. Solo se ven afectados los archivos que esa operación reescribió: el número (100, 400...) depende del merge o del checkout, no del tamaño del repositorio.

Para verlo, git ls-files --eol muestra el fin de línea del índice (i/) y del directorio de trabajo (w/). Los archivos fantasma aparecen como i/lf w/crlf.

La opción --ignore-cr-at-eol pertenece a git diff (ignora el retorno de carro al final de la línea al comparar), no a git status; por eso este último responde unknown option.

El error de los worktrees tiene un origen parecido. Un worktree enlazado tiene un archivo .git que apunta a un directorio privado dentro del .git del repositorio principal (.git/worktrees/<nombre>), y ese directorio guarda a su vez la ruta del worktree. Si el worktree lo creó Git para Windows, esas rutas se escribieron tal como Windows ve el repositorio (una ruta UNC como //wsl.localhost/Ubuntu/... o una ruta de unidad como C:/...), y Git en Linux no sabe resolverlas.

Solución

  1. Confirma que son cambios fantasma y separa los reales. Desde WSL, en la raíz del repositorio:

    git diff --ignore-cr-at-eol --stat
    git diff --ignore-cr-at-eol --name-only
    git ls-files --eol | grep 'w/crlf'

    Si el primer comando no imprime nada, todas las diferencias son de fin de línea. Si imprime archivos, esos sí tienen cambios reales: el segundo comando los lista y conviene conservarlos. El tercero muestra los archivos con CRLF en disco.

  2. Restaura desde WSL los archivos afectados. Desde una terminal de WSL, o desde PowerShell anteponiendo wsl.exe:

    wsl.exe -d Ubuntu -e bash -lc "cd ~/mi-app && git restore ."

    git restore . (equivalente a git checkout -- . en versiones antiguas) descarta todos los cambios sin confirmar. Si el paso anterior mostró cambios reales, exclúyelos con un pathspec:

    git restore -- . ':(exclude)src/cambio-real.ts'

    Los archivos se reescriben con LF, que es lo que tiene el índice.

  3. Fija el fin de línea con .gitattributes. En la raíz del repositorio:

    * text=auto eol=lf
    *.bat text eol=crlf
    *.cmd text eol=crlf

    Según la documentación de Git, core.autocrlf solo se consulta cuando el atributo text no está definido; con este archivo, las dos instalaciones de Git se comportan igual. La combinación text=auto eol=lf requiere Git 2.10 o posterior. Las dos últimas líneas conservan CRLF en los scripts de Windows, que suelen necesitarlo. Después renormaliza y revisa antes de confirmar:

    git add .gitattributes
    git add --renormalize .
    git status
    git commit -m "chore: normalize line endings to LF"

    Si el repositorio tenía archivos guardados con CRLF en el índice (git ls-files --eol | grep 'i/crlf'), aparecerán en git status como cambios preparados: es la conversión única a LF y es lo esperado.

  4. Repara los worktrees creados desde Windows. En WSL, desde el repositorio principal, indica la ruta Linux real del worktree:

    git worktree list
    git worktree repair ~/mi-app-rama-x

    La documentación de git worktree describe repair justo para este caso: restablece la conexión entre el repositorio principal y los worktrees cuando sus archivos administrativos quedaron desactualizados. Si prefieres hacerlo a mano, el archivo .git del worktree debe contener gitdir: /home/usuario/mi-app/.git/worktrees/rama-x, y el archivo gitdir dentro de .git/worktrees/rama-x/ debe contener la ruta Linux de ese .git. Como última opción, usa git worktree prune y vuelve a crear el worktree desde WSL con git worktree add; el contenido sin confirmar del directorio viejo no se mueve solo, así que cópialo antes.

  5. Haz las operaciones que escriben desde un único Git. Ejecuta checkout, merge, pull, rebase, stash y worktree add dentro de WSL, aunque edites los archivos con herramientas de Windows:

    wsl.exe -d Ubuntu -e bash -lc "cd ~/mi-app && git pull --ff-only"

    Tampoco ejecutes sobre todo el repositorio formateadores ni conversiones de fin de línea del editor que escriban CRLF.

Cómo verificar que quedó resuelto

git status --short
git diff --ignore-cr-at-eol --stat
git ls-files --eol | grep -c 'w/crlf'
git config --show-origin --get-all core.autocrlf

Los dos primeros no deben imprimir nada. El tercero debe dar 0, salvo por los .bat y .cmd si los tienes, que conservan CRLF a propósito. Después haz un git checkout o un git pull desde WSL y repite el git status: debe seguir limpio. Por último, abre el repositorio con tus herramientas de Windows (editor, cliente gráfico) y confirma que tampoco muestran cambios.

Variantes y errores relacionados

El repositorio está en /mnt/c/...

Funciona igual, pero la documentación de Microsoft sobre sistemas de archivos en WSL recomienda no trabajar con los mismos archivos desde ambos sistemas operativos salvo que haya una razón, y guardarlos en el sistema de archivos donde corren las herramientas. Si trabajas en WSL, un repositorio en ~/mi-app deja a Git para Windows fuera de la ecuación.

No puedes confirmar un .gitattributes

Alinea las configuraciones. La documentación de solución de problemas de VS Code propone, en Windows, git config --global core.autocrlf input (convierte a LF al confirmar y no escribe CRLF) o false (sin conversión), y avisa de que puede hacer falta volver a clonar para que el cambio surta efecto.

El diff muestra old mode y new mode

Ya no son fines de línea sino cambios en el bit ejecutable. La documentación de core.fileMode menciona que un repositorio visitado desde otro entorno puede perder ese bit y que en ese caso conviene ponerlo en false.

Worktrees que se abren desde ambos lados

git worktree admite enlaces con rutas relativas (--relative-paths o worktree.useRelativePaths), pensados para repositorios que se mueven entre entornos. La propia documentación avisa de que los Git antiguos no los entienden. Pruébalo primero en un repositorio de ensayo.

Resumen

  • Cientos de archivos "modificados" sin cambios reales suelen ser CRLF escrito por Git para Windows (core.autocrlf=true) y leído por un Git en WSL sin conversión.
  • --ignore-cr-at-eol es una opción de git diff: úsala con --stat o --name-only para separar lo real de lo fantasma.
  • Restaura desde WSL con git restore ., excluyendo con un pathspec los archivos que sí cambiaste.
  • Evita la recaída con * text=auto eol=lf en .gitattributes y ejecutando las operaciones que escriben desde un solo Git.
  • Los worktrees creados desde Windows guardan rutas que Linux no resuelve: arréglalos con git worktree repair <ruta-linux>.

Referencias

Comentarios