Dale Forester
Dale Forester

Reputation: 18907

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

Upvotes: 389

Views: 160510

Answers (13)

VonC
VonC

Reputation: 1323125

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.


Note: Before Git 2.47 (Q4 2024), batch 4, the patch parser in 'git apply'(man) was a bit more lenient against unexpected mode bits, like 100664, recorded on extended header lines.

See commit e95d515 (05 Aug 2024) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 0b2c4bc, 14 Aug 2024)

apply: canonicalize modes read from patches

Reported-by: Andrew Morton
Signed-off-by: Jeff King

Git stores only canonical modes for blobs.
So for a regular file, we care about only "100644" or "100755" (depending only on the executable bit), but never modes where the group or other permissions are more exotic.
So never "100664", "100700", etc.
When a file in the working tree has such a mode, we quietly turn it into one of the two canonical modes, and that's what is stored both in the index and in tree objects.

However, we don't canonicalize modes we read from incoming patches in git-apply.
These may appear in a few lines:

  • "old mode" / "new mode" lines for mode changes
  • "new file mode" lines for newly created files
  • "deleted file mode" for removing files

For "new mode" and for "new file mode", this is harmless.
The patch is asking the result to have a certain mode, but:

  • when we add an index entry (for --index or --cached), it is canonicalized as we create the entry, via create_ce_mode().
  • for a working tree file, try_create_file() passes either 0777 or 0666 to open(), so what you get depends only on your umask, not any other bits (aside from the executable bit) in the original mode.

However, for "old mode" and "deleted file mode", there is a minor annoyance.
We compare the patch's expected preimage mode with the current state.
But that current state is always going to be a canonical mode itself:

  • updating an index entry via --cached will have the canonical mode in the index
  • for updating a working tree file, check_preimage() runs the mode through ce_mode_from_stat(), which does the usual canonicalization

So if the patch feeds a non-canonical mode, it's impossible for it to match, and we will always complain with something like:

file has type 100644, expected 100664

Since this is just a warning, the operation proceeds, but it's confusing and annoying.

These cases should be pretty rare in practice.
Git would never produce a patch with non-canonical modes itself (since it doesn't store them).
And while we do accept patches from other programs, all of those lines were invented by Git.
So you'd need a program trying to be Git compatible, but not handling canonicalization the same way.
Reportedly "quilt" is such a program.

We should canonicalize the modes as we read them so that the user never sees the useless warning.

Upvotes: 1

muhqu
muhqu

Reputation: 12799

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.

Upvotes: 768

Lk77
Lk77

Reputation: 2462

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

Upvotes: 5

Cristik
Cristik

Reputation: 32770

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.

Upvotes: 1

user2285882
user2285882

Reputation: 69

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'

Upvotes: 7

alijandro
alijandro

Reputation: 12147

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

Upvotes: 1

Tim Henigan
Tim Henigan

Reputation: 62168

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.

Upvotes: 192

Doug
Doug

Reputation: 17

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

See: Customizing Git - Git Hooks

Upvotes: 0

MoreIT
MoreIT

Reputation: 83

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.

Upvotes: -3

ivan_pozdeev
ivan_pozdeev

Reputation: 35986

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)

Upvotes: 0

zainengineer
zainengineer

Reputation: 13889

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

Upvotes: 10

kroger
kroger

Reputation: 466

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.

Upvotes: 10

Pat Notz
Pat Notz

Reputation: 214176

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.

Upvotes: -2

Related Questions