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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 20:14:13  来源:igfitidea点击:

Retaining file permissions with Git

linuxgitfile-permissions

提问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-metamentioned 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_metafile 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 forloop's donein his pre-commitscript.

@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

This will save directory permissions as well.

这也将保存目录权限。

回答by Tammer Saleh

We can improve on the other answers by changing the format of the .permissionsfile to be executable chmodstatements, and to make use of the -printfparameter to find. Here is the simpler .git/hooks/pre-commitfile:

我们可以通过将.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-checkoutfile:

...这是简化的.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-checkoutscript that also includes the git-lfscommands:

请记住,其他工具可能已经配置了这些脚本,因此您可能需要将它们合并在一起。例如,这是一个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 "$@"