1

Not sure why this is happening, but the problem is I am changing file permissions on a branch after I push that branch to the remote. Then I checkout a new branch from our integration branch and it has the permissions from the "dead branch", this is how it goes:

# on feature branch
git checkout --no-track -b foo
git reset --soft "remotes/origin/dev"
git add .
git add -A
git commit --allow-empty -am "bar"
git push -u origin foo
chmod -R -w .  # remove all write permissions in current dir

# later on
git branch --no-track z "remotes/origin/dev"
git checkout z
### ughh this new branch z files are not writable, but whyyyy?

basically we changed the files to non-writable and that branch never gets merged into any branch - we pushed it to the remote before modifying the file permissions.

Why the do the non-writable file permissions show up in other branches that never got merged with the non-writable file branch?

1 Answers1

0

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.

torek
  • 448,244
  • 59
  • 642
  • 775
  • hmmm, makes sense, how do create a new worktree for a given branch so I can change the file permissions without affecting the original worktree? –  Sep 02 '18 at 02:07
  • In Git 2.5 or later, you can use `git worktree add`. There are some squirrelly issues with `git worktree`, only some of which were just recently fixed, but if you use it with specific branch names you should be fine. Pre-2.5, there is a hacky script to do the job, but you are better off just making an extra clone. If you make a local clone that lives on the same mounted file system, Git will use hard-links for all the repository data anyway and it takes almost no extra disk space. (This still works in 2.5 and later, too.) – torek Sep 02 '18 at 02:14
  • huh hardlinks sounds risky doesn't that mean if you delete one you delete the other? –  Sep 02 '18 at 02:16
  • No, hard links have reference counts. *Symbolic* links run that risk. With a hard link, deleting the repository that you cloned to make the other repository just decrements the link counts on the linked files. The OS will not remove the underlying file until the link count goes to zero (and even then, all open instances must be closed first: holding a file open forces the system to retain the file blocks). – torek Sep 02 '18 at 02:18