Writing a git post-receive hook to deal with a specific branch

56,206

Solution 1

A post-receive hook gets its arguments from stdin, in the form:

<oldrev> <newrev> <refname>

Since these arguments are coming from stdin, not from a command line argument, you need to use read instead of $1 $2 $3.

The post-receive hook can receive multiple branches at once (for example if someone does a git push --all), so we also need to wrap the read in a while loop.

A working snippet looks something like this:

#!/bin/bash
while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [ "master" = "$branch" ]; then
        # Do something
    fi
done

Solution 2

The last parameter that a post-receive hook gets on stdin is what ref was changed, so we can use that to check if that value was "refs/heads/master." A bit of ruby similar to what I use in a post-receive hook:

STDIN.each do |line|
    (old_rev, new_rev, ref_name) = line.split
    if ref_name =~ /master/
         # do your push
    end
end

Note that it gets a line for each ref that was pushed, so if you pushed more than just master, it will still work.

Solution 3

Stefan's answer didn't work for me, but this did:

#!/bin/bash

echo "determining branch"

if ! [ -t 0 ]; then
  read -a ref
fi

IFS='/' read -ra REF <<< "${ref[2]}"
branch="${REF[2]}"

if [ "master" == "$branch" ]; then
  echo 'master was pushed'
fi

if [ "staging" == "$branch" ]; then
  echo 'staging was pushed'
fi

echo "done"

Solution 4

Neither of the solutions above worked for me. After much, much debugging, it turns out that using the 'read' command doesn't work -- instead, parsing command line arguments the usual way works fine.

Here is the exact post-update hook that I just successfully tested now on CentOS 6.3.

#!/bin/bash

echo "determining branch"

branch=`echo $1 | cut -d/ -f3`

if [ "master" == "$branch" ]; then
    echo "master branch selected"
fi

if [ "staging" == "$branch" ]; then
    echo "staging branch selected"
fi

exec git update-server-info

UPDATE: on an even stranger note, the pre-receive hook takes its input via stdin, therefore read with 'read' (wow, never thought I'd say that). The post-update hook still works with $1 for me.

Solution 5

The answer from @pauljz works fine for certain git hooks like pre-push, but pre-commit does not have access to those variables oldrev newrev refname

So I created this alternate version which works for pre-commit, or really and hook. This is a pre-commit hook that will run a husky script if we're NOT on the master branch.

#!/bin/bash
# git 'commit' does not have access to these variables: oldrev newrev refname
# So get the branch name off the head

branchPath=$(git symbolic-ref -q HEAD) # Something like refs/heads/myBranchName
branch=${branchPath##*/}      # Get text behind the last / of the branch path

echo "Head: $branchPath";
echo "Current Branch: $branch";

if [ "master" != "$branch" ]; then

   # If we're NOT on the Master branch, then Do something
   # Original Pre-push script from husky 0.14.3

   command_exists () {
     command -v "$1" >/dev/null 2>&1
   }

   has_hook_script () {
     [ -f package.json ] && cat package.json | grep -q "\"$1\"[[:space:]]*:"
   }

   cd "frontend" # change to your project directory, if .git is a level higher

   # Check if precommit script is defined, skip if not
   has_hook_script precommit || exit 0

   # Node standard installation
   export PATH="$PATH:/c/Program Files/nodejs"

   # Check that npm exists
   command_exists npm || {
     echo >&2 "husky > can't find npm in PATH, skipping precommit script in package.json"
     exit 0
   }

   # Export Git hook params
   export GIT_PARAMS="$*"

   # Run npm script
   echo "husky > npm run -s precommit (node `node -v`)"
   echo

   npm run -s precommit || {
     echo
     echo "husky > pre-commit hook failed (add --no-verify to bypass)"
     exit 1
   }
fi

I hope that helps someone. You can easily modify for your needs, anything in between the if and fi statements.

Share:
56,206

Related videos on Youtube

Jorge Guberte
Author by

Jorge Guberte

Updated on December 11, 2020

Comments

  • Jorge Guberte
    Jorge Guberte over 3 years

    Here's my current hook in a bare repo that lives in the company's server: git push origin master This hooks pushes to Assembla. What i need is to push only one branch (master, ideally) when someone pushes changes to that branch on our server, and ignore pushes to other branches. Is it possible to select the branch from a bare repo and push only that branch to Assembla?

    • Stefan Kendall
      Stefan Kendall almost 13 years
      What do you mean? git push origin master will only push the master branch to the origin remote, which I assume is defined to be Assembla. Are you saying that you need to trigger the hook only when someone pushes to master, as opposed to feature1, or something like that?
    • Jorge Guberte
      Jorge Guberte almost 13 years
      @Stefan Exactly that. I couldn't find the word, hehe.
    • insign
      insign over 8 years
  • Ray
    Ray over 10 years
    The "==" doesn't work for me. With single "=" works for me well.
  • pauljz
    pauljz over 10 years
    That's odd. Double equals and single equals should both work fine here (see stackoverflow.com/questions/2600281/…). Are you possibly using something other than bash? Regardless, glad you found a fix for it.
  • pauljz
    pauljz over 10 years
    For what it's worth, the above solutions may not have worked because they are specifically for post-receive hooks, not post-update hooks. They take their input in different ways.
  • gin93r
    gin93r about 10 years
    Sorry to bring up an old thread, but I'm getting an error at the if statement. fatal: the remote end hung up unexpectedly. error: error in the sideband demultiplexer. It will echo the $branch outside the if statement.
  • pauljz
    pauljz about 10 years
    @Veo check out stackoverflow.com/questions/4582849/… Repo might be in a bad state?
  • gin93r
    gin93r about 10 years
    Thanks @pauljz I ended up getting it working. It had something to do with the if statement. Changing it to a switch statement seemed to fix the issue.
  • leifericf
    leifericf about 10 years
    Thanks for the Ruby example. I'm going to do something similar to this.
  • shuttle87
    shuttle87 almost 9 years
    @Ray, do you have #!/bin/sh instead of #!/bin/bash?
  • Shahar Hamuzim Rajuan
    Shahar Hamuzim Rajuan over 8 years
    Worked for me for branchs with a simple name (master,test,etc.), But when I have branch name such : prod12/proj250/ropesPatch12 . it doesn't work to well. Do you have a solution that can work with those special characters?
  • h4xnoodle
    h4xnoodle over 8 years
    post-receive takes stdin as noted here: git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
  • Zitrax
    Zitrax over 8 years
    Whats the advantage of using rev-parse here instead of simply using the last part of refname (refs/heads/(.*)) directly ?
  • pauljz
    pauljz over 8 years
    One risk I can think of would be tags, since their names can overlap with branch names. If you look for refs/heads/master instead of refs/tags/master you should be okay. There may be other edge-cases like this I can't think of though. It could be a good StackOverflow question in its own right.
  • Jérôme
    Jérôme over 6 years
    I've been trying this and I get this error when deleting a branch using git push origin --delete: remote: fatal: ambiguous argument 'refs/heads/test': unknown revision or path not in the working tree
  • Jérôme
    Jérôme over 6 years
    @pauljz I use if branch=$(git rev-parse --symbolic --abbrev-ref $refname 2>/dev/null); then so that git doesn't complain when I remove a branch.