Conservación de permisos de archivos con Git

109

Quiero controlar la versión de mi servidor web como se describe en Control de versión para mi servidor web , creando un repositorio git desde mi /var/www directory. Mi esperanza era poder enviar contenido web desde nuestro servidor de desarrollo a github, llevarlo a nuestro servidor de producción y pasar el resto del día en el grupo.

Aparentemente, un problema en mi plan es que Git no respetará los permisos de archivos (no lo he probado, solo lo leo ahora). Supongo que esto tiene sentido ya que es probable que diferentes cajas tengan configuraciones de usuario / grupo diferentes. Pero si quisiera forzar la propagación de los permisos, sabiendo que mis servidores están configurados de la misma manera, ¿tengo alguna opción? ¿O hay una manera más fácil de abordar lo que estoy tratando de hacer?

Yarin
fuente
1
Sí, supongo que sí, aunque la solución a la que apuntan, francamente, no estoy seguro de qué hacer con ella. Esperaba un enfoque más sencillo.
Yarin
¿Qué pasa con la situación en la que el código fuente proviene del entorno de desarrollo (por ejemplo, Windows - XAMPP, etc.) que no tiene información de propiedad del archivo? Los archivos al final del proceso de git deben coincidir con la propiedad y los permisos para la ubicación de destino. ¿Puede git-cache-meta lidiar con esto? De acuerdo con Yarin ... seguramente este es un caso de uso bastante común, que debería tener una solución bastante sencilla.
user3600150

Respuestas:

43

La git-cache-metapregunta mencionada en SO " git - ¿cómo recuperar los permisos de archivo que git cree que debería ser? " (Y las preguntas frecuentes de git ) es el enfoque más directo.

La idea es almacenar en un .git_cache_metaarchivo los permisos de los archivos y directorios.
Es un archivo separado que no se versiona directamente en el repositorio de Git.

Es por eso que su uso es:

$ git bundle create mybundle.bdl master; git-cache-meta --store
$ scp mybundle.bdl .git_cache_meta machine2: 
#then on machine2:
$ git init; git pull mybundle.bdl master; git-cache-meta --apply

Vos tambien:

  • agrupe su repositorio y guarde los permisos de archivo asociados.
  • copia esos dos archivos en el servidor remoto
  • restaurar el repositorio allí y aplicar el permiso
VonC
fuente
2
VonC- Gracias por esto, lo probaré, pero ¿es necesario el empaquetado? ¿No podría conservar mi flujo de trabajo (dev -> github -> producción) y simplemente registrar / retirar el metarchivo?
Yarin
@Yarin: no, el paquete no es obligatorio. Sin embargo, es una buena manera de transferir repositorios cuando no hay ningún otro protocolo de transferencia disponible.
VonC
3
El uso del paquete aquí fue una gran distracción para mí. De hecho, me desanimó por completo la respuesta. (No tengo dificultades para extraer el repositorio del servidor). La respuesta de @ omid-ariyan a continuación con ganchos de confirmación previos / posteriores fue mucho más comprensible. Más tarde me di cuenta de que esos scripts de gancho están haciendo exactamente el mismo trabajo que git-cache-meta. Ve a ver lo que quiero decir: gist.github.com/andris9/1978266 . Están analizando y almacenando el retorno de git ls-files.
pauljohn32
El enlace a git-cache-meta está muerto. ¿Puede alguien que sepa de esto localizarlo y editar la publicación?
rosuav
@rosuav Seguro: he editado la respuesta y restaurado el enlace. Gracias por informarme de este enlace muerto.
VonC
63

Git es un sistema de control de versiones, creado para el desarrollo de software, por lo que, de todo el conjunto de modos y permisos, solo almacena el bit ejecutable (para archivos normales) y el bit de enlace simbólico. Si desea almacenar permisos completos, necesita una herramienta de terceros, como git-cache-meta( mencionada por VonC ) o Metastore (utilizada por etckeeper ). O puede usar IsiSetup , que IIRC usa git como backend.

Consulte la página Interfaces, interfaces y herramientas en Git Wiki.

Jakub Narębski
fuente
2
Gracias Jakub, ¿puedes explicarme por qué Git se preocupa por el bit ejecutable y solo eso?
Yarin
5
@Yarin: ¿solo bit ejecutable? Cuando clona un conjunto completo de archivos de un sistema a otro, la noción de "solo lectura" o "lectura-escritura" no es exactamente relevante (como dijo en su pregunta: diferentes usuarios / grupos). Pero la noción de "ejecutable" no depende de usuarios y grupos y se puede reutilizar de un sistema a otro (remoto).
VonC
1
Jakub, en ese caso no debería cambiar los permisos. Quiero decir, debería dejar las permanentes en paz o manejarlas, pero no meterse con ellas si no las va a manejar.
CommaToast
3
Además, encontré /usr/share/git-core/contrib/hooks/setgitperms.perlen mi git-contribpaquete un script para un propósito similar. ("Este script se puede usar para guardar / restaurar permisos completos y datos de propiedad dentro de un árbol de trabajo de git").
imz - Ivan Zakharyaschev
¿Sigue siendo exacto o github de alguna manera hace algo además de git? Acabo de cambiar un archivo a ejecutable y lo comprometí, y el registro de cambios para la confirmación aparece como 0 líneas cambiadas para el archivo, pero tiene 100644 → 100755 al lado del nombre del archivo. Esto realmente parece que los permisos completos están almacenados con el archivo.
Cruncher
23

Esto es bastante tarde, pero podría ayudar a otros. Hago lo que quieres hacer agregando dos ganchos de git a mi repositorio.

.git / hooks / pre-commit:

#!/bin/bash
#
# A hook script called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if it wants
# to stop the commit.

SELF_DIR=`git rev-parse --show-toplevel`
DATABASE=$SELF_DIR/.permissions

# Clear the permissions database file
> $DATABASE

echo -n "Backing-up permissions..."

IFS_OLD=$IFS; IFS=$'\n'
for FILE in `git ls-files --full-name`
do
   # Save the permissions of all the files in the index
   echo $FILE";"`stat -c "%a;%U;%G" $FILE` >> $DATABASE
done

for DIRECTORY in `git ls-files --full-name | xargs -n 1 dirname | uniq`
do
   # Save the permissions of all the directories in the index
   echo $DIRECTORY";"`stat -c "%a;%U;%G" $DIRECTORY` >> $DATABASE
done
IFS=$IFS_OLD

# Add the permissions database file to the index
git add $DATABASE -f

echo "OK"

.git / hooks / post-checkout:

#!/bin/bash

SELF_DIR=`git rev-parse --show-toplevel`
DATABASE=$SELF_DIR/.permissions

echo -n "Restoring permissions..."

IFS_OLD=$IFS; IFS=$'\n'
while read -r LINE || [[ -n "$LINE" ]];
do
   ITEM=`echo $LINE | cut -d ";" -f 1`
   PERMISSIONS=`echo $LINE | cut -d ";" -f 2`
   USER=`echo $LINE | cut -d ";" -f 3`
   GROUP=`echo $LINE | cut -d ";" -f 4`

   # Set the file/directory permissions
   chmod $PERMISSIONS $ITEM

   # Set the file/directory owner and groups
   chown $USER:$GROUP $ITEM

done < $DATABASE
IFS=$IFS_OLD

echo "OK"

exit 0

El primer gancho se llama cuando "confirma" y leerá la propiedad y los permisos de todos los archivos en el repositorio y los almacenará en un archivo en la raíz del repositorio llamado .permissions y luego agregará el archivo .permissions a la confirmación.

El segundo gancho se llama cuando "finaliza la compra" y revisará la lista de archivos en el archivo .permissions y restaurará la propiedad y los permisos de esos archivos.

  • Es posible que deba realizar la confirmación y el pago usando sudo.
  • Asegúrese de que los scripts de confirmación previa y finalización de la compra tengan permiso de ejecución.
Omid Ariyan
fuente
Omid ... ¡gracias! Encontré que tu código es una solución perfecta para mí.
Ricalsin
@Ricalsin ¡De nada! Me alegro de haber ayudado :)
Omid Ariyan
1
$SELF_DIR/../../no es necesariamente la raíz del repositorio ... pero lo git rev-parse --show-topleveles. (No estoy seguro de por qué no lo usaría simplemente pwdpara el directorio actual, pero eso es discutible de todos modos.)
PJSCopeland
Tal como está, lo anterior dividirá los nombres de los archivos con espacios en ellos. Según esta respuesta , puede configurar IFS=$'\n'antes del forciclo para detener eso (y unset IFSluego para estar seguro).
PJSCopeland
Esto no le permite transferir los permisos a un sistema diferente, con un sistema operativo diferente, donde tiene un nombre de usuario diferente. Me pregunté "¿qué necesito realmente ?" y corte toda la solución hacia chmod 0600 .pgpassadentro post-checkout. Sí, tendré que actualizarlo manualmente cada vez que tenga un archivo que necesite permisos específicos, pero esos son los descansos.
PJSCopeland
2

En caso de que esté entrando en esto ahora mismo, acabo de revisarlo hoy y puedo resumir dónde se encuentra esto. Si aún no lo ha intentado, algunos detalles aquí pueden ayudar.

Creo que el enfoque de @Omid Ariyan es la mejor manera. Agregue los scripts de confirmación previa y posterior a la compra. NO olvide nombrarlos exactamente como lo hace Omid y NO olvide hacerlos ejecutables. Si olvida alguno de ellos, no tienen ningún efecto y ejecuta "git commit" una y otra vez preguntándose por qué no sucede nada :) Además, si corta y pega fuera del navegador web, tenga cuidado de que las comillas y las marcas de verificación no sean alterado.

Si ejecuta el script de confirmación previa una vez (ejecutando una confirmación de git), se creará el archivo .permissions. Puede agregarlo al repositorio y creo que no es necesario agregarlo una y otra vez al final del script de confirmación previa. Pero no duele, creo (espero).

Hay algunos pequeños problemas sobre el nombre del directorio y la existencia de espacios en los nombres de los archivos en los scripts de Omid. Los espacios eran un problema aquí y tuve algunos problemas con la solución IFS. Para que conste, este script de confirmación previa funcionó correctamente para mí:

#!/bin/bash  

SELF_DIR=`git rev-parse --show-toplevel`
DATABASE=$SELF_DIR/.permissions

# Clear the permissions database file
> $DATABASE

echo -n "Backing-up file permissions..."

IFSold=$IFS
IFS=$'\n'
for FILE  in `git ls-files`
do
   # Save the permissions of all the files in the index
   echo $FILE";"`stat -c "%a;%U;%G" $FILE` >> $DATABASE
done
IFS=${IFSold}
# Add the permissions database file to the index
git add $DATABASE

echo "OK"

Ahora bien, ¿qué sacamos de esto?

El archivo .permissions está en el nivel superior del repositorio de git. Tiene una línea por archivo, aquí está la parte superior de mi ejemplo:

$ cat .permissions
.gitignore;660;pauljohn;pauljohn
05.WhatToReport/05.WhatToReport.doc;664;pauljohn;pauljohn
05.WhatToReport/05.WhatToReport.pdf;664;pauljohn;pauljohn

Como puede ver, tenemos

filepath;perms;owner;group

En los comentarios sobre este enfoque, uno de los carteles se queja de que solo funciona con el mismo nombre de usuario, y eso es técnicamente cierto, pero es muy fácil de solucionar. Tenga en cuenta que el script posterior al pago tiene 2 acciones,

# Set the file permissions
chmod $PERMISSIONS $FILE
# Set the file owner and groups
chown $USER:$GROUP $FILE

Así que solo me quedo con el primero, eso es todo lo que necesito. Mi nombre de usuario en el servidor web es diferente, pero lo más importante es que no puede ejecutar chown a menos que sea root. Sin embargo, puede ejecutar "chgrp". Está bastante claro cómo poner eso en práctica.

En la primera respuesta de esta publicación, la que es más ampliamente aceptada, la sugerencia es usar git-cache-meta, un script que está haciendo el mismo trabajo que los scripts de pre / post hook aquí (analizando el resultado de git ls-files) . Estos scripts son más fáciles de entender para mí, el código git-cache-meta es bastante más elaborado. Es posible mantener git-cache-meta en la ruta y escribir scripts de pre-confirmación y post-pago que lo usarían.

Los espacios en los nombres de los archivos son un problema con los dos scripts de Omid. En la secuencia de comandos posterior al pago, sabrá que tiene los espacios en los nombres de los archivos si ve errores como este

$ git checkout -- upload.sh
Restoring file permissions...chmod: cannot access  '04.StartingValuesInLISREL/Open': No such file or directory
chmod: cannot access 'Notebook.onetoc2': No such file or directory
chown: cannot access '04.StartingValuesInLISREL/Open': No such file or directory
chown: cannot access 'Notebook.onetoc2': No such file or directory

Estoy buscando soluciones para eso. Aquí hay algo que parece funcionar, pero solo lo he probado en un caso

#!/bin/bash

SELF_DIR=`git rev-parse --show-toplevel`
DATABASE=$SELF_DIR/.permissions

echo -n "Restoring file permissions..."
IFSold=${IFS}
IFS=$
while read -r LINE || [[ -n "$LINE" ]];
do
   FILE=`echo $LINE | cut -d ";" -f 1`
   PERMISSIONS=`echo $LINE | cut -d ";" -f 2`
   USER=`echo $LINE | cut -d ";" -f 3`
   GROUP=`echo $LINE | cut -d ";" -f 4`

   # Set the file permissions
   chmod $PERMISSIONS $FILE
   # Set the file owner and groups
   chown $USER:$GROUP $FILE
done < $DATABASE
IFS=${IFSold}
echo "OK"

exit 0

Dado que la información de permisos es una línea a la vez, configuro IFS en $, por lo que solo los saltos de línea se ven como cosas nuevas.

¡Leí que es MUY IMPORTANTE volver a configurar la variable de entorno IFS como estaba! Puede ver por qué una sesión de shell puede salir mal si deja $ como único separador.

pauljohn32
fuente
2

Podemos mejorar las otras respuestas cambiando el formato del .permissionsarchivo para que sean chmoddeclaraciones ejecutables y para hacer uso del -printfparámetro a find. Aquí está el .git/hooks/pre-commitarchivo más simple :

#!/usr/bin/env bash

echo -n "Backing-up file permissions... "

cd "$(git rev-parse --show-toplevel)"

find . -printf 'chmod %m "%p"\n' > .permissions

git add .permissions

echo done.

... y aquí está el .git/hooks/post-checkoutarchivo simplificado :

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

cd "$(git rev-parse --show-toplevel)"

. .permissions

echo "done."

Recuerde que es posible que otras herramientas ya hayan configurado estos scripts, por lo que es posible que deba fusionarlos. Por ejemplo, aquí hay un post-checkoutscript que también incluye los git-lfscomandos:

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

cd "$(git rev-parse --show-toplevel)"

. .permissions

echo "done."

command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on you
r path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-checkout.\n"; exit 2; }
git lfs post-checkout "$@"
Tammer Saleh
fuente
1

En pre-commit / post-checkout, una opción sería usar "mtree" (FreeBSD), o la utilidad "fmtree" (Ubuntu) que "compara una jerarquía de archivos con una especificación, crea una especificación para una jerarquía de archivos o modifica una especificación."

El conjunto predeterminado son flags, gid, link, mode, nlink, size, time, type y uid. Esto se puede ajustar al propósito específico con el interruptor -k.

Vladimir Botka
fuente
1

Estoy ejecutando FreeBSD 11.1, el concepto de virtualización de la cárcel de freebsd hace que el sistema operativo sea óptimo. La versión actual de Git que estoy usando es 2.15.1, también prefiero ejecutar todo en scripts de shell. Con eso en mente, modifiqué las sugerencias anteriores de la siguiente manera:

git push: .git / hooks / pre-commit

#! /bin/sh -
#
# A hook script called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if it wants
# to stop the commit.

SELF_DIR=$(git rev-parse --show-toplevel);
DATABASE=$SELF_DIR/.permissions;

# Clear the permissions database file
> $DATABASE;

printf "Backing-up file permissions...\n";

OLDIFS=$IFS;
IFS=$'\n';
for FILE in $(git ls-files);
do
   # Save the permissions of all the files in the index
    printf "%s;%s\n" $FILE $(stat -f "%Lp;%u;%g" $FILE) >> $DATABASE;
done
IFS=$OLDIFS;

# Add the permissions database file to the index
git add $DATABASE;

printf "OK\n";

git pull: .git / hooks / post-merge

#! /bin/sh -

SELF_DIR=$(git rev-parse --show-toplevel);
DATABASE=$SELF_DIR/.permissions;

printf "Restoring file permissions...\n";

OLDIFS=$IFS;
IFS=$'\n';
while read -r LINE || [ -n "$LINE" ];
do
   FILE=$(printf "%s" $LINE | cut -d ";" -f 1);
   PERMISSIONS=$(printf "%s" $LINE | cut -d ";" -f 2);
   USER=$(printf "%s" $LINE | cut -d ";" -f 3);
   GROUP=$(printf "%s" $LINE | cut -d ";" -f 4);

   # Set the file permissions
   chmod $PERMISSIONS $FILE;

   # Set the file owner and groups
   chown $USER:$GROUP $FILE;

done < $DATABASE
IFS=$OLDIFS

pritnf "OK\n";

exit 0;

Si por alguna razón necesita volver a crear el script, la salida del archivo .permissions debe tener el siguiente formato:

.gitignore;644;0;0

Para un archivo .gitignore con 644 permisos otorgados a root: wheel

Tenga en cuenta que tuve que hacer algunos cambios en las opciones de estadísticas.

Disfrutar,

Albaro Pereyra
fuente
1

Una adición a la respuesta de @Omid Ariyan son los permisos en los directorios. Agregue esto después del forbucle doneen su pre-commitguión.

for DIR in $(find ./ -mindepth 1 -type d -not -path "./.git" -not -path "./.git/*" | sed 's@^\./@@')
do
    # Save the permissions of all the files in the index
    echo $DIR";"`stat -c "%a;%U;%G" $DIR` >> $DATABASE
done

Esto también guardará los permisos del directorio.

Peter Berbec
fuente