Mempertahankan izin file dengan Git

109

Saya ingin mengontrol versi server web saya seperti yang dijelaskan dalam Kontrol versi untuk server web saya , dengan membuat repo git dari file /var/www directory. Harapan saya adalah saya kemudian dapat mendorong konten web dari server dev kami ke github, menariknya ke server produksi kami, dan menghabiskan sisa hari itu di pool.

Rupanya kekusutan dalam rencana saya adalah Git tidak akan menghormati izin file (saya belum mencobanya, hanya membaca tentangnya sekarang.) Saya rasa ini masuk akal karena kotak yang berbeda mungkin memiliki pengaturan pengguna / grup yang berbeda. Tetapi jika saya ingin memaksa izin untuk menyebar, mengetahui server saya dikonfigurasi sama, apakah saya punya pilihan? Atau adakah cara yang lebih mudah untuk mendekati apa yang saya coba lakukan?

Yarin
sumber
1
Ya rasa begitu, meskipun solusi yang mereka tunjuk, saya terus terang tidak yakin apa yang harus dilakukan. Berharap untuk pendekatan yang lebih lugas.
Yarin
Bagaimana dengan situasi di mana kode sumber berasal dari lingkungan Dev (misalnya Windows - XAMPP dll) yang tidak memiliki info kepemilikan file? File di akhir proses git harus cocok dengan kepemilikan & izin untuk lokasi target. Bisakah git-cache-meta menangani ini? Setuju dengan Yarin ... tentunya ini adalah kasus penggunaan yang cukup umum, yang seharusnya memiliki solusi yang cukup mudah?
pengguna3600150

Jawaban:

43

Yang git-cache-metadisebutkan dalam pertanyaan SO " git - bagaimana memulihkan hak akses file menurut git file itu? " (Dan FAQ git ) adalah pendekatan yang lebih tepat.

Idenya adalah untuk menyimpan dalam sebuah .git_cache_metafile hak akses dari file dan direktori.
Ini adalah file terpisah yang tidak diversi secara langsung di repo Git.

Itu sebabnya penggunaannya adalah:

$ 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

Jadi kamu:

  • bundel repo Anda dan simpan izin file terkait.
  • salin kedua file tersebut di server jauh
  • pulihkan repo di sana, dan terapkan izin
VonC
sumber
2
VonC- Terima kasih untuk ini, saya akan mencobanya- tetapi apakah bundling itu perlu? Tidak bisakah saya mempertahankan alur kerja saya (dev -> github -> produksi) dan hanya check in / checkout metafile?
Yarin
@ Yarin: tidak, bundle tidak wajib. Ini adalah cara yang rapi untuk mentransfer repo ketika tidak ada protokol transfer lain yang tersedia.
VonC
3
Penggunaan bundle di sini adalah gangguan besar bagi saya. Sebenarnya, itu membuat saya kehilangan jawaban sepenuhnya. (Saya tidak mengalami kesulitan menarik repo dari server.) Jawaban @ omid-ariyan di bawah ini dengan hook komit pra / posting jauh lebih bisa dimengerti. Kemudian saya menyadari bahwa skrip hook tersebut melakukan pekerjaan yang sama persis dengan git-cache-meta. Lihat apa yang saya maksud: gist.github.com/andris9/1978266 . Mereka mengurai dan menyimpan pengembalian dari git ls-files.
pauljohn32
Tautan ke git-cache-meta sudah mati - dapatkah seseorang yang mengetahui tentang ini menemukannya dan mengedit kiriman?
rosuav
@rosuav Tentu: Saya telah mengedit jawabannya dan memulihkan tautannya. Terima kasih telah memberi tahu saya tentang tautan mati ini.
VonC
63

Git adalah Sistem Kontrol Versi, dibuat untuk pengembangan perangkat lunak, jadi dari seluruh rangkaian mode dan izin hanya menyimpan bit yang dapat dieksekusi (untuk file biasa) dan bit symlink. Jika Anda ingin menyimpan izin penuh, Anda memerlukan alat pihak ketiga, seperti git-cache-meta( disebutkan oleh VonC ), atau Metastore (digunakan oleh etckeeper ). Atau Anda dapat menggunakan IsiSetup , yang IIRC menggunakan git sebagai backend.

Lihat halaman Antarmuka, bagian depan, dan alat di Git Wiki.

Jakub Narębski
sumber
2
Terima kasih Jakub- bisakah Anda menjelaskan kepada saya mengapa Git peduli dengan bit yang dapat dieksekusi dan hanya itu?
Yarin
5
@ Yarin: hanya bit yang dapat dieksekusi? Saat Anda mengkloning semua kumpulan file dari satu sistem ke sistem lain, gagasan "hanya baca" atau "baca-tulis" tidak terlalu relevan (seperti yang Anda katakan dalam pertanyaan Anda: pengguna / grup berbeda). Tetapi gagasan tentang "dapat dieksekusi" tidak bergantung pada pengguna dan grup dan dapat digunakan kembali dari sistem ke sistem (jarak jauh).
VonC
1
Jakub, dalam hal ini seharusnya tidak mengubah izin. Maksud saya, itu harus membiarkan perms sendiri atau mengelolanya, tetapi tidak mengacaukannya jika itu tidak akan mengelolanya.
CommaToast
3
Selain itu, saya telah menemukan /usr/share/git-core/contrib/hooks/setgitperms.perldi git-contribpaket saya-- skrip untuk tujuan serupa. ("Skrip ini dapat digunakan untuk menyimpan / memulihkan izin penuh dan data kepemilikan dalam pohon kerja git.")
imz - Ivan Zakharyaschev
Apakah ini masih akurat atau apakah github melakukan sesuatu di atas git? Saya baru saja mengubah file menjadi dapat dieksekusi dan berkomitmen, dan changelog untuk komit muncul sebagai 0 baris yang diubah untuk file tersebut, tetapi memiliki 100644 → 100755 di samping nama file. Ini benar-benar terlihat seperti izin penuh disimpan dengan file.
Cruncher
23

Ini cukup terlambat tetapi mungkin membantu beberapa orang lain. Saya melakukan apa yang ingin Anda lakukan dengan menambahkan dua kait git ke repositori saya.

.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

Hook pertama dipanggil saat Anda "berkomitmen" dan akan membaca kepemilikan dan izin untuk semua file di repositori dan menyimpannya dalam file di root repositori yang disebut .permissions dan kemudian menambahkan file .permissions ke komit.

Hook kedua dipanggil saat Anda "checkout" dan akan memeriksa daftar file di file .permissions dan memulihkan kepemilikan dan izin file tersebut.

  • Anda mungkin perlu melakukan commit dan checkout menggunakan sudo.
  • Pastikan skrip pra-komit dan pasca-pembayaran memiliki izin eksekusi.
Omid Ariyan
sumber
Omid ... terima kasih! Saya menemukan kode Anda untuk menjadi solusi sempurna bagi saya.
Ricalsin
@Ricalsin Terima kasih kembali! Saya senang bisa membantu :)
Omid Ariyan
1
$SELF_DIR/../../belum tentu root dari repositori ... tetapi git rev-parse --show-topleveladalah. (Tidak yakin mengapa Anda tidak hanya menggunakan pwduntuk direktori saat ini, tapi itu tetap diperdebatkan.)
PJSCopeland
Seperti berdiri, di atas akan membagi nama file dengan spasi di dalamnya. Sesuai jawaban ini , Anda dapat mengatur IFS=$'\n'sebelum forloop untuk menghentikannya (dan unset IFSsetelah itu aman).
PJSCopeland
Ini tidak memungkinkan Anda untuk membawa izin ke sistem yang berbeda, dengan OS yang berbeda, di mana Anda memiliki nama pengguna yang berbeda. Saya bertanya pada diri sendiri "apa yang sebenarnya saya butuhkan?" dan memotong seluruh solusi menjadi chmod 0600 .pgpassmasuk post-checkout. Ya, saya harus memperbaruinya secara manual setiap kali saya memiliki file yang memerlukan izin khusus, tetapi itulah jeda.
PJSCopeland
2

Jika Anda sedang mengalami masalah ini sekarang, saya baru saja mengalaminya hari ini dan dapat meringkas di mana ini berdiri. Jika Anda belum mencobanya, beberapa detail di sini mungkin bisa membantu.

Saya pikir pendekatan @Omid Ariyan adalah cara terbaik. Tambahkan skrip pra-komit dan pasca-pembayaran. JANGAN lupa untuk menamainya persis seperti yang dilakukan Omid dan JANGAN lupa untuk membuatnya dapat dieksekusi. Jika Anda lupa salah satunya, mereka tidak akan berpengaruh dan Anda menjalankan "git commit" berulang kali sambil bertanya-tanya mengapa tidak terjadi apa-apa :) Selain itu, jika Anda memotong dan menempelkan dari browser web, berhati-hatilah agar tanda kutip dan centang tidak diubah.

Jika Anda menjalankan skrip pra-komit satu kali (dengan menjalankan git komit), maka file .permissions akan dibuat. Anda dapat menambahkannya ke repositori dan menurut saya tidak perlu menambahkannya berulang kali di akhir skrip pra-komit. Tapi tidak ada salahnya, saya pikir (harapan).

Ada beberapa masalah kecil tentang nama direktori dan keberadaan spasi pada nama file di skrip Omid. Ruang adalah masalah di sini dan saya mengalami masalah dengan perbaikan IFS. Sebagai catatan, skrip pra-komit ini berfungsi dengan benar untuk saya:

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

Sekarang, apa yang kita dapatkan dari ini?

File .permissions ada di level teratas dari git repo. Ini memiliki satu baris per file, berikut adalah bagian atas contoh saya:

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

Seperti yang Anda lihat, kami punya

filepath;perms;owner;group

Dalam komentar tentang pendekatan ini, salah satu poster mengeluh bahwa itu hanya berfungsi dengan nama pengguna yang sama, dan itu secara teknis benar, tetapi sangat mudah untuk memperbaikinya. Perhatikan bahwa skrip pasca-pembayaran memiliki 2 bagian tindakan,

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

Jadi saya hanya menyimpan yang pertama, itu yang saya butuhkan. Nama pengguna saya di server Web memang berbeda, tetapi yang lebih penting Anda tidak dapat menjalankan chown kecuali Anda adalah root. Namun, dapat menjalankan "chgrp". Cukup jelas bagaimana menggunakannya.

Dalam jawaban pertama di posting ini, yang paling diterima secara luas, sarannya adalah gunakan git-cache-meta, skrip yang melakukan pekerjaan yang sama dengan yang dilakukan oleh skrip hook pra / posting di sini (parsing output dari git ls-files) . Skrip ini lebih mudah untuk saya pahami, kode git-cache-meta agak lebih rumit. Dimungkinkan untuk menyimpan git-cache-meta di jalur dan menulis skrip pra-komit dan pasca-checkout yang akan menggunakannya.

Spasi dalam nama file merupakan masalah dengan kedua skrip Omid. Dalam skrip pasca-pembayaran, Anda akan tahu bahwa Anda memiliki spasi dalam nama file jika Anda melihat kesalahan seperti ini

$ 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

Saya sedang memeriksa solusi untuk itu. Ini sesuatu yang tampaknya berhasil, tetapi saya hanya menguji dalam satu kasus

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

Karena informasi perizinan adalah satu baris pada satu waktu, saya menetapkan IFS ke $, jadi hanya jeda baris yang dilihat sebagai hal baru.

Saya membaca bahwa SANGAT PENTING untuk menyetel variabel lingkungan IFS kembali seperti semula! Anda dapat melihat mengapa sesi shell mungkin berjalan buruk jika Anda meninggalkan $ sebagai satu-satunya pemisah.

pauljohn32
sumber
2

Kami dapat memperbaiki jawaban lain dengan mengubah format .permissionsfile menjadi chmodpernyataan yang dapat dieksekusi , dan menggunakan -printfparameter ke find. Ini .git/hooks/pre-commitfile yang lebih sederhana :

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

... dan ini .git/hooks/post-checkoutfile yang disederhanakan :

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

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

. .permissions

echo "done."

Ingatlah bahwa alat lain mungkin telah mengonfigurasi skrip ini, jadi Anda mungkin perlu menggabungkannya. Misalnya, berikut ini post-checkoutskrip yang juga menyertakan git-lfsperintah:

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

Dalam pra-komit / pasca-checkout, opsi akan menggunakan "mtree" (FreeBSD), atau "fmtree" (Ubuntu) utilitas yang "membandingkan hierarki file dengan spesifikasi, membuat spesifikasi untuk hierarki file, atau memodifikasi spesifikasi."

Set default adalah flags, gid, link, mode, nlink, size, time, type, dan uid. Ini dapat dipasang untuk tujuan tertentu dengan sakelar -k.

Vladimir Botka
sumber
1

Saya menjalankan FreeBSD 11.1, konsep virtualisasi jail freebsd membuat sistem operasi menjadi optimal. Versi Git yang saya gunakan saat ini adalah 2.15.1, saya juga lebih suka menjalankan semuanya pada skrip shell. Dengan pemikiran tersebut saya memodifikasi saran di atas sebagai berikut:

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;

Jika karena alasan tertentu Anda perlu membuat ulang skrip, output file .permissions harus memiliki format berikut:

.gitignore;644;0;0

Untuk file .gitignore dengan 644 izin yang diberikan ke root: wheel

Perhatikan saya harus membuat beberapa perubahan pada opsi stat.

Nikmati,

Albaro Pereyra
sumber
1

Satu tambahan untuk jawaban @Omid Ariyan adalah perizinan pada direktori. Tambahkan ini setelah forloop donedi pre-commitskripnya.

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

Ini akan menghemat izin direktori juga.

Peter Berbec
sumber