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
33 # All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
34 # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
35 # give information for debugging.
38 # ---------------------------- Functions
40 . $(dirname $0)/functions
43 # Top level email generation function. This decides what type of update
44 # this is and calls the appropriate body-generation routine after outputting
47 # Note this function doesn't actually generate any email output, that is
48 # taken care of by the functions it calls:
49 # - generate_email_header
50 # - generate_create_XXXX_email
51 # - generate_update_XXXX_email
52 # - generate_delete_XXXX_email
57 oldrev=$(git rev-parse $1)
58 newrev=$(git rev-parse $2)
65 # The revision type tells us what type the commit is, combined with
66 # the location of the ref we can decide between
71 case "$refname","$rev_type" in
76 short_refname=${refname##refs/tags/}
80 refname_type="annotated tag"
82 short_refname=${refname##refs/tags/}
84 if [ -n "$announcerecipients" ]; then
85 recipients="$announcerecipients"
92 short_refname=${refname##refs/heads/}
94 refs/remotes/*,commit)
96 refname_type="tracking branch"
97 short_refname=${refname##refs/remotes/}
98 echo >&2 "*** Push-update of tracking branch, $refname"
99 echo >&2 "*** - no email generated."
103 # Anything else (is there anything else?)
104 echo >&2 "*** Unknown type of update to $refname ($rev_type)"
105 echo >&2 "*** - no email generated"
110 # Check if we've got anyone to send to
111 if [ -z "$recipients" ]; then
112 case "$refname_type" in
114 config_name="hooks.post-receive-email.announcelist"
117 config_name="hooks.post-receive-email.mailinglist"
120 echo >&2 "*** $config_name is not set so no email will be sent"
121 echo >&2 "*** for $refname update $oldrev->$newrev"
125 generate_email_header
126 generate_${change_type}_${function}_email
129 generate_email_header()
131 # --- Email (all stdout will be the email)
136 Subject: ${emailprefix} $short_refname $refname_type ${change_type}d. $describe
137 X-Git-Refname: $refname
138 X-Git-Reftype: $refname_type
139 X-Git-Oldrev: $oldrev
140 X-Git-Newrev: $newrev
142 The $refname_type, $short_refname has been ${change_type}d
147 # --------------- Branches
150 # Called for the creation of a branch
152 generate_create_branch_email()
154 # This is a new branch and so oldrev is not valid
155 git rev-list --pretty=format:" at %h %s" --no-walk "$newrev" | grep -vP "^commit"
161 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
163 git rev-list --no-walk --pretty "$commit"
164 git diff-tree --cc "$commit"
169 oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
170 if [ "$oldest_new" != "" ] ; then
172 echo "Summary of changes:"
173 git diff-tree --stat $oldest_new^..$newrev
178 # Called for the change of a pre-existing branch
180 generate_update_branch_email()
182 # List all of the revisions that were removed by this update (hopefully empty)
183 git rev-list --first-parent --pretty=format:" discards %h %s" $newrev..$oldrev | grep -vP "^commit"
185 # List all of the revisions that were added by this update
186 git rev-list --first-parent --pretty=format:" via %h %s" $oldrev..$newrev | grep -vP "^commit"
188 removed=$(git rev-list $newrev..$oldrev)
189 if [ "$removed" == "" ] ; then
190 git rev-list --no-walk --pretty=format:" from %h %s" $oldrev | grep -vP "^commit"
192 # Must be rewind, could be rewind+addition
195 # Find the common ancestor of the old and new revisions and compare it with newrev
196 baserev=$(git merge-base $oldrev $newrev)
198 if [ "$baserev" = "$newrev" ]; then
199 echo "This update discarded existing revisions and left the branch pointing at"
200 echo "a previous point in the repository history."
202 echo " * -- * -- N ($newrev)"
204 echo " O -- O -- O ($oldrev)"
206 echo "The removed revisions are not necessarilly gone - if another reference"
207 echo "still refers to them they will stay in the repository."
210 echo "This update added new revisions after undoing existing revisions. That is"
211 echo "to say, the old revision is not a strict subset of the new revision. This"
212 echo "situation occurs when you --force push a change and generate a repository"
213 echo "containing something like this:"
215 echo " * -- * -- B -- O -- O -- O ($oldrev)"
217 echo " N -- N -- N ($newrev)"
219 echo "When this happens we assume that you've already had alert emails for all"
220 echo "of the O revisions, and so we here report only the revisions in the N"
221 echo "branch from the common base, B."
226 if [ -z "$rewind_only" ]; then
227 echo "Those revisions listed above that are new to this repository have"
228 echo "not appeared on any other notification email; so we list those"
229 echo "revisions in full, below."
235 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
237 git rev-list --no-walk --pretty "$commit"
238 git diff-tree --cc "$commit"
243 # XXX: Need a way of detecting whether git rev-list actually
244 # outputted anything, so that we can issue a "no new
245 # revisions added by this update" message
247 echo "No new revisions were added by this update."
250 # Show the diffstat which is what really happened (new commits/whatever aside)
252 echo "Summary of changes:"
253 git diff-tree --stat --find-copies-harder $oldrev..$newrev
257 # Called for the deletion of a branch
259 generate_delete_branch_email()
264 git show -s --pretty=oneline $oldrev
268 # --------------- Annotated tags
271 # Called for the creation of an annotated tag
273 generate_create_atag_email()
275 echo " at $newrev ($newrev_type)"
280 # Called for the update of an annotated tag (this is probably a rare event
281 # and may not even be allowed)
283 generate_update_atag_email()
285 echo " to $newrev ($newrev_type)"
286 echo " from $oldrev (which is now obsolete)"
291 # Called when an annotated tag is created or changed
293 generate_atag_email()
295 # Use git for-each-ref to pull out the individual fields from the
297 eval $(git for-each-ref --shell --format='
298 tagobject=%(*objectname)
299 tagtype=%(*objecttype)
301 tagged=%(taggerdate)' $refname
304 echo " tagging $tagobject ($tagtype)"
307 # If the tagged object is a commit, then we assume this is a
308 # release, and so we calculate which tag this tag is replacing
309 prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
310 if [ -n "$prevtag" ]; then
311 echo " replaces $prevtag"
315 echo " length $(git cat-file -s $tagobject) bytes"
318 echo " tagged by $tagger"
324 # Show the content of the tag message; this might contain a change
325 # log or release notes so is worth displaying.
326 git cat-file tag $newrev | sed -e '1,/^$/d'
331 # Only commit tags make sense to have rev-list operations
333 if [ -n "$prevtag" ]; then
334 # Show changes since the previous release
335 git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
337 # No previous tag, show all the changes since time
339 git rev-list --pretty=short $newrev | git shortlog
343 # XXX: Is there anything useful we can do for non-commit
352 # Called for the deletion of an annotated tag
354 generate_delete_atag_email()
356 echo " was $oldrev ($oldrev_type)"
359 git show -s --pretty=oneline $oldrev
363 # --------------- General references
366 # Called when any other type of reference is created (most likely a
369 generate_create_ltag_email()
371 echo " at $newrev ($newrev_type)"
376 # Called when any other type of reference is updated (most likely a
379 generate_update_ltag_email()
381 echo " to $newrev ($newrev_type)"
382 echo " from $oldrev ($oldrev_type)"
387 # Called for creation or update of any other type of reference
389 generate_ltag_email()
391 # Unannotated tags are more about marking a point than releasing a
392 # version; therefore we don't do the shortlog summary that we do for
393 # annotated tags above - we simply show that the point has been
394 # marked, and print the log message for the marked point for
397 # Note this section also catches any other reference type (although
398 # there aren't any) and deals with them in the same way.
401 if [ "$newrev_type" = "commit" ]; then
403 git show --no-color --root -s --pretty=medium $newrev
406 # What can we do here? The tag marks an object that is not
407 # a commit, so there is no log for us to display. It's
408 # probably not wise to output git cat-file as it could be a
409 # binary blob. We'll just say how big it is
410 echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
415 # Called for the deletion of any other type of reference
417 generate_delete_ltag_email()
419 echo " was $oldrev ($oldrev_type)"
422 git show -s --pretty=oneline $oldrev
428 if [ -n "$envelopesender" ] ; then
429 $sendmail -t -f "$envelopesender"
435 # ---------------------------- main()
438 LOGBEGIN="- Log -----------------------------------------------------------------"
439 LOGEND="-----------------------------------------------------------------------"
442 # Set GIT_DIR either from the working directory or the environment variable.
443 GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
444 if [ -z "$GIT_DIR" ]; then
445 echo >&2 "fatal: post-receive: GIT_DIR not set"
449 projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
450 # Shorten the description if it's the default
451 if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null ; then
452 projectdesc="UNNAMED"
455 recipients=$(git config hooks.post-receive-email.mailinglist)
456 announcerecipients=$(git config hooks.post-receive-email.announcelist)
457 envelopesender=$(git config hooks.post-receive-email.envelopesender)
458 emailprefix="[$projectdesc]"
459 debug=$(git config hooks.post-receive-email.debug)
460 sendmail=$(git config hooks.post-receive-email.sendmail)
463 # Allow dual mode: run from the command line just like the update hook, or
464 # if no arguments are given then run as a hook script
465 if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
466 # Output to the terminal in command line mode - if someone wanted to
467 # resend an email; they could redirect the output to sendmail
469 PAGER= generate_email $2 $3 $1
471 while read oldrev newrev refname
473 if [ "$debug" == "true" ] ; then
474 generate_email $oldrev $newrev $refname > "${refname//\//.}.out"
476 generate_email $oldrev $newrev $refname | send_mail