Git: How to add and remove execute permissions
POSIX permissions include the execute permission, which allows the file to be executed as a program. The permission can be set for each of the three classes: user, group, and others. So, for example, you can have a file that is executable only by its owning user.
Git doesn’t store per-class permissions, nor read and write permissions, since they wouldn’t make sense when copying repositories onto other computers. However it does store the execute permission, as a single bit, so you can keep programs in Git and run them when checked out.
Despite only storing this one bit, Git displays full POSIX file permissions. You may have noticed such permissions being displayed when running certain commands. For example, when you commit, Git shows “create mode” and a permissions number for new files:
$ git commit -m "Start on masterpiece" [main 9e9584b] Start on masterpiece 1 file changed, 1 insertion(+) create mode 100644 huge-ships.txt
Likewise, Git’s diffs report mode numbers, at the end of the “index” lines:
$ git diff diff --git a/huge-ships.txt b/huge-ships.txt index 8005d59..2259717 100644 --- a/huge-ships.txt +++ b/huge-ships.txt @@ -1 +1,2 @@ How to Avoid Huge Ships +Or: I Never Met a Ship I Liked
For regular files, Git will show:
- 100644 for non-executable files.
- 100755 for executable files.
The 100 at the start of each means “regular file”, as opposed to a symlink (120).
644 means “readable by all classes, and writeable by user”. And 755 means the same, with the executable bit added for each class, which increments each digit.
Thus, Git stores the executable permission as a single bit, but represents it with three bits, one in each class.
Change execute permissions
On POSIX operating systems (Linux, macOS, BSD), use the standard
chmod command to change a file’s execute permissions. Git will detect the change and track it when committing:
$ chmod +x download_ships.py $ git commit -am "Mark download_ships.py as executable" [main 6899d99] Mark download_ships.py as executable 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 download_ships.py
chmod -x to remove execute permissions.
On Windows, though, there’s a wrinkle. Windows has a much more complicated permissions model, so Git doesn’t detect execute permissions there. Git’s escape hatch here is
add --chmod - pass
+x to add execute permissions:
$ git add --chmod +x download_ships.py
-x to remove execute permissions.
Listing files with permissions
To list all files with their execute permissions tracked in Git, use the
ls-files command with this format:
$ git ls-files --format '%(objectmode) %(path)' 100755 download_ships.py 100644 huge-ships.txt
To filter by name, pass a Git pathspec:
$ git ls-files --format='%(objectmode) %(path)' '*.py' 100755 download_ships.py
To filter by their mode, use grep:
$ git ls-files --format '%(objectmode) %(path)' | grep '^100644' 100644 huge-ships.txt
Remove all executable permissions
Sometimes when you transfer files from Windows to a POSIX system, they’re all marked as executable. If this is the case in your repository, you can remove executable permissions from every file with:
$ git ls-files -z | xargs -0 chmod -x
You can ignore files with a certain extension with a negative pathspec:
$ git ls-files -z '!*.py' | xargs -0 chmod -x
That’ll fix it.
Prevent bad execute permissions with pre-commit
You might find contributors to your repository accidentally add execute permissions to non-executable scripts, or fail to add execute permissions to executable scripts. Either can cause confusion, or even errors. The pre-commit framework can prevent such incorrect files from being committed.
With pre-commit set up add the pre-commit-hooks repo, and select two hooks:
check-shebang-scripts-are-executable. Here’s a minimal configuration file:
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable
The first check will fail for executable text files that don’t start with a shebang (
$ pre-commit run --all check that executables have shebangs.....................................Failed - hook id: check-executables-have-shebangs - exit code: 1 download_ships.py: marked executable but has no (or invalid) shebang! If it isn't supposed to be executable, try: `chmod -x download_ships.py` If on Windows, you may also need to: `git add --chmod=-x download_ships.py` If it is supposed to be executable, double-check its shebang.
…and the second will fail in the opposite case:
$ pre-commit run --all check that executables have shebangs.....................................Passed check that scripts with shebangs are executable..........................Failed - hook id: check-shebang-scripts-are-executable - exit code: 1 download_ships.py: has a shebang but is not marked executable! If it is supposed to be executable, try: `chmod +x download_ships.py` If on Windows, you may also need to: `git add --chmod=+x download_ships.py` If it not supposed to be executable, double-check its shebang is wanted.
Learn more about pre-commit, particularly for Python projects, in my DX book.
One summary email a week, no spam, I pinky promise.
- Git: How to change the case of filenames
- How to Run a Command on Many Files in Your Git Repository
- Cheap Bug Protection With pre-commit’s Regex Hooks
Tags: git, pre-commit