Git. Directory-Level Access Control in a Repository

For security reasons, and to make working with the repository easier for freelance layout developers, I needed to restrict access by directory. I couldn’t solve this with gitolite: it still leaves read access to the files. I had to create two repositories and use Git’s post-update hook to keep both repositories updated symmetrically.
Creating an additional repository
In gitolite, it looks like this:
repo example
RW+ = developer
repo example_markup
RW+ = freelancer
Clone the example_markup.git repository locally, copy the directories you need from example.git into it, and commit all that good stuff.
Connecting to the server running gitolite and creating hooks
Go to the example_markup.git repository directory and clone both repositories there so that it’s convenient to make commits from them:
sudo su - git
cd ~/repositories/example_markup.git
git clone file:///home/git/repositories/example_markup.git
git clone file:///home/git/repositories/example.git
If you haven’t configured git under the “git” user before, you’ll need to specify at least an email address and a name for commits, as well as the workflow to use when pushing (I chose “simple”; you can find more details in “man git-config”):
git config --global user.email "git@example.org" git config --global user.name "Mr. Git" git config --global push.default simple
Next, create a post-update hook:
sudo su - git
cd ~/repositories/example_markup.git/hooks
touch post-update
chmod +x post-update
emacs post-update
Put the following contents in it:
#!/bin/sh
GIT=`/usr/bin/which git`
echo "Update example_markup.git..."
unset GIT_DIR
cd example_markup/ && $GIT pull && cd -
echo "Copy files from example_markup.git to example.git..."
cp -R example_markup/* example/
echo "Сommit and push changes to example.git"
unset GIT_DIR
cd example && $GIT add . && $GIT commit -am "Auto-update from example_markup.git" && $GIT push
echo "Finished!"
exec git-update-server-info
Now do the reverse for the example.git repository:
sudo su - git
cd ~/repositories/example.git/hooks
touch post-update
chmod +x post-update
emacs post-update
Put the following contents in it:
#!/bin/sh
GIT=`/usr/bin/which git`
echo "Update example.git..."
unset GIT_DIR
cd ../example_markup.git/example && $GIT pull && cd -
echo "Copy files from example_markup.git to example.git..."
cp -R ../example_markup.git/example/templates/* ../example_markup.git/example_markup/templates/
cp -R ../example_markup.git/example/static/* ../example_markup.git/example_markup/static/
echo "Сommit and push changes to example_markup.git"
unset GIT_DIR
cd ../example_markup.git/example_markup && $GIT add . && $GIT commit -am "Auto-update from example.git" && $GIT push
echo "Finished!"
exec git-update-server-info
Let’s try pushing something from our local machine to example_markup.git:
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 362 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Update example_markup.git...
remote: From file:///home/git/repositories/example_markup
remote: 840b8e9..2a9be4e master -> origin/master
remote: Updating 840b8e9..2a9be4e
remote: Fast-forward
remote: README | 0
remote: 1 file changed, 0 insertions(+), 0 deletions(-)
remote: create mode 100644 README
remote: /home/git/repositories/example_markup.git
remote: Copy files from example_markup.git to example.git...
remote: Сommit and push changes to example.git
remote: [master 2dbf0be] Auto-update from example_markup.git
remote: 5 files changed, 104 insertions(+), 250 deletions(-)
remote: remote: Update example.git...
remote: remote: From file:///home/git/repositories/example
remote: remote: 663fee4..2dbf0be master -> origin/master
remote: remote: Already up-to-date.
remote: remote: /home/git/repositories/example.git
remote: remote: Copy files from example_markup.git to example.git...
remote: remote: Сommit and push changes to example_markup.git
remote: remote: On branch master
remote: remote: Your branch is up-to-date with 'origin/master'.
remote: remote:
remote: remote: nothing to commit, working directory clean
remote: remote: Finished!
remote: To file:///home/git/repositories/example.git
remote: 663fee4..2dbf0be master -> master
remote: Finished!
To git@git.example.org:example_markup
840b8e9..2a9be4e master -> master
That’s it! Now the layout developer will only be able to change and commit their own files, while the other project members will automatically see the changes in their repository, without having to copy the changes manually.
If you know a better way to restrict access by file, or how to configure gitolite for this, leave a comment. Constructive feedback is always welcome!
P.S. This approach only supports working with a designated branch—in our case, master. If the layout developer needs to use branches, the hooks will need a little more work.
Comments are closed. Read the original discussion in Russian →