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”.