Tag Archives: git

Running php linter before pushing changes to a git repository

A problem

Have you ever reviewed code that contained parse errors and wasn’t even proper PHP? I have and that’s why I have finally decided to stop that. Well, I have not decided to stop reviewing code, I have decided to prevent people committing PHP code which contain parse errors. With git hooks it’s fairly easy.

One way with pre-commit hook

One way to do it would be with pre-commit hook. Travis Swicegood explained it very well in his blog post. One problem with that is that every developer would have to deploy that hook locally in every repository. So that was really not an option for us.

Or another with pre-receive hook

What I wanted to do was to prevent people pushing broken code to git repositories stored on our git server (sort of in-house github). So I would deploy a hook on the server and the check would be done in there. pre-receive hook seems to be an ideal place for that. You can find our current version of the hook below:

Installation

To get it installed simply copy pre-receive hook into hooks subdirectory of a repository on a git server and make the file executable.

Internals

So how does it work? It’s actually pretty simple. If the hook exists with a non zero error code the push will fail, otherwise it will succeed. So all we have to do is to get a list of files changed in a given changeset and run php linter against them.

The hook accepts 3 parameters:

  • old revision id (ie. 325fbce03ba542c37c8529f36bf66998594e8384)
  • new revision id (ie. 58127dc11f4fd0c433cc6485a10925be5b56e1bb)
  • full reference name (ie. refs/heads/master)

Running git diff with –name-only option will give us list of changed files. But we cannot simply run php linter at this point as you have to remember that on git server there is just bare repository, without working directory tree. So we have to fetch the files straight from git context. We can fetch an object contents from index using git cat-file command, but it requires a unique object identifier to return file contents, so first we need to get this first. git ls-tree command is just what we need. Running it with new revision id and file name, we will get all the information we need!

#> git ls-tree 58127dc11f4fd0c433cc6485a10925be5b56e1bb Test/Application/WebTest.test.php
100644 blob 30a5e1914fe075db11c31780454fcc9d1d83aa9d	Test/Application/WebTest.test.php

Now, when we run git cat-file with the object type we got back (blob in case of files) and object id, we will get contents of that file, that we can finally pipe into php linter process. If the process returns any non-zero error code we display an appropriate message and stop script execution with a non-zero code:

smarek@28:~/git/modules/Sandbox (master)$ git push origin master
Counting objects: 14, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 857 bytes, done.
Total 9 (delta 2), reused 0 (delta 0)

Running php linter...
Error parsing file 'Test/Application/WebTest.test.php'

error: hooks/pre-receive exited with error code 255
To git:Sandbox.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git:Sandbox.git'

otherwise we just stop the script with a zero code!

smarek@28:~/git/modules/Sandbox (master)$ git push origin master
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 658 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)

Running php linter...
No errors detected

To git:Sandbox.git
90f43b9..36582e1  master -> master

No more parse errors! FTW!

git completion on MacOsX

For most of the day I work on a linux machine and I quite got used to having git completion feature for free. But as an occasional MacOsX user I felt quite bad not having the same functionality available. So yesterday I spent a couple of hours googling and playing until I finally got it all working on my MacBook.

Here’s a quick guide.

First of all, you need git-completion script. You can either get it from git source archive

#> curl http://kernel.org/pub/software/scm/git/git-1.7.1.tar.gz -O
#> tar -xzvf git-1.7.1.tar.gz
#> cp git-1.7.1/contrib/completion/git-completion.bash ~/git-completion.bash

or download it directly from github

#> curl http://github.com/git/git/raw/master/contrib/completion/git-completion.bash -O
#> cp git-completion.bash ~/git-completion.bash

Next, modify your ~/.bash_profile file by adding the following line to it:

source ~/git-completion.bash

Now restart you terminal and start using TAB key to autocomplete git commands!

But I didn’t stop there. When I enter a directory with a git repository I like to know which branch it is currently on. To achieve that all you need to do is to modify PS1 environment variable. Add the following line to  your ~/.bash_profile file:

PS1='\u@\h:\W $(__git_ps1 "(%s)")\$ '

Now restart you terminal and cd into a git repository.

sebmarek@proofek:~ $ cd ~/git/phpUnderControl
sebmarek@proofek:phpUnderControl (master)$

To make it even more visible I use bash colouring feature to highlight branch name in a colour of my choice. See bash prompt HOWTO for more details. That’s how my PS1 environment variable looks like:

PS1='\u@\h:\W \[\033[32m\]$(__git_ps1 "(%s)")\[\033[0m\]\$ '

and that’s the result:

sebmarek@proofek:~ $ cd ~/git/phpUnderControl
sebmarek@proofek:phpUnderControl (master)$

Happy giting!

Sharing your git repository

… or maybe not.

We’ve had this problem for some time and couldn’t really find a good solution for that. We wanted to have some repositories to be only writeable for selected users and even some repositories to be accessible for some users and completely invisible for the others.

First requirement seemed to be pretty simple as git access control is based on file permissions. So creating a repository only writeable for a selected group seemed to work… until somebody hasn’t committed a change which created a new object in .git directory which wasn’t group writable! The next person trying to change this object after was getting permission denied error like that:

#> git push origin master
Counting objects: 5, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 286 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
fatal: failed to write object
error: unpack failed: unpack-objects abnormal exit
To smarek@mygitserver.com:/home/smarek/git/myRepo.git
 ! [remote rejected] master -> master (n/a (unpacker error))
error: failed to push some refs to 'smarek@mygitserver.com:/home/smarek/git/myRepo.git'

Obviously git atomic commits did its trick and didn’t break the repository, but at the same time this issue prevented us pushing the changes to remote repository! We had tried different git hooks, but it didn’t work the way we wanted. So basically we ended up with a script that must have been run manually on git server when the issue was encountered.

#!/bin/bash
if [ $# -lt 1 ]; then
    echo "Usage: $0 <repo>"
    exit 1
fi
chown -R owner:group /path/to/repo/$1.git/objects
chmod -R g+w /path/to/repo/$1.git/objects

Until now. Friend of mine (thanks rodrigez) actually discovered a native git feature, that allows you to control access to a repository – core.sharedRepository. It accepts the following values:

  • umask (or false) – the default value. Git uses permissions reported by umask
  • group (or true) – makes the repository group-writable
  • all (or world or everybody) – same as group, but make the repository readable by all users
  • 0xxx: 0xxx is an octal number and each file will have mode 0xxx. 0xxx will override users umask value. 0640 will create a repository which is group-readable but not writable. 0660 is equivalent to group.

You can set that using either git config command if your repository already exists, or you want to make the default value either global or system wide:

#> git config core.sharedRepository group

or during brand new repository creation:

#> git init --shared=group

So for me setting core.sharedRepository to group solves my first issues, while removing all access to other users to all files in the repository and setting core.sharedRepository to 0770 will restrict access to the repository to only limited number of people.

Git Up and Get Going

I have used git for the first time about a year ago. We’ve been talking about abandoning CVS for quite a while and Subversion seemed to be the best choice at the time. Yes we’ve looked at different version control systems, we tried sample migrations and still we couldn’t make a decision. And CVS… it was a constant battle, continuos corruptions, old hardware, hours long merges, but  it worked, so the general response was – why to change something that works.

We needed a strong kick in our butt and fortunately there was a man that was brave enough to kick until it hurt (thanks Rys!). With management convinced we only needed to plan the migration, schedule the training and just start using it.

That was about a year ago. Now, we use it on a daily basis, and I can’t imagine how we could live without it before. Yes, it’s not as mature as Subversion, Windows tool are poor and far from ideal, but it definitely speeded up our development process. I remember all this scepticism at the time and I only bring it up now because in the past few weeks I have actually seen more and more developers and projects switching to git. I was really glad to see PHPUnit and phpUnderControl moving over to github. It makes contribution to these projects so much easier.

I think git is the future of the open source software and it makes its development so much easier and faster. So if you haven’t used it yet or you’re just simply afraid of it, please put your fears aside and git up and going!