Get tests passing again.
[git-central.git] / server / post-receive-email
1 #!/bin/bash
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 # Differences from the contrib script (off the top of my head):
11 #
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
16 #
17 # Config
18 # ------
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
31 # USER_EMAIL
32 #   Environment variable that should be set by your repository-specific
33 #   post-receive hook. E.g. export USER_EMAIL=${USER}@example.com
34 #
35 # Notes
36 # -----
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.
40 #
41
42 # ---------------------------- Functions
43
44 . $(dirname $0)/functions
45
46 #
47 # Top level email generation function.  This decides what type of update
48 # this is and calls the appropriate body-generation routine after outputting
49 # the common header
50 #
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
57 #
58 generate_email()
59 {
60         # --- Arguments
61         oldrev=$(git rev-parse $1)
62         newrev=$(git rev-parse $2)
63         refname="$3"
64
65         set_change_type
66         set_rev_types
67         set_describe_tags
68
69         # The revision type tells us what type the commit is, combined with
70         # the location of the ref we can decide between
71         #  - working branch
72         #  - tracking branch
73         #  - unannoted tag
74         #  - annotated tag
75         case "$refname","$rev_type" in
76                 refs/tags/*,commit)
77                         # un-annotated tag
78                         refname_type="tag"
79                         function="ltag"
80                         short_refname=${refname##refs/tags/}
81                         ;;
82                 refs/tags/*,tag)
83                         # annotated tag
84                         refname_type="annotated tag"
85                         function="atag"
86                         short_refname=${refname##refs/tags/}
87                         # change recipients
88                         if [ -n "$announcerecipients" ]; then
89                                 recipients="$announcerecipients"
90                         fi
91                         ;;
92                 refs/heads/*,commit)
93                         # branch
94                         refname_type="branch"
95                         function="branch"
96                         short_refname=${refname##refs/heads/}
97                         ;;
98                 refs/remotes/*,commit)
99                         # tracking branch
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."
104                         exit 0
105                         ;;
106                 *)
107                         # Anything else (is there anything else?)
108                         echo >&2 "*** Unknown type of update to $refname ($rev_type)"
109                         echo >&2 "***  - no email generated"
110                         exit 1
111                         ;;
112         esac
113
114         # Check if we've got anyone to send to
115         if [ -z "$recipients" ]; then
116                 case "$refname_type" in
117                         "annotated tag")
118                                 config_name="hooks.post-receive-email.announcelist"
119                                 ;;
120                         *)
121                                 config_name="hooks.post-receive-email.mailinglist"
122                                 ;;
123                 esac
124                 echo >&2 "*** $config_name is not set so no email will be sent"
125                 echo >&2 "*** for $refname update $oldrev->$newrev"
126                 exit 0
127         fi
128
129         generate_email_header
130         generate_${change_type}_${function}_email
131 }
132
133 generate_email_header()
134 {
135         # --- Email (all stdout will be the email)
136         # Generate header
137         cat <<-EOF
138         From: $USER_EMAIL
139         To: $recipients
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
145
146         The $refname_type, $short_refname has been ${change_type}d
147         EOF
148 }
149
150
151 # --------------- Branches
152
153 #
154 # Called for the creation of a branch
155 #
156 generate_create_branch_email()
157 {
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"
160
161         set_new_commits
162
163         echo ""
164         echo $LOGBEGIN
165         echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
166                 echo ""
167                 git rev-list --no-walk --pretty "$commit"
168                 git diff-tree --cc "$commit"
169                 echo ""
170                 echo $LOGEND
171         done
172
173         oldest_new=$(echo "$new_commits" | git rev-list --stdin | tail -n 1)
174         if [ "$oldest_new" != "" ] ; then
175                 echo ""
176                 echo "Summary of changes:"
177                 git diff-tree --stat $oldest_new^..$newrev
178         fi
179 }
180
181 #
182 # Called for the change of a pre-existing branch
183 #
184 generate_update_branch_email()
185 {
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"
188
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"
191
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"
195         else
196                 # Must be rewind, could be rewind+addition
197                 echo ""
198
199                 # Find the common ancestor of the old and new revisions and compare it with newrev
200                 baserev=$(git merge-base $oldrev $newrev)
201                 rewind_only=""
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."
205                         echo ""
206                         echo " * -- * -- N ($newrev)"
207                         echo "            \\"
208                         echo "             O -- O -- O ($oldrev)"
209                         echo ""
210                         echo "The removed revisions are not necessarilly gone - if another reference"
211                         echo "still refers to them they will stay in the repository."
212                         rewind_only=1
213                 else
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:"
218                         echo ""
219                         echo " * -- * -- B -- O -- O -- O ($oldrev)"
220                         echo "            \\"
221                         echo "             N -- N -- N ($newrev)"
222                         echo ""
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."
226                 fi
227         fi
228
229         echo ""
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."
234
235                 set_new_commits
236
237                 echo ""
238                 echo $LOGBEGIN
239                 echo "$new_commits" | git rev-list --reverse --stdin | while read commit ; do
240                         echo ""
241                         git rev-list --no-walk --pretty "$commit"
242                         git diff-tree --cc "$commit"
243                         echo ""
244                         echo $LOGEND
245                 done
246
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
250         else
251                 echo "No new revisions were added by this update."
252         fi
253
254         # Show the diffstat which is what really happened (new commits/whatever aside)
255         echo ""
256         echo "Summary of changes:"
257         git diff-tree --stat --find-copies-harder $oldrev..$newrev
258 }
259
260 #
261 # Called for the deletion of a branch
262 #
263 generate_delete_branch_email()
264 {
265         echo "       was  $oldrev"
266         echo ""
267         echo $LOGEND
268         git show -s --pretty=oneline $oldrev
269         echo $LOGEND
270 }
271
272 # --------------- Annotated tags
273
274 #
275 # Called for the creation of an annotated tag
276 #
277 generate_create_atag_email()
278 {
279         echo "        at $newrev ($newrev_type)"
280         generate_atag_email
281 }
282
283 #
284 # Called for the update of an annotated tag (this is probably a rare event
285 # and may not even be allowed)
286 #
287 generate_update_atag_email()
288 {
289         echo "        to $newrev ($newrev_type)"
290         echo "      from $oldrev (which is now obsolete)"
291         generate_atag_email
292 }
293
294 #
295 # Called when an annotated tag is created or changed
296 #
297 generate_atag_email()
298 {
299         # Use git for-each-ref to pull out the individual fields from the
300         # tag
301         eval $(git for-each-ref --shell --format='
302         tagobject=%(*objectname)
303         tagtype=%(*objecttype)
304         tagger=%(taggername)
305         tagged=%(taggerdate)' $refname
306         )
307
308         echo "   tagging $tagobject ($tagtype)"
309         case "$tagtype" in
310         commit)
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"
316                 fi
317                 ;;
318         *)
319                 echo "    length $(git cat-file -s $tagobject) bytes"
320                 ;;
321         esac
322         echo " tagged by $tagger"
323         echo "        on $tagged"
324
325         echo ""
326         echo $LOGBEGIN
327
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'
331
332         echo ""
333         case "$tagtype" in
334         commit)
335                 # Only commit tags make sense to have rev-list operations
336                 # performed on them
337                 if [ -n "$prevtag" ]; then
338                         # Show changes since the previous release
339                         git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
340                 else
341                         # No previous tag, show all the changes since time
342                         # began
343                         git rev-list --pretty=short $newrev | git shortlog
344                 fi
345                 ;;
346         *)
347                 # XXX: Is there anything useful we can do for non-commit
348                 # objects?
349                 ;;
350         esac
351
352         echo $LOGEND
353 }
354
355 #
356 # Called for the deletion of an annotated tag
357 #
358 generate_delete_atag_email()
359 {
360         echo "       was $oldrev ($oldrev_type)"
361         echo ""
362         echo $LOGEND
363         git show -s --pretty=oneline $oldrev
364         echo $LOGEND
365 }
366
367 # --------------- General references
368
369 #
370 # Called when any other type of reference is created (most likely a
371 # non-annotated tag)
372 #
373 generate_create_ltag_email()
374 {
375         echo "        at $newrev ($newrev_type)"
376         generate_ltag_email
377 }
378
379 #
380 # Called when any other type of reference is updated (most likely a
381 # non-annotated tag)
382 #
383 generate_update_ltag_email()
384 {
385         echo "        to $newrev ($newrev_type)"
386         echo "      from $oldrev ($oldrev_type)"
387         generate_ltag_email
388 }
389
390 #
391 # Called for creation or update of any other type of reference
392 #
393 generate_ltag_email()
394 {
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
399         # reference purposes
400         #
401         # Note this section also catches any other reference type (although
402         # there aren't any) and deals with them in the same way.
403
404         echo ""
405         if [ "$newrev_type" = "commit" ]; then
406                 echo $LOGBEGIN
407                 git show --no-color --root -s --pretty=medium $newrev
408                 echo $LOGEND
409         else
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."
415         fi
416 }
417
418 #
419 # Called for the deletion of any other type of reference
420 #
421 generate_delete_ltag_email()
422 {
423         echo "       was $oldrev ($oldrev_type)"
424         echo ""
425         echo $LOGEND
426         git show -s --pretty=oneline $oldrev
427         echo $LOGEND
428 }
429
430 send_mail()
431 {
432         if [ -n "$envelopesender" ] ; then
433                 $sendmail -t -f "$envelopesender"
434         else
435                 $sendmail -t
436         fi
437 }
438
439 # ---------------------------- main()
440
441 # --- Constants
442 LOGBEGIN="- Log -----------------------------------------------------------------"
443 LOGEND="-----------------------------------------------------------------------"
444
445 # --- Config
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"
450         exit 1
451 fi
452
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"
457 fi
458
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)
465
466 # --- Main loop
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
472         # themselves
473         PAGER= generate_email $2 $3 $1
474 else
475         while read oldrev newrev refname
476         do
477                 if [ "$debug" == "true" ] ; then
478                         generate_email $oldrev $newrev $refname > "${refname//\//.}.out"
479                 else
480                         generate_email $oldrev $newrev $refname | send_mail
481                 fi
482         done
483 fi
484