0

I'd like to set up a pre-push hook for a git repo that:

1) checks that we're pushing to master, and

2) if we are, runs webpack, copies the compiled .js file to another repo, and commits that compiled file to a utility repo.

The background for this: I'm using webpack + react to build a map using leaflet.js. I want to have the utility repo always hold the most recent compiled .js file so I can source it in a different static site using rawgit.

This is what I have so far (not working correctly):

#!/bin/bash current_branch=$(git rev-parse --abbrev-ref HEAD) if [[ $current_branch == 'master' ]]; then webpack cp bundle.js ../utility eval 'cd ../utility && git add bundle.js && git commit -m "updated east_boston.js"' fi 

I understand that there are some automatically set environment variables (like $GIT_DIR and $GIT_INDEX) but I'm just not sure how to make this work. I keep getting strange behavior, where the README for the main repo will be accidentally committed to the utility repo, or I'll get a commit on the utility repo that deletes files in the main repo (which weren't ever present on the utility repo).

Basically I don't know how to use git hooks properly. Am I using the right hook for what I want? Is this possible?

I should note that the above shell script does work correctly on it's own - so if I do bash pre-push I get the behavior I want.

1
  • This page might be of use here. I don't know that it is directly related to your current attempt but it should help you get an approach to what you want working. Commented Jan 1, 2016 at 19:41

1 Answer 1

1

There are quite a few issues to work around here. The first is the one that's tripping you up: git hooks may be run from odd directories (so ../something may not be what you expect) and have $GIT_DIR set to something (typically . or .git depending on which directory they're in). Moving to another directory and running a git command doesn't work, because you wind up in the wrong place or $GIT_DIR points to the wrong place, or both.

I believe the pre-push hook is run from the directory you're thinking it's in (in most cases: it all falls apart if you use git --work-tree=<path> from somewhere entirely outside <path>), so it's just the $GIT_DIR setting that's causing the immediate problem.

Less obviously but still quite important, the current branch, which you're finding with git rev-parse, is not necessarily the branch being pushed-to, and the commit being pushed-from is not necessarily the tip of any branch. For instance:

$ git push origin zog~3:master 

tells git to ask the remote to set their master to whatever commit-ID zog~3 results in, even if you're currently on branch rye. You might not care about handling this case, but it is possible to handle: a pre-push hook receives, on its standard input, a series of lines of the form:

<local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF 

(this is direct from the documentation). To handle this in sh or bash:

while read lref lsha rref rsha; do ... done 

where the ... part uses $lref and $lsha to work out the local reference name (if any) and SHA-1, and the remote reference name and SHA-1 (the remote name will be fully qualified, so a push to master will give you refs/heads/master in $rref).

The trickiest bit is that if you want to something with the commit that the remote will set their label to (assuming the push eventually succeeds—this is not something you can tell in the hook!), you can't rely on the current work-tree's files:

  • they may be modified but not yet added and/or committed, or
  • they may not be associated with the commit being pushed.

To do a really thorough job of inspecting or using them, you'd need to extract all those files to a temporary work area (which is actually pretty easy, just git --work-tree=$tmpdir checkout $lsha where $lsha is the SHA-1 you read from the input, and $tmpdir is a suitable temporary work-tree that you remove once you're done with it; but then you may also need to override $GIT_INDEX_FILE as well).

Ignoring most of that, though, the following quite cheesy (and untested) script (written in sh but should work fine with bash) may get most of the way there:

#! /bin/sh . git-sh-setup # for require_clean_work_tree() and die() check_push_to_master() { # we're pushing to remote "master"; reject if # the work tree is not clean or is not at the # commit-ID ($1) being pushed local headsha pushsha require_clean_work_tree 'push to master' headsha=$(git rev-parse HEAD) pushsha=$1 [ $headsha = $pushsha ] || die "HEAD commit $headsha does not match push to master $pushsha" } to_master=false while read lref lsha rref rsha; do case $rref in refs/heads/master) check_push_to_master $lsha; to_master=true;; esac done # if we get here it's OK to try the push, but first... if $to_master; then unset GIT_DIR # make git work normally again # set -e # might want this to make sure any failures => stop push webpack cp bundle.js ../utility cd ../utility && git add bundle.js && git commit -m "updated east_boston.js" fi exit 0 # allow push to proceed 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much for this extensive answer! When I get home tonight I'll try this out!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.