使用Git保留文件权限

时间:2010-07-08 20:30:41

标签: linux git file-permissions

我希望通过/var/www directory创建一个git repo来控制我的Web服务器,如Version control for my web server中所述。我希望我能够将我们的开发服务器中的web内容推送到github,将其拉到我们的生产服务器,然后在池中度过剩下的时间。

显然我的计划中的一个问题是Git不会尊重文件权限(我没有尝试过,现在只读它。)我想这是有道理的,因为不同的盒子容易有不同的用户/组设置。但是,如果我想强制传播权限,知道我的服务器配置相同,我有任何选择吗?或者有更简单的方法来接近我正在尝试做的事情吗?

8 个答案:

答案 0 :(得分:59)

Git是为软件开发而创建的版本控制系统,所以从整个模式和权限集中它只存储可执行位(对于普通文件)和符号链接位。如果您要存储完整权限,则需要第三方工具,例如git-cache-metamentioned by VonC)或Metastore(由etckeeper使用)。或者您可以使用IsiSetup,IIRC使用git作为后端。

请参阅Git Wiki上的Interfaces, frontends, and tools页面。

答案 1 :(得分:42)

问题“git-cache-meta”(以及git - how to recover the file permissions git thinks the file should be?)中提到的git FAQ是更明确的方法。

我们的想法是在.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

所以你:

  • bundle your repo并保存相关的文件权限。
  • 将这两个文件复制到远程服务器上
  • 恢复那里的回购,并应用权限

答案 2 :(得分:20)

这已经很晚了但可能对其他人有所帮助。我通过在我的存储库中添加两个git hook来做你想做的事。

<强>的.git /钩/预提交:

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

IFS_OLD=$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=$IFS_OLD

# Add the permissions database file to the index
git add $DATABASE

echo "OK"

<强>的.git /钩/结帐后:

#!/bin/bash

SELF_DIR=`git rev-parse --show-toplevel`
DATABASE=$SELF_DIR/.permissions

echo -n "Restoring file permissions..."

IFS_OLD=$IFS; IFS=$'\n'
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=$IFS_OLD

echo "OK"

exit 0

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

当您“结帐”时会调用第二个挂钩,并将浏览.permissions文件中的文件列表并恢复这些文件的所有权和权限。

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

答案 3 :(得分:2)

如果您现在就进入此状态,我今天刚刚通过它,可以总结一下它的位置。如果你还没有尝试这个,这里的一些细节可能会有所帮助。

我认为@Omid Ariyan的方法是最好的方法。添加预提交和结帐后脚本。不要忘记给他们完全按照奥米德的方式命名,并且忘记让他们可执行。如果你忘了其中任何一个,它们都没有效果,你运行&#34; git commit&#34;一遍又一遍地想知道为什么没有发生任何事情:)另外,如果您剪切并粘贴出Web浏览器,请注意引号和刻度不会改变。

如果您运行预提交脚本一次(通过运行git提交),则将创建文件.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。但是,可以运行&#34; chgrp&#34;很明显,如何使用它。

在这篇文章的第一个答案中,最广泛接受的一个,建议是使用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会话可能会出错。

答案 4 :(得分:1)

在预先提交/结账后,一个选项是使用&#34; mtree&#34; (FreeBSD),或者#34; fmtree&#34; (Ubuntu)实用程序,它将文件层次结构与规范进行比较,为文件层次结构创建规范,或修改规范。&#34;

默认设置为flags,gid,link,mode,nlink,size,time,type和uid。这可以通过-k开关适合特定用途。

答案 5 :(得分:1)

我在FreeBSD 11.1上运行,freebsd jail虚拟化概念使操作系统达到最佳状态。我正在使用的当前版本的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

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

享受,

答案 6 :(得分:1)

@Omid Ariyan的答案之一是对目录的权限。在for脚本的done循环pre-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

这也将保存目录权限。

答案 7 :(得分:1)

我们可以通过将.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 "$@"