local git hook for “git remote update”?

I have a local git repository, created with:

git clone --mirror git://github.com/<user>/<project>.git

Occasionally changes will get pushed to github, and this machine will pull them with:

  • Can I connect from VS 2015 to VSTS and GitHub the same time?
  • I accidentally created a local branch named origin/foo. Now what?
  • Using SASS with Git, what files do I ignore and how?
  • Heroku Rails “Application Error”
  • Git: new branch not getting pushed
  • Why does git sometimes mark added lines as changed lines (i.e. an empty conflict over an added piece of code)
  • git remote update --prune
    

    This all works fine, but after the update, I want to run a hook, and I’m not sure which to run. I have tried “post-receive” and “post-merge”, but neither seems to execute after the update. In both cases, the contents of the hook file are:

    #!/bin/sh 
    echo foo > foo.log
    

    When I run them from the command-line via bash.exe (yes, on Windows), a foo.log file is created, so that works. But nothing happens as a result of the “git remote update”. Between each “git remote update” I am pushing a useless change to make sure there’s something to pull.

  • Integrating MsBuild with Git
  • Git - How to selectively apply changes from one branch to another?
  • How to install latest version of git on CentOS 6.x/7.x
  • Copying only files from git tag
  • github not showing last 9 commits
  • Head commit for all remote branches using Git
  • 2 Solutions collect form web for “local git hook for “git remote update”?”

    I ended up solving this by making a little bash script to wrap the “git remote update” command. The script looks at all refspecs before and after the update, then calls the post-update hook in the same way that git would if it had the missing feature. Credit to @torek and @larsks for the idea in their comments to the original question.

    Note: this script uses bash associative arrays which are only available in bash versions >= 4.0.

    #!/bin/bash
    
    git_dir=$(git rev-parse --git-dir) || exit
    cd "$git_dir"
    
    declare -A before after all
    
    # create the 'before' list
    while read commit_hash refspec; do
      before[$refspec]="$commit_hash"
      all[$refspec]=''
    done < <(git show-ref --heads)
    
    # do the remote update
    git remote update --prune
    
    # create the 'after' list
    while read commit_hash refspec; do
      after[$refspec]="$commit_hash"
      all[$refspec]=''
    done < <(git show-ref --heads)
    
    # see if there were any changes, and if so, run the post-receive hook
    changes=0
    for refspec in "${!all[@]}"; do
      [ "${before[$refspec]}" != "${after[$refspec]}" ] && { changes=1; break; }
    done
    
    if [ "$changes" == "1" ]; then
      none="$(printf "%0.s0" {1..40})" # forty zeroes, or git's "don't care" ref
      for refspec in "${!all[@]}"; do
        # if the refspec changed, pass it to the post-receive hook
        [ "${before[$refspec]}" != "${after[$refspec]}" ] && \
          echo "${before[$refspec]:-$none} ${after[$refspec]:-$none} $refspec"
      done | hooks/post-receive
    fi
    

    Here’s an awk for Windows people who don’t have bash 4:

    #!/bin/sh
    GIT_DIR=`git rev-parse --git-dir` || exit
    cd "$GIT_DIR"
    before=`git show-ref`
    git remote update --prune
    after=`git show-ref`
    { echo "$before"
      echo 
      echo "$after"
    } | awk '
            /^$/    {++after; next}
            1       { ++seen[$2]
                      if (!after) old[$2]=$1; else new[$2]=$1
                    }
            END     { z8="00000000"; none = z8 z8 z8 z8 z8
                      for ( k in seen ) if (old[k] != new[k])
                              print (old[k]?old[k]:none), (new[k]?new[k]:none), k
                    }
    ' | hooks/post-receive
    
    Git Baby is a git and github fan, let's start git clone.