Skip to content

Instantly share code, notes, and snippets.

@jlis
Created November 11, 2015 16:20
Show Gist options
  • Select an option

  • Save jlis/4a89eb510e6c7eb6118c to your computer and use it in GitHub Desktop.

Select an option

Save jlis/4a89eb510e6c7eb6118c to your computer and use it in GitHub Desktop.
PHP commit-msg git hook which checks your syntax, the code style, runs unit tests and checks for a ticket nummer inside of the commit message.
#!/usr/bin/env php
<?php
/**
* The commit-msg hook script.
* Just create a symlink in your .git/hooks/ folder to this file.
*
* @author Julius Ehrlich <julius.ehrlich@lovoo.com>
*/
class PreCommitHook
{
/**
* @var array
*/
private $changedFiles = [];
/**
* @var string
*/
private $commitMessage;
public function __construct()
{
$this->changedFiles = $this->getChangedFiles();
}
public function run()
{
$this->checkCommitMessage();
$this->checkSyntax();
$this->checkStyle();
$this->checkTests();
}
/**
* @param array $argv
*/
public function setCommitMessage(array $argv)
{
if (count($argv) < 2) {
return;
}
$commitFile = $argv[1];
if (file_exists($commitFile)) {
$this->commitMessage = file_get_contents($commitFile);
}
}
/**
* Checks the codestyle of the altered files with PHPCS.
*
* @throws \RuntimeException
*/
private function checkStyle()
{
if ($this->checkIsDisabled('style')) {
return;
}
$options = '--extensions=php --error-severity=1 --standard=PSR2';
foreach ($this->changedFiles as $file) {
if (false === strpos($file, '.php')) {
continue;
}
exec(sprintf('vendor/bin/phpcs %s %s', $options, $file), $output, $returnCode);
if ($returnCode !== 0) {
printf('PHP style check failed for file: %s %s', $file, PHP_EOL);
printf('%s %s', implode(PHP_EOL, $output), PHP_EOL);
throw new \RuntimeException();
}
}
}
/**
* Runs a lint/sytax check against the altered files.
*
* @throws \RuntimeException
*/
private function checkSyntax()
{
if ($this->checkIsDisabled('syntax')) {
return;
}
foreach ($this->changedFiles as $file) {
exec('php -l '.$file, $output, $returnCode);
if ($returnCode !== 0) {
printf('PHP syntax check failed for file: %s %s', $file, PHP_EOL);
printf('%s %s', $output, PHP_EOL);
throw new \RuntimeException();
}
}
}
/**
* Runs the PHPUnit tests to ensure no tests are broken.
*
* @throws \RuntimeException
*/
private function checkTests()
{
if ($this->checkIsDisabled('tests')) {
return;
}
$projectName = basename(getcwd());
exec('vendor/bin/phpunit -c phpunit.xml', $output, $returnCode);
if ($returnCode !== 0) {
$summary = array_pop($output);
printf('Test suite for %s failed: ', $projectName);
printf('( %s ) %s', $summary, PHP_EOL);
throw new \RuntimeException();
}
}
/**
* Checks if the commit message includes a ticket number
*
* @throws \RuntimeException
*/
private function checkCommitMessage()
{
if (null === $this->commitMessage || $this->checkIsDisabled('commitmessage')) {
return;
}
$pattern = '(ISSUE-|BUG-)';
if (1 !== preg_match($pattern, $this->commitMessage)) {
printf('Failed to find a matching ticket pattern: %s %s', $pattern, PHP_EOL);
throw new \RuntimeException();
}
}
/**
* @return array
*/
private function getChangedFiles()
{
exec('git diff --cached --name-only', $output);
return is_array($output) ? $output : [];
}
/**
* @param $check
*
* @return bool
*/
private function checkIsDisabled($check)
{
$skipTag = '#no'.$check;
if (false !== strpos($this->commitMessage, $skipTag)) {
printf('Found %s, skipping the "%s" tests %s', $skipTag, $check, PHP_EOL);
return true;
}
return false;
}
}
try {
$hook = new PreCommitHook();
if (is_array($argv)) {
$hook->setCommitMessage($argv);
}
$hook->run();
exit(0);
} catch (\RuntimeException $e) {
exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment