How to restore the permissions of files and directories within git if they have been modified?
Asked Answered
L

13

381

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 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?

Lavina answered 25/3, 2010 at 16:26 Comment(1)
S
747

Git keeps track of filepermission and exposes permission changes when creating patches using git diff -p. So all we need is:

  1. create a reverse patch
  2. include only the permission changes
  3. apply the patch to our working copy

As a one-liner (run from the repository's root directory):

git diff -p -R --no-ext-diff --no-color --diff-filter=M \
    | 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 --diff-filter=M | 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.

Sligo answered 10/12, 2010 at 11:31 Comment(28)
I'm in OS X, this is not working. I've identified the problem is in the git apply. It doesn't apply the file permissions changes.Atc
Oh, it worked, I was trying to apply from a directory different than the repository root. git apply only works there.Atc
Is there some reason you wouldnt just do "git diff -p -R" instead of doing the sed's to make it reverse?Dymphia
@Dymphia no. Looks like I just missed it. ;-) ...will update the answer.Sligo
Please note that this ofcourse also reverts changes inside of the file. I needed to update files (compiled js files) so the content changes should stay, but the file permissions were messed up. The answer of Tim Henigan below fixed my permission changes, by ignoring them (yeah i'm forced to use windows :( )Gordie
@RobQuist my local changes weren't removed when using muhqu's commandUnconformity
I'm getting fatal: unrecognized inputCrawler
Oh, that was caused by staged changes. git reset before calling command helped :)Crawler
Thanks for the answer. I faced the same fatal: unrecognized input message. Turns out my color settings were set to 'always', which caused the color escape chars to be part of the output even when piping or writing to a file. Changing from 'always' to 'auto' did the trick.Coldshoulder
@Coldshoulder thanks for pointing out. maybe you can leave it 'always' when you just add --no-color to the git diff command?Sligo
@Sligo - that was my first attempt, but it didn't work out because, running from cygwin I have GNU Grep which doesn't support that flag. I now found that it has a --color=never option that does the trick, so... cool! I anyway think that 'auto' is a better color option for grep so I'll stick with that. Thanks!Coldshoulder
@Coldshoulder ur welcome, i updated the answer to be more explicit.Sligo
@Sligo Alright. I just checked under Ubuntu and --color=never is actually the correct grep flag, so this is not just Win/GNU specific. Great!Coldshoulder
git diff -p doesn't always report all discrepanciesOutfoot
Re fatal: unrecognized input - if you already staged your changes (you are in the middle of an interactive rebase for instance ready to hit continue) add --cached to the git diff commandQuintana
this is what I have tried git diff -p -R --no-color --cached | grep -E "^(diff|(old|new) mode)" --color=never | git apply Still get 'fatal: unrecognized input" I have tried all the tricks mentioned above, git reset... The result of my git diff -p -R --no-color --cached | grep -E "^(diff|(old|new) mode)" --color=never | git apply is diff --git b/file1.out a/file1.out is that expected?Jarid
@Jarid the diff should include lines starting with "diff", "new mode" and "old mode". If the new/old mode lines are missing then either git doesn't know about the permissions or there are no differences to them... Hope that helps.Sligo
@Torek - Another +1 to the Git developers for taking a simple task, and making it ridiculously complicated. God forbid they allow git checkout <file> actually work as expected.Orwin
the command given above may work for some, but not if you have external diff configured, in that case add --no-ext-diff like this: git diff -p -R --no-color --no-ext-diff \ | grep -E "^(diff|(old|new) mode)" --color=never \ | git applyScratchy
Change these commands to git -c core.fileMode=true ... to ensure they'll work even in environments where global or local options have disabled checking the file modes.Bunyan
git apply fails silently for meOyez
If you're getting fatal: unrecognized input, you may have applied git config core.fileMode false which is suggested below. You should set git config core.fileMode true before running the above command!Ashjian
fatal: unrecognized input can also be due to git diff not outputting any results for me this was because I was attempting to fix the previous commit another developer made. The finished result for fixing the last commit looked like this on a mac git diff -p -R --no-color --no-ext-diff HEAD^ HEAD | grep -E "^(diff|(old|new) mode)" --color=never | git apply NOTE: the HEAD^ HEAD was what I was missing.Fenske
In case you run into error: unrecognized input it might be that stdin git apply for apply is empty, i.e. there are no permission changes in this repo. Especially when recursively applying it to submodules, it can be handy to replace git apply by ifne git apply using ifne from moreutils manpages.ubuntu.com/manpages/eoan/en/man1/ifne.1.htmlBounded
dont' know why, after running the >> As a one-liner: part, the files are removed.Cordula
If anybody is trying this with a dirty git submodule - the script crashes, but I fixed it by first running the script in the submodule, stashing any changes, then running it in the main repo and then manually unstashing changes. Easy peasy.Constant
If I want to rever file change permission from an specific commit?Bunny
does this reset to last commit only? Since commit looks like not specifiedGoldia
D
186

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.

Detainer answered 25/3, 2010 at 19:58 Comment(4)
Thanks, this is what I ended up doing. Very used to cvs not tracking permissions so this works.Lavina
@shovas: I am glad this helped. I experienced a similar issue when sharing repos between Linux and Windows. BTW: if this answered your question, please mark the response as correct.Detainer
Is it possible that git checkout origin/master sets file permissions committed to the server to my local working copy? Because whenever I build V8 for ArangoDB, the file permissions are changed so that access is denied to the entire build folder (even with elevated rights; Windows 7+ that is). I need to fix all local file permissions before I can continue the build process. Can core.filemode false fix that too? I suspect git to set Linux permissions on my Windows machine. The build scripts might just preserve them and apply the same permissions to newly created files...Antenna
Im wondering if there's any downside to setting filemode to false !Fluting
M
10

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.

Monegasque answered 25/3, 2010 at 16:30 Comment(4)
Sorry, but this is incorrect. Git does, indeed, track permissions.Princeling
It's roughly accurate, see git.wiki.kernel.org/index.php/ContentLimitations. The exact permissions that get set appear to based on the server & possibly the client umask as well as a config setting, see https://mcmap.net/q/22312/-prevent-git-from-changing-permissions-on-pull.Ascot
@Princeling no, it doesn't. Can't believe your comment got so many upvotes.Michelmichelangelo
@Will, this is roughly correct. Per the docs ...a mode of 100644, which means it’s a normal file. Other options are 100755, which means it’s an executable file; and 120000, which specifies a symbolic link. The mode is taken from normal UNIX modes but is much less flexible — these three modes are the only ones that are valid for files (blobs) in Git (although other modes are used for directories and submodules).Prudie
R
10
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

Raisaraise answered 30/4, 2015 at 7:58 Comment(0)
E
4

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'
Erythrocyte answered 7/7, 2020 at 15:18 Comment(0)
T
3

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
Thibault answered 15/7, 2020 at 15:27 Comment(0)
N
1

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.

Neonatal answered 19/7, 2021 at 8:45 Comment(1)
In general grep + awk can preferably be simplified to just awk (e.g. awk '/modified/{print $3}'), although this solution will not properly handle spaces in file names. Fortunately there is a native git equivalent that does: git ls-files -m -z | xargs -0 chmod a-x.Sikh
F
1

If you are using the accepted answer, using git apply

git diff -p -R --no-ext-diff --no-color --diff-filter=M \
    | grep -E "^(diff|(old|new) mode)" --color=never  \
    | git apply

Make sure to use With Git 2.45 (Q2 2024), batch 1, "git apply"(man) on a filesystem without filemode support have learned to take a hint from what is in the index for the path, even when not working with the "--index" or "--cached" option, when checking the executable bit match what is required by the preimage in the patch.

See commit 45b6251, commit 01aff0a (26 Dec 2023) by Junio C Hamano (gitster).
See commit 0482c32 (26 Dec 2023) by Chandra Pratap (Pratapchandradeo).
(Merged by Junio C Hamano -- gitster -- in commit cf47fb7, 26 Feb 2024)

apply: ignore working tree filemode when !core.filemode

Signed-off-by: Chandra Pratap
Reviewed-by: Johannes Schindelin

When applying a patch that adds an executable file, git apply(man) ignores the core.fileMode setting (core.fileMode in git config(man) specifies whether the executable bit on files in the working tree should be honored or not) resulting in warnings like:

warning: script.sh has type 100644, expected 100755  

even when core.fileMode is set to false, which is undesired.
This is extra true for systems like Windows.

Fix this by inferring the correct file mode from either the existing index entry, and when it is unavailable, assuming that the file mode was OK by pretending it had the mode that the preimage wants to see, when core.filemode is set to false.

Fatsoluble answered 3/3 at 14:58 Comment(0)
S
0

You could also try a pre/post checkout hook might do the trick.

See: Customizing Git - Git Hooks

Swamp answered 2/12, 2010 at 22:53 Comment(0)
O
0

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 is false (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)

Outfoot answered 22/4, 2016 at 23:9 Comment(0)
G
0

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

Goya answered 18/2, 2020 at 8:56 Comment(0)
A
-2

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.

Accouplement answered 25/3, 2010 at 18:24 Comment(2)
Here's an example from git show: diff --git a/OpenWatch/src/org/ale/openwatch/fb/FBUtils.java b/OpenWatch/src/org/ale/openwatch/fb/FBUtils.java index cd6fa6a..e5b0935 100644 That bit in bold there is the file permissions.Serrulation
This seemed easiest to me, too. Unfortunately, I encountered the same problem as Conrado - I could not change permission from 100644 to 100755. I don't think you deserve a downvote; Git should be down voted. It is so broken in so many ways at so many different levels...Orwin
C
-3

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.

Crossbones answered 14/1, 2016 at 12:14 Comment(1)
What does it set permissions to? If it doesn't read Git metadata or invoke Git, it doesn't do what the OP requested.Outfoot

© 2022 - 2024 — McMap. All rights reserved.