Gatekeeping your code using git hooks

PIYUESH KUMAR   |  24th January, 2014

Git-hooks are scripts that Git runs after/before events like: commit, push and pull. These come in bundled with git itself. So, no need to download any package to use them. Couple of interesting git hooks from a developers perspective are:

  • pre-commit: Check for the line of code thats getting committed. This is something i will be explaining in detail in this article.
  • pre-receive: Checks for the code that is getting pushed to git-repo. Invoked before the line of code gets pushed to git repo.
  • post-receive: Gets triggered once the code is merged to the remote git repo. A simple usecase could be to notify other team members by sending an email with commit link and the commit message or to deploy the committed code to production

All of these hooks are there in the disabled state by default in any project that we clone on our local instance. You might be wondering why haven't i seen them yet!! Let me show you where these files are:

Gatekeeping your code

So, these files reside in hooks under the .git folder which gets created while initializing a git repo.

Being a Drupal developer and code reviewer, i have noticed people commiting code which lacks coding standard and at times they have syntactical errors as well. This used to waste a lot of my time as well as the developers leading to obvious problems towards the end of the Projects. So, i started looking for a way in which the code that comes to me for review is clean of these two problems at least, so that i can concentrate on reviewing the code for logic and best practices. This is when i found git-hooks and started playing around with it. Right now i have a pre-commit hook that tests the files for coding standards and syntactical errors before them getting committed and reaching to me. The following code in the pre-commit file takes care of parsing any file thats getting committed against Drupalcoding standards using PHP_Codesniffer and then parsing them against php lint.

#!/usr/bin/php
 
$output = array();
$return = 0;
exec('git rev-parse --verify HEAD', $output, $return);
 
// Get GIT revision
$against = $return == 0 ? 'HEAD' : '';
 
// Get the list of files in this commit.
exec("git diff-index --cached --name-only {$against}", $output);
 
// Pattern to identify the files that need to be run with phpcs and php lint
$filename_pattern = '/\.(php|module|inc|install)$/';
$exit_status = 0;
 
// Loop through files.
foreach ($output as $file) {
    if ( ! preg_match($filename_pattern, $file)) {
        // don't check files that don't match the pattern
        continue;
    }
 
    // If file is removed from git do not sniff.
    if ( ! file_exists($file))
    {
        continue;
    }
 
    $lint_output = array();
    // Run the sniff
    exec("phpcs --standard=Drupal " . escapeshellarg($file), $lint_output, $return_phpcs);
    // Run php lint
    exec("php -l " . escapeshellarg($file), $lint_output, $return_lint);
 
    if (($return_phpcs == 0) && ($return_lint == 0)) {
        continue;
    }
    echo implode("\n", $lint_output), "\n";
    $exit_status = 1;
}
 
exit($exit_status);

One thing to note here is, for your pre-commit hook to work, it should be executable. So, 

chmod +x {pre-commit filename}

Php lint comes packaged with php.You can run it for a specific file using

php -l <filename/path>
 

To install PHP_Codesniffer, follow the following:

pear install PHP_CodeSniffer

 

This should install PHP_CodeSniffer on your machine. In case that doesn't work for you, download a copy of PHP_Codesniffer from here.

pear install PHP_CodeSniffer
wget http://download.pear.php.net/package/PHP_CodeSniffer-1.5.1.tgz
tar -xvzf PHP_CodeSniffer-1.5.1.tgz
sudo ln -s <full path="" php_codesniffer-1.5.1="" to="">/scripts/phpcs /usr/bin</full>
 

Our next step is to get the Drupal Coding standards rule-set so that phpcs can evaluate files against them.

drush dl coder
cd coder/coder_sniffer
cp -r Drupal <full path to php_codesniffer-1.5.1>/CodeSniffer/Standards/

 

 

To test phpcs, use the following with a drupal file

phpcs --standard=Drupal <drupal file=""></drupal>

 

Now, your machine would not let you send any commit that is either syntactically wrong or doesn't honor Drupal coding standards. Though most contributed modules follow coding standards; in your project you might end up encountering a contrib module which doesn't adhere to drupal coding standard or stricter checks. To commit those without verification  you can bypass this ckeck using --no-verify switch.

git commit -m "commit message" --no-verify
 

Another tip that would save a lot of time setting pre-commit hooks for all projects that you clone on your machine:

Place the git hooks at /usr/local/git/share/git-core/templates/hooks/ and next time whenever you clone a project, all the hooks from here will get copied into the project's git folder.

Keep forgetting JIRA ID in your commit message?

If you use JIRA and  for you to track commits against a JIRA issue, developers would need to indicate JIRA ID in their commit message. But well, developers are humans and they forget; fret no more, git commit message hooks are for rescue. A simple script can remind you to enter JIRA ID for each commit message. Just Replace the project_key variable with your project's key. 

#!/bin/sh
project_key="JIRA"
test "" != "$(grep $project_key "$1")" || {
        echo >&2 "ERROR: Commit message is missing JIRA ticket number.\n\n Please append the JIRA ticket number relative to this commit into the commit message."
        exit 1
}

 

 

Gatekeeping your code

Git hooks are not specific to any programming language as you can see the pre-commit hook is using PHP while my commit-msg hook is using a bash script.  You might be a python guy, so write a hook in python. 

Why stop git checks at coding standards and semantics, if you have been checking in lot of drunk code which doesn't make sense -- do check out the video below.

Looking for a Drupal partner ?

We are drupal 8 ready