Linux 使用 Git 保留文件权限
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3207728/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Retaining file permissions with Git
提问by Yarin
I want to version control my web server as described in Version control for my web server, by creating a git repo out of my /var/www directory
. My hope was that I would then be able to push web content from our dev server to github, pull it to our production server, and spend the rest of the day at the pool.
我想版本控制我的Web服务器中的说明 对我的Web服务器版本控制,通过创建一个git回购了我的/var/www directory
。我希望我能够将 Web 内容从我们的开发服务器推送到 github,将其拉到我们的生产服务器,然后在池中度过剩下的一天。
Apparently a kink in my plan is that Git won't respect file permissions (I haven't tried it, only reading about it now.) I guess this makes sense in that different boxes are liable to have different user/group setups. But if I wanted to force permissions to propagate, knowing my servers are configured the same, do I have any options? Or is there an easier way to approach what I'm trying to do?
显然,我的计划中的一个问题是 Git 不会尊重文件权限(我还没有尝试过,现在只阅读它。)我想这是有道理的,因为不同的盒子可能有不同的用户/组设置。但是如果我想强制传播权限,知道我的服务器配置相同,我有什么选择吗?或者有没有更简单的方法来处理我想要做的事情?
采纳答案by VonC
The git-cache-meta
mentioned in SO question "git - how to recover the file permissions git thinks the file should be?" (and the git FAQ) is the more staightforward approach.
将git-cache-meta
在SO问题中提到“混帐-如何恢复混帐认为该文件是文件权限”(和git的FAQ)是更staightforward方法。
The idea is to store in a .git_cache_meta
file the permissions of the files and directories.
It is a separate file not versioned directly in the Git repo.
这个想法是.git_cache_meta
将文件和目录的权限存储在一个文件中。
它是一个单独的文件,未直接在 Git 存储库中进行版本控制。
That is why the usage for it is:
这就是为什么它的用法是:
$ 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
So you:
那么你:
- bundle your repoand save the associated file permissions.
- copy those two files on the remote server
- restore the repo there, and apply the permission
- 捆绑您的 repo并保存相关的文件权限。
- 将这两个文件复制到远程服务器上
- 在那里恢复 repo,并应用权限
回答by Jakub Nar?bski
Git is Version Control System, created for software development, so from the whole set of modes and permissions it stores only executable bit (for ordinary files) and symlink bit. If you want to store full permissions, you need third party tool, like git-cache-meta
(mentioned by VonC), or Metastore(used by etckeeper). Or you can use IsiSetup, which IIRC uses git as backend.
Git 是版本控制系统,为软件开发而创建,因此从整套模式和权限来看,它仅存储可执行位(对于普通文件)和符号链接位。如果要存储完整权限,则需要第三方工具,例如git-cache-meta
(由 VonC 提及)或Metastore(由etckeeper 使用)。或者您可以使用IsiSetup,IIRC 使用 git 作为后端。
See Interfaces, frontends, and toolspage on Git Wiki.
请参阅Git Wiki 上的接口、前端和工具页面。
回答by Omid Ariyan
This is quite late but might help some others. I do what you want to do by adding two git hooks to my repository.
这已经很晚了,但可能会帮助其他人。我通过向我的存储库添加两个 git 钩子来做你想做的事情。
.git/hooks/pre-commit:
.git/hooks/预提交:
#!/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:
.git/hooks/结帐后:
#!/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
The first hook is called when you "commit" and will read the ownership and permissions for all the files in the repository and store them in a file in the root of the repository called .permissions and then add the .permissions file to the commit.
第一个钩子在您“提交”时调用,它将读取存储库中所有文件的所有权和权限,并将它们存储在存储库根目录中名为 .permissions 的文件中,然后将 .permissions 文件添加到提交中。
The second hook is called when you "checkout" and will go through the list of files in the .permissions file and restore the ownership and permissions of those files.
第二个钩子在您“结帐”时调用,它将遍历 .permissions 文件中的文件列表并恢复这些文件的所有权和权限。
- You might need to do the commit and checkout using sudo.
- Make sure the pre-commit and post-checkout scripts have execution permission.
- 您可能需要使用 sudo 进行提交和签出。
- 确保 pre-commit 和 post-checkout 脚本具有执行权限。
回答by Vladimir Botka
In pre-commit/post-checkout an option would be to use "mtree" (FreeBSD), or "fmtree" (Ubuntu) utility which "compares a file hierarchy against a specification, creates a specification for a file hierarchy, or modifies a specification."
在 pre-commit/post-checkout 中,一个选项是使用“mtree”(FreeBSD)或“fmtree”(Ubuntu)实用程序,它“将文件层次结构与规范进行比较,为文件层次结构创建规范,或修改规格。”
The default set are flags, gid, link, mode, nlink, size, time, type, and uid. This can be fitted to the specific purpose with -k switch.
默认设置是标志、gid、链接、模式、nlink、大小、时间、类型和 uid。这可以通过 -k 开关适应特定目的。
回答by pauljohn32
In case you are coming into this right now, I've just been through it today and can summarize where this stands. If you did not try this yet, some details here might help.
如果你现在正在讨论这个问题,我今天刚刚经历了它并且可以总结它的立场。如果您还没有尝试过,这里的一些细节可能会有所帮助。
I think @Omid Ariyan's approach is the best way. Add the pre-commit and post-checkout scripts. DON'T forget to name them exactly the way Omid does and DON'T forget to make them executable. If you forget either of those, they have no effect and you run "git commit" over and over wondering why nothing happens :) Also, if you cut and paste out of the web browser, be careful that the quotation marks and ticks are not altered.
我认为@Omid Ariyan 的方法是最好的方法。添加 pre-commit 和 post-checkout 脚本。不要忘记完全按照 Omid 的方式命名它们,不要忘记使它们可执行。如果您忘记了其中任何一个,它们都无效,并且您一遍又一遍地运行“git commit”,想知道为什么什么也没有发生:) 另外,如果您从 Web 浏览器中剪切和粘贴,请注意引号和勾号不是改变了。
If you run the pre-commit script once (by running a git commit), then the file .permissions will be created. You can add it to the repository and I think it is unnecessary to add it over and over at the end of the pre-commit script. But it does not hurt, I think (hope).
如果您运行一次预提交脚本(通过运行 git commit),则会创建文件 .permissions。您可以将它添加到存储库中,我认为没有必要在预提交脚本的末尾一遍又一遍地添加它。但它并没有伤害,我认为(希望)。
There are a few little issues about the directory name and the existence of spaces in the file names in Omid's scripts. The spaces were a problem here and I had some trouble with the IFS fix. For the record, this pre-commit script did work correctly for me:
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"
Now, what do we get out of this?
现在,我们能从中得到什么?
The .permissions file is in the top level of the git repo. It has one line per file, here is the top of my example:
.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
As you can see, we have
如您所见,我们有
filepath;perms;owner;group
In the comments about this approach, one of the posters complains that it only works with same username, and that is technically true, but it is very easy to fix it. Note the post-checkout script has 2 action pieces,
在关于这种方法的评论中,其中一位发帖者抱怨它只能使用相同的用户名,这在技术上是正确的,但修复它非常容易。请注意,结帐后脚本有 2 个操作部分,
# Set the file permissions
chmod $PERMISSIONS $FILE
# Set the file owner and groups
chown $USER:$GROUP $FILE
So I am only keeping the first one, that's all I need. My user name on the Web server is indeed different, but more importantly you can't run chown unless you are root. Can run "chgrp", however. It is plain enough how to put that to use.
所以我只保留第一个,这就是我所需要的。我在Web服务器上的用户名确实不同,但更重要的是除非你是root,否则你不能运行chown。但是,可以运行“chgrp”。如何使用它是显而易见的。
In the first answer in this post, the one that is most widely accepted, the suggestion is so use git-cache-meta, a script that is doing the same work that the pre/post hook scripts here are doing (parsing output from git ls-files
). These scripts are easier for me to understand, the git-cache-meta code is rather more elaborate. It is possible to keep git-cache-meta in the path and write pre-commit and post-checkout scripts that would use it.
在这篇文章的第一个答案中,这是最被广泛接受的答案,建议使用 git-cache-meta,该脚本执行的工作与此处的前/后挂钩脚本所做的工作相同(解析来自 的输出git ls-files
) . 这些脚本对我来说更容易理解,git-cache-meta 代码更复杂。可以将 git-cache-meta 保留在路径中并编写使用它的预提交和检出后脚本。
Spaces in file names are a problem with both of Omid's scripts. In the post-checkout script, you'll know you have the spaces in file names if you see errors like this
文件名中的空格是 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
I'm checking on solutions for that. Here's something that seems to work, but I've only tested in one case
我正在检查解决方案。这是一些似乎有效的方法,但我只在一种情况下进行了测试
#!/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
Since the permissions information is one line at a time, I set IFS to $, so only line breaks are seen as new things.
由于权限信息一次一行,所以我将IFS设置为$,所以只有换行符才被视为新事物。
I read that it is VERY IMPORTANT to set the IFS environment variable back the way it was! You can see why a shell session might go badly if you leave $ as the only separator.
我读到将 IFS 环境变量设置回原来的样子非常重要!如果您将 $ 作为唯一的分隔符,您就会明白为什么 shell 会话可能会很糟糕。
回答by Albaro Pereyra
I am running on FreeBSD 11.1, the freebsd jail virtualization concept makes the operating system optimal. The current version of Git I am using is 2.15.1, I also prefer to run everything on shell scripts. With that in mind I modified the suggestions above as followed:
我在 FreeBSD 11.1 上运行,freebsd jail 虚拟化概念使操作系统达到最佳状态。我使用的当前 Git 版本是 2.15.1,我也更喜欢在 shell 脚本上运行所有内容。考虑到这一点,我修改了上述建议如下:
git push: .git/hooks/pre-commit
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
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;
If for some reason you need to recreate the script the .permissions file output should have the following format:
如果由于某种原因您需要重新创建脚本,则 .permissions 文件输出应具有以下格式:
.gitignore;644;0;0
For a .gitignore file with 644 permissions given to root:wheel
对于具有 644 权限的 .gitignore 文件给予 root:wheel
Notice I had to make a few changes to the stat options.
请注意,我必须对 stat 选项进行一些更改。
Enjoy,
享受,
回答by Peter Berbec
One addition to @Omid Ariyan's answer is permissions on directories. Add this after the for
loop's done
in his pre-commit
script.
@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
This will save directory permissions as well.
这也将保存目录权限。
回答by Tammer Saleh
We can improve on the other answers by changing the format of the .permissions
file to be executable chmod
statements, and to make use of the -printf
parameter to find
. Here is the simpler .git/hooks/pre-commit
file:
我们可以通过将.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.
...and here is the simplified .git/hooks/post-checkout
file:
...这是简化的.git/hooks/post-checkout
文件:
#!/usr/bin/env bash
echo -n "Restoring file permissions... "
cd "$(git rev-parse --show-toplevel)"
. .permissions
echo "done."
Remember that other tools might have already configured these scripts, so you may need to merge them together. For example, here's a post-checkout
script that also includes the git-lfs
commands:
请记住,其他工具可能已经配置了这些脚本,因此您可能需要将它们合并在一起。例如,这是一个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 "$@"