f5350ef00a8f6e480417027e701b8fe527f3b7fb
[git-central.git] / server / post-receive-email
1 #!/bin/sh
2 #
3 # Copyright (c) 2007 Andy Parkins
4 # Copyright (c) 2008 Stephen Haberman
5 #
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.
9 #
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
14 # push.
15 #
16 # Config
17 # ------
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
30 #
31 # Notes
32 # -----
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.
36 #
37
38 # ---------------------------- Functions
39
40 . $(dirname $0)/functions
41
42 #
43 # Top level email generation function.  This decides what type of update
44 # this is and calls the appropriate body-generation routine after outputting
45 # the common header
46 #
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
53 #
54 generate_email()
55 {
56         # --- Arguments
57         oldrev=$(git rev-parse $1)
58         newrev=$(git rev-parse $2)
59         refname="$3"
60
61         set_change_type
62         set_rev_types
63         set_describe
64
65         # The revision type tells us what type the commit is, combined with
66         # the location of the ref we can decide between
67         #  - working branch
68         #  - tracking branch
69         #  - unannoted tag
70         #  - annotated tag
71         case "$refname","$rev_type" in
72                 refs/tags/*,commit)
73                         # un-annotated tag
74                         refname_type="tag"
75                         function="ltag"
76                         short_refname=${refname##refs/tags/}
77                         ;;
78                 refs/tags/*,tag)
79                         # annotated tag
80                         refname_type="annotated tag"
81                         function="atag"
82                         short_refname=${refname##refs/tags/}
83                         # change recipients
84                         if [ -n "$announcerecipients" ]; then
85                                 recipients="$announcerecipients"
86                         fi
87                         ;;
88                 refs/heads/*,commit)
89                         # branch
90                         refname_type="branch"
91                         function="branch"
92                         short_refname=${refname##refs/heads/}
93                         ;;
94                 refs/remotes/*,commit)
95                         # tracking branch
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."
100                         exit 0
101                         ;;
102                 *)
103                         # Anything else (is there anything else?)
104                         echo >&2 "*** Unknown type of update to $refname ($rev_type)"
105                         echo >&2 "***  - no email generated"
106                         exit 1
107                         ;;
108         esac
109
110         # Check if we've got anyone to send to
111         if [ -z "$recipients" ]; then
112                 case "$refname_type" in
113                         "annotated tag")
114                                 config_name="hooks.post-receive-email.announcelist"
115                                 ;;
116                         *)
117                                 config_name="hooks.post-receive-email.mailinglist"
118                                 ;;
119                 esac
120                 echo >&2 "*** $config_name is not set so no email will be sent"
121                 echo >&2 "*** for $refname update $oldrev->$newrev"
122                 exit 0
123         fi
124
125         generate_email_header
126         generate_${change_type}_${function}_email
127 }
128
129 generate_email_header()
130 {
131         # --- Email (all stdout will be the email)
132         # Generate header
133         cat <<-EOF
134         From: $USER_EMAIL
135         To: $recipients
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
141
142         The $refname_type, $short_refname has been ${change_type}d
143         EOF
144 }
145
146
147 # --------------- Branches
148
149 #
150 # Called for the creation of a branch
151 #
152 generate_create_branch_email()
153 {
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"
156
157         set_new_commits
158
159         echo ""
160         echo $LOGBEGIN
161         echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
162                 echo ""
163                 git rev-list --no-walk --pretty "$commit"
164                 git diff-tree --cc "$commit"
165                 echo ""
166                 echo $LOGEND
167         done
168
169         oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
170         if [ "$oldest_new" != "" ] ; then
171                 echo ""
172                 echo "Summary of changes:"
173                 git diff-tree --stat $oldest_new^..$newrev
174         fi
175 }
176
177 #
178 # Called for the change of a pre-existing branch
179 #
180 generate_update_branch_email()
181 {
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"
184
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"
187
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"
191         else
192                 # Must be rewind, could be rewind+addition
193                 echo ""
194
195                 # Find the common ancestor of the old and new revisions and compare it with newrev
196                 baserev=$(git merge-base $oldrev $newrev)
197                 rewind_only=""
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."
201                         echo ""
202                         echo " * -- * -- N ($newrev)"
203                         echo "            \\"
204                         echo "             O -- O -- O ($oldrev)"
205                         echo ""
206                         echo "The removed revisions are not necessarilly gone - if another reference"
207                         echo "still refers to them they will stay in the repository."
208                         rewind_only=1
209                 else
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:"
214                         echo ""
215                         echo " * -- * -- B -- O -- O -- O ($oldrev)"
216                         echo "            \\"
217                         echo "             N -- N -- N ($newrev)"
218                         echo ""
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."
222                 fi
223         fi
224
225         echo ""
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."
230
231                 set_new_commits
232
233                 echo ""
234                 echo $LOGBEGIN
235                 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
236                         echo ""
237                         git rev-list --no-walk --pretty "$commit"
238                         git diff-tree --cc "$commit"
239                         echo ""
240                         echo $LOGEND
241                 done
242
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
246         else
247                 echo "No new revisions were added by this update."
248         fi
249
250         # Show the diffstat which is what really happened (new commits/whatever aside)
251         echo ""
252         echo "Summary of changes:"
253         git diff-tree --stat --find-copies-harder $oldrev..$newrev
254 }
255
256 #
257 # Called for the deletion of a branch
258 #
259 generate_delete_branch_email()
260 {
261         echo "       was  $oldrev"
262         echo ""
263         echo $LOGEND
264         git show -s --pretty=oneline $oldrev
265         echo $LOGEND
266 }
267
268 # --------------- Annotated tags
269
270 #
271 # Called for the creation of an annotated tag
272 #
273 generate_create_atag_email()
274 {
275         echo "        at $newrev ($newrev_type)"
276         generate_atag_email
277 }
278
279 #
280 # Called for the update of an annotated tag (this is probably a rare event
281 # and may not even be allowed)
282 #
283 generate_update_atag_email()
284 {
285         echo "        to $newrev ($newrev_type)"
286         echo "      from $oldrev (which is now obsolete)"
287         generate_atag_email
288 }
289
290 #
291 # Called when an annotated tag is created or changed
292 #
293 generate_atag_email()
294 {
295         # Use git for-each-ref to pull out the individual fields from the
296         # tag
297         eval $(git for-each-ref --shell --format='
298         tagobject=%(*objectname)
299         tagtype=%(*objecttype)
300         tagger=%(taggername)
301         tagged=%(taggerdate)' $refname
302         )
303
304         echo "   tagging $tagobject ($tagtype)"
305         case "$tagtype" in
306         commit)
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"
312                 fi
313                 ;;
314         *)
315                 echo "    length $(git cat-file -s $tagobject) bytes"
316                 ;;
317         esac
318         echo " tagged by $tagger"
319         echo "        on $tagged"
320
321         echo ""
322         echo $LOGBEGIN
323
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'
327
328         echo ""
329         case "$tagtype" in
330         commit)
331                 # Only commit tags make sense to have rev-list operations
332                 # performed on them
333                 if [ -n "$prevtag" ]; then
334                         # Show changes since the previous release
335                         git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
336                 else
337                         # No previous tag, show all the changes since time
338                         # began
339                         git rev-list --pretty=short $newrev | git shortlog
340                 fi
341                 ;;
342         *)
343                 # XXX: Is there anything useful we can do for non-commit
344                 # objects?
345                 ;;
346         esac
347
348         echo $LOGEND
349 }
350
351 #
352 # Called for the deletion of an annotated tag
353 #
354 generate_delete_atag_email()
355 {
356         echo "       was $oldrev ($oldrev_type)"
357         echo ""
358         echo $LOGEND
359         git show -s --pretty=oneline $oldrev
360         echo $LOGEND
361 }
362
363 # --------------- General references
364
365 #
366 # Called when any other type of reference is created (most likely a
367 # non-annotated tag)
368 #
369 generate_create_ltag_email()
370 {
371         echo "        at $newrev ($newrev_type)"
372         generate_ltag_email
373 }
374
375 #
376 # Called when any other type of reference is updated (most likely a
377 # non-annotated tag)
378 #
379 generate_update_ltag_email()
380 {
381         echo "        to $newrev ($newrev_type)"
382         echo "      from $oldrev ($oldrev_type)"
383         generate_ltag_email
384 }
385
386 #
387 # Called for creation or update of any other type of reference
388 #
389 generate_ltag_email()
390 {
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
395         # reference purposes
396         #
397         # Note this section also catches any other reference type (although
398         # there aren't any) and deals with them in the same way.
399
400         echo ""
401         if [ "$newrev_type" = "commit" ]; then
402                 echo $LOGBEGIN
403                 git show --no-color --root -s --pretty=medium $newrev
404                 echo $LOGEND
405         else
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."
411         fi
412 }
413
414 #
415 # Called for the deletion of any other type of reference
416 #
417 generate_delete_ltag_email()
418 {
419         echo "       was $oldrev ($oldrev_type)"
420         echo ""
421         echo $LOGEND
422         git show -s --pretty=oneline $oldrev
423         echo $LOGEND
424 }
425
426 send_mail()
427 {
428         if [ -n "$envelopesender" ] ; then
429                 $sendmail -t -f "$envelopesender"
430         else
431                 $sendmail -t
432         fi
433 }
434
435 # ---------------------------- main()
436
437 # --- Constants
438 LOGBEGIN="- Log -----------------------------------------------------------------"
439 LOGEND="-----------------------------------------------------------------------"
440
441 # --- Config
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"
446         exit 1
447 fi
448
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"
453 fi
454
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)
461
462 # --- Main loop
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
468         # themselves
469         PAGER= generate_email $2 $3 $1
470 else
471         while read oldrev newrev refname
472         do
473                 if [ "$debug" == "true" ] ; then
474                         generate_email $oldrev $newrev $refname > "${refname//\//.}.out"
475                 else
476                         generate_email $oldrev $newrev $refname | send_mail
477                 fi
478         done
479 fi
480