How can I mark a committed file as read-only in Git?

The answer to the implied subject line question (“can I make git check out a particular file as read-only”) is “no, at least not directly”, since git only stores a single permissions bit per file: executable, or not-executable. All other bits are set the same for each file.

That said, though, there are a few tricks using hooks. As some commenters suggested, you can test something in a server-side hook to prevent pushes. You can use smudge and clean filters. You can have a post-checkout hook set the file read-only.

The drawback to any and all hooks is that they must be set up for each repository, and users can override them (except for a server-side hook, presuming the user has no direct access to the server).1 This is also the advantage to a hook, although for naive users it’s probably more drawback than advantage, since git itself won’t set up the hooks automatically.

The post-checkout hook is probably the most obvious place to set file permissions, since git’s documentation includes this bit:

This hook can be used to … set working dir metadata properties.

Conveniently, the hook appears to always be run in the top level directory, regardless of where the user is, as long as the user is actually inside the git work tree.2 So this very simple hook suffices to change one file to read-only:

#! /bin/sh
# post-checkout hook to make one file read-only
chmod -w path/to/file

(on any system with chmod anyway, and remember to set the hook up as an executable file).

The user must put this hook into his/her repository, in .git/hooks/post-checkout (though you can commit the file itself into the repository, and then have the user copy or link it into place, perhaps through an auxiliary setup script).


1Thus, a server-side hook is the place to go if you want to strictly enforce a policy (this is true in general).

2That is, the following defeats the hook:

$ pwd
/home/user/dir/example
$ ls -l .git/hooks/post-checkout
-rwxr-xr-x  1 user  group  27 Dec 18 11:10 .git/hooks/post-checkout
$ cd /tmp
$ GIT_DIR=/home/user/dir/example/.git git checkout master

Here, the current working directory is just /tmp, and there is no way for the hook to figure out what it should be (you can read $GIT_DIR, but that’s not necessarily helpful since the .git directory need not be directly connected to the work-tree in the first place, and that’s what setting GIT_DIR is meant for in the first place).

Note that being in the work tree, in a sub-directory, does not defeat the hook; that’s what I mean by “appears to always be run in the top level directory”.

Leave a Comment