#!/bin/sh # # An example hook script to mail out commit update information. # It can also blocks tags that aren't annotated. # Called by git-receive-pack with arguments: refname sha1-old sha1-new # # To enable this hook, make this file executable by "chmod +x update". # # Config # ------ # hooks.mailinglist # This is the list that all pushes will go to; leave it blank to not send # emails frequently. The log email will list every log entry in full between # the old ref value and the new ref value. # hooks.announcelist # This is the list that all pushes of annotated tags will go to. Leave it # blank to just use the mailinglist field. The announce emails list the # short log summary of the changes since the last annotated tag # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # # Notes # ----- # All emails have their subjects prefixed with "[SCM]" to aid filtering. # All emails include the headers "X-Git-Refname", "X-Git-Oldrev", # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and info. # --- Constants EMAILPREFIX="[SCM] " LOGBEGIN="- Log -----------------------------------------------------------------" LOGEND="-----------------------------------------------------------------------" DATEFORMAT="%F %R %z" # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config projectdesc=$(cat $GIT_DIR/description) recipients=$(git-repo-config hooks.mailinglist) announcerecipients=$(git-repo-config hooks.announcelist) allowunannotated=$(git-repo-config --bool hooks.allowunannotated) # --- Check types newrev_type=$(git-cat-file -t "$newrev") case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag refname_type="tag" short_refname=${refname##refs/tags/} if [ $allowunannotated != "true" ]; then echo "*** The un-annotated tag, $short_refname is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag refname_type="annotated tag" short_refname=${refname##refs/tags/} # change recipients if [ -n "$announcerecipients" ]; then recipients="$announcerecipients" fi ;; refs/heads/*,commit) # branch refname_type="branch" short_refname=${refname##refs/heads/} ;; refs/remotes/*,commit) # tracking branch refname_type="tracking branch" short_refname=${refname##refs/remotes/} # Should this even be allowed? echo "*** Push-update of tracking branch, $refname. No email generated." >&2 exit 0 ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update, \"$newrev_type\", to ref $refname" >&2 exit 1 ;; esac # Check if we've got anyone to send to if [ -z "$recipients" ]; then # If the email isn't sent, then at least give the user some idea of what command # would generate the email at a later date echo "*** No recipients found - no email will be sent, but the push will continue" >&2 echo "*** for $0 $1 $2 $3" >&2 exit 0 fi # --- Email parameters committer=$(git show --pretty=full -s $newrev | grep "^Commit: " | sed -e "s/^Commit: //") describe=$(git describe $newrev 2>/dev/null) if [ -z "$describe" ]; then describe=$newrev fi # --- Email (all stdout will be the email) ( # Generate header cat <<-EOF From: $committer To: $recipients Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname now at $describe X-Git-Refname: $refname X-Git-Reftype: $refname_type X-Git-Oldrev: $oldrev X-Git-Newrev: $newrev Hello, This is an automated email from the git hooks/update script, it was generated because a ref change was pushed to the repository. Updating $refname_type, $short_refname, EOF case "$refname_type" in "tracking branch"|branch) if expr "$oldrev" : '0*$' >/dev/null then # If the old reference is "0000..0000" then this is a new branch # and so oldrev is not valid echo " as a new $refname_type" echo " to $newrev ($newrev_type)" echo "" 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 git-rev-parse --not --all | git-rev-list --stdin --pretty $newref echo $LOGEND else # oldrev is valid oldrev_type=$(git-cat-file -t "$oldrev") # Now the problem is for cases like this: # * --- * --- * --- * (oldrev) # \ # * --- * --- * (newrev) # i.e. there is no guarantee that newrev is a strict subset # of oldrev - (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 baserev=$(git-merge-base $oldrev $newrev) # Commit with a parent for rev in $(git-rev-list $newrev ^$baserev) do revtype=$(git-cat-file -t "$rev") echo " via $rev ($revtype)" done if [ "$baserev" = "$oldrev" ]; then echo " from $oldrev ($oldrev_type)" else echo " based on $baserev" echo " from $oldrev ($oldrev_type)" echo "" echo "This ref update crossed a branch point; i.e. the old rev is not a strict subset" echo "of the new rev. This occurs, when you --force push a change in a situation" echo "like this:" echo "" echo " * -- * -- B -- O -- O -- O ($oldrev)" echo " \\" echo " N -- N -- N ($newrev)" echo "" echo "Therefore, we assume that you've already had alert emails for all of the O" echo "revisions, and now give you all the revisions in the N branch from the common" echo "base, B ($baserev), up to the new revision." fi echo "" echo $LOGBEGIN git-rev-list --pretty $newrev ^$baserev echo $LOGEND echo "" echo "Diffstat:" git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$baserev fi ;; "annotated tag") # Should we allow changes to annotated tags? if expr "$oldrev" : '0*$' >/dev/null then # If the old reference is "0000..0000" then this is a new atag # and so oldrev is not valid echo " to $newrev ($newrev_type)" else echo " to $newrev ($newrev_type)" echo " from $oldrev" fi # If this tag succeeds another, then show which tag it replaces prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//') if [ -n "$prevtag" ]; then echo " replaces $prevtag" fi # Read the tag details eval $(git cat-file tag $newrev | \ sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p') tagged=$(date --date="1970-01-01 00:00:00 +0000 $ts seconds" +"$DATEFORMAT") echo " tagged by $tagger" echo " on $tagged" echo "" echo $LOGBEGIN echo "" if [ -n "$prevtag" ]; then git rev-list --pretty=short "$prevtag..$newrev" | git shortlog else git rev-list --pretty=short $newrev | git shortlog fi echo $LOGEND echo "" ;; *) # By default, unannotated tags aren't allowed in; if # they are though, it's debatable whether we would even want an # email to be generated; however, I don't want to add another config # option just for that. # # Unannotated tags are more about marking a point than releasing # a version; therefore we don't do the shortlog summary that we # do for annotated tags above - we simply show that the point has # been marked, and print the log message for the marked point for # reference purposes # # Note this section also catches any other reference type (although # there aren't any) and deals with them in the same way. if expr "$oldrev" : '0*$' >/dev/null then # If the old reference is "0000..0000" then this is a new tag # and so oldrev is not valid echo " as a new $refname_type" echo " to $newrev ($newrev_type)" else echo " to $newrev ($newrev_type)" echo " from $oldrev" fi echo "" echo $LOGBEGIN git-show --no-color --root -s $newrev echo $LOGEND echo "" ;; esac # Footer cat <<-EOF hooks/update --- Git Source Code Management System $0 $1 \\ $2 \\ $3 EOF #) | cat >&2 ) | /usr/sbin/sendmail -t # --- Finished exit 0