Zachowywanie uprawnień do plików za pomocą Git

109

Chcę kontrolować wersję mojego serwera internetowego zgodnie z opisem w sekcji Kontrola wersji mojego serwera WWW , tworząc repozytorium git z pliku /var/www directory. Miałem nadzieję, że uda mi się wtedy przesłać zawartość sieciową z naszego serwera deweloperskiego na github, ściągnąć ją na nasz serwer produkcyjny i spędzić resztę dnia na puli.

Najwyraźniej dziwactwem w moim planie jest to, że Git nie respektuje uprawnień do plików (nie próbowałem tego, tylko czytam o tym teraz). Myślę, że ma to sens, ponieważ różne skrzynki mogą mieć różne ustawienia użytkownika / grupy. Ale jeśli chcę wymusić propagację uprawnień, wiedząc, że moje serwery są tak samo skonfigurowane, czy mam jakieś opcje? Czy jest łatwiejszy sposób podejścia do tego, co próbuję zrobić?

Yarin
źródło
1
Tak, chyba tak, chociaż rozwiązanie, na które wskazują, szczerze mówiąc nie jestem pewien, co zrobić. Miał nadzieję na prostsze podejście.
Yarin
A co z sytuacją, w której kod źródłowy pochodzi ze środowiska Dev (np. Windows - XAMPP itp.), Które nie ma informacji o właścicielu pliku? Pliki na końcu procesu git muszą być zgodne z własnością i uprawnieniami dla lokalizacji docelowej. Czy git-cache-meta sobie z tym poradzi? Zgadzam się z Yarinem ... z pewnością jest to dość powszechny przypadek użycia, który powinien mieć całkiem proste rozwiązanie?
user3600150

Odpowiedzi:

43

git-cache-metaWymienionych w SO pytanie „ git - jak odzyskać uprawnienia do plików git uważa, że plik powinien być ” (oraz FAQ git ) jest bardziej staightforward podejście.

Chodzi o to, aby przechowywać w .git_cache_metapliku uprawnienia do plików i katalogów.
Jest to osobny plik, który nie jest wersjonowany bezpośrednio w repozytorium Git.

Dlatego jego użycie jest następujące:

$ 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

Więc ty:

  • spakuj swoje repozytorium i zapisz powiązane uprawnienia do plików.
  • skopiuj te dwa pliki na zdalny serwer
  • przywróć tam repozytorium i zastosuj zezwolenie
VonC
źródło
2
VonC - Dzięki za to, spróbuję - ale czy pakiet jest konieczny? Czy nie mógłbym zachować mojego przepływu pracy (programowanie -> github -> produkcja) i po prostu sprawdzić / pobrać metaplik?
Yarin
@Yarin: nie, pakiet nie jest obowiązkowy. Jest to zgrabny sposób przesyłania repozytorium, gdy żaden inny protokół transferu nie jest dostępny.
VonC,
3
Użycie tutaj pakietu było dla mnie głównym rozpraszaniem. Właściwie to całkowicie zniechęciło mnie do odpowiedzi. (Nie mam trudności z wyciągnięciem repozytorium z serwera). Odpowiedź @ omid-ariyan poniżej z punktami przechwytywania przed / po zatwierdzeniu była znacznie bardziej zrozumiała. Później zdałem sobie sprawę, że te skrypty przechwytujące wykonują dokładnie taką samą pracę jak git-cache-meta. Zobacz, co mam na myśli: gist.github.com/andris9/1978266 . Przetwarzają i przechowują zwrot z git ls-files.
pauljohn32
Link do git-cache-meta jest martwy - czy ktoś, kto wie o tym, może go zlokalizować i edytować post?
rosuav
@rosuav Jasne: zredagowałem odpowiedź i przywróciłem link. Dziękuję za poinformowanie mnie o tym martwym łączu.
VonC
63

Git to system kontroli wersji, stworzony do tworzenia oprogramowania, więc z całego zestawu trybów i uprawnień przechowuje tylko wykonywalny bit (dla zwykłych plików) i bit dowiązania symbolicznego. Jeśli chcesz przechowywać pełne uprawnienia, potrzebujesz narzędzia innej firmy, takiego jak git-cache-meta( wspomniane przez VonC ) lub Metastore (używane przez etckeeper ). Lub możesz użyć IsiSetup , który IIRC używa git jako zaplecza.

Zobacz stronę Interfejsy, nakładki i narzędzia w witrynie Git Wiki.

Jakub Narębski
źródło
2
Dzięki Jakub - czy możesz mi wyjaśnić, dlaczego Git dba o wykonywalny bit i tylko to?
Yarin
5
@Yarin: tylko bit wykonywalny? Kiedy klonujesz cały zestaw plików z jednego systemu do drugiego, pojęcie „tylko do odczytu” lub „do odczytu i zapisu” nie jest do końca istotne (jak powiedziałeś w swoim pytaniu: różni użytkownicy / grupy). Ale pojęcie „wykonywalnego” nie zależy od użytkowników i grup i może być ponownie używane z systemu do (zdalnego) systemu.
VonC,
1
Jakub, w takim razie nie powinno zmieniać uprawnień. Mam na myśli, że powinien zostawić perm w spokoju lub zarządzać nimi, ale nie zadzierać z nimi, jeśli nie ma zamiaru nimi zarządzać.
CommaToast
3
Dodatkowo znalazłem /usr/share/git-core/contrib/hooks/setgitperms.perlw swoim git-contribpakiecie - skrypt do podobnego celu. ( „Skrypt ten może być używany do zapisu / przywrócenia pełne uprawnienia i dane własnościowe w drzewie roboczego git.”)
IMZ - Ivan Zakharyaschev
Czy to nadal jest poprawne, czy w jakiś sposób github robi coś ponad git? Właśnie zmieniłem plik na wykonywalny i zatwierdziłem go, a dziennik zmian dla zatwierdzenia pojawia się jako 0 linii zmienionych dla pliku, ale ma 100644 → 100755 obok nazwy pliku. To naprawdę wygląda na to, że pełne uprawnienia są przechowywane w pliku.
Cruncher
23

To dość późno, ale może pomóc innym. Robię to, co chcesz, dodając dwa punkty zaczepienia git do mojego repozytorium.

.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

Pierwszy hak jest wywoływany, gdy "zatwierdzasz" i odczytuje prawa własności i uprawnienia do wszystkich plików w repozytorium i przechowuje je w pliku w katalogu głównym repozytorium o nazwie .permissions, a następnie dodaje plik .permissions do zatwierdzenia.

Drugi hak jest wywoływany, gdy „wyewidencjonujesz” i przejdzie przez listę plików w pliku .permissions i przywróci własność oraz uprawnienia do tych plików.

  • Może być konieczne wykonanie zatwierdzenia i wyewidencjonowania za pomocą sudo.
  • Upewnij się, że skrypty przed zatwierdzeniem i po pobraniu mają uprawnienia do wykonywania.
Omid Ariyan
źródło
Omid ... dziękuję! Twój kod jest dla mnie idealnym rozwiązaniem.
Ricalsin
@Ricalsin Nie ma za co! Cieszę się, że pomogłem :)
Omid Ariyan
1
$SELF_DIR/../../niekoniecznie jest katalogiem głównym repozytorium ... ale nim git rev-parse --show-topleveljest. (Nie jestem pewien, dlaczego nie używałbyś tylko pwddla bieżącego katalogu, ale i tak jest to dyskusyjne.)
PJSCopeland
W obecnej postaci powyższe spowoduje podzielenie nazw plików ze spacjami. Zgodnie z tą odpowiedzią możesz ustawić IFS=$'\n'przed forpętlą, aby to zatrzymała (a unset IFSpóźniej, aby była bezpieczna).
PJSCopeland,
Nie pozwala to łatwo przenosić uprawnień do innego systemu, z innym systemem operacyjnym, w którym masz inną nazwę użytkownika. Zadałem sobie pytanie „czego naprawdę potrzebuję?” i wyciąć całego rozwiązania dół do chmod 0600 .pgpassw post-checkout. Tak, będę musiał zaktualizować go ręcznie, ilekroć mam plik, który wymaga określonych uprawnień, ale to przerwy.
PJSCopeland
2

Na wypadek, gdybyś teraz wchodził w to, właśnie przez to przeszedłem dzisiaj i mogę podsumować, na czym to polega. Jeśli jeszcze tego nie próbowałeś, niektóre szczegóły mogą pomóc.

Myślę, że podejście @Omid Ariyan jest najlepszym sposobem. Dodaj skrypty pre-commit i post-checkout. NIE zapomnij nazwać ich dokładnie tak, jak robi to Omid i NIE zapomnij uczynić ich wykonywalnymi. Jeśli zapomnisz któregokolwiek z nich, nie przyniosą one żadnego efektu i w kółko uruchamiasz "git commit", zastanawiając się, dlaczego nic się nie dzieje :) Ponadto, jeśli wycinasz i wklejasz z przeglądarki internetowej, uważaj, aby cudzysłowy i znaczniki nie były zmieniony.

Jeśli raz uruchomisz skrypt pre-commit (przez uruchomienie git commit), to zostanie utworzony plik .permissions. Możesz dodać go do repozytorium i myślę, że nie jest konieczne ciągłe dodawanie go na końcu skryptu pre-commit. Ale myślę, że to nie boli (mam nadzieję).

Istnieje kilka drobnych problemów związanych z nazwą katalogu i istnieniem spacji w nazwach plików w skryptach Omid. Przestrzenie były tutaj problemem i miałem problem z poprawką IFS. Dla przypomnienia, ten skrypt przed zatwierdzeniem działał poprawnie dla mnie:

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

Co z tego mamy?

Plik .permissions znajduje się na najwyższym poziomie repozytorium git. Ma jedną linię na plik, oto początek mojego przykładu:

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

Jak widać, mamy

filepath;perms;owner;group

W komentarzach na temat tego podejścia jeden z plakatów narzeka, że ​​działa tylko z tą samą nazwą użytkownika i jest to technicznie prawda, ale bardzo łatwo to naprawić. Zwróć uwagę, że skrypt po kasie ma 2 elementy akcji,

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

Więc zatrzymuję tylko pierwszy, to wszystko, czego potrzebuję. Moja nazwa użytkownika na serwerze WWW jest rzeczywiście inna, ale co ważniejsze, nie możesz uruchomić chown, jeśli nie jesteś rootem. Może jednak uruchomić "chgrp". Jest wystarczająco jasne, jak to wykorzystać.

W pierwszej odpowiedzi w tym poście, tej, która jest najczęściej akceptowana, sugestia jest taka, aby użyć git-cache-meta, skryptu, który wykonuje tę samą pracę, którą wykonują skrypty przechwytujące przed / po (parsowanie danych wyjściowych z git ls-files) . Te skrypty są dla mnie łatwiejsze do zrozumienia, kod git-cache-meta jest raczej bardziej rozbudowany. Możliwe jest zachowanie git-cache-meta w ścieżce i pisanie skryptów przed zatwierdzeniem i po pobraniu, które by go używały.

Spacje w nazwach plików są problemem w obu skryptach Omid. W skrypcie po zakupie będziesz wiedział, że masz spacje w nazwach plików, jeśli zobaczysz takie błędy

$ 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

Sprawdzam rozwiązania tego problemu. Oto coś, co wydaje się działać, ale przetestowałem tylko w jednym przypadku

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

Ponieważ informacje o uprawnieniach są wyświetlane po jednym wierszu na raz, ustawiłem IFS na $, więc tylko podziały wierszy są postrzegane jako nowe rzeczy.

Czytałem, że BARDZO WAŻNE jest ustawienie zmiennej środowiskowej IFS z powrotem tak, jak było! Możesz zobaczyć, dlaczego sesja powłoki może pójść źle, jeśli zostawisz $ jako jedyny separator.

pauljohn32
źródło
2

Możemy poprawić inne odpowiedzi, zmieniając format .permissionspliku na chmodinstrukcje wykonywalne i wykorzystując -printfparametr na find. Oto prostszy .git/hooks/pre-commitplik:

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

... a oto uproszczony .git/hooks/post-checkoutplik:

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

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

. .permissions

echo "done."

Pamiętaj, że inne narzędzia mogły już skonfigurować te skrypty, więc może być konieczne ich scalenie. Na przykład, oto post-checkoutskrypt, który zawiera również git-lfspolecenia:

#!/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
źródło
1

W przed zatwierdzeniem / po pobraniu opcją byłoby użycie narzędzia „mtree” (FreeBSD) lub „fmtree” (Ubuntu), które „porównuje hierarchię plików ze specyfikacją, tworzy specyfikację hierarchii plików lub modyfikuje specyfikacja."

Domyślny zestaw to flagi, gid, link, tryb, nlink, rozmiar, czas, typ i uid. Można to dopasować do określonego celu za pomocą przełącznika -k.

Vladimir Botka
źródło
1

Używam FreeBSD 11.1, koncepcja wirtualizacji jail freebsd sprawia, że ​​system operacyjny jest optymalny. Obecna wersja Gita, której używam to 2.15.1, wolę też uruchamiać wszystko na skryptach powłoki. Mając to na uwadze, zmodyfikowałem powyższe sugestie w następujący sposób:

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;

Jeśli z jakiegoś powodu musisz odtworzyć skrypt, plik wyjściowy .permissions powinien mieć następujący format:

.gitignore;644;0;0

Dla pliku .gitignore z uprawnieniami 644 nadanymi root: wheel

Zauważ, że musiałem wprowadzić kilka zmian w opcjach statystyk.

Cieszyć się,

Albaro Pereyra
źródło
1

Dodatkiem do odpowiedzi @Omid Ariyan są uprawnienia do katalogów. Dodaj to po forpętli donew swoim pre-commitskrypcie.

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

Spowoduje to również zapisanie uprawnień do katalogu.

Peter Berbec
źródło