如果文件和目录的权限已被修改,如何在git中恢复它们的权限?

378

我有一个 Git checkout。所有文件的权限都与 Git 认为应该有的不同,因此它们都被显示为已修改。

在不触及文件内容的情况下(只想修改权限),如何将所有文件的权限设置为 Git 认为应该具备的权限?


12个回答

741
Git在创建补丁时会跟踪文件权限并公开权限更改,使用git diff -p命令。所以我们只需要:
  1. 创建一个反向补丁
  2. 只包含权限更改
  3. 将补丁应用到我们的工作副本中
作为一行代码:
git diff -p -R --no-ext-diff --no-color --diff-filter=M \
    | grep -E "^(diff|(old|new) mode)" --color=never  \
    | git apply

你还可以将它作为一个别名添加到你的git配置中...
git config --global --add alias.permission-reset '!git diff -p -R --no-ext-diff --no-color --diff-filter=M | grep -E "^(diff|(old|new) mode)" --color=never | git apply'

...而且你可以通过以下方式调用它:
git permission-reset

请注意,如果您的shell是bash,请确保在!git周围使用'而不是"引号,否则它会被替换为您最后运行的git命令。
感谢@Mixologic指出,通过在git diff上简单使用-R,就不再需要繁琐的sed命令了。

3
我使用的是OS X系统,但这个方法无效。我已经确定问题出在 git apply 上,它没有应用文件权限的更改。 - oblitum
20
哦,原来是这样,我一直在尝试从与存储库根目录不同的目录中应用。git apply 只能在那里工作。 - oblitum
6
你为什么不直接使用“git diff -p -R”而非使用sed来进行反转呢? - Mixologic
7
@RobQuist,当使用muhqu的命令时,我的本地更改没有被删除。请您检查一下。 - fvdnabee
29
我收到了“fatal: unrecognized input”报错信息。 - Tieme
显示剩余23条评论

185
尝试使用git config core.fileMode false 来自git config手册:

core.fileMode

如果设置为false,则忽略索引和工作副本之间的可执行位差异;在像FAT这样的损坏文件系统上很有用。请参见git-update-index(1)

默认值为true,除非在创建存储库时git-clone(1)或git-init(1)会探查并设置core.fileMode为false。


谢谢,这就是我最后做的。由于我习惯了 CVS 不追踪权限,所以这个方法很适用。 - Dale Forester
3
我很高兴这能够帮到你。在Linux和Windows之间共享仓库时,我也遇到了类似的问题。顺便说一句:如果这个回答解决了你的问题,请标记它为正确答案。 - Tim Henigan
git checkout origin/master 是否可能将提交到服务器的文件权限设置为我的本地工作副本?因为每当我为ArangoDB构建V8时,文件权限都会更改,以便拒绝访问整个构建文件夹(即使具有提升的权限;Windows 7+)。在继续构建过程之前,我需要修复所有本地文件权限。core.filemode false 能否也解决这个问题?我怀疑 git 在我的 Windows 机器上设置了 Linux 权限。构建脚本可能只是保留它们并将相同的权限应用于新创建的文件... - CodeManX
我在想将filemode设置为false是否有任何不利影响! - kevoroid

10
git diff -p \
| grep -E '^(diff|old mode|new mode)' \
| sed -e 's/^old/NEW/;s/^new/old/;s/^NEW/new/' \
| git apply

这个命令通常可以工作,但是如果你安装了外部的 diff 工具(比如 meld),你需要添加 --no-ext-diff 参数。

git diff --no-ext-diff -p \
    | grep -E '^(diff|old mode|new mode)' \
    | sed -e 's/^old/NEW/;s/^new/old/;s/^NEW/new/' \
    | git apply

在我的情况下是必要的。


10

Git不会存储除可执行脚本之外的文件权限。建议使用类似git-cache-meta的工具来保存文件所有权和权限。

Git只能存储两种类型的模式:755(可执行)和644(不可执行)。如果您的文件是444,Git将把它存储为644。


20
抱歉,但这是不正确的。Git确实会跟踪权限。 - Will
4
大致准确,参见 https://git.wiki.kernel.org/index.php/ContentLimitations。确切的权限似乎取决于服务器和可能的客户端“umask”,以及配置设置,请参见https://dev59.com/hGcs5IYBdhLWcg3wk0-X#12735291。 - Motti Strom
20
不,它没有这样做。难以相信你的评论会有这么多赞。 - eis
2
@Will,这大致正确。根据文档所述:……一个100644的模式表示它是普通文件。其他选项有100755(表示可执行文件)和120000(指定符号链接)。该模式来自于标准的 UNIX 模式,但不够灵活——这三种模式是 Git 中文件(blob)有效的唯一模式(虽然其他模式用于目录和子模块)。 - esmail

4

感谢 @muhqu 提供了他出色的 回答。在我的情况下,由于没有更改所有更改文件的权限,导致该命令无法工作。

$ git diff -p -R --no-ext-diff --no-color | grep -E "^(diff|(old|new) mode)" --color=never
diff --git b/file1 a/file1
diff --git b/file2 a/file2
old mode 100755
new mode 100644
$ git diff -p -R --no-ext-diff --no-color | grep -E "^(diff|(old|new) mode)" --color=never | git apply
warning: file1 has type 100644, expected 100755

补丁会停止,文件将不受影响。

如果有类似问题的人,可以尝试调整命令以仅搜索权限更改的文件来解决问题:

grep -E "^old mode (100644|100755)" -B1 -A1

或者用于 Git 别名。
git config --global --add alias.permission-reset '!git diff -p -R --no-ext-diff --no-color | grep -E "^old mode (100644|100755)" -B1 -A1 --color=never | git apply'

2
我知道这已经很老了,但我从谷歌过来却没有找到答案。
如果你不想保留任何更改,我有一个简单的解决方案:
git config core.fileMode true
git reset --hard HEAD

1

我遇到了类似的问题,有人在服务器上给所有文件添加了可执行标志,但是除了权限出问题的文件外,我还有本地修改过的文件。然而,由于git跟踪的唯一权限是可执行标志,所以这个管道对我解决了问题:

git status | grep 'modified:' | awk '{print $3}' | xargs chmod a-x

基本上,该命令运行git status,过滤报告为修改的文件,通过awk提取它们的路径,并移除可执行标志。

通常情况下,grep + awk 可以简化为只使用 awk(例如 awk '/modified/{print $3}'),尽管这种解决方案无法正确处理文件名中的空格。幸运的是,有一个本地的 git 等效命令可以做到:git ls-files -m -z | xargs -0 chmod a-x - hlovdal

0

git diff -pmuhqu's answer 中使用可能无法显示所有差异。

  • 我在 Cygwin 中看到了这个问题,针对我没有拥有的文件
  • 如果 core.filemodefalse(这是 MSysGit 的默认设置),则完全忽略模式更改

此代码直接读取元数据:

(set -o errexit pipefail nounset;
git ls-tree HEAD -z | while read -r -d $'\0' mask type blob path
do
    if [ "$type" != "blob" ]; then continue; fi;
    case "$mask" in
    #do not touch other bits
    100644) chmod a-x "$path";;
    100755) chmod a+x "$path";;
    *) echo "invalid: $mask $type $blob\t$path" >&2; false;;
    esac
done)

一个非生产级别的一行代码(完全替代掩码):
git ls-tree HEAD | perl -ne '/^10(0\d{3}) blob \S+\t(.+)$/ && { system "chmod",$1,$2 || die }'

(感谢http://transnum.blogspot.ru/2008/11/bashs-read-built-in-supports-0-as.html提供"$'\0'"的信用)


0

0
我在Windows上使用cygwin中的git,但git apply解决方案无法使用。这是我的解决方案:对每个文件运行chmod以重置其权限。
#!/bin/bash
IFS=$'\n'
for c in `git diff -p |sed -n '/diff --git/{N;s/diff --git//g;s/\n/ /g;s# a/.* b/##g;s/old mode //g;s/\(.*\) 100\(.*\)/chmod \2 \1/g;p}'`
do
        eval $c
done
unset IFS


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接