KansaiRobot
KansaiRobot

Reputation: 9912

How can I keep the execution permissions when cloning a repository in windows?

I have a gitlab repo that contains several shell files and also what I think are executables. (I know built executables should better not be included in repos but I didn't build the repo) (oh and a mk file too)

When I git clone the repo from a linux machine and I do

ls the/path/I/am/interested -l

I can see that both sh files and also the executables have the x permission. (and the mk file too! although I don't know if mk files should)

However when I git clone the repo from a windows bash shell (To clone it I did

git clone -c core.symlinks=true  -c core.filemode=false therepo.git

and I do the same I got

I would like for all the files to keep their X permissions. What can I do?

For reference my git config has

core.autocrlf=input
core.fscache=true
core.symlinks=true
core.editor=nano.exe

core.autocrlf=input
core.symlinks=true
core.filemode=false
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.symlinks=true
core.filemode=false

P.S. Is there a way to check permissions in the Gitlab repository itself?

Upvotes: 3

Views: 4245

Answers (1)

torek
torek

Reputation: 487755

I would like for all the files to keep their X permissions. What can I do?

Probably not much, except perhaps to install and use WSL (or Cygwin, but note that it does not play well with other NTFS users). Be sure to use NFTS, not FAT. (Or, just install and use Linux, perhaps in a VM.) Read on for more painful ways to work with this stuff.

P.S. Is there a way to check permissions in the Gitlab repository itself?

There is a way to inspect the permissions in each file in each commit, and to update them, even if you aren't using WSL. It's a little bit painful though.

To understand how this works, let's start with how the Unix/Linux permissions themselves really work, on a real Linux/Unix file system. There are three groupings of three "mode bits": read, write, and execute are the mode bits, and user, group, and other are the groupings.

  • A grouped-triple of rwx means that whoever gets this, has all three permissions on the underlying file: they can read, write, and execute the file.

    A grouped-triple of --- means that whoever gets this has no permissions at all.

  • Typically, the permissions are either rw- or rwx for the user: the file is readable and writable but not executable, or has full permissions.

  • Typically, for most everyone else, the permissions are either r-- or r-x: readable, but neither writable nor executable; or read-only.

  • In between, members of a "group"—each file is owned by one user-ID and one group-ID—might be r-- or r-x, meaning the group is treated like everyone not the user; or rw- or rwx, meaning the group is treated like the owner. This group-ID stuff has limited usefulness: most modern systems use ACLs instead. But this explains the Git modes, so it's worth mentioning.

These three groups of three permissions get presented in the order: user (the file's owner), group, and other. (Note: o for owner would make more sense here, but o got used for other, so this became u for user.) Hence files are most typically either rw-r--r--, meaning read/write for the user but read-only for everyone else, or rwxr-xr-x, meaning read/write/execute for the user, but read-and-execute-only for everyone else.

Now, here is the tricky bit: the three groups of bits get expressed in octal, or base-8 notation, with bit 2 (value 4) representing the r bit, bit 1 (value 2) representing the w bit, and bit 0 (value 1) representing the x bit. So for file whose user permissions are rwx, the value is 7 (4+2+1); for one whose user permissions are rw-, the value is 6 (4+2). For a file whose group permissions are r-x, the value is 5 (4+1); for r--, it is 4. For a file whose other permissions are r-x, the value is again 5, and for r-- it is again 4`.

When we put these together, in this order, we get:

755 (rwxr-xr-x)
644 (rw-r--r--)

These are the two most common numbers found in lstat system call results, on a Linux/Unix-style file system, for the permission bits.1 But any combination is technically possible: a user can set up their files so that only everyone else can read and write them, for instance (---rw-rw- or 066). This is not very sensible, but it is allowed.


1Note that for a directory, read permission means that you can list the names in the directory, while execute permission means that you can use the name in the directory, to try to lstat or open the file for instance. Hence directories that lack execute permission are frustrating: you can see the names in them, but never open any of those files, regardless of their permissions! This particular quirk has a weird and sometimes useful effect in certain security models, but mount points allow users to bypass the quirk, so one must be very careful when trying to use this model.


So what does all this have to do with Git?

When Linus Torvalds wrote Git initially, he decided to support exactly this model for the permissions for each file in a Git commit. Git's index therefore stores, for each file, a mode, as one of several possible numbers, usually expressed in octal. The mode has a few extra higher-order bits that distinguish between regular files (mode 100xxx) and symbolic links (mode 120xxx), which he copied directly from the internal Unix/Linux model as well.

This let you store files that, when checked out, couldn't be used by yourself, only by everyone else (mode 100066 for instance) since Git would create the files with that exact mode: 066 or ---rw-rw-. As already noted, this is basically useless in the file system; it also turned out to be useless and harmful in Git, so Git was modified, before the initial 1.0 release I think—certainly before Git 1.5—to prohibit this. The only modes allowed now for regular files are 100644, or rw-r--r--, and 100755, or rwx-r-xr-x. But the weird numbering still shows up.

If you run git ls-tree -r HEAD, for instance, to see how the files in the HEAD commit are stored, you will see these very same numbers. Here are some of the files in a commit in the Git repository for Git:

...
100644 blob 311841f9bed577151a81f87174ccdd6939aa3eee    .gitignore
100644 blob cbeebdab7a5e2c6afec338c3534930f569c90f63    .gitmodules
...
100755 blob 9c125f298aea6863a9370c16a0f70b7cccc73b2d    GIT-VERSION-GEN
100644 blob 66389ce05915d776e5c8ff49aea40e20bc40eb62    INSTALL
100644 blob d38b1b92bdb2893eb4505667375563f2d6d4086b    LGPL-2.1
100644 blob c3565fc0f8f7df81232deca663bf55e14737ae97    Makefile

Note the 100755 in the middle here: that's an executable file. The remaining files are all 100644, i.e., non-executable. These mode strings are literally how Git stores the X permissions, and you can see them directly in any existing commit, using git ls-tree (remember to use -r and a commit hash ID; see the documentation for more about this).

Now, this only shows the modes of files stored in commits. The mode for a file that's not yet in a commit is stored in Git's index (aka staging area, aka cache). To see these entries, use the git ls-files command, with the --stage or -s option (two spellings for the same thing). This, too, prints out the mode: 100644 or 100755.

The mode entry in the staging entry determines what will be in the next commit. The actual mode of the file in your own file system is not relevant. However, the git add command can change the mode stored in the staging area.

How git add affects the staging mode

When you run git add file, or git add -u, or git add ., or whatever, Git will update the index copy of each file to match the working tree copy of that file. This gets a little bit complicated:

  • First, suppose the file is deleted from the file system. In this case, git add also deletes the file from the index. Everything else becomes irrelevant. But if not, we go on to maybe update the file's mode.

  • Suppose that core.filemode (or core.fileMode) is true. Then Git looks at the mode as reported by the lstat system call. If that mode has any x bits set, Git will set the index's mode to 100755. Otherwise, Git will set the index's mode to 100644.

This relies on your lstat system call reporting the executable permissions correctly. If your lstat system call doesn't report correctly, you should have core.filemode set to false. Git now ignores the lstat mode results: the mode of the entry in the index remains unchanged. Whatever git ls-files --stage said before, that's still the mode.

Manually updating modes when your file system does not coöperate

Should you wish to change the mode, you have two options:

  • git add --chmod=... will update the index from your working tree copy and set the mode you specify. This overrides the lstat result, and also overrides core.filemode.

  • git update-index will let you override the mode. There are numerous ways to do this with git update-index; the one that only overrides the mode is git update-index --chmod=....

The argument to the --chmod command option should be either +x or -x. The +x option means set the mode to 100755 and the -x option means set the mode to 100644.

A final note about core.filemode

Whenever you create a new repository, Git will probe the file system to see how it behaves. The precise way that Git does this is a bit peculiar, but essentially, it tries to see whether chmods that set and remove executable-ness on a file-system file will "stick", i.e., be reflected back by a later lstat system call. If they will, Git sets core.filemode to true for you. If they won't, Git sets core.filemode to false for you. This ensures that Git behaves correctly.

If you override this core.filemode setting, Git may begin behaving strangely. If you do change this setting, be sure you understand all of the above.

Upvotes: 6

Related Questions