Conservazione dei permessi sui file con Git


109

Voglio controllare la versione del mio server web come descritto in Controllo della versione per il mio server web , creando un repository git dal mio /var/www directory. La mia speranza era che sarei stato quindi in grado di eseguire il push dei contenuti web dal nostro server di sviluppo a GitHub, portarli al nostro server di produzione e trascorrere il resto della giornata in piscina.

Apparentemente un nodo nel mio piano è che Git non rispetterà i permessi dei file (non l'ho provato, ne sto solo leggendo ora.) Immagino che questo abbia senso in quanto diverse caselle possono avere diverse impostazioni di utenti / gruppi. Ma se volessi forzare la propagazione delle autorizzazioni, sapendo che i miei server sono configurati allo stesso modo, ho delle opzioni? O c'è un modo più semplice per avvicinarsi a ciò che sto cercando di fare?



1
Sì, immagino di sì, anche se la soluzione a cui puntano non sono francamente sicuro di cosa farne. Speravo in un approccio più diretto.
Yarin

E la situazione in cui il codice sorgente proviene da un ambiente Dev (ad esempio Windows - XAMPP ecc.) Che non ha informazioni sulla proprietà dei file? I file alla fine del processo git devono corrispondere alla proprietà e alle autorizzazioni per la posizione di destinazione. Può git-cache-meta occuparsi di questo? D'accordo con Yarin ... sicuramente questo è un caso d'uso piuttosto mainstream, che dovrebbe avere una soluzione piuttosto semplice?
user3600150

Risposte:


43

La git-cache-metadomanda menzionata in SO " git - come recuperare i permessi del file git pensa che il file dovrebbe essere? " (E le FAQ di git ) è l'approccio più diretto.

L'idea è di memorizzare in un .git_cache_metafile i permessi dei file e delle directory.
È un file separato non aggiornato direttamente nel repository Git.

Questo è il motivo per cui l'utilizzo è:

$ 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

Quindi tu:

  • raggruppa il tuo repository e salva i permessi dei file associati.
  • copia questi due file sul server remoto
  • ripristinare lì il repository e applicare l'autorizzazione

2
VonC- Grazie per questo, lo proverò- ma il raggruppamento è necessario? Non potrei mantenere il mio flusso di lavoro (dev -> github -> produzione) e semplicemente fare il checkin / checkout del metafile?
Yarin

@Yarin: no, il pacchetto non è obbligatorio. È un modo accurato per trasferire il repo quando non sono disponibili altri protocolli di trasferimento.
VonC

3
L'uso di bundle qui è stata una grande distrazione per me. Mi ha scoraggiato completamente dalla risposta, in realtà. (Non ho difficoltà a estrarre il repo dal server.) La risposta di @omid-ariyan di seguito con gli hook pre / post commit era molto più comprensibile. Successivamente mi sono reso conto che quegli script hook stanno facendo esattamente lo stesso lavoro di git-cache-meta. Vai a vedere cosa intendo: gist.github.com/andris9/1978266 . Stanno analizzando e archiviando il ritorno da git ls-files.
pauljohn32

Il collegamento a git-cache-meta è morto: qualcuno che lo sa può individuarlo e modificare il post?
rosuav

@rosuav Certo: ho modificato la risposta e ripristinato il collegamento. Grazie per avermi informato di questo collegamento morto.
VonC

63

Git è il sistema di controllo della versione, creato per lo sviluppo del software, quindi dall'intero set di modalità e autorizzazioni memorizza solo il bit eseguibile (per file ordinari) e il bit di collegamento simbolico. Se vuoi memorizzare le autorizzazioni complete, hai bisogno di uno strumento di terze parti, come git-cache-meta( menzionato da VonC ) o Metastore (usato da etckeeper ). Oppure puoi usare IsiSetup , che IIRC usa git come backend.

Vedi la pagina Interfacce, frontend e strumenti su Git Wiki.


2
Grazie Jakub, puoi spiegarmi perché a Git interessa il bit eseguibile e solo quello?
Yarin

5
@Yarin: solo bit eseguibile? Quando si clona un insieme completo di file da un sistema a un altro, la nozione di "sola lettura" o "lettura-scrittura" non è esattamente rilevante (come hai detto nella tua domanda: utenti / gruppi diversi). Ma la nozione di "eseguibile" non dipende da utenti e gruppi e può essere riutilizzata da sistema a sistema (remoto).
VonC

1
Jakub, in tal caso non dovrebbe cambiare i permessi. Voglio dire, dovrebbe lasciare le permanenti da sole o gestirle, ma non scherzare con loro se non le gestirà.
CommaToast

3
Inoltre, ho trovato /usr/share/git-core/contrib/hooks/setgitperms.perlnel mio git-contribpacchetto ... uno script con uno scopo simile. ("Questo script può essere utilizzato per salvare / ripristinare le autorizzazioni complete e i dati di proprietà all'interno di un albero funzionante git.")
imz - Ivan Zakharyaschev

È ancora accurato o github in qualche modo fa qualcosa sopra git? Ho appena cambiato un file in eseguibile e l'ho eseguito il commit, e il log delle modifiche per il commit viene visualizzato come 0 righe modificate per il file, ma ha 100644 → 100755 accanto al nome del file. Sembra davvero che le autorizzazioni complete siano memorizzate con il file.
Cruncher

23

Questo è abbastanza tardi ma potrebbe aiutare altri. Faccio quello che vuoi fare aggiungendo due git hook al mio repository.

.git / ganci / 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 / ganci / 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

Il primo hook viene chiamato quando si "esegue il commit" e leggerà la proprietà e le autorizzazioni per tutti i file nel repository e li memorizzerà in un file nella radice del repository chiamato .permissions e quindi aggiungerà il file .permissions al commit.

Il secondo hook viene chiamato quando effettui il "checkout" e passerà attraverso l'elenco dei file nel file .permissions e ripristinerà la proprietà e le autorizzazioni di quei file.

  • Potrebbe essere necessario eseguire il commit e il checkout utilizzando sudo.
  • Assicurati che gli script di pre-commit e post-checkout abbiano i permessi di esecuzione.

Omid ... grazie! Ho trovato il tuo codice una soluzione perfetta per me.
Ricalsin

@Ricalsin Sei davvero il benvenuto! Sono contento di aver aiutato :)
Omid Ariyan

1
$SELF_DIR/../../non è necessariamente la radice del repository ... ma lo git rev-parse --show-toplevelè. (Non sono sicuro del motivo per cui non dovresti usarlo solo pwdper la directory corrente, ma è comunque discutibile.)
PJSCopeland

Allo stato attuale, quanto sopra dividerà i nomi dei file con spazi al loro interno. Secondo questa risposta , puoi impostare IFS=$'\n'prima del forciclo per fermarlo (e unset IFSdopo per essere sicuro).
PJSCopeland

Questo non ti consente prontamente di trasferire le autorizzazioni a un sistema diverso, con un sistema operativo diverso, in cui hai un nome utente diverso. Mi sono chiesto "di cosa ho veramente bisogno?" e tagliare l'intera giù soluzione chmod 0600 .pgpassin post-checkout. Sì, dovrò aggiornarlo manualmente ogni volta che ho un file che richiede autorizzazioni specifiche, ma sono le interruzioni.
PJSCopeland

2

Nel caso tu stia entrando in questo in questo momento, l'ho appena passato oggi e posso riassumere dove si trova. Se non l'hai ancora provato, alcuni dettagli qui potrebbero aiutarti.

Penso che l'approccio di @Omid Ariyan sia il modo migliore. Aggiungi gli script di pre-commit e post-checkout. NON dimenticare di nominarli esattamente come fa Omid e NON dimenticare di renderli eseguibili. Se dimentichi uno di questi, non hanno effetto e esegui "git commit" più e più volte chiedendoti perché non succede nulla :) Inoltre, se tagli e incolli dal browser web, fai attenzione che le virgolette e le zecche non lo siano alterato.

Se esegui lo script pre-commit una volta (eseguendo un commit git), verrà creato il file .permissions. Puoi aggiungerlo al repository e penso che non sia necessario aggiungerlo più e più volte alla fine dello script di pre-commit. Ma non fa male, penso (spero).

Ci sono alcuni piccoli problemi sul nome della directory e l'esistenza di spazi nei nomi dei file negli script Omid. Gli spazi erano un problema qui e ho avuto qualche problema con la correzione IFS. Per la cronaca, questo script pre-commit ha funzionato correttamente per me:

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

Ora, cosa ne ricaviamo?

Il file .permissions si trova nel livello superiore del repository git. Ha una riga per file, ecco l'inizio del mio esempio:

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

Come puoi vedere, abbiamo

filepath;perms;owner;group

Nei commenti su questo approccio, uno dei poster si lamenta del fatto che funziona solo con lo stesso nome utente, e questo è tecnicamente vero, ma è molto facile risolverlo. Nota che lo script post-checkout ha 2 parti di azione,

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

Quindi tengo solo il primo, è tutto ciò di cui ho bisogno. Il mio nome utente sul server Web è effettivamente diverso, ma soprattutto non puoi eseguire chown a meno che tu non sia root. Può eseguire "chgrp", tuttavia. È abbastanza chiaro come utilizzarlo.

Nella prima risposta in questo post, quella più ampiamente accettata, il suggerimento è quindi di usare git-cache-meta, uno script che sta facendo lo stesso lavoro che stanno facendo gli script pre / post hook qui (analizzare l'output da git ls-files) . Questi script sono più facili da capire per me, il codice git-cache-meta è piuttosto più elaborato. È possibile mantenere git-cache-meta nel percorso e scrivere script pre-commit e post-checkout che lo utilizzerebbero.

Gli spazi nei nomi dei file sono un problema con entrambi gli script di Omid. Nello script post-checkout, saprai di avere gli spazi nei nomi dei file se vedi errori come questo

$ 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

Sto verificando le soluzioni per questo. Ecco qualcosa che sembra funzionare, ma l'ho testato solo in 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

Poiché le informazioni sui permessi sono una riga alla volta, ho impostato IFS su $, quindi solo le interruzioni di riga vengono viste come cose nuove.

Ho letto che è MOLTO IMPORTANTE riportare la variabile d'ambiente IFS nel modo in cui era! Puoi vedere perché una sessione di shell potrebbe andare male se lasci $ come unico separatore.


2

Possiamo migliorare le altre risposte modificando il formato del .permissionsfile in modo che sia chmodistruzioni eseguibili e utilizzando il -printfparametro to find. Ecco il .git/hooks/pre-commitfile più semplice :

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

... ed ecco il .git/hooks/post-checkoutfile semplificato :

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

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

. .permissions

echo "done."

Ricorda che altri strumenti potrebbero aver già configurato questi script, quindi potrebbe essere necessario unirli insieme. Ad esempio, ecco uno post-checkoutscript che include anche i git-lfscomandi:

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

1

In pre-commit / post-checkout un'opzione potrebbe essere quella di usare "mtree" (FreeBSD), o "fmtree" (Ubuntu) utility che "confronta una gerarchia di file con una specifica, crea una specifica per una gerarchia di file o modifica un specifica."

Il set predefinito sono flag, gid, link, mode, nlink, size, time, type e uid. Questo può essere adattato allo scopo specifico con l'interruttore -k.


1

Sto utilizzando FreeBSD 11.1, il concetto di virtualizzazione jail di freebsd rende il sistema operativo ottimale. La versione corrente di Git che sto usando è la 2.15.1, inoltre preferisco eseguire tutto su script di shell. Con questo in mente ho modificato i suggerimenti sopra come segue:

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;

Se per qualche motivo è necessario ricreare lo script, l'output del file .permissions dovrebbe avere il seguente formato:

.gitignore;644;0;0

Per un file .gitignore con 644 permessi dati a root: wheel

Notare che ho dovuto apportare alcune modifiche alle opzioni delle statistiche.

Godere,


1

Un'aggiunta alla risposta di @Omid Ariyan sono i permessi sulle directory. Aggiungilo dopo che il forciclo è donenel suo 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

Ciò salverà anche i permessi di directory.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.