How to restore the permissions of files and directories within git if they have been modified?
I have a git checkout. All the file permissions are different than what git thinks they should be therefore they all show up as modified.
Without touching the开发者_C百科 content of the files (just want to modify the permissions) how do I set all the files permissions to what git thinks they should be?
Git keeps track of filepermission and exposes permission changes when creating patches using git diff -p
. So all we need is:
- create a reverse patch
- include only the permission changes
- apply the patch to our working copy
As a one-liner:
git diff -p -R --no-ext-diff --no-color \
| grep -E "^(diff|(old|new) mode)" --color=never \
| git apply
you can also add it as an alias to your git config...
git config --global --add alias.permission-reset '!git diff -p -R --no-ext-diff --no-color | grep -E "^(diff|(old|new) mode)" --color=never | git apply'
...and you can invoke it via:
git permission-reset
Note, if you shell is bash
, make sure to use '
instead of "
quotes around the !git
, otherwise it gets substituted with the last git
command you ran.
Thx to @Mixologic for pointing out that by simply using -R
on git diff
, the cumbersome sed
command is no longer required.
Try git config core.fileMode false
From the git config
man page:
core.fileMode
If false, the executable bit differences between the index and the working copy are ignored; useful on broken filesystems like FAT. See git-update-index(1).
The default is true, except git-clone(1) or git-init(1) will probe and set core.fileMode false if appropriate when the repository is created.
Git doesn't store file permissions other than executable scripts. Consider using something like git-cache-meta to save file ownership and permissions.
Git can only store two types of modes: 755 (executable) and 644 (not executable). If your file was 444 git would store it has 644.
git diff -p \
| grep -E '^(diff|old mode|new mode)' \
| sed -e 's/^old/NEW/;s/^new/old/;s/^NEW/new/' \
| git apply
will work in most cases but if you have external diff tools like meld installed you have to add --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
was needed in my situation
i know this is old, but i came from google and i didn't find an answer
i have a simple solution if you have no change you want to keep :
git config core.fileMode true
git reset --hard HEAD
Thanks @muhqu for his great answer. In my case not all changes files had permissions changed which prevented the command to work.
$ 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
The patch would then stop and files would be left untouched.
In case some people have similar problem I solved this by tweaking the command to grep only files with permission changed:
grep -E "^old mode (100644|100755)" -B1 -A1
or for the git alias
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'
I run into a similar problem, someone added the executable flag to all the files on the server, however I also had local modified files besides the ones with the broken permissions. However, since the only permission git tracks is the executable flag, this pipeline fixed the problem for me:
git status | grep 'modified:' | awk '{print $3}' | xargs chmod a-x
Basically the command runs git status, filters the files reported as modifier, extracts their path via awk
, and removes the executable flag.
git diff -p
used in muhqu's answer may not show all discrepancies.
- saw this in Cygwin for files I didn't own
- mode changes are ignored completely if
core.filemode
isfalse
(which is the default for MSysGit)
This code reads the metadata directly instead:
(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)
A non-production-grade one-liner (replaces masks entirely):
git ls-tree HEAD | perl -ne '/^10(0\d{3}) blob \S+\t(.+)$/ && { system "chmod",$1,$2 || die }'
(Credit for "$'\0'" goes to http://transnum.blogspot.ru/2008/11/bashs-read-built-in-supports-0-as.html)
You could also try a pre/post checkout hook might do the trick.
See: Customizing Git - Git Hooks
I use git from cygwin on Windows, the git apply
solution doesn't work for me. Here is my solution, run chmod
on every file to reset its permissions.
#!/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
The easiest thing to do is to just change the permissions back. As @kroger noted git only tracks executable bits. So you probably just need to run chmod -x filename
to fix it (or +x
if that's what's needed.
The etckeeper
tool can handle permissions and with:
etckeeper init -d /mydir
You can use it for other dirs than /etc
.
Install by using your package manager or get sources from above link.
精彩评论