How to achieve 'pre-checkout' hook in Git/bitbucket?
Asked Answered
M

5

20

Result of lots of searching on net is that pre-checkout hook in git is not implemented yet. The reason can be:

  • There is no practical use. I do have a case
  • It can be achieved by any other means. Please tell me how?
  • Its too difficult to implement. I don't think this is a valid reason

Here my problem is:

I have implemented the pre-commit, post-merge & post-checkout hooks for maintaining the database backup different for each branch.

Scenario: Now when I commit the backup of database is saved in a file using pre-commit hook. And when I checkout the the branch or merge occurs the database stored in file is restored using post-merge & post-checkout hooks. Now the situation is if someone make changes in database after commit and checkout, the changes are lost, as database was not backed-up. The checkout succeeded as there is no change in file structure.

So in this case I want a pre-checkout hook to handle the task of backing up database in above scenario.

Mays answered 17/1, 2015 at 14:18 Comment(2)
Just thought I'd add my use-case: I have a repo with database migrations managed by shmig. When I check out a new branch, I always want the database to be up to date with what's in that branch. That requires first rolling back any updates to the branch I was just in that are not in the target branch. I could easily do this with a pre-checkout hook, but since the only hook is post-checkout, I have to check the migrations from the from branch out into a tmp dir, run shmig on them, then run shmig on the current codebase. Pain in the ass.Petite
My use-case is very similar; thank you for the workaround suggestion @PetiteOvida
M
0

I got the reason why it was not implemented. In my situation I do backup the database and store it in a file, which will fail the check-out every-time. So this will be impractical to implement this functionality.

Mays answered 26/1, 2015 at 3:13 Comment(0)
L
8

You can simulate a pre-checkout git hook:

#!/bin/bash
# .git/hooks/pre-checkout

export PRE_CHECKOUT=true
git switch --quiet -
BRANCH=$(git branch --show-current)
echo "first go back to $BRANCH branch to do stuff"
git switch --quiet -

Call this script from post-checkout and it will switch back to the source branch to do stuff, and then back again to the target branch. It uses a PRE_CHECKOUT environment variable to track its own execution, so the post-checkout hook requires some logic to execute only once:

#!/bin/bash
# .git/hooks/post-checkout

# confirm the environment variable exists and it is 'true'
if [ ! -z $PRE_CHECKOUT ] && $PRE_CHECKOUT
then
    exit 0
fi

. .git/hooks/pre-checkout
BRANCH=$(git branch --show-current)
echo "now in $BRANCH branch doing post-checkout stuff"
Lindane answered 26/5, 2022 at 13:9 Comment(0)
G
4

You could write a script that does your backup then checkout. Then create a shell alias so it runs that script instead when you type git checkout.

Giant answered 11/1, 2016 at 22:18 Comment(1)
Can you give me an example?Mays
C
1

Pre-checkout use case: delete un-versioned local files generated by gulp watch:css task so they are overwritten after checkout and rebuilt by gulp.

Instead, I'm just going to write a shell script that gets the repo root and then deletes any of a list of gulp-generated files found.

Colwen answered 7/11, 2016 at 19:27 Comment(2)
why won't post-checkout work for that?Dowd
because in the other branch, perhaps there is static assets in the same places, and it's easier to track what is generated (or just call a clean script) from the branch that did it in the first place.Pratincole
M
0

I got the reason why it was not implemented. In my situation I do backup the database and store it in a file, which will fail the check-out every-time. So this will be impractical to implement this functionality.

Mays answered 26/1, 2015 at 3:13 Comment(0)
A
0

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();
      }
   }
}
Addis answered 2/8, 2021 at 23:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.