Reter permissões de arquivo com Git

109

Quero controlar a versão do meu servidor web conforme descrito em Controle de versão do meu servidor web , criando um repositório git a partir do meu /var/www directory. Minha esperança era poder enviar o conteúdo da web de nosso servidor de desenvolvimento para o github, colocá-lo em nosso servidor de produção e passar o resto do dia no pool.

Aparentemente, uma falha em meu plano é que o Git não respeitará as permissões de arquivo (não tentei, só estou lendo sobre isso agora). Acho que isso faz sentido, pois diferentes caixas podem ter configurações de usuário / grupo diferentes. Mas se eu quisesse forçar a propagação de permissões, sabendo que meus servidores estão configurados da mesma forma, tenho alguma opção? Ou existe uma maneira mais fácil de abordar o que estou tentando fazer?

Yarin
fonte
1
Sim, acho que sim, embora a solução que eles apontam eu francamente não tenho certeza do que fazer com. Esperava uma abordagem mais direta.
Yarin
E sobre a situação em que o código-fonte vem do ambiente Dev (por exemplo, Windows - XAMPP etc), que não tem informações de propriedade do arquivo? Os arquivos no final do processo git precisam corresponder à propriedade e às permissões do local de destino. O git-cache-meta pode lidar com isso? Concordo com o Yarin ... certamente este é um caso de uso bastante comum, que deveria ter uma solução bastante direta?
user3600150 01 de

Respostas:

43

A git-cache-metamencionada na pergunta do SO " git - como recuperar as permissões do arquivo que o git acha que o arquivo deveria ser? " (E o FAQ do git ) é a abordagem mais direta.

A ideia é armazenar em um .git_cache_metaarquivo as permissões dos arquivos e diretórios.
É um arquivo separado sem versão diretamente no repositório Git.

É por isso que o uso dele é:

$ 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

Então você:

  • agrupe seu repo e salve as permissões de arquivo associadas.
  • copie esses dois arquivos no servidor remoto
  • restaure o repositório lá e aplique a permissão
VonC
fonte
2
VonC- Obrigado por isso, vou tentar, mas o pacote é necessário? Eu não poderia reter meu fluxo de trabalho (dev -> github -> produção) e apenas fazer check-in / checkout do meta-arquivo?
Yarin
@Yarin: não, o pacote não é obrigatório. É uma maneira bacana de transferir repo quando nenhum outro protocolo de transferência está disponível.
VonC
3
O uso de pacote aqui foi uma grande distração para mim. Isso me tirou completamente da resposta, na verdade. (Não tenho dificuldade em extrair o repo do servidor.) A resposta de @ omid-ariyan abaixo com ganchos de commit pré / pós foi muito mais compreensível. Mais tarde, percebi que esses scripts de gancho estão fazendo exatamente o mesmo trabalho que git-cache-meta. Vá ver o que quero dizer: gist.github.com/andris9/1978266 . Eles estão analisando e armazenando o retorno de git ls-files.
pauljohn32
O link para git-cache-meta está morto - alguém que sabe sobre isso pode localizá-lo e editar a postagem?
rosuav de
@rosuav Claro: editei a resposta e restaurei o link. Obrigado por me informar sobre este link inativo.
VonC de
63

Git é um sistema de controle de versão, criado para o desenvolvimento de software, portanto, de todo o conjunto de modos e permissões, ele armazena apenas bits executáveis ​​(para arquivos comuns) e bits de link simbólico. Se você deseja armazenar permissões completas, você precisa de uma ferramenta de terceiros, como git-cache-meta( mencionada por VonC ) ou Metastore (usada por etckeeper ). Ou você pode usar IsiSetup , que IIRC usa git como back-end.

Consulte a página Interfaces, frontends e ferramentas no Git Wiki.

Jakub Narębski
fonte
2
Obrigado Jakub- você pode me explicar por que o Git se preocupa com a parte executável e apenas isso?
Yarin
5
@Yarin: bit executável apenas? Quando você clona um conjunto completo de arquivos de um sistema para outro, a noção de "somente leitura" ou "leitura e gravação" não é exatamente relevante (como você disse em sua pergunta: diferentes usuários / grupos). Mas a noção de "executável" não depende de usuários e grupos e pode ser reutilizada de um sistema para outro (remoto).
VonC de
1
Jakub, nesse caso, não deve alterar as permissões. Quero dizer, ele deve deixar os permanentes sozinhos ou gerenciá-los, mas não mexer com eles se não for gerenciá-los.
CommaToast de
3
Além disso, encontrei /usr/share/git-core/contrib/hooks/setgitperms.perlem meu git-contribpacote - um script para um propósito semelhante. ("Este script pode ser usado para salvar / restaurar permissões completas e dados de propriedade em uma árvore de trabalho git.")
imz - Ivan Zakharyaschev
Isso ainda é preciso ou o github de alguma forma faz algo em cima do git? Acabei de alterar um arquivo para executável e confirmá-lo, e o changelog para o commit aparece como 0 linhas alteradas para o arquivo, mas tem 100644 → 100755 ao lado do nome do arquivo. Isso realmente parece que todas as permissões são armazenadas com o arquivo.
Cruncher
23

É muito tarde, mas pode ajudar outras pessoas. Eu faço o que você quer, adicionando dois ganchos git ao meu repositório.

.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

O primeiro gancho é chamado quando você "confirma" e lê a propriedade e as permissões de todos os arquivos no repositório e os armazena em um arquivo na raiz do repositório chamado .permissions e, em seguida, adiciona o arquivo .permissions ao commit.

O segundo gancho é chamado quando você faz "checkout" e percorre a lista de arquivos no arquivo .permissions e restaura a propriedade e as permissões desses arquivos.

  • Você pode precisar fazer o commit e checkout usando sudo.
  • Certifique-se de que os scripts de pré-confirmação e pós-checkout tenham permissão de execução.
Omid Ariyan
fonte
Omid ... obrigado! Achei seu código uma solução perfeita para mim.
Ricalsin
@Ricalsin De nada! Estou feliz por ter ajudado :)
Omid Ariyan
1
$SELF_DIR/../../não é necessariamente a raiz do repositório ... mas git rev-parse --show-toplevelé. (Não sei por que você não usaria apenas pwdpara o diretório atual, mas isso é discutível de qualquer maneira.)
PJSCopeland
Da forma como está, o acima irá dividir os nomes dos arquivos com espaços. De acordo com esta resposta , você pode definir IFS=$'\n'antes do forloop para interromper isso (e unset IFSdepois para ser seguro).
PJSCopeland de
Isso não permite que você leve as permissões para um sistema diferente, com um sistema operacional diferente, onde você tem um nome de usuário diferente. Eu me perguntei "o que eu realmente preciso?" e reduzir toda a solução chmod 0600 .pgpassem post-checkout. Sim, terei que atualizá-lo manualmente sempre que tiver um arquivo que precise de permissões específicas, mas isso está quebrado.
PJSCopeland
2

Caso você esteja entrando nisso agora, acabei de passar por isso hoje e posso resumir onde isso está. Se você ainda não tentou fazer isso, alguns detalhes aqui podem ajudar.

Acho que a abordagem de @Omid Ariyan é a melhor maneira. Adicione os scripts de pré-confirmação e pós-checkout. NÃO se esqueça de nomeá-los exatamente como o Omid faz e NÃO se esqueça de torná-los executáveis. Se você esquecer qualquer um deles, eles não terão efeito e você executará "git commit" repetidamente se perguntando por que nada acontece :) Além disso, se você recortar e colar do navegador da web, tome cuidado para que as aspas e tiques não sejam alterado.

Se você executar o script pre-commit uma vez (executando um git commit), o arquivo .permissions será criado. Você pode adicioná-lo ao repositório e acho que é desnecessário adicioná-lo repetidamente no final do script de pré-confirmação. Mas não dói, eu acho (espero).

Existem alguns pequenos problemas sobre o nome do diretório e a existência de espaços nos nomes dos arquivos nos scripts do Omid. Os espaços eram um problema aqui e eu tive alguns problemas com a correção do IFS. Para registro, este script de pré-confirmação funcionou corretamente para mim:

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

Agora, o que ganhamos com isso?

O arquivo .permissions está no nível superior do repositório git. Tem uma linha por arquivo, aqui está o topo do meu exemplo:

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

Como você pode ver, nós temos

filepath;perms;owner;group

Nos comentários sobre essa abordagem, um dos postadores reclama que só funciona com o mesmo nome de usuário, o que é tecnicamente verdade, mas é muito fácil de corrigir. Observe que o script pós-checkout tem 2 ações,

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

Então, estou ficando apenas com o primeiro, é tudo de que preciso. Meu nome de usuário no servidor Web é realmente diferente, mas o mais importante é que você não pode executar o chown a menos que seja root. Pode executar "chgrp", no entanto. É bastante claro como colocar isso em prática.

Na primeira resposta neste post, a que é mais amplamente aceita, a sugestão é usar git-cache-meta, um script que está fazendo o mesmo trabalho que os scripts de pré / pós-gancho aqui estão fazendo (analisando a saída de git ls-files) . Esses scripts são mais fáceis de entender, o código git-cache-meta é um pouco mais elaborado. É possível manter git-cache-meta no caminho e escrever scripts de pré-confirmação e pós-checkout que o usariam.

Os espaços nos nomes dos arquivos são um problema com os dois scripts do Omid. No script pós-checkout, você saberá que tem espaços nos nomes de arquivo se vir erros 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

Estou verificando soluções para isso. Aqui está algo que parece funcionar, mas eu só testei em um 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

Uma vez que as informações de permissões são uma linha de cada vez, eu defino IFS como $, então apenas as quebras de linha são vistas como coisas novas.

Eu li que é MUITO IMPORTANTE definir a variável de ambiente IFS de volta do jeito que estava! Você pode ver porque uma sessão de shell pode dar errado se você deixar $ como o único separador.

pauljohn32
fonte
2

Podemos melhorar as outras respostas alterando o formato do .permissionsarquivo para chmodinstruções executáveis e fazendo uso do -printfparâmetro para find. Aqui está o .git/hooks/pre-commitarquivo mais simples :

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

... e aqui está o .git/hooks/post-checkoutarquivo simplificado :

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

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

. .permissions

echo "done."

Lembre-se de que outras ferramentas podem já ter configurado esses scripts, portanto, pode ser necessário mesclá-los. Por exemplo, aqui está um post-checkoutscript que também inclui os 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
fonte
1

No pré-commit / post-checkout, uma opção seria usar o utilitário "mtree" (FreeBSD) ou "fmtree" (Ubuntu) que "compara uma hierarquia de arquivo com uma especificação, cria uma especificação para uma hierarquia de arquivo ou modifica um especificação."

O conjunto padrão é flags, gid, link, mode, nlink, size, time, type e uid. Isso pode ser ajustado para a finalidade específica com a chave -k.

Vladimir Botka
fonte
1

Estou executando o FreeBSD 11.1, o conceito de virtualização freebsd jail torna o sistema operacional ideal. A versão atual do Git que estou usando é 2.15.1, também prefiro executar tudo em scripts de shell. Com isso em mente, modifiquei as sugestões acima da seguinte forma:

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 por algum motivo você precisar recriar o script, a saída do arquivo .permissions deve ter o seguinte formato:

.gitignore;644;0;0

Para um arquivo .gitignore com 644 permissões concedidas ao root: wheel

Observe que tive que fazer algumas alterações nas opções de estatísticas.

Aproveitar,

Albaro Pereyra
fonte
1

Uma adição à resposta de @Omid Ariyan são as permissões nos diretórios. Adicione isso após o forloop doneem seu 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

Isso também salvará as permissões do diretório.

Peter Berbec
fonte