使用Git保留文件权限


109

我想按照我的Web服务器的版本控制中所述,通过在我的Web服务器上创建git repo来对Web服务器进行 版本控制/var/www directory。我的希望是,然后我能够将Web内容从开发服务器推送到github,再将其拉到生产服务器,并在剩下的一天中度过。

显然,我的计划中的一个缺点是Git不尊重文件权限(我还没有尝试过,仅现在就阅读它。)我想这很有意义,因为不同的盒子可能具有不同的用户/组设置。但是,如果我想强制传播权限,知道我的服务器配置相同,我有什么选择吗?还是有一种更简单的方法来处理我要尝试的操作?



1
是的,是这样,尽管坦率地说他们指出的解决方案我不确定该怎么做。希望有一种更直接的方法。
Yarin

如果源代码来自没有文件所有权信息的开发环境(例如Windows-XAMPP等),情况如何?git进程结束时的文件需要匹配目标位置的所有权和权限。git-cache-meta可以处理吗?同意Yarin ...当然这是一个相当主流的用例,应该有一个非常简单的解决方案?
user3600150 '02

Answers:


43

git-cache-meta在SO问题中提到“ 混帐-如何恢复混帐认为该文件是文件权限 ”(和git的FAQ)是更staightforward方法。

这个想法是在.git_cache_meta文件中存储文件和目录的权限。
它是一个单独的文件,未在Git存储库中直接进行版本控制。

这就是为什么它的用法是:

$ 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

那么你:

  • 捆绑您的仓库,并保存相关的文件权限。
  • 将这两个文件复制到远程服务器上
  • 在此处恢复仓库,并申请许可

2
VonC-谢谢您,我会尝试的-但是捆绑是否有必要?我不能保留我的工作流程(开发-> github->生产)并仅签入/签出图元文件吗?
Yarin 2010年

@Yarin:不,捆绑不是强制性的。当没有其他传输协议可用时,这是传输回购协议的一种好方法。
VonC 2010年

3
在这里使用捆绑软件对我来说是一个主要的干扰。实际上,这使我完全没有答案。(我从服务器提取回购邮件没有困难。)@ omid-ariyan在下面带有pre / post提交钩子的答案更容易理解。后来我意识到这些钩子脚本所做的工作与git-cache-meta完全相同。去看看我的意思:gist.github.com/andris9/1978266。它们正在解析并存储来自的返回git ls-files
pauljohn32 '16

git-cache-meta的链接已死-知道这一点的人可以找到它并编辑帖子吗?
rosuav '16

@rosuav确定:我已经编辑了答案并恢复了链接。感谢您让我知道此无效链接。
VonC

63

Git是版本控制系统,是为软件开发而创建的,因此从整个模式和权限集中,它仅存储可执行位(用于普通文件)和symlink位。如果要存储全部权限,则需要第三方工具,例如git-cache-meta由VonC提及)或Metastore(由etckeeper使用)。或者,您可以使用IsiSetup,而IIRC使用git作为后端。

请参阅Git Wiki上的“ 界面,前端和工具”页面。


2
谢谢雅库布-您能向我解释为什么Git只关心可执行位吗?
Yarin 2010年

5
@Yarin:仅可执行位?当您将所有文件集从一个系统克隆到另一个系统时,“只读”或“读写”的概念并不完全相关(正如您在问题中所说的:不同的用户/组)。但是“可执行”的概念不依赖于用户和组,可以在系统之间(远程)重用。
VonC 2010年

1
Jakub,在这种情况下,它不应更改权限。我的意思是,它应该让烫发不动或管理烫发,但如果不打算管理烫发,那就不要弄乱烫发。
CommaToast 2014年

3
另外,我发现/usr/share/git-core/contrib/hooks/setgitperms.perl在我的git-contribpackage--脚本,针对一类似的目的。(“此脚本可用于在git工作树中保存/恢复完整的权限和所有权数据。”)
imz – Ivan Zakharyaschev

这仍然是准确的还是github以某种方式在git之上做某事?我只是将文件更改为可执行文件并提交,提交的更改日志显示为文件的0行更改,但文件名旁边有100644→100755。看起来完全权限与文件一起存储。
Cruncher

23

这已经很晚了,但可能会对其他人有所帮助。我通过向我的存储库添加两个git钩子来完成您想做的事情。

.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

当您“提交”时,将调用第一个钩子,它将读取存储库中所有文件的所有权和权限,并将它们存储在存储库根目录中名为.permissions的文件中,然后将.permissions文件添加到提交中。

当您“签出”时,将调用第二个挂钩,它将通过.permissions文件中的文件列表,并恢复这些文件的所有权和权限。

  • 您可能需要使用sudo进行提交和签出。
  • 确保提交前和签出后脚本具有执行权限。

奥米德...谢谢!我发现您的代码对我来说是一个完美的解决方案。
Ricalsin

@Ricalsin非常欢迎!我很高兴为您提供帮助:)
Omid Ariyan '16

1
$SELF_DIR/../../不一定是存储库的根...而是git rev-parse --show-toplevel。(不知道为什么不只使用pwd当前目录,但还是有争议的。)
PJSCopeland

就目前情况而言,以上内容将拆分文件名,并在其中包含空格。按照此答案,您可以IFS=$'\n'for循环之前设置停止它(unset IFS然后是安全的)。
PJSCopeland '16

这不能轻易让您将权限传递给具有不同用户名的具有不同OS的其他系统。我问自己“我真正需要什么?” 并将整个解决方案缩减为chmod 0600 .pgpassin post-checkout。是的,每当我有一个需要特定权限的文件时,我都必须手动更新它,但这是个麻烦。
PJSCopeland '16

2

如果您现在就开始讨论这个问题,那么我今天已经讲完了,可以总结一下它的立场。如果您尚未尝试此操作,则此处的一些详细信息可能会有所帮助。

我认为@Omid Ariyan的方法是最好的方法。添加提交前和签出后脚本。不要忘记使用Omid的方式来命名它们,也不要忘记使它们具有可执行性。如果您忘记了它们中的任何一个,它们就没有效果,并且您一遍又一遍地运行“ git commit”,想知道为什么什么也没发生:)另外,如果您在网络浏览器中剪切粘贴,请注意不要使用引号和对勾改变了。

如果您一次运行预提交脚本(通过运行git commit),则将创建文件.permissions。您可以将其添加到存储库中,我认为没有必要在预提交脚本的末尾一遍又一遍地添加它。但是,我认为(希望)这不会造成伤害。

关于目录名以及Omid脚本中文件名中是否存在空格,存在一些小问题。这里的空格是个问题,我在使用IFS修复程序时遇到了一些麻烦。作为记录,此预提交脚本对我而言正确运行:

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

现在,我们从中得到什么?

.permissions文件位于git repo的顶层。每个文件只有一行,这是我的示例的顶部:

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

如您所见,我们有

filepath;perms;owner;group

在有关此方法的评论中,其中一位张贴者抱怨说,它只能使用相同的用户名使用,从技术上讲这是正确的,但是很容易解决。请注意,结帐后脚本有2个动作片段,

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

所以我只保留第一个,这就是我所需要的。我在Web服务器上的用户名确实有所不同,但更重要的是,除非您是root用户,否则您不能运行chown。可以运行“ chgrp”。足够简单地知道如何使用它。

在本文的第一个答案中,该答案被最广泛接受,建议是使用git-cache-meta,该脚本的作用与此处的前/后钩子脚本相同(从解析输出git ls-files) 。这些脚本对我来说更容易理解,git-cache-meta代码相当详尽。可以将git-cache-meta保留在路径中,并编写将使用它的提交前和签出后脚本。

两个Omid脚本都存在文件名中的空格问题。在检出后脚本中,如果您看到这样的错误,您将知道文件名中有空格

$ 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

我正在寻找解决方案。这似乎可行,但我仅在一种情况下进行了测试

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

由于权限信息一次仅一行,因此我将IFS设置为$,因此只有换行符才被视为新事物。

我读到,将IFS环境变量恢复为原来的状态非常重要!您可以看到为什么如果将$保留为唯一的分隔符,那么shell会话可能会失败的原因。


2

我们可以通过将.permissions文件的格式更改为可执行chmod语句并将-printf参数设置为来改善其他答案find。这是更简单的.git/hooks/pre-commit文件:

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

...这是简化的.git/hooks/post-checkout文件:

#!/usr/bin/env bash

echo -n "Restoring file permissions... "

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

. .permissions

echo "done."

请记住,其他工具可能已经配置了这些脚本,因此您可能需要将它们合并在一起。例如,下面的post-checkout脚本还包含以下git-lfs命令:

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

在提交前/结帐后,可以选择使用“ mtree”(FreeBSD)或“ fmtree”(Ubuntu)实用程序,该实用程序“根据规范比较文件层次结构,为文件层次结构创建规范或修改规范。”

默认设置为标志,gid,链接,模式,nlink,大小,时间,类型和uid。可以使用-k开关将其安装到特定的目的。


1

我在FreeBSD 11.1上运行,freebsd监狱虚拟化概念使操作系统最佳。我正在使用的Git当前版本是2.15.1,我也更喜欢在shell脚本上运行所有内容。考虑到这一点,我修改了以下建议:

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;

如果由于某种原因需要重新创建脚本,.permissions文件输出应采用以下格式:

.gitignore;644;0;0

对于具有644权限的.gitignore文件,授予root:wheel

注意,我必须对stat选项进行一些更改。

请享用,


1

@Omid Ariyan答案的另一项是目录权限。在其脚本的for循环之后添加此内容。donepre-commit

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

这也将保存目录权限。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.