Subes unos scripts desde Windows, el servidor Ubuntu los descarga con git pull y ninguno se puede ejecutar:
$ ls -l tools/
-rw-r--r-- 1 deploy deploy 412 oct 11 10:00 build.py
$ ./tools/build.py
bash: ./tools/build.py: Permission denied
Si los lanza systemd u otro agente, el servicio falla al arrancar el proceso (en el estado de systemd suele verse status=203/EXEC). Lo habitual es arreglarlo en el servidor con chmod +x, y entonces aparece el segundo problema: en el siguiente despliegue, git pull se aborta.
error: Your local changes to the following files would be overwritten by merge:
tools/build.py
Please commit your changes or stash them before you merge.
Aborting
Este artículo explica por qué los archivos llegan sin permiso de ejecución, cómo corregirlo en el repositorio (para que no dependa de nadie en el servidor) y cómo salir del bloqueo del git pull.
Por qué pasa
- Git solo guarda un bit de permisos. La documentación de
git fast-importlista los únicos modos que admite:100644(archivo normal, no ejecutable),100755(archivo ejecutable),120000(enlace simbólico) y160000(gitlink). Para un script, la diferencia entre funcionar o no en Linux es el modo100644frente a100755. - En Windows el bit no se puede leer del sistema de archivos. Al ejecutar
git initogit clone, Git comprueba si el sistema de archivos maneja bien el bit ejecutable y fijacore.fileModeen consecuencia (esto lo describe la documentación degit config). En un repositorio nuevo creado en Windows se observacore.fileMode = false. Con ese valor, Git ignora el bit ejecutable ygit addregistra los archivos nuevos como100644, incluso si empiezan con una línea#!. - En Linux el valor por defecto es
true. Elgit pulldel servidor crea los scripts con el modo registrado (100644), es decir, sin permiso de ejecución. - Por eso se aborta el
git pulldespués de unchmodmanual. Concore.fileMode = true, un cambio de modo cuenta como modificación local. Si el siguiente commit toca esos mismos archivos (por ejemplo, el commit que corrige el modo), Git se niega a sobrescribirlos.
Solución
-
Confirma el modo registrado. Desde Windows o desde cualquier equipo con el repositorio:
git ls-files -s tools/ git ls-tree HEAD tools/La primera columna es el modo. Si ves
100644en los scripts, esa es la causa. -
Marca los scripts como ejecutables en el índice. En Git Bash, Linux o macOS, el shell expande el comodín:
git update-index --chmod=+x tools/*.pyEn PowerShell el comodín no se expande, y
git update-indexlo trata como un nombre literal y falla confatal: git update-index: cannot chmod +x 'tools/*.py'. Deja que Git resuelva el patrón:git ls-files "tools/*.py" | ForEach-Object { git update-index --chmod=+x $_ }Para un archivo que todavía no está en el repositorio, usa
git add --chmod=+x tools/nuevo.py. Según la documentación, la opción solo cambia el bit en el índice y deja intactos los archivos en disco; por eso funciona aunque estés en Windows. -
Revisa el cambio, haz commit y sube.
git diff --cached --summary git commit -m "fix: marcar scripts como ejecutables" git pushEl resumen debe listar una línea
mode change 100644 => 100755por cada script y ninguna modificación de contenido. -
Despliega y comprueba en el servidor. Tras el
git pull,ls -l tools/debe mostrar-rwxr-xr-xen los scripts. -
Si el servidor tiene cambios de modo locales y el pull se aborta, primero confirma que son solo de modo:
git diff --summary git diff --statEl primer comando muestra líneas
mode change; el segundo indica 0 inserciones y 0 eliminaciones si no cambió ninguna línea de contenido. Con eso confirmado, tienes dos salidas:- Ignorar el modo solo en ese pull (no descarta nada):
git -c core.fileMode=false pull. En una prueba con un repositorio desechable, este comando completó el pull que antes se abortaba. Si los modos del servidor coinciden con los que ahora registra el repositorio,git statusdebe quedar limpio. - Restaurar los archivos:
git checkout -- .. Esto descarta todos los cambios locales, también los de contenido, así que úsalo solo sigit diff --statno mostró ninguna línea cambiada.
- Ignorar el modo solo en ese pull (no descarta nada):
-
Opcional: una red de seguridad en el script de despliegue.
git pull --ff-only chmod +x tools/*.pyMientras el repositorio tenga el modo
100755, esechmodno cambia nada. Pero si alguien añade un script nuevo como100644y el despliegue lo hace ejecutable, el servidor acumula otra vez cambios de modo locales y puede repetirse el bloqueo del pull. Trátalo como un parche temporal y corrige siempre el modo en el repositorio.
Cómo verificar que quedó resuelto
git ls-files -s tools/
git ls-tree HEAD tools/
Ambos deben mostrar 100755. En el servidor, después del pull, ls -l debe mostrar el bit x y git status debe estar limpio. Una prueba de humo que comprueba que cada herramienta existe, es ejecutable y arranca (ajusta la opción a lo que acepten tus scripts):
for f in tools/*.py; do
[ -x "$f" ] || { echo "No ejecutable: $f"; exit 1; }
"$f" --help > /dev/null || { echo "Falla: $f"; exit 1; }
done
echo "OK"
Para que no vuelva a ocurrir, puedes añadir esta comprobación a tu integración continua. Falla si algún script del repositorio no está registrado como ejecutable:
if git ls-files -s -- 'tools/*.py' | grep -v '^100755'; then
echo "Hay scripts sin bit de ejecución"; exit 1
fi
Variantes y errores relacionados
Quitar el bit ejecutable
La opción simétrica es git update-index --chmod=-x archivo.
El script tiene finales de línea CRLF
Es otro problema con síntomas distintos: el error no será Permission denied sino un fallo con el intérprete indicado en la línea #!. Para que los scripts mantengan siempre finales de línea LF, usa .gitattributes con una regla como *.sh text eol=lf; la documentación de gitattributes indica que eol=lf deja en el directorio de trabajo los mismos finales de línea que hay en el índice.
No silencies el problema con core.fileMode = false en el servidor
La documentación lo prevé para sistemas de archivos donde el bit ejecutable no es fiable. En un servidor Linux, desactivarlo oculta cambios de modo reales y no arregla el modo registrado en el repositorio. Para comprobar el valor y de dónde viene, usa git config --show-origin core.fileMode.
Ejecutar con el intérprete
Si no puedes cambiar el repositorio, python3 tools/build.py o bash deploy.sh funcionan sin el bit ejecutable, porque lo que se ejecuta es el intérprete. Solo es un rodeo: las unidades de systemd o los agentes que lanzan el archivo directamente seguirán fallando.
Resumen
- Git registra solo dos modos para archivos normales:
100644y100755; desde Windows los scripts nuevos entran como100644porquecore.fileModesuele serfalse. - Corrige el modo en el repositorio:
git update-index --chmod=+x(ogit add --chmod=+xpara archivos nuevos), commit y push. - En PowerShell el comodín no se expande: pasa los nombres con
git ls-filesyForEach-Object. - Si un
chmodmanual bloquea elgit pull, comprueba congit diff --summaryque solo hay cambios de modo y usagit -c core.fileMode=false pull. - Verifica con
git ls-files -s(modo100755) y con una prueba de humo en el servidor.
Comentarios
Publicar un comentario