# Git post-receive hook writen as a POSIX shell script (utilising a named pipe)

This is my first piece of code to post here. I wrote this script as a post-receive hook for a Git repository but I’m not so concerned with the Git parts (I’d like to have the part marked with TODO working but it’s not a priority).

It’s my first time to use either named pipes or the trap command to clean up temporary files. As the first comment states, I’ve written the script to be POSIX compatible – though I’ve only run it using Bash in POSIX mode.

Finally, I’m also happy to get feedback on any stylistic issues or common conventions that I could be using.

#!/bin/sh
# This shell script is POSIX compatible.  If using Bash, the use of named pipes
# could be replaced by process substitution (see further comments).

# 1. Sends an email to the project collaborators with details of commits being
# pushed to this repository.
# 2. Checks out changes in the branches into different working directories to
# be served as virtual sites by Apache and provides immediate feedback (on
# stdout) to the git user pushing to the repository.

# Send email message with info about the changes being pushed.

# Keep track of which branch(es) are being pushed to since refname is only
# available while a line from stdin is being processed.
# Create an "empty" variable for each branch that may be checked out.
master_branch=''
beta_branch=''

# A named pipe is used to store the details of each commit from each branch
# being pushed. The original idea was to pipe the output of the do...while loop
# into the mail command. However, using a pipe means that the first part of the
# pipe (the loop) is executed in a subshell environment with the result that
# changes made to shell variables are lost when the subshell terminates.  A
# named pipe (or process substitution) is required to avoid creating a subshell
# whose variables aren't visible outside the subshell.
# See http://mywiki.wooledge.org/BashFAQ/024

readonly NAMED_PIPE=/tmp/${PROJECT}_pipe.$$# Set trap to ensure the named pipe is cleaned up no matter how the script exits. trap "rm -f$NAMED_PIPE" EXIT
# Create the named pipe if it doesn't already exist (it shouldn't).
if [ ! -p $NAMED_PIPE ]; then mkfifo$NAMED_PIPE
fi

# Run the mail command in the background so it stays open for as long as the
# named pipe has data.
mail "$RECIPIENTS" -s "Pushed commits for$PROJECT" < $NAMED_PIPE & # For each branch being pushed to, there's a corresponding line on stdin. while read oldrev newrev refname do printf "\n====================================================\n" # If a new branch is being pushed to; this would result in a # fatal: Invalid symmetric difference expression" error. # In this case,$oldrev is set to 0000000000000000000000000000000000000000
if [ "$oldrev" != 0000000000000000000000000000000000000000 ]; then # Alternative solution is to use Bash to compare SHAs as hexadecimal # numbers using -eq but string comparison is simpler & more portable. printf "\nNew commits for branch: %s"${refname##*/}
printf "\n----------------------------------------\n\n"
git log --no-color --stat ${oldrev}...${newrev}
# git rev-list "$oldrev..$newrev"
else
printf "\n\nNew branch: %s\n" ${refname##*/} # TODO: Show the commits in the new branch, i.e., revisions # reachable from$newrev but not from any pre-existing branches.
fi

# Record which branch(es) are being updated.
if [ "${refname##*/}" = "master" ]; then master_branch="true" elif [ "${refname##*/}" = "development" ]; then
beta_branch="true"
fi
done > $NAMED_PIPE # done > >(mail "$RECIPIENTS" -s "Pushed commits for $PROJECT") # Bash process substitution. # Check out the branches to their appropriate directories while printing # command output to stdout. Note that one or more branches may have changes. if [ "$master_branch" ]; then
echo  Checking out the master branch into primary_site.staging
git --work-tree /var/www/vhosts/primary_site.staging/ checkout -f master
echo
fi

if [ "$beta_branch" ]; then echo Checking out the development branch into beta_site.staging git --work-tree /var/www/vhosts/beta_site.staging/ checkout -f development echo fi  ## 1 Answer • Make sure your script will stop early: set -o errexit -o noclobber -o nounset, but be aware of the caveats. • The fact that the master_branch and beta_branch variables are mutually exclusive is a hint that they can be merged: branch="${refname##*/}"

case "\$branch" in
master)
...
;;
development)
...
;;
esac

• You should use mktemp rather than PID to create a guaranteed unique file. A common best practice is to create a temporary directory since it's an atomic operation.

• Use More Quotes™!

Other than this, it's very well written.

• Thanks a lot for the feedback. Setting errexit, etc. makes sense and the case statement is cleaner and more readable. However, the temporary file I’m creating is a named pipe so I can’t use mktemp (AFAIK mknod is the only alternative to mkfifo` for creating a named pipe). Finally, I’ve been looking at some of the scripts submitted to this site and reviewed by Janos; he/she warns against over-quoting so I’m trying to tread a middle ground. :) – Anthony Geoghegan Nov 6 '15 at 13:12