The only permissions that Git cares about and stores for each file are the "is or is not executable" permission. The TL;DR for this behavior of chmod is "don't do that"—use separate clones or separate work-trees for this instead. For more details, read on.
Specifically, within each commit snapshot, every file (or blob, really) is marked as either mode 100644
(not executable) or 100755
(executable). You will see this in git ls-tree
output, as run on any existing commit. All other permissions, including the ability to read or write, are up to you. On Unix and Unix-like systems, when Git creates a work-tree file, it actually uses either mode 0777
(if the file is to be executable) or 0666
(if not). Your umask strips any unwanted permissions from these; typical umask values are 022
(remove group and other write permission) or 002
(remove only non-group/other write permission), but secure subsystems might use 077
(remove all group and other permissions), for instance.
Note that Git does have the ability to keep internal repository data group-writable, but these are not work-tree files: these mainly affect the directories in which Git stores loose and packed objects, reference values, and the like. These are controlled by the core.sharedRepository
setting; see the git config
documentation. (Remember that the ability to create and remove files within a directory is determined by the current user-and-group ID's permission to write on the directory itself. Well, that is, unless you get ACLs involved; then it becomes seriously complicated.)
When using git checkout
to switch from one commit to another, Git only removes and replaces work-tree files as needed. This need is determined in large part by the index contents, with the index indexing the work-tree. This explains why some, but not all, file permissions wind up being retained. For (much) more about this, see Checkout another branch when there are uncommitted changes on the current branch.