Search code examples
gitterminalpermissionsexecutechmod

Remove execute permission of the symbolic link macOS


I'm having the following folder structure:

ls -l
total 0
lrwxr-xr-x  1 user  DF\Domain Users  23 Jun  1 13:25 name1.h -> ./../../name1.h

After executing the following commands:

chmod -x name1.h
chmod -h -x name1.h
chmod g-x name1.h
chmod o-x name1.h

The result is still the same:

ls -l
total 0
lrwxr-xr-x  1 user  DF\Domain Users  23 Jun  1 13:25 name1.h -> ./../../name1.h

name1.h is a symbolic link created with the command: ln -s.

How can I remove the execute permission from that file on macOS in order to commit that to Git.

Another approach that doesn't work:

git update-index --chmod=-x fs_ccf_log.h
fatal: git update-index: cannot chmod -x 'src/SwiftPackage/include/name1.h'

Solution

  • Setting the executable flag

    Adding/removing the executable flag of a file in Git can be done in multiple ways and it can go wrong in some instances (example at the end of this answer). The safest way is to update it explicitly in the working directory and the index.

    Here is how to remove the executable flag on a file (use +x to add it) reliably:

    chmod -x test.sh             # direct change of mode in the working directory
    git add --chmod=-x test.sh   # stage while explicitly changing the mode
    git commit
    
    Symlink handling

    Internally, Git uses a simplified model with a limited number of recognised modes. For blobs/files, these are as follows: 100644 = normal file, 100755 = executable file, 120000 = symbolic link (reference). In consequence, Git does not support storing permissions for symlinks. This makes sense as Git follows UNIX and in Linux permissions on symlinks cannot be changed (=0777) and they are not used in any operations (reference). git update-index fails to change the mode (executable flag) since this only works on regular files. I could not find this specific hint in the documentation but a quick look at the source code makes it clear.

    On MacOS, a symlink can have permissions but they cannot be tracked by Git. It is important to note that (on MacOS) you cannot execute a linked file using a symlink with executable=true when the referenced file does not have the executable flag set.

    Problematic workflow

    I have seen developers doing the mistake of changing the executable flag in the staging area (index) and then re-adding the file to the index with the original mode. Overall, the desired change is not applied.

    This does not work (!):

    git update-index --chmod=-x test.sh
    git add test.sh
    git commit
    

    After the first line, the change of the mode to 100644 is staged as we expect:

    $ git diff --cached
    diff --git a/test.sh b/test.sh
    old mode 100755
    new mode 100644
    

    However, the mode of the file in the working directory was not changed which now shows up as an unstaged change (in the staging area the mode is 100644):

    $ git diff
    diff --git a/test.sh b/test.sh
    old mode 100644
    new mode 100755
    

    When we now execute git add test.sh (or any other command which stages the changes to the file), the mode is changed back to 100755. As a result, there are no staged changes to the mode of the file anymore.