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 # Differences from the contrib script (off the top of my head):
12 # * Sends combined diff output which is great for viewing merge commits
13 # * Changes order of commit listing to be oldest to newest
14 # * Configurable sendmail path
15 # * Use git describe --tags for the email subject to pick up commitnumbers
19 # hooks.post-receive-email.mailinglist
20 # This is the list that all pushes will go to; leave it blank to not send
21 # emails for every ref update.
22 # hooks.post-receive-email.announcelist
23 # This is the list that all pushes of annotated tags will go to. Leave it
24 # blank to default to the mailinglist field. The announce emails lists
25 # the short log summary of the changes since the last annotated tag.
26 # hooks.post-receive-email.envelopesender
27 # If set then the -f option is passed to sendmail to allow the envelope
28 # sender address to be set
29 # hooks.post-receive-email.sendmail
30 # The path to sendmail, e.g. /usr/sbin/sendmail or /bin/msmtp
32 # Environment variable that should be set by your repository-specific
33 # post-receive hook. E.g. export USER_EMAIL=${USER}@example.com
37 # All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
38 # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
39 # give information for debugging.
42 # ---------------------------- Functions
44 . $(dirname $0)/functions
47 # Top level email generation function. This decides what type of update
48 # this is and calls the appropriate body-generation routine after outputting
51 # Note this function doesn't actually generate any email output, that is
52 # taken care of by the functions it calls:
53 # - generate_email_header
54 # - generate_create_XXXX_email
55 # - generate_update_XXXX_email
56 # - generate_delete_XXXX_email
61 oldrev=$(git rev-parse $1)
62 newrev=$(git rev-parse $2)
69 # The revision type tells us what type the commit is, combined with
70 # the location of the ref we can decide between
75 case "$refname","$rev_type" in
80 short_refname=${refname##refs/tags/}
84 refname_type="annotated tag"
86 short_refname=${refname##refs/tags/}
88 if [ -n "$announcerecipients" ]; then
89 recipients="$announcerecipients"
96 short_refname=${refname##refs/heads/}
98 refs/remotes/*,commit)
100 refname_type="tracking branch"
101 short_refname=${refname##refs/remotes/}
102 echo >&2 "*** Push-update of tracking branch, $refname"
103 echo >&2 "*** - no email generated."
107 # Anything else (is there anything else?)
108 echo >&2 "*** Unknown type of update to $refname ($rev_type)"
109 echo >&2 "*** - no email generated"
114 # Check if we've got anyone to send to
115 if [ -z "$recipients" ]; then
116 case "$refname_type" in
118 config_name="hooks.post-receive-email.announcelist"
121 config_name="hooks.post-receive-email.mailinglist"
124 echo >&2 "*** $config_name is not set so no email will be sent"
125 echo >&2 "*** for $refname update $oldrev->$newrev"
129 generate_email_header
130 generate_${change_type}_${function}_email
133 generate_email_header()
135 # --- Email (all stdout will be the email)
140 Subject: ${emailprefix} $short_refname $refname_type ${change_type}d. $describe_tags
141 X-Git-Refname: $refname
142 X-Git-Reftype: $refname_type
143 X-Git-Oldrev: $oldrev
144 X-Git-Newrev: $newrev
146 The $refname_type, $short_refname has been ${change_type}d
151 # --------------- Branches
154 # Called for the creation of a branch
156 generate_create_branch_email()
158 # This is a new branch and so oldrev is not valid
159 git rev-list --pretty=format:" at %h %s" --no-walk "$newrev" | grep -vP "^commit"
165 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
167 git rev-list --no-walk --pretty "$commit"
168 git diff-tree --cc "$commit"
173 oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
174 if [ "$oldest_new" != "" ] ; then
176 echo "Summary of changes:"
177 git diff-tree --stat $oldest_new^..$newrev
182 # Called for the change of a pre-existing branch
184 generate_update_branch_email()
186 # List all of the revisions that were removed by this update (hopefully empty)
187 git rev-list --first-parent --pretty=format:" discards %h %s" $newrev..$oldrev | grep -vP "^commit"
189 # List all of the revisions that were added by this update
190 git rev-list --first-parent --pretty=format:" via %h %s" $oldrev..$newrev | grep -vP "^commit"
192 removed=$(git rev-list $newrev..$oldrev)
193 if [ "$removed" == "" ] ; then
194 git rev-list --no-walk --pretty=format:" from %h %s" $oldrev | grep -vP "^commit"
196 # Must be rewind, could be rewind+addition
199 # Find the common ancestor of the old and new revisions and compare it with newrev
200 baserev=$(git merge-base $oldrev $newrev)
202 if [ "$baserev" = "$newrev" ]; then
203 echo "This update discarded existing revisions and left the branch pointing at"
204 echo "a previous point in the repository history."
206 echo " * -- * -- N ($newrev)"
208 echo " O -- O -- O ($oldrev)"
210 echo "The removed revisions are not necessarilly gone - if another reference"
211 echo "still refers to them they will stay in the repository."
214 echo "This update added new revisions after undoing existing revisions. That is"
215 echo "to say, the old revision is not a strict subset of the new revision. This"
216 echo "situation occurs when you --force push a change and generate a repository"
217 echo "containing something like this:"
219 echo " * -- * -- B -- O -- O -- O ($oldrev)"
221 echo " N -- N -- N ($newrev)"
223 echo "When this happens we assume that you've already had alert emails for all"
224 echo "of the O revisions, and so we here report only the revisions in the N"
225 echo "branch from the common base, B."
230 if [ -z "$rewind_only" ]; then
231 echo "Those revisions listed above that are new to this repository have"
232 echo "not appeared on any other notification email; so we list those"
233 echo "revisions in full, below."
239 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
241 git rev-list --no-walk --pretty "$commit"
242 git diff-tree --cc "$commit"
247 # XXX: Need a way of detecting whether git rev-list actually
248 # outputted anything, so that we can issue a "no new
249 # revisions added by this update" message
251 echo "No new revisions were added by this update."
254 # Show the diffstat which is what really happened (new commits/whatever aside)
256 echo "Summary of changes:"
257 git diff-tree --stat --find-copies-harder $oldrev..$newrev
261 # Called for the deletion of a branch
263 generate_delete_branch_email()
268 git show -s --pretty=oneline $oldrev
272 # --------------- Annotated tags
275 # Called for the creation of an annotated tag
277 generate_create_atag_email()
279 echo " at $newrev ($newrev_type)"
284 # Called for the update of an annotated tag (this is probably a rare event
285 # and may not even be allowed)
287 generate_update_atag_email()
289 echo " to $newrev ($newrev_type)"
290 echo " from $oldrev (which is now obsolete)"
295 # Called when an annotated tag is created or changed
297 generate_atag_email()
299 # Use git for-each-ref to pull out the individual fields from the
301 eval $(git for-each-ref --shell --format='
302 tagobject=%(*objectname)
303 tagtype=%(*objecttype)
305 tagged=%(taggerdate)' $refname
308 echo " tagging $tagobject ($tagtype)"
311 # If the tagged object is a commit, then we assume this is a
312 # release, and so we calculate which tag this tag is replacing
313 prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
314 if [ -n "$prevtag" ]; then
315 echo " replaces $prevtag"
319 echo " length $(git cat-file -s $tagobject) bytes"
322 echo " tagged by $tagger"
328 # Show the content of the tag message; this might contain a change
329 # log or release notes so is worth displaying.
330 git cat-file tag $newrev | sed -e '1,/^$/d'
335 # Only commit tags make sense to have rev-list operations
337 if [ -n "$prevtag" ]; then
338 # Show changes since the previous release
339 git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
341 # No previous tag, show all the changes since time
343 git rev-list --pretty=short $newrev | git shortlog
347 # XXX: Is there anything useful we can do for non-commit
356 # Called for the deletion of an annotated tag
358 generate_delete_atag_email()
360 echo " was $oldrev ($oldrev_type)"
363 git show -s --pretty=oneline $oldrev
367 # --------------- General references
370 # Called when any other type of reference is created (most likely a
373 generate_create_ltag_email()
375 echo " at $newrev ($newrev_type)"
380 # Called when any other type of reference is updated (most likely a
383 generate_update_ltag_email()
385 echo " to $newrev ($newrev_type)"
386 echo " from $oldrev ($oldrev_type)"
391 # Called for creation or update of any other type of reference
393 generate_ltag_email()
395 # Unannotated tags are more about marking a point than releasing a
396 # version; therefore we don't do the shortlog summary that we do for
397 # annotated tags above - we simply show that the point has been
398 # marked, and print the log message for the marked point for
401 # Note this section also catches any other reference type (although
402 # there aren't any) and deals with them in the same way.
405 if [ "$newrev_type" = "commit" ]; then
407 git show --no-color --root -s --pretty=medium $newrev
410 # What can we do here? The tag marks an object that is not
411 # a commit, so there is no log for us to display. It's
412 # probably not wise to output git cat-file as it could be a
413 # binary blob. We'll just say how big it is
414 echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
419 # Called for the deletion of any other type of reference
421 generate_delete_ltag_email()
423 echo " was $oldrev ($oldrev_type)"
426 git show -s --pretty=oneline $oldrev
432 if [ -n "$envelopesender" ] ; then
433 $sendmail -t -f "$envelopesender"
439 # ---------------------------- main()
442 LOGBEGIN="- Log -----------------------------------------------------------------"
443 LOGEND="-----------------------------------------------------------------------"
446 # Set GIT_DIR either from the working directory or the environment variable.
447 GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
448 if [ -z "$GIT_DIR" ]; then
449 echo >&2 "fatal: post-receive: GIT_DIR not set"
453 projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
454 # Shorten the description if it's the default
455 if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null ; then
456 projectdesc="UNNAMED"
459 recipients=$(git config hooks.post-receive-email.mailinglist)
460 announcerecipients=$(git config hooks.post-receive-email.announcelist)
461 envelopesender=$(git config hooks.post-receive-email.envelopesender)
462 emailprefix="[$projectdesc]"
463 debug=$(git config hooks.post-receive-email.debug)
464 sendmail=$(git config hooks.post-receive-email.sendmail)
467 # Allow dual mode: run from the command line just like the update hook, or
468 # if no arguments are given then run as a hook script
469 if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
470 # Output to the terminal in command line mode - if someone wanted to
471 # resend an email; they could redirect the output to sendmail
473 PAGER= generate_email $2 $3 $1
475 while read oldrev newrev refname
477 if [ "$debug" == "true" ] ; then
478 generate_email $oldrev $newrev $refname > "${refname//\//.}.out"
480 generate_email $oldrev $newrev $refname | send_mail