I had a usecase where a pre-checkout hook was needed as well (we had devs abusing git checkout -B and git push --force which caused all kinds of nightmares). Git doesnt have very many hooks to begin with so I also used this same solution to add new hooks to git without modifying the binary.
NOTE: This solution requires you to rename /usr/bin/git to /usr/bin/git.real and replaces it with a PHP script. Because of this, it will slow down command prompt display slightly by a few milliseconds if you have it set to display Git statuses in it such as: test@Work-VirtualBox /var/www/html/someApp (master) #
These hooks are useful for TRUE "Pre" and "Post" hooks on the machine running the command (not on the remote repo). Do NOT use them if you need a hook to fire DURING a git command. You'll have to use the real hooks to actually hook into git WHILE it's processing a command (such as a git commit message hook, hooks on the remote repo, etc). Why git doesnt have hooks on every command, I have no idea. It would be useful. This solution can also be adopted to use ANY language (bash, perl, ruby, etc), I just prefer PHP since it's what I know the most.
To use, run the following (assuming a linux platform)
mv /usr/bin/git /usr/bin/git.real
chmod +x /usr/bin/git.php
ln -s /usr/bin/git.php /usr/bin/git
git status
#!/usr/bin/php
<?php
init();
//Process pre hooks IE: pre_checkout
process_hooks('pre_');
//Process the git command itself
process_git_command();
//Process post hooks: IE: post_checkout
process_hooks('post_');
function pre_status()
{
global $allow_cmd;
echo "PRE-STATUS HOOK FIRED SUCCESSFULLY\n";
$allow_cmd = TRUE; //Change to false to cause an example error
}
function post_status()
{
global $allow_cmd;
echo "POST-STATUS HOOK FIRED SUCCESSFULLY\n";
$allow_cmd = TRUE;
}
function init()
{
global $argv;
unset($argv[0]);
$GLOBALS['git'] = '/usr/bin/git.real'; //New path to the real Git binary
$GLOBALS['cmd'] = parse_cmd($argv);
$GLOBALS['cmd_orig'] = $argv;
$GLOBALS['cmd_orig_str'] = implode(' ', $argv);
$GLOBALS['cmd_without_hook'] = array();
//Default to allowing ANYTHING as long as it passes the defined hooks, change to FALSE if you want to really lock down the git command hard. It would only allow git commands that have passed all git hooks defined for the command that was run. Useful for forcing a git command line standards.
$GLOBALS['allow_cmd'] = TRUE;
//Define the hooks we'll use. These will have pre- and post- hooks.
$GLOBALS['hooks'][] = 'checkout';
$GLOBALS['hooks'][] = 'commit';
$GLOBALS['hooks'][] = 'add';
$GLOBALS['hooks'][] = 'revert';
$GLOBALS['hooks'][] = 'push';
$GLOBALS['hooks'][] = 'pull';
$GLOBALS['hooks'][] = 'merge';
$GLOBALS['hooks'][] = 'rm';
$GLOBALS['hooks'][] = 'mv';
$GLOBALS['hooks'][] = 'status'; //Used mostly as a test hook
}
//Parse the cmdline into a switches and arguments array
function parse_cmd($cmd_arr)
{
$cmd = array();
$cmd['switches'] = array();
$cmd['arguments'] = array();
if (!empty($cmd_arr) && is_array($cmd_arr))
{
foreach ($cmd_arr AS $key => $value)
{
if (!empty($value))
{
if ($value[0] == '-')
{
$cmd['switches'][$key] = $value;
}
else
{
$cmd['arguments'][$key] = $value;
}
}
}
}
return $cmd;
}
function process_git_command()
{
global $allow_cmd;
global $git;
global $cmd_orig_str;
if ($allow_cmd == TRUE)
{
passthru("{$git} {$cmd_orig_str}");
}
else
{
echo "ERROR, something went wrong\n"; //Modify as needed.
exit(1);
}
}
function process_hooks($type)
{
global $hooks;
global $cmd;
global $cmd_without_hook;
$cmd_without_hook = $cmd;
// See if there is an argument that matches a hook, if so, process it.
foreach ($cmd['arguments'] AS $key => $arg)
{
if (!empty($arg) && in_array($arg, $hooks) && function_exists("{$type}{$arg}"))
{
//Remove the git hook from the command line (usually the command such as "checkout"). Makes parsing the command line a little easier/cleaner in the hook functions.
unset($cmd_without_hook['arguments'][$key]);
//The hook was found in the arguments, so fire it
$function = "{$type}{$arg}";
$function();
}
}
}
from
branch out into a tmp dir, run shmig on them, then run shmig on the current codebase. Pain in the ass. – Petite