Saving file permissions with Git

I want to control the versions of my web server as described in Version Control for my web server by creating a git repository from my /var/www directory . I hope that then I can pull the web content from our dev server to github, pull it to our production server and spend the rest of the day in the pool.

Apparently, the bend in my plan is that git will not respect file permissions (I haven't tried it yet, just read about it now.) I assume that it makes sense that in different boxes there may be different users / group settings. But if I wanted to get permissions to spread, knowing that my servers are configured the same way, do I have any options? Or is there an easier way to get closer to what I'm trying to do?

+98
git linux file-permissions
Jul 08 '10 at 20:30
source share
8 answers

git-cache-meta mentioned in the SO question " git - how to restore file permissions, does git think the file should be? " (And the git FAQ ) is a simpler approach.

The idea is to save file and directory permissions in the .git_cache_meta file.
This is a separate file that is not supported directly in the Git repository.

This is why use for this:

 $ git bundle create mybundle.bdl master; git-cache-meta --store $ scp mybundle.bdl .git_cache_meta machine2: #then on machine2: $ git init; git pull mybundle.bdl master; git-cache-meta --apply 

So that you:

  • Link the repo and save the appropriate file permissions.
  • copy these two files to the remote server
  • restore repo there and apply permission
+43
Jul 08 2018-10-18T00:
source share

Git is a version control system designed for software development, therefore, from the entire set of modes and permissions, it stores only the executable bit (for regular files) and the symbolic link bit. If you want to store full permissions, you need a third-party tool like git-cache-meta ( mentioned by VonC ) or Metastore (used by etckeeper ). Or you can use IsiSetup , which IIRC uses git as a backend.

See Interfaces, Interfaces, and Tools on the git Wiki page.

+60
Jul 09 2018-10-09T00:
source share

This is pretty late, but may help some others. I am doing what you want to do by adding two git hooks to my repository.

<strong> .git / hooks / pre-commit:

 #!/bin/bash # # A hook script called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if it wants # to stop the commit. SELF_DIR=`git rev-parse --show-toplevel` DATABASE=$SELF_DIR/.permissions # Clear the permissions database file > $DATABASE echo -n "Backing-up file permissions..." IFS_OLD=$IFS; IFS=$'\n' for FILE in `git ls-files` do # Save the permissions of all the files in the index echo $FILE";"`stat -c "%a;%U;%G" $FILE` >> $DATABASE done IFS=$IFS_OLD # Add the permissions database file to the index git add $DATABASE echo "OK" 

.git / Hooks / Post-Checkout:

 #!/bin/bash SELF_DIR=`git rev-parse --show-toplevel` DATABASE=$SELF_DIR/.permissions echo -n "Restoring file permissions..." IFS_OLD=$IFS; IFS=$'\n' while read -r LINE || [[ -n "$LINE" ]]; do FILE=`echo $LINE | cut -d ";" -f 1` PERMISSIONS=`echo $LINE | cut -d ";" -f 2` USER=`echo $LINE | cut -d ";" -f 3` GROUP=`echo $LINE | cut -d ";" -f 4` # Set the file permissions chmod $PERMISSIONS $FILE # Set the file owner and groups chown $USER:$GROUP $FILE done < $DATABASE IFS=$IFS_OLD echo "OK" exit 0 

The first hook is called when you “commit”, and read the ownership and permissions for all files in the repository and store them in a file in the root directory of the repository named .permissions, and then add the .permissions file to the commit.

The second hook is called when you “check” and look at the list of files in the .permissions file and restore the ownership and permissions of these files.

  • You may need to commit and verify using sudo.
  • Verify that the pre-commit and post-checkout scripts have permission to execute.
+22
Jul 02 '16 at 19:32
source share

In case you are now entering this right, I just went through it and can generalize where it stands. If you have not tried this yet, some of the details here may help.

I think @Omid Ariyan's approach is the best way. Add pre-commit and post-checkout scripts. DO NOT forget to call them exactly what Omid does, and DO NOT forget to make them executable. If you forget about it, they will have no effect, and you will run git commit again and again, wondering why nothing is happening :) Also, if you cut and paste from a web browser, be careful that the quotation marks and ticks do not change.

If you run the pre-commit script once (by running git commit), then .permissions files will be created. You can add it to the repository, and I think there is no need to add it again and again at the end of the pre-commit script. But it doesn’t hurt, I think (hopefully).

There are a few minor issues with the directory name and the presence of spaces in the file names in Omid scripts. Spaces were a problem here, and I had some problems fixing IFS. For the record, this pre-commit script worked correctly for me:

 #!/bin/bash SELF_DIR=`git rev-parse --show-toplevel` DATABASE=$SELF_DIR/.permissions # Clear the permissions database file > $DATABASE echo -n "Backing-up file permissions..." IFSold=$IFS IFS=$'\n' for FILE in `git ls-files` do # Save the permissions of all the files in the index echo $FILE";"`stat -c "%a;%U;%G" $FILE` >> $DATABASE done IFS=${IFSold} # Add the permissions database file to the index git add $DATABASE echo "OK" 

Now, what do we get from this?

The .permissions file is at the top level of the git repository. It has one line in the file, here is the top of my example:

 $ cat .permissions .gitignore;660;pauljohn;pauljohn 05.WhatToReport/05.WhatToReport.doc;664;pauljohn;pauljohn 05.WhatToReport/05.WhatToReport.pdf;664;pauljohn;pauljohn 

As you can see, we have

 filepath;perms;owner;group 

In the comments about this approach, one of the posters complains that it only works with the same username, and this is technically true, but it is very easy to fix. Please note that after checking the script, there are 2 parts to the action,

 # Set the file permissions chmod $PERMISSIONS $FILE # Set the file owner and groups chown $USER:$GROUP $FILE 

This way I save only the first thing I need. My username on the web server is really different, but more importantly, you cannot run chown if you are not root. However, you can run "chgrp". Simple enough how to use this.

The first answer in this most widespread post suggests using git-cache-meta, a script that does the same work as the pre / post hook scripts here (parsing the output from git ls-files ). These scripts are easier for me to understand, git-cache-meta code is more complex. You can save git-cache-meta in the path and write pre-commit and post-checkout scripts that will use it.

Spaces in file names are a problem in both Omid scripts. In the post-checkout script you will find out that you have spaces in the file names if you see errors like this

 $ git checkout -- upload.sh Restoring file permissions...chmod: cannot access '04.StartingValuesInLISREL/Open': No such file or directory chmod: cannot access 'Notebook.onetoc2': No such file or directory chown: cannot access '04.StartingValuesInLISREL/Open': No such file or directory chown: cannot access 'Notebook.onetoc2': No such file or directory 

I am checking solutions for this. Something works here, but I tested in only one case

 #!/bin/bash SELF_DIR=`git rev-parse --show-toplevel` DATABASE=$SELF_DIR/.permissions echo -n "Restoring file permissions..." IFSold=${IFS} IFS=$ while read -r LINE || [[ -n "$LINE" ]]; do FILE=`echo $LINE | cut -d ";" -f 1` PERMISSIONS=`echo $LINE | cut -d ";" -f 2` USER=`echo $LINE | cut -d ";" -f 3` GROUP=`echo $LINE | cut -d ";" -f 4` # Set the file permissions chmod $PERMISSIONS $FILE # Set the file owner and groups chown $USER:$GROUP $FILE done < $DATABASE IFS=${IFSold} echo "OK" exit 0 

Since permission information is one line at a time, I set IFS to $, so only line breaks are treated as new.

I read that it is VERY IMPORTANT to set the IFS environment variable as it was! You can understand why a shell session might go bad if you leave $ as the only delimiter.

+2
Aug 30 '16 at 23:57
source share

The pre-commit / post-checkout will use the mtree (FreeBSD) or fmtree (Ubuntu) function, which "compares the file hierarchy with the specification, creates a specification for the file hierarchy, or changes the specification."

The default flags are gid, link, mode, nlink, size, time, type and uid. This can be set for a specific purpose using the -k switch.

+1
Jul 14 '16 at 7:41
source share

I work on FreeBSD 11.1, the virtualization concept of freebsd jail makes the operating system optimal. The current version of Git that I use is 2.15.1, I also prefer to run everything on shell scripts. With this in mind, I modified the above sentences as follows:

git push: .git / hooks / pre-commit

 #! /bin/sh - # # A hook script called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if it wants # to stop the commit. SELF_DIR=$(git rev-parse --show-toplevel); DATABASE=$SELF_DIR/.permissions; # Clear the permissions database file > $DATABASE; printf "Backing-up file permissions...\n"; OLDIFS=$IFS; IFS=$'\n'; for FILE in $(git ls-files); do # Save the permissions of all the files in the index printf "%s;%s\n" $FILE $(stat -f "%Lp;%u;%g" $FILE) >> $DATABASE; done IFS=$OLDIFS; # Add the permissions database file to the index git add $DATABASE; printf "OK\n"; 

git pull: .git / hooks / post-merge

 #! /bin/sh - SELF_DIR=$(git rev-parse --show-toplevel); DATABASE=$SELF_DIR/.permissions; printf "Restoring file permissions...\n"; OLDIFS=$IFS; IFS=$'\n'; while read -r LINE || [ -n "$LINE" ]; do FILE=$(printf "%s" $LINE | cut -d ";" -f 1); PERMISSIONS=$(printf "%s" $LINE | cut -d ";" -f 2); USER=$(printf "%s" $LINE | cut -d ";" -f 3); GROUP=$(printf "%s" $LINE | cut -d ";" -f 4); # Set the file permissions chmod $PERMISSIONS $FILE; # Set the file owner and groups chown $USER:$GROUP $FILE; done < $DATABASE IFS=$OLDIFS pritnf "OK\n"; exit 0; 

If for any reason you need to recreate the script, the output .permissions file should have the following format:

 .gitignore;644;0;0 

For a .gitignore file with 644 permissions granted to root: wheel

Please note that I had to make several changes to the statistics parameters.

Enjoy,

+1
Feb 19 '18 at 9:25
source share

One of the additions to @Omid Ariyan's answer is directory permissions. Add this after the for done loop in its pre-commit script.

 for DIR in $(find ./ -mindepth 1 -type d -not -path "./.git" -not -path "./.git/*" | sed 's@^\./@@') do # Save the permissions of all the files in the index echo $DIR";"'stat -c "%a;%U;%G" $DIR' >> $DATABASE done 

This will also retain directory permissions.

+1
May 24 '18 at 22:35
source share

We can improve other answers by changing the format of the .permissions file to chmod statements and using the -printf for find . Here is a simpler .git/hooks/pre-commit file:

 #!/usr/bin/env bash echo -n "Backing-up file permissions... " cd "$(git rev-parse --show-toplevel)" find . -printf 'chmod %m "%p"\n' > .permissions git add .permissions echo done. 

... and here is a simplified .git/hooks/post-checkout file:

 #!/usr/bin/env bash echo -n "Restoring file permissions... " cd "$(git rev-parse --show-toplevel)" . .permissions echo "done." 

Remember that other tools may already have configured these scripts, so you may need to combine them. For example, here is a post-checkout script that also includes git-lfs commands:

 #!/usr/bin/env bash echo -n "Restoring file permissions... " cd "$(git rev-parse --show-toplevel)" . .permissions echo "done." command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on you r path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/post-checkout.\n"; exit 2; } git lfs post-checkout "$@" 
+1
May 17 '19 at 2:07
source share



All Articles