372

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?

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
Dale Forester
  • 18,145
  • 10
  • 27
  • 27
  • Possible duplicate of [How do I remove files saying "old mode 100755 new mode 100644" from unstaged changes in Git?](http://stackoverflow.com/questions/1257592/how-do-i-remove-files-saying-old-mode-100755-new-mode-100644-from-unstaged-cha) – jww Dec 10 '16 at 04:03

12 Answers12

731

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:

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.

Milo Christiansen
  • 3,204
  • 2
  • 23
  • 36
muhqu
  • 12,329
  • 6
  • 28
  • 30
  • 3
    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. – oblitum Aug 28 '12 at 21:36
  • 20
    Oh, it worked, I was trying to apply from a directory different than the repository root. git apply only works there. – oblitum Aug 28 '12 at 22:06
  • 6
    Is there some reason you wouldnt just do "git diff -p -R" instead of doing the sed's to make it reverse? – Mixologic Jun 17 '13 at 18:34
  • 1
    @Mixologic no. Looks like I just missed it. ;-) ...will update the answer. – muhqu Jun 24 '13 at 06:01
  • 2
    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 :( ) – Rob Dec 13 '13 at 12:33
  • 7
    @RobQuist my local changes weren't removed when using muhqu's command – fvdnabee Mar 13 '14 at 15:33
  • 28
    I'm getting `fatal: unrecognized input` – Tieme Apr 30 '15 at 09:22
  • 1
    Oh, that was caused by staged changes. `git reset` before calling command helped :) – Tieme Apr 30 '15 at 09:23
  • 1
    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. – AVIDeveloper Apr 25 '16 at 11:27
  • @AVIDeveloper thanks for pointing out. maybe you can leave it 'always' when you just add `--no-color` to the `git diff` command? – muhqu Apr 25 '16 at 11:30
  • @muhqu - 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! – AVIDeveloper Apr 25 '16 at 11:39
  • @AVIDeveloper ur welcome, i updated the answer to be more explicit. – muhqu Apr 25 '16 at 11:54
  • @muhqu Alright. I just checked under Ubuntu and `--color=never` is actually the correct grep flag, so this is not just Win/GNU specific. Great! – AVIDeveloper Apr 25 '16 at 12:05
  • `git diff -p` doesn't always report all discrepancies – ivan_pozdeev May 14 '16 at 19:43
  • 2
    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 command – Mr_and_Mrs_D Jun 23 '16 at 14:03
  • 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? – gigi2 Sep 14 '16 at 14:20
  • @gigi2 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. – muhqu Sep 14 '16 at 15:24
  • 1
    @Torek - Another +1 to the Git developers for taking a simple task, and making it ridiculously complicated. God forbid they allow `git checkout ` actually work as expected. – jww Dec 10 '16 at 03:52
  • 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 apply` – Tjunkie Jan 17 '17 at 01:26
  • 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. – Brian White Jan 31 '17 at 01:42
  • 1
    git apply fails silently for me – Shardj Jul 20 '18 at 15:12
  • 3
    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! – Root Fool Oct 08 '18 at 01:19
  • 1
    `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. – ccjjmartin Apr 23 '19 at 19:07
  • 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` http://manpages.ubuntu.com/manpages/eoan/en/man1/ifne.1.html – tomsal Nov 22 '19 at 10:38
  • dont' know why, after running the `>> As a one-liner:` part, the files are removed. – ddwolf May 05 '20 at 09:00
  • 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. – Vidur Oct 05 '20 at 00:51
  • If I want to rever file change permission from an specific commit? – Arnold Roa Apr 11 '21 at 16:00
  • does this reset to last commit only? Since commit looks like not specified – Darius.V May 30 '22 at 13:26
183

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.

eritbh
  • 726
  • 1
  • 9
  • 18
Tim Henigan
  • 60,452
  • 11
  • 85
  • 78
  • Thanks, this is what I ended up doing. Very used to cvs not tracking permissions so this works. – Dale Forester Mar 26 '10 at 14:39
  • 3
    @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. – Tim Henigan Mar 26 '10 at 16:50
  • 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... – CodeManX Aug 17 '15 at 17:04
  • Im wondering if there's any downside to setting `filemode` to `false` ! – kevoroid Feb 19 '20 at 04:57
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.

Community
  • 1
  • 1
kroger
  • 448
  • 3
  • 10
  • 20
    Sorry, but this is incorrect. Git does, indeed, track permissions. – Will Apr 05 '13 at 19:24
  • 4
    It's roughly accurate, see https://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 http://stackoverflow.com/a/12735291/125150. – Motti Strom Jan 09 '14 at 13:19
  • 20
    @Will no, it doesn't. Can't believe your comment got so many upvotes. – eis Feb 22 '17 at 06:45
  • 2
    @Will, this is roughly correct. Per [the docs](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) `...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).` – esmail Dec 04 '19 at 17:35
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

zainengineer
  • 13,289
  • 6
  • 38
  • 28
3

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'
2

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
Robert Sinclair
  • 4,550
  • 2
  • 44
  • 46
Lk77
  • 2,203
  • 1
  • 10
  • 15
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.

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 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`. – hlovdal Dec 26 '21 at 22:45
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)

Community
  • 1
  • 1
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
0

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

See: Customizing Git - Git Hooks

kenorb
  • 155,785
  • 88
  • 678
  • 743
Doug
  • 17
  • 1
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

alijandro
  • 11,627
  • 2
  • 58
  • 74
-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.

Pat Notz
  • 208,672
  • 30
  • 90
  • 92
  • 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. – Conrado Jul 31 '13 at 17:28
  • 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... – jww Dec 10 '16 at 03:55
-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.

kenorb
  • 155,785
  • 88
  • 678
  • 743
MoreIT
  • 73
  • 3
  • 5
    What does it set permissions to? If it doesn't read Git metadata or invoke Git, it doesn't do what the OP requested. – ivan_pozdeev Mar 26 '17 at 07:25