Conserver les autorisations de fichiers avec Git

109

Je souhaite contrôler la version de mon serveur Web comme décrit dans Contrôle de version de mon serveur Web , en créant un dépôt git à partir de mon /var/www directory. J'espérais que je serais alors en mesure de pousser le contenu Web de notre serveur de développement vers github, de le transférer vers notre serveur de production et de passer le reste de la journée à la piscine.

Apparemment, un problème dans mon plan est que Git ne respectera pas les autorisations de fichiers (je ne l'ai pas essayé, je ne l'ai lu que maintenant.) Je suppose que cela a du sens dans la mesure où différentes boîtes sont susceptibles d'avoir des configurations utilisateur / groupe différentes. Mais si je voulais forcer la propagation des autorisations, sachant que mes serveurs sont configurés de la même manière, ai-je des options? Ou y a-t-il un moyen plus simple d'aborder ce que j'essaie de faire?

Yarin
la source
1
Ouais, je suppose, bien que la solution qu'ils suggèrent, je ne sais franchement pas quoi faire. J'espérais une approche plus simple.
Yarin
Qu'en est-il de la situation où le code source provient de l'environnement de développement (par exemple Windows - XAMPP, etc.) qui n'a pas d'informations sur la propriété du fichier? Les fichiers à la fin du processus git doivent correspondre à la propriété et aux autorisations de l'emplacement cible. Est-ce que git-cache-meta peut gérer cela? D'accord avec Yarin ... c'est sûrement un cas d'utilisation assez courant, qui devrait avoir une solution assez simple?
user3600150

Réponses:

43

La git-cache-metaquestion mentionnée dans SO " git - comment récupérer les permissions de fichiers que git pense que le fichier devrait être? " (Et la FAQ git ) est l'approche la plus directe.

L'idée est de stocker dans un .git_cache_metafichier les permissions des fichiers et répertoires.
Il s'agit d'un fichier séparé non versionné directement dans le dépôt Git.

C'est pourquoi son utilisation est:

$ 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

Alors vous:

  • regroupez votre dépôt et enregistrez les autorisations de fichier associées.
  • copiez ces deux fichiers sur le serveur distant
  • restaurer le dépôt là-bas et appliquer l'autorisation
VonC
la source
2
VonC- Merci pour cela, je vais l'essayer- mais le regroupement est-il nécessaire? Ne pourrais-je pas conserver mon workflow (dev -> github -> production) et simplement archiver / extraire le métafichier?
Yarin
@Yarin: non, le bundle n'est pas obligatoire. C'est un moyen efficace de transférer des dépôts lorsqu'aucun autre protocole de transfert n'est disponible.
VonC le
3
L'utilisation de bundle ici a été une distraction majeure pour moi. Cela m'a complètement découragé, en fait. (Je n'ai pas de difficulté à extraire le dépôt du serveur.) La réponse de @ omid-ariyan ci-dessous avec les hooks de validation pré / post était beaucoup plus compréhensible. Plus tard, j'ai réalisé que ces scripts hook faisaient exactement le même travail que git-cache-meta. Allez voir ce que je veux dire: gist.github.com/andris9/1978266 . Ils analysent et stockent les retours git ls-files.
pauljohn32
Le lien vers git-cache-meta est mort - quelqu'un qui en a connaissance peut-il le localiser et modifier le message?
rosuav
@rosuav Bien sûr: j'ai modifié la réponse et restauré le lien. Merci de m'avoir fait part de ce lien mort.
VonC
63

Git est un système de contrôle de version, créé pour le développement de logiciels, donc à partir de l'ensemble des modes et des autorisations, il ne stocke que le bit exécutable (pour les fichiers ordinaires) et le bit de lien symbolique. Si vous souhaitez stocker des autorisations complètes, vous avez besoin d'un outil tiers, comme git-cache-meta( mentionné par VonC ), ou Metastore (utilisé par etckeeper ). Ou vous pouvez utiliser IsiSetup , dont l'IIRC utilise git comme backend.

Consultez la page Interfaces, interfaces et outils sur Git Wiki.

Jakub Narębski
la source
2
Merci Jakub- pouvez-vous m'expliquer pourquoi Git se soucie du bit exécutable et seulement de cela?
Yarin
5
@Yarin: bit exécutable uniquement? Lorsque vous clonez un ensemble complet de fichiers d'un système à un autre, la notion de «lecture seule» ou de «lecture-écriture» n'est pas exactement pertinente (comme vous l'avez dit dans votre question: différents utilisateurs / groupes). Mais la notion d '«exécutable» ne dépend pas des utilisateurs et des groupes et peut être réutilisée du système au système (distant).
VonC
1
Jakub, dans ce cas, il ne devrait pas changer les permissions. Je veux dire, cela devrait laisser les permanentes tranquilles ou les gérer, mais ne pas les gâcher si cela ne va pas les gérer.
CommaToast
3
De plus, j'ai trouvé /usr/share/git-core/contrib/hooks/setgitperms.perldans mon git-contribpackage - un script dans un but similaire. ("Ce script peut être utilisé pour enregistrer / restaurer les autorisations complètes et les données de propriété dans un arbre de travail git.")
imz - Ivan Zakharyaschev
Est-ce toujours exact ou est-ce que github fait quelque chose par-dessus git? Je viens de changer un fichier en exécutable et de le valider, et le journal des modifications du commit apparaît comme 0 ligne modifiée pour le fichier, mais a 100644 → 100755 à côté du nom de fichier. Cela ressemble vraiment à ce que les autorisations complètes sont stockées avec le fichier.
Cruncher le
23

C'est assez tard mais cela pourrait en aider d'autres. Je fais ce que vous voulez faire en ajoutant deux hooks git à mon référentiel.

.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

Le premier hook est appelé lorsque vous "commit" et lira la propriété et les autorisations de tous les fichiers du référentiel et les stockera dans un fichier à la racine du référentiel appelé .permissions, puis ajoutera le fichier .permissions au commit.

Le deuxième hook est appelé lorsque vous "checkout" et parcourra la liste des fichiers dans le fichier .permissions et restaurera la propriété et les autorisations de ces fichiers.

  • Vous devrez peut-être effectuer la validation et le paiement en utilisant sudo.
  • Assurez-vous que les scripts de pré-validation et de post-extraction ont l'autorisation d'exécution.
Omid Ariyan
la source
Omid ... merci! J'ai trouvé que votre code était une solution parfaite pour moi.
Ricalsin le
@Ricalsin Vous êtes les bienvenus! Je suis content d'avoir aidé :)
Omid Ariyan
1
$SELF_DIR/../../n'est pas nécessairement la racine du référentiel ... mais l' git rev-parse --show-toplevelest. (Je ne sais pas pourquoi vous ne l'utiliseriez pas uniquement pwdpour le répertoire actuel, mais c'est quand même discutable.)
PJSCopeland
Dans l'état actuel des choses, ce qui précède divisera les noms de fichiers avec des espaces. Selon cette réponse , vous pouvez définir IFS=$'\n'avant la forboucle pour arrêter cela (et unset IFSensuite pour être sûr).
PJSCopeland
Cela ne vous permet pas facilement de transférer les autorisations sur un système différent, avec un système d'exploitation différent, où vous avez un nom d'utilisateur différent. Je me suis demandé "de quoi ai-je vraiment besoin?" et couper vers le bas toute la solution chmod 0600 .pgpassdans post-checkout. Oui, je devrai le mettre à jour manuellement chaque fois que j'ai un fichier qui nécessite des autorisations spécifiques, mais ce sont les pauses.
PJSCopeland
2

Au cas où vous entreriez dans ce sujet maintenant, je viens de le traverser aujourd'hui et je peux résumer où cela en est. Si vous ne l'avez pas encore essayé, quelques détails ici pourraient vous aider.

Je pense que l'approche de @Omid Ariyan est le meilleur moyen. Ajoutez les scripts de pré-validation et de post-paiement. N'OUBLIEZ PAS de les nommer exactement comme le fait Omid et N'oubliez pas de les rendre exécutables. Si vous oubliez l'un ou l'autre, ils n'ont aucun effet et vous exécutez "git commit" encore et encore en vous demandant pourquoi rien ne se passe :) De plus, si vous coupez et collez hors du navigateur Web, faites attention à ce que les guillemets et les coches ne soient pas modifié.

Si vous exécutez le script de pré-validation une fois (en exécutant un commit git), le fichier .permissions sera créé. Vous pouvez l'ajouter au référentiel et je pense qu'il n'est pas nécessaire de l'ajouter encore et encore à la fin du script de pré-commit. Mais ça ne fait pas mal, je pense (espérer).

Il y a quelques petits problèmes concernant le nom du répertoire et l'existence d'espaces dans les noms de fichiers dans les scripts d'Omid. Les espaces étaient un problème ici et j'ai eu quelques problèmes avec le correctif IFS. Pour mémoire, ce script de pré-commit a fonctionné correctement pour moi:

#!/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"

Maintenant, que retirons-nous de cela?

Le fichier .permissions se trouve au niveau supérieur du dépôt git. Il a une ligne par fichier, voici le haut de mon exemple:

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

Comme vous pouvez le voir, nous avons

filepath;perms;owner;group

Dans les commentaires sur cette approche, l'une des affiches se plaint que cela ne fonctionne qu'avec le même nom d'utilisateur, et c'est techniquement vrai, mais il est très facile de le réparer. Notez que le script post-paiement comporte 2 actions,

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

Donc je ne garde que le premier, c'est tout ce dont j'ai besoin. Mon nom d'utilisateur sur le serveur Web est en effet différent, mais plus important encore, vous ne pouvez exécuter chown que si vous êtes root. Peut cependant exécuter "chgrp". Il est assez clair comment mettre cela à profit.

Dans la première réponse de cet article, celle qui est la plus largement acceptée, la suggestion est donc d'utiliser git-cache-meta, un script qui fait le même travail que les scripts de pré / post hook ici (analyse de la sortie git ls-files) . Ces scripts sont plus faciles à comprendre pour moi, le code git-cache-meta est un peu plus élaboré. Il est possible de garder git-cache-meta dans le chemin et d'écrire des scripts pré-commit et post-checkout qui l'utilisent.

Les espaces dans les noms de fichiers posent un problème avec les deux scripts d'Omid. Dans le script post-extraction, vous saurez que vous avez les espaces dans les noms de fichiers si vous voyez des erreurs comme celle-ci

$ 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

Je vérifie les solutions pour cela. Voici quelque chose qui semble fonctionner, mais je n'ai testé que dans un cas

#!/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

Étant donné que les informations sur les autorisations sont une ligne à la fois, j'ai défini IFS sur $, donc seuls les sauts de ligne sont considérés comme de nouvelles choses.

J'ai lu qu'il est TRÈS IMPORTANT de remettre la variable d'environnement IFS comme elle était! Vous pouvez voir pourquoi une session shell pourrait mal tourner si vous laissez $ comme seul séparateur.

pauljohn32
la source
2

Nous pouvons améliorer les autres réponses en changeant le format du .permissionsfichier en chmodinstructions exécutables et en utilisant le -printfparamètre to find. Voici le .git/hooks/pre-commitfichier le plus 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.

... et voici le .git/hooks/post-checkoutfichier simplifié :

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

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

. .permissions

echo "done."

N'oubliez pas que d'autres outils ont peut-être déjà configuré ces scripts, vous devrez donc peut-être les fusionner. Par exemple, voici un post-checkoutscript qui inclut également les git-lfscommandes:

#!/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
la source
1

En pré-commit / post-checkout, une option serait d'utiliser "mtree" (FreeBSD), ou "fmtree" (Ubuntu) qui "compare une hiérarchie de fichiers à une spécification, crée une spécification pour une hiérarchie de fichiers ou modifie un spécification."

L'ensemble par défaut est les flags, gid, link, mode, nlink, size, time, type et uid. Cela peut être adapté à l'objectif spécifique avec le commutateur -k.

Vladimir Botka
la source
1

J'utilise FreeBSD 11.1, le concept de virtualisation de la prison freebsd rend le système d'exploitation optimal. La version actuelle de Git que j'utilise est la 2.15.1, je préfère également tout exécuter sur des scripts shell. Dans cet esprit, j'ai modifié les suggestions ci-dessus comme suit:

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, pour une raison quelconque, vous devez recréer le script, la sortie du fichier .permissions doit avoir le format suivant:

.gitignore;644;0;0

Pour un fichier .gitignore avec 644 permissions données à root: wheel

Remarquez que j'ai dû apporter quelques modifications aux options de statistiques.

Prendre plaisir,

Albaro Pereyra
la source
1

Un ajout à la réponse de @Omid Ariyan concerne les autorisations sur les répertoires. Ajoutez ceci après la forboucle donedans son pre-commitscript.

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

Cela enregistrera également les autorisations de répertoire.

Peter Berbec
la source