3 # Copyright (c) 2007 Andy Parkins
4 # Copyright (c) 2008 Stephen Haberman
6 # This hook sends emails listing new revisions to the repository introduced by
7 # the change being reported. The rule is that (for branch updates) each commit
8 # will appear on one email and one email only.
10 # This hook script assumes it is enabled on the central repository of a
11 # project, with all users pushing only to it and not between each other. It
12 # will still work if you don't operate in that style, but it would become
13 # possible for the email to be from someone other than the person doing the
18 # hooks.post-receive-email.mailinglist
19 # This is the list that all pushes will go to; leave it blank to not send
20 # emails for every ref update.
21 # hooks.post-receive-email.announcelist
22 # This is the list that all pushes of annotated tags will go to. Leave it
23 # blank to default to the mailinglist field. The announce emails lists
24 # the short log summary of the changes since the last annotated tag.
25 # hooks.post-receive-email.envelopesender
26 # If set then the -f option is passed to sendmail to allow the envelope
27 # sender address to be set
28 # hooks.post-receive-email.sendmail
29 # The path to sendmail, e.g. /usr/sbin/sendmail or /bin/msmtp
31 # Environment variable that should be set by your repository-specific
32 # post-receive hook. E.g. export USER_EMAIL=${USER}@example.com
36 # All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
37 # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
38 # give information for debugging.
41 # ---------------------------- Functions
43 . $(dirname $0)/functions
46 # Top level email generation function. This decides what type of update
47 # this is and calls the appropriate body-generation routine after outputting
50 # Note this function doesn't actually generate any email output, that is
51 # taken care of by the functions it calls:
52 # - generate_email_header
53 # - generate_create_XXXX_email
54 # - generate_update_XXXX_email
55 # - generate_delete_XXXX_email
60 oldrev=$(git rev-parse $1)
61 newrev=$(git rev-parse $2)
68 # The revision type tells us what type the commit is, combined with
69 # the location of the ref we can decide between
74 case "$refname","$rev_type" in
79 short_refname=${refname##refs/tags/}
83 refname_type="annotated tag"
85 short_refname=${refname##refs/tags/}
87 if [ -n "$announcerecipients" ]; then
88 recipients="$announcerecipients"
95 short_refname=${refname##refs/heads/}
97 refs/remotes/*,commit)
99 refname_type="tracking branch"
100 short_refname=${refname##refs/remotes/}
101 echo >&2 "*** Push-update of tracking branch, $refname"
102 echo >&2 "*** - no email generated."
106 # Anything else (is there anything else?)
107 echo >&2 "*** Unknown type of update to $refname ($rev_type)"
108 echo >&2 "*** - no email generated"
113 # Check if we've got anyone to send to
114 if [ -z "$recipients" ]; then
115 case "$refname_type" in
117 config_name="hooks.post-receive-email.announcelist"
120 config_name="hooks.post-receive-email.mailinglist"
123 echo >&2 "*** $config_name is not set so no email will be sent"
124 echo >&2 "*** for $refname update $oldrev->$newrev"
128 generate_email_header
129 generate_${change_type}_${function}_email
132 generate_email_header()
134 # --- Email (all stdout will be the email)
139 Subject: ${emailprefix} $short_refname $refname_type ${change_type}d. $describe_tags
140 X-Git-Refname: $refname
141 X-Git-Reftype: $refname_type
142 X-Git-Oldrev: $oldrev
143 X-Git-Newrev: $newrev
145 The $refname_type, $short_refname has been ${change_type}d
150 # --------------- Branches
153 # Called for the creation of a branch
155 generate_create_branch_email()
157 # This is a new branch and so oldrev is not valid
158 git rev-list --pretty=format:" at %h %s" --no-walk "$newrev" | grep -vP "^commit"
164 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
166 git rev-list --no-walk --pretty "$commit"
167 git diff-tree --cc "$commit"
172 oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
173 if [ "$oldest_new" != "" ] ; then
175 echo "Summary of changes:"
176 git diff-tree --stat $oldest_new^..$newrev
181 # Called for the change of a pre-existing branch
183 generate_update_branch_email()
185 # List all of the revisions that were removed by this update (hopefully empty)
186 git rev-list --first-parent --pretty=format:" discards %h %s" $newrev..$oldrev | grep -vP "^commit"
188 # List all of the revisions that were added by this update
189 git rev-list --first-parent --pretty=format:" via %h %s" $oldrev..$newrev | grep -vP "^commit"
191 removed=$(git rev-list $newrev..$oldrev)
192 if [ "$removed" == "" ] ; then
193 git rev-list --no-walk --pretty=format:" from %h %s" $oldrev | grep -vP "^commit"
195 # Must be rewind, could be rewind+addition
198 # Find the common ancestor of the old and new revisions and compare it with newrev
199 baserev=$(git merge-base $oldrev $newrev)
201 if [ "$baserev" = "$newrev" ]; then
202 echo "This update discarded existing revisions and left the branch pointing at"
203 echo "a previous point in the repository history."
205 echo " * -- * -- N ($newrev)"
207 echo " O -- O -- O ($oldrev)"
209 echo "The removed revisions are not necessarilly gone - if another reference"
210 echo "still refers to them they will stay in the repository."
213 echo "This update added new revisions after undoing existing revisions. That is"
214 echo "to say, the old revision is not a strict subset of the new revision. This"
215 echo "situation occurs when you --force push a change and generate a repository"
216 echo "containing something like this:"
218 echo " * -- * -- B -- O -- O -- O ($oldrev)"
220 echo " N -- N -- N ($newrev)"
222 echo "When this happens we assume that you've already had alert emails for all"
223 echo "of the O revisions, and so we here report only the revisions in the N"
224 echo "branch from the common base, B."
229 if [ -z "$rewind_only" ]; then
230 echo "Those revisions listed above that are new to this repository have"
231 echo "not appeared on any other notification email; so we list those"
232 echo "revisions in full, below."
238 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
240 git rev-list --no-walk --pretty "$commit"
241 git diff-tree --cc "$commit"
246 # XXX: Need a way of detecting whether git rev-list actually
247 # outputted anything, so that we can issue a "no new
248 # revisions added by this update" message
250 echo "No new revisions were added by this update."
253 # Show the diffstat which is what really happened (new commits/whatever aside)
255 echo "Summary of changes:"
256 git diff-tree --stat --find-copies-harder $oldrev..$newrev
260 # Called for the deletion of a branch
262 generate_delete_branch_email()
267 git show -s --pretty=oneline $oldrev
271 # --------------- Annotated tags
274 # Called for the creation of an annotated tag
276 generate_create_atag_email()
278 echo " at $newrev ($newrev_type)"
283 # Called for the update of an annotated tag (this is probably a rare event
284 # and may not even be allowed)
286 generate_update_atag_email()
288 echo " to $newrev ($newrev_type)"
289 echo " from $oldrev (which is now obsolete)"
294 # Called when an annotated tag is created or changed
296 generate_atag_email()
298 # Use git for-each-ref to pull out the individual fields from the
300 eval $(git for-each-ref --shell --format='
301 tagobject=%(*objectname)
302 tagtype=%(*objecttype)
304 tagged=%(taggerdate)' $refname
307 echo " tagging $tagobject ($tagtype)"
310 # If the tagged object is a commit, then we assume this is a
311 # release, and so we calculate which tag this tag is replacing
312 prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
313 if [ -n "$prevtag" ]; then
314 echo " replaces $prevtag"
318 echo " length $(git cat-file -s $tagobject) bytes"
321 echo " tagged by $tagger"
327 # Show the content of the tag message; this might contain a change
328 # log or release notes so is worth displaying.
329 git cat-file tag $newrev | sed -e '1,/^$/d'
334 # Only commit tags make sense to have rev-list operations
336 if [ -n "$prevtag" ]; then
337 # Show changes since the previous release
338 git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
340 # No previous tag, show all the changes since time
342 git rev-list --pretty=short $newrev | git shortlog
346 # XXX: Is there anything useful we can do for non-commit
355 # Called for the deletion of an annotated tag
357 generate_delete_atag_email()
359 echo " was $oldrev ($oldrev_type)"
362 git show -s --pretty=oneline $oldrev
366 # --------------- General references
369 # Called when any other type of reference is created (most likely a
372 generate_create_ltag_email()
374 echo " at $newrev ($newrev_type)"
379 # Called when any other type of reference is updated (most likely a
382 generate_update_ltag_email()
384 echo " to $newrev ($newrev_type)"
385 echo " from $oldrev ($oldrev_type)"
390 # Called for creation or update of any other type of reference
392 generate_ltag_email()
394 # Unannotated tags are more about marking a point than releasing a
395 # version; therefore we don't do the shortlog summary that we do for
396 # annotated tags above - we simply show that the point has been
397 # marked, and print the log message for the marked point for
400 # Note this section also catches any other reference type (although
401 # there aren't any) and deals with them in the same way.
404 if [ "$newrev_type" = "commit" ]; then
406 git show --no-color --root -s --pretty=medium $newrev
409 # What can we do here? The tag marks an object that is not
410 # a commit, so there is no log for us to display. It's
411 # probably not wise to output git cat-file as it could be a
412 # binary blob. We'll just say how big it is
413 echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
418 # Called for the deletion of any other type of reference
420 generate_delete_ltag_email()
422 echo " was $oldrev ($oldrev_type)"
425 git show -s --pretty=oneline $oldrev
431 if [ -n "$envelopesender" ] ; then
432 $sendmail -t -f "$envelopesender"
438 # ---------------------------- main()
441 LOGBEGIN="- Log -----------------------------------------------------------------"
442 LOGEND="-----------------------------------------------------------------------"
445 # Set GIT_DIR either from the working directory or the environment variable.
446 GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
447 if [ -z "$GIT_DIR" ]; then
448 echo >&2 "fatal: post-receive: GIT_DIR not set"
452 projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
453 # Shorten the description if it's the default
454 if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null ; then
455 projectdesc="UNNAMED"
458 recipients=$(git config hooks.post-receive-email.mailinglist)
459 announcerecipients=$(git config hooks.post-receive-email.announcelist)
460 envelopesender=$(git config hooks.post-receive-email.envelopesender)
461 emailprefix="[$projectdesc]"
462 debug=$(git config hooks.post-receive-email.debug)
463 sendmail=$(git config hooks.post-receive-email.sendmail)
466 # Allow dual mode: run from the command line just like the update hook, or
467 # if no arguments are given then run as a hook script
468 if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
469 # Output to the terminal in command line mode - if someone wanted to
470 # resend an email; they could redirect the output to sendmail
472 PAGER= generate_email $2 $3 $1
474 while read oldrev newrev refname
476 if [ "$debug" == "true" ] ; then
477 generate_email $oldrev $newrev $refname > "${refname//\//.}.out"
479 generate_email $oldrev $newrev $refname | send_mail