#!/bin/sh
#
# Copyright (c) 2007 Andy Parkins
+# Copyright (c) 2008 Stephen Haberman
#
-# An example hook script to mail out commit update information. This hook
-# sends emails listing new revisions to the repository introduced by the
-# change being reported. The rule is that (for branch updates) each commit
+# This hook sends emails listing new revisions to the repository introduced by
+# the change being reported. The rule is that (for branch updates) each commit
# will appear on one email and one email only.
#
-# This hook is stored in the contrib/hooks directory. Your distribution
-# will have put this somewhere standard. You should make this script
-# executable then link to it in the repository you would like to use it in.
-# For example, on debian the hook is stored in
-# /usr/share/doc/git-core/contrib/hooks/post-receive-email:
-#
-# chmod a+x post-receive-email
-# cd /path/to/your/repository.git
-# ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
-#
# This hook script assumes it is enabled on the central repository of a
# project, with all users pushing only to it and not between each other. It
# will still work if you don't operate in that style, but it would become
# hooks.post-receive-email.envelopesender
# If set then the -f option is passed to sendmail to allow the envelope
# sender address to be set
+# hooks.post-receive-email.sendmail
+# The path to sendmail, e.g. /usr/sbin/sendmail or /bin/msmtp
+# USER_EMAIL
+# Environment variable that should be set by your repository-specific
+# post-receive hook. E.g. export USER_EMAIL=${USER}@example.com
#
# Notes
# -----
# ---------------------------- Functions
+. $(dirname $0)/functions
+
#
# Top level email generation function. This decides what type of update
# this is and calls the appropriate body-generation routine after outputting
# - generate_create_XXXX_email
# - generate_update_XXXX_email
# - generate_delete_XXXX_email
-# - generate_email_footer
#
generate_email()
{
newrev=$(git rev-parse $2)
refname="$3"
- # --- Interpret
- # 0000->1234 (create)
- # 1234->2345 (update)
- # 2345->0000 (delete)
- if expr "$oldrev" : '0*$' >/dev/null
- then
- change_type="create"
- else
- if expr "$newrev" : '0*$' >/dev/null
- then
- change_type="delete"
- else
- change_type="update"
- fi
- fi
-
- # --- Get the revision types
- newrev_type=$(git cat-file -t $newrev 2> /dev/null)
- oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
- case "$change_type" in
- create|update)
- rev="$newrev"
- rev_type="$newrev_type"
- ;;
- delete)
- rev="$oldrev"
- rev_type="$oldrev_type"
- ;;
- esac
+ set_change_type
+ set_rev_types
+ set_describe
# The revision type tells us what type the commit is, combined with
# the location of the ref we can decide between
refs/tags/*,commit)
# un-annotated tag
refname_type="tag"
+ function="ltag"
short_refname=${refname##refs/tags/}
;;
refs/tags/*,tag)
# annotated tag
refname_type="annotated tag"
+ function="atag"
short_refname=${refname##refs/tags/}
# change recipients
if [ -n "$announcerecipients" ]; then
refs/heads/*,commit)
# branch
refname_type="branch"
+ function="branch"
short_refname=${refname##refs/heads/}
;;
refs/remotes/*,commit)
exit 0
fi
- # Email parameters
- # The email subject will contain the best description of the ref
- # that we can build from the parameters
- describe=$(git describe $rev 2>/dev/null)
- if [ -z "$describe" ]; then
- describe=$rev
- fi
-
generate_email_header
-
- # Call the correct body generation function
- fn_name=general
- case "$refname_type" in
- "tracking branch"|branch)
- fn_name=branch
- ;;
- "annotated tag")
- fn_name=atag
- ;;
- esac
- generate_${change_type}_${fn_name}_email
-
- generate_email_footer
+ generate_${change_type}_${function}_email
}
generate_email_header()
# --- Email (all stdout will be the email)
# Generate header
cat <<-EOF
- From: ${USER}@payflex.com
+ From: $USER_EMAIL
To: $recipients
Subject: ${emailprefix} $short_refname $refname_type ${change_type}d. $describe
X-Git-Refname: $refname
EOF
}
-generate_email_footer()
-{
- cat <<-EOF
-
-
- hooks/post-receive
- --
- $projectdesc
- EOF
-}
# --------------- Branches
generate_create_branch_email()
{
# This is a new branch and so oldrev is not valid
- echo " at $newrev ($newrev_type)"
- echo ""
+ git rev-list --pretty=format:" at %h %s" --no-walk "$newrev" | grep -vP "^commit"
- echo $LOGBEGIN
- # This shows all log entries that are not already covered by
- # another ref - i.e. commits that are now accessible from this
- # ref that were previously not accessible
- # (see generate_update_branch_email for the explanation of this
- # command)
- git rev-parse --not --branches | grep -v $(git rev-parse $refname) | git rev-list --pretty --reverse --stdin $newrev
- echo $LOGEND
+ set_new_commits
echo ""
- echo "Summary of changes:"
- oldest_new=$(git rev-parse --not --branches | grep -v $(git rev-parse $refname) | git rev-list --stdin $newrev | tail -n 1)
- git diff-tree --stat -p $oldest_new^..$newrev
+ echo $LOGBEGIN
+ echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
+ echo ""
+ git rev-list --no-walk --pretty "$commit"
+ git diff-tree --cc "$commit"
+ echo ""
+ echo $LOGEND
+ done
+
+ oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
+ if [ "$oldest_new" != "" ] ; then
+ echo ""
+ echo "Summary of changes:"
+ git diff-tree --stat $oldest_new^..$newrev
+ fi
}
#
#
generate_update_branch_email()
{
- # Consider this:
- # 1 --- 2 --- O --- X --- 3 --- 4 --- N
- #
- # O is $oldrev for $refname
- # N is $newrev for $refname
- # X is a revision pointed to by some other ref, for which we may
- # assume that an email has already been generated.
- # In this case we want to issue an email containing only revisions
- # 3, 4, and N. Given (almost) by
- #
- # git rev-list N ^O --not --all
- #
- # The reason for the "almost", is that the "--not --all" will take
- # precedence over the "N", and effectively will translate to
- #
- # git rev-list N ^O ^X ^N
- #
- # So, we need to build up the list more carefully. git rev-parse
- # will generate a list of revs that may be fed into git rev-list.
- # We can get it to make the "--not --all" part and then filter out
- # the "^N" with:
- #
- # git rev-parse --not --all | grep -v N
- #
- # Then, using the --stdin switch to git rev-list we have effectively
- # manufactured
- #
- # git rev-list N ^O ^X
- #
- # This leaves a problem when someone else updates the repository
- # while this script is running. Their new value of the ref we're
- # working on would be included in the "--not --all" output; and as
- # our $newrev would be an ancestor of that commit, it would exclude
- # all of our commits. What we really want is to exclude the current
- # value of $refname from the --not list, rather than N itself. So:
- #
- # git rev-parse --not --all | grep -v $(git rev-parse $refname)
- #
- # Get's us to something pretty safe (apart from the small time
- # between refname being read, and git rev-parse running - for that,
- # I give up)
- #
- #
- # Next problem, consider this:
- # * --- B --- * --- O ($oldrev)
- # \
- # * --- X --- * --- N ($newrev)
- #
- # That is to say, there is no guarantee that oldrev is a strict
- # subset of newrev (it would have required a --force, but that's
- # allowed). So, we can't simply say rev-list $oldrev..$newrev.
- # Instead we find the common base of the two revs and list from
- # there.
- #
- # As above, we need to take into account the presence of X; if
- # another branch is already in the repository and points at some of
- # the revisions that we are about to output - we don't want them.
- # The solution is as before: git rev-parse output filtered.
- #
- # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
- #
- # Tags pushed into the repository generate nice shortlog emails that
- # summarise the commits between them and the previous tag. However,
- # those emails don't include the full commit messages that we output
- # for a branch update. Therefore we still want to output revisions
- # that have been output on a tag email.
- #
- # Luckily, git rev-parse includes just the tool. Instead of using
- # "--all" we use "--branches"; this has the added benefit that
- # "remotes/" will be ignored as well.
-
- # List all of the revisions that were removed by this update, in a
- # fast forward update, this list will be empty, because rev-list O
- # ^N is empty. For a non fast forward, O ^N is the list of removed
- # revisions
- fast_forward=""
- rev=""
- for rev in $(git rev-list $newrev..$oldrev)
- do
- revtype=$(git cat-file -t "$rev")
- echo " discards $rev ($revtype)"
- done
- if [ -z "$rev" ]; then
- fast_forward=1
- fi
+ # List all of the revisions that were removed by this update (hopefully empty)
+ git rev-list --first-parent --pretty=format:" discards %h %s" $newrev..$oldrev | grep -vP "^commit"
- # List all the revisions from baserev to newrev in a kind of
- # "table-of-contents"; note this list can include revisions that
- # have already had notification emails and is present to show the
- # full detail of the change from rolling back the old revision to
- # the base revision and then forward to the new revision
- for rev in $(git rev-list $oldrev..$newrev)
- do
- revtype=$(git cat-file -t "$rev")
- echo " via $rev ($revtype)"
- done
+ # List all of the revisions that were added by this update
+ git rev-list --first-parent --pretty=format:" via %h %s" $oldrev..$newrev | grep -vP "^commit"
- if [ "$fast_forward" ]; then
- echo " from $oldrev ($oldrev_type)"
+ removed=$(git rev-list $newrev..$oldrev)
+ if [ "$removed" == "" ] ; then
+ git rev-list --no-walk --pretty=format:" from %h %s" $oldrev | grep -vP "^commit"
else
- # 1. Existing revisions were removed. In this case newrev
- # is a subset of oldrev - this is the reverse of a
- # fast-forward, a rewind
- # 2. New revisions were added on top of an old revision,
- # this is a rewind and addition.
-
- # (1) certainly happened, (2) possibly. When (2) hasn't
- # happened, we set a flag to indicate that no log printout
- # is required.
-
+ # Must be rewind, could be rewind+addition
echo ""
- # Find the common ancestor of the old and new revisions and
- # compare it with newrev
+ # Find the common ancestor of the old and new revisions and compare it with newrev
baserev=$(git merge-base $oldrev $newrev)
rewind_only=""
if [ "$baserev" = "$newrev" ]; then
echo "not appeared on any other notification email; so we list those"
echo "revisions in full, below."
+ set_new_commits
+
echo ""
echo $LOGBEGIN
- git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
- git rev-list --reverse --pretty --stdin $oldrev..$newrev
+ echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
+ echo ""
+ git rev-list --no-walk --pretty "$commit"
+ git diff-tree --cc "$commit"
+ echo ""
+ echo $LOGEND
+ done
# XXX: Need a way of detecting whether git rev-list actually
# outputted anything, so that we can issue a "no new
# revisions added by this update" message
-
- echo $LOGEND
else
echo "No new revisions were added by this update."
fi
- # The diffstat is shown from the old revision to the new revision.
- # This is to show the truth of what happened in this change.
- # There's no point showing the stat from the base to the new
- # revision because the base is effectively a random revision at this
- # point - the user will be interested in what this revision changed
- # - including the undoing of previous revisions in the case of
- # non-fast forward updates.
+ # Show the diffstat which is what really happened (new commits/whatever aside)
echo ""
echo "Summary of changes:"
- git diff-tree --stat -p --find-copies-harder $oldrev..$newrev
+ git diff-tree --stat --find-copies-harder $oldrev..$newrev
}
#
#
generate_create_atag_email()
{
- echo " at $newrev ($newrev_type)"
-
+ echo " at $newrev ($newrev_type)"
generate_atag_email
}
#
generate_update_atag_email()
{
- echo " to $newrev ($newrev_type)"
- echo " from $oldrev (which is now obsolete)"
-
+ echo " to $newrev ($newrev_type)"
+ echo " from $oldrev (which is now obsolete)"
generate_atag_email
}
tagged=%(taggerdate)' $refname
)
- echo " tagging $tagobject ($tagtype)"
+ echo " tagging $tagobject ($tagtype)"
case "$tagtype" in
commit)
-
# If the tagged object is a commit, then we assume this is a
- # release, and so we calculate which tag this tag is
- # replacing
+ # release, and so we calculate which tag this tag is replacing
prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
-
if [ -n "$prevtag" ]; then
- echo " replaces $prevtag"
+ echo " replaces $prevtag"
fi
;;
*)
- echo " length $(git cat-file -s $tagobject) bytes"
+ echo " length $(git cat-file -s $tagobject) bytes"
;;
esac
- echo " tagged by $tagger"
- echo " on $tagged"
+ echo " tagged by $tagger"
+ echo " on $tagged"
echo ""
echo $LOGBEGIN
#
generate_delete_atag_email()
{
- echo " was $oldrev"
+ echo " was $oldrev ($oldrev_type)"
echo ""
echo $LOGEND
git show -s --pretty=oneline $oldrev
# Called when any other type of reference is created (most likely a
# non-annotated tag)
#
-generate_create_general_email()
+generate_create_ltag_email()
{
- echo " at $newrev ($newrev_type)"
-
- generate_general_email
+ echo " at $newrev ($newrev_type)"
+ generate_ltag_email
}
#
# Called when any other type of reference is updated (most likely a
# non-annotated tag)
#
-generate_update_general_email()
+generate_update_ltag_email()
{
- echo " to $newrev ($newrev_type)"
- echo " from $oldrev"
-
- generate_general_email
+ echo " to $newrev ($newrev_type)"
+ echo " from $oldrev ($oldrev_type)"
+ generate_ltag_email
}
#
# Called for creation or update of any other type of reference
#
-generate_general_email()
+generate_ltag_email()
{
# Unannotated tags are more about marking a point than releasing a
# version; therefore we don't do the shortlog summary that we do for
#
# Called for the deletion of any other type of reference
#
-generate_delete_general_email()
+generate_delete_ltag_email()
{
- echo " was $oldrev"
+ echo " was $oldrev ($oldrev_type)"
echo ""
echo $LOGEND
git show -s --pretty=oneline $oldrev
send_mail()
{
if [ -n "$envelopesender" ] ; then
- /usr/sbin/sendmail -t -f "$envelopesender"
+ $sendmail -t -f "$envelopesender"
else
- # /usr/sbin/sendmail -t
- /home/BIPFS/shaberman/local/bin/msmtp -t
+ $sendmail -t
fi
}
LOGEND="-----------------------------------------------------------------------"
# --- Config
-# Set GIT_DIR either from the working directory, or from the environment
-# variable.
+# Set GIT_DIR either from the working directory or the environment variable.
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
if [ -z "$GIT_DIR" ]; then
echo >&2 "fatal: post-receive: GIT_DIR not set"
fi
projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
-# Check if the description is unchanged from it's default, and shorten it to
-# a more manageable length if it is
-if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
-then
- projectdesc="UNNAMED PROJECT"
+# Shorten the description if it's the default
+if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null ; then
+ projectdesc="UNNAMED"
fi
recipients=$(git config hooks.post-receive-email.mailinglist)
envelopesender=$(git config hooks.post-receive-email.envelopesender)
emailprefix="[$projectdesc]"
debug=$(git config hooks.post-receive-email.debug)
+sendmail=$(git config hooks.post-receive-email.sendmail)
# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
fi
done
fi
+