3 # Copyright (c) 2007 Andy Parkins
5 # An example hook script to mail out commit update information. This hook
6 # sends emails listing new revisions to the repository introduced by the
7 # 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 is stored in the contrib/hooks directory. Your distribution
11 # will have put this somewhere standard. You should make this script
12 # executable then link to it in the repository you would like to use it in.
13 # For example, on debian the hook is stored in
14 # /usr/share/doc/git-core/contrib/hooks/post-receive-email:
16 # chmod a+x post-receive-email
17 # cd /path/to/your/repository.git
18 # ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
20 # This hook script assumes it is enabled on the central repository of a
21 # project, with all users pushing only to it and not between each other. It
22 # will still work if you don't operate in that style, but it would become
23 # possible for the email to be from someone other than the person doing the
28 # hooks.post-receive-email.mailinglist
29 # This is the list that all pushes will go to; leave it blank to not send
30 # emails for every ref update.
31 # hooks.post-receive-email.announcelist
32 # This is the list that all pushes of annotated tags will go to. Leave it
33 # blank to default to the mailinglist field. The announce emails lists
34 # the short log summary of the changes since the last annotated tag.
35 # hooks.post-receive-email.envelopesender
36 # If set then the -f option is passed to sendmail to allow the envelope
37 # sender address to be set
38 # hooks.post-receive-email.sendmail
39 # The path to sendmail, e.g. /usr/sbin/sendmail or /bin/msmtp
43 # All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
44 # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
45 # give information for debugging.
48 # ---------------------------- Functions
50 . $(dirname $0)/functions
53 # Top level email generation function. This decides what type of update
54 # this is and calls the appropriate body-generation routine after outputting
57 # Note this function doesn't actually generate any email output, that is
58 # taken care of by the functions it calls:
59 # - generate_email_header
60 # - generate_create_XXXX_email
61 # - generate_update_XXXX_email
62 # - generate_delete_XXXX_email
67 oldrev=$(git rev-parse $1)
68 newrev=$(git rev-parse $2)
75 # The revision type tells us what type the commit is, combined with
76 # the location of the ref we can decide between
81 case "$refname","$rev_type" in
86 short_refname=${refname##refs/tags/}
90 refname_type="annotated tag"
92 short_refname=${refname##refs/tags/}
94 if [ -n "$announcerecipients" ]; then
95 recipients="$announcerecipients"
100 refname_type="branch"
102 short_refname=${refname##refs/heads/}
104 refs/remotes/*,commit)
106 refname_type="tracking branch"
107 short_refname=${refname##refs/remotes/}
108 echo >&2 "*** Push-update of tracking branch, $refname"
109 echo >&2 "*** - no email generated."
113 # Anything else (is there anything else?)
114 echo >&2 "*** Unknown type of update to $refname ($rev_type)"
115 echo >&2 "*** - no email generated"
120 # Check if we've got anyone to send to
121 if [ -z "$recipients" ]; then
122 case "$refname_type" in
124 config_name="hooks.post-receive-email.announcelist"
127 config_name="hooks.post-receive-email.mailinglist"
130 echo >&2 "*** $config_name is not set so no email will be sent"
131 echo >&2 "*** for $refname update $oldrev->$newrev"
135 generate_email_header
136 generate_${change_type}_${function}_email
139 generate_email_header()
141 # --- Email (all stdout will be the email)
146 Subject: ${emailprefix} $short_refname $refname_type ${change_type}d. $describe
147 X-Git-Refname: $refname
148 X-Git-Reftype: $refname_type
149 X-Git-Oldrev: $oldrev
150 X-Git-Newrev: $newrev
152 The $refname_type, $short_refname has been ${change_type}d
157 # --------------- Branches
160 # Called for the creation of a branch
162 generate_create_branch_email()
164 # This is a new branch and so oldrev is not valid
165 git rev-list --pretty=format:" at %h %s" --no-walk "$newrev" | grep -vP "^commit"
171 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
173 git rev-list --no-walk --pretty "$commit"
174 git diff-tree --cc "$commit"
179 oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
180 if [ "$oldest_new" != "" ] ; then
182 echo "Summary of changes:"
183 git diff-tree --stat $oldest_new^..$newrev
188 # Called for the change of a pre-existing branch
190 generate_update_branch_email()
192 # List all of the revisions that were removed by this update (hopefully empty)
193 git rev-list --first-parent --pretty=format:" discards %h %s" $newrev..$oldrev | grep -vP "^commit"
195 # List all of the revisions that were added by this update
196 git rev-list --first-parent --pretty=format:" via %h %s" $oldrev..$newrev | grep -vP "^commit"
198 removed=$(git rev-list $newrev..$oldrev)
199 if [ "$removed" == "" ] ; then
200 git rev-list --no-walk --pretty=format:" from %h %s" $oldrev | grep -vP "^commit"
202 # Must be rewind, could be rewind+addition
205 # Find the common ancestor of the old and new revisions and compare it with newrev
206 baserev=$(git merge-base $oldrev $newrev)
208 if [ "$baserev" = "$newrev" ]; then
209 echo "This update discarded existing revisions and left the branch pointing at"
210 echo "a previous point in the repository history."
212 echo " * -- * -- N ($newrev)"
214 echo " O -- O -- O ($oldrev)"
216 echo "The removed revisions are not necessarilly gone - if another reference"
217 echo "still refers to them they will stay in the repository."
220 echo "This update added new revisions after undoing existing revisions. That is"
221 echo "to say, the old revision is not a strict subset of the new revision. This"
222 echo "situation occurs when you --force push a change and generate a repository"
223 echo "containing something like this:"
225 echo " * -- * -- B -- O -- O -- O ($oldrev)"
227 echo " N -- N -- N ($newrev)"
229 echo "When this happens we assume that you've already had alert emails for all"
230 echo "of the O revisions, and so we here report only the revisions in the N"
231 echo "branch from the common base, B."
236 if [ -z "$rewind_only" ]; then
237 echo "Those revisions listed above that are new to this repository have"
238 echo "not appeared on any other notification email; so we list those"
239 echo "revisions in full, below."
245 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
247 git rev-list --no-walk --pretty "$commit"
248 git diff-tree --cc "$commit"
253 # XXX: Need a way of detecting whether git rev-list actually
254 # outputted anything, so that we can issue a "no new
255 # revisions added by this update" message
257 echo "No new revisions were added by this update."
260 # Show the diffstat which is what really happened (new commits/whatever aside)
262 echo "Summary of changes:"
263 git diff-tree --stat --find-copies-harder $oldrev..$newrev
267 # Called for the deletion of a branch
269 generate_delete_branch_email()
274 git show -s --pretty=oneline $oldrev
278 # --------------- Annotated tags
281 # Called for the creation of an annotated tag
283 generate_create_atag_email()
285 echo " at $newrev ($newrev_type)"
290 # Called for the update of an annotated tag (this is probably a rare event
291 # and may not even be allowed)
293 generate_update_atag_email()
295 echo " to $newrev ($newrev_type)"
296 echo " from $oldrev (which is now obsolete)"
301 # Called when an annotated tag is created or changed
303 generate_atag_email()
305 # Use git for-each-ref to pull out the individual fields from the
307 eval $(git for-each-ref --shell --format='
308 tagobject=%(*objectname)
309 tagtype=%(*objecttype)
311 tagged=%(taggerdate)' $refname
314 echo " tagging $tagobject ($tagtype)"
317 # If the tagged object is a commit, then we assume this is a
318 # release, and so we calculate which tag this tag is replacing
319 prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
320 if [ -n "$prevtag" ]; then
321 echo " replaces $prevtag"
325 echo " length $(git cat-file -s $tagobject) bytes"
328 echo " tagged by $tagger"
334 # Show the content of the tag message; this might contain a change
335 # log or release notes so is worth displaying.
336 git cat-file tag $newrev | sed -e '1,/^$/d'
341 # Only commit tags make sense to have rev-list operations
343 if [ -n "$prevtag" ]; then
344 # Show changes since the previous release
345 git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
347 # No previous tag, show all the changes since time
349 git rev-list --pretty=short $newrev | git shortlog
353 # XXX: Is there anything useful we can do for non-commit
362 # Called for the deletion of an annotated tag
364 generate_delete_atag_email()
366 echo " was $oldrev ($oldrev_type)"
369 git show -s --pretty=oneline $oldrev
373 # --------------- General references
376 # Called when any other type of reference is created (most likely a
379 generate_create_ltag_email()
381 echo " at $newrev ($newrev_type)"
386 # Called when any other type of reference is updated (most likely a
389 generate_update_ltag_email()
391 echo " to $newrev ($newrev_type)"
392 echo " from $oldrev ($oldrev_type)"
397 # Called for creation or update of any other type of reference
399 generate_ltag_email()
401 # Unannotated tags are more about marking a point than releasing a
402 # version; therefore we don't do the shortlog summary that we do for
403 # annotated tags above - we simply show that the point has been
404 # marked, and print the log message for the marked point for
407 # Note this section also catches any other reference type (although
408 # there aren't any) and deals with them in the same way.
411 if [ "$newrev_type" = "commit" ]; then
413 git show --no-color --root -s --pretty=medium $newrev
416 # What can we do here? The tag marks an object that is not
417 # a commit, so there is no log for us to display. It's
418 # probably not wise to output git cat-file as it could be a
419 # binary blob. We'll just say how big it is
420 echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
425 # Called for the deletion of any other type of reference
427 generate_delete_ltag_email()
429 echo " was $oldrev ($oldrev_type)"
432 git show -s --pretty=oneline $oldrev
438 if [ -n "$envelopesender" ] ; then
439 $sendmail -t -f "$envelopesender"
445 # ---------------------------- main()
448 LOGBEGIN="- Log -----------------------------------------------------------------"
449 LOGEND="-----------------------------------------------------------------------"
452 # Set GIT_DIR either from the working directory or the environment variable.
453 GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
454 if [ -z "$GIT_DIR" ]; then
455 echo >&2 "fatal: post-receive: GIT_DIR not set"
459 projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
460 # Shorten the description if it's the default
461 if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null ; then
462 projectdesc="UNNAMED"
465 recipients=$(git config hooks.post-receive-email.mailinglist)
466 announcerecipients=$(git config hooks.post-receive-email.announcelist)
467 envelopesender=$(git config hooks.post-receive-email.envelopesender)
468 emailprefix="[$projectdesc]"
469 debug=$(git config hooks.post-receive-email.debug)
470 sendmail=$(git config hooks.post-receive-email.sendmail)
473 # Allow dual mode: run from the command line just like the update hook, or
474 # if no arguments are given then run as a hook script
475 if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
476 # Output to the terminal in command line mode - if someone wanted to
477 # resend an email; they could redirect the output to sendmail
479 PAGER= generate_email $2 $3 $1
481 while read oldrev newrev refname
483 if [ "$debug" == "true" ] ; then
484 generate_email $oldrev $newrev $refname > "${refname//\//.}.out"
486 generate_email $oldrev $newrev $refname | send_mail