]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/hooks/post-receive-email
post-receive-email: remove spurious commas in email subject
[thirdparty/git.git] / contrib / hooks / post-receive-email
CommitLineData
4557e0de
AP
1#!/bin/sh
2#
3# Copyright (c) 2007 Andy Parkins
4#
15a2f530
GP
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.
4557e0de 9#
15a2f530
GP
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:
4557e0de
AP
15#
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
19#
15a2f530
GP
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
24# push.
4557e0de 25#
cc24a1d8
JN
26# To help with debugging and use on pre-v1.5.1 git servers, this script will
27# also obey the interface of hooks/update, taking its arguments on the
28# command line. Unfortunately, hooks/update is called once for each ref.
29# To avoid firing one email per ref, this script just prints its output to
30# the screen when used in this mode. The output can then be redirected if
31# wanted.
32#
4557e0de
AP
33# Config
34# ------
35# hooks.mailinglist
36# This is the list that all pushes will go to; leave it blank to not send
37# emails for every ref update.
38# hooks.announcelist
39# This is the list that all pushes of annotated tags will go to. Leave it
15a2f530
GP
40# blank to default to the mailinglist field. The announce emails lists
41# the short log summary of the changes since the last annotated tag.
b5786c82 42# hooks.envelopesender
15a2f530
GP
43# If set then the -f option is passed to sendmail to allow the envelope
44# sender address to be set
e7509ee3
GP
45# hooks.emailprefix
46# All emails have their subjects prefixed with this prefix, or "[SCM]"
47# if emailprefix is unset, to aid filtering
b0a7d111
PH
48# hooks.showrev
49# The shell command used to format each revision in the email, with
50# "%s" replaced with the commit id. Defaults to "git rev-list -1
51# --pretty %s", displaying the commit id, author, date and log
52# message. To list full patches separated by a blank line, you
53# could set this to "git show -C %s; echo".
5ffd3113
JM
54# To list a gitweb/cgit URL *and* a full patch for each change set, use this:
55# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo"
56# Be careful if "..." contains things that will be expanded by shell "eval"
57# or printf.
4557e0de
AP
58#
59# Notes
60# -----
4557e0de
AP
61# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
62# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
63# give information for debugging.
64#
65
66# ---------------------------- Functions
67
68#
69# Top level email generation function. This decides what type of update
70# this is and calls the appropriate body-generation routine after outputting
71# the common header
72#
15a2f530
GP
73# Note this function doesn't actually generate any email output, that is
74# taken care of by the functions it calls:
4557e0de
AP
75# - generate_email_header
76# - generate_create_XXXX_email
77# - generate_update_XXXX_email
78# - generate_delete_XXXX_email
79# - generate_email_footer
80#
81generate_email()
82{
83 # --- Arguments
84 oldrev=$(git rev-parse $1)
85 newrev=$(git rev-parse $2)
86 refname="$3"
87
88 # --- Interpret
89 # 0000->1234 (create)
90 # 1234->2345 (update)
91 # 2345->0000 (delete)
92 if expr "$oldrev" : '0*$' >/dev/null
93 then
94 change_type="create"
95 else
96 if expr "$newrev" : '0*$' >/dev/null
97 then
98 change_type="delete"
99 else
100 change_type="update"
101 fi
102 fi
103
104 # --- Get the revision types
105 newrev_type=$(git cat-file -t $newrev 2> /dev/null)
106 oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
107 case "$change_type" in
108 create|update)
109 rev="$newrev"
110 rev_type="$newrev_type"
111 ;;
112 delete)
113 rev="$oldrev"
114 rev_type="$oldrev_type"
115 ;;
116 esac
117
118 # The revision type tells us what type the commit is, combined with
119 # the location of the ref we can decide between
120 # - working branch
121 # - tracking branch
122 # - unannoted tag
123 # - annotated tag
124 case "$refname","$rev_type" in
125 refs/tags/*,commit)
126 # un-annotated tag
127 refname_type="tag"
128 short_refname=${refname##refs/tags/}
129 ;;
130 refs/tags/*,tag)
131 # annotated tag
132 refname_type="annotated tag"
133 short_refname=${refname##refs/tags/}
134 # change recipients
135 if [ -n "$announcerecipients" ]; then
136 recipients="$announcerecipients"
137 fi
138 ;;
139 refs/heads/*,commit)
140 # branch
141 refname_type="branch"
142 short_refname=${refname##refs/heads/}
143 ;;
144 refs/remotes/*,commit)
145 # tracking branch
146 refname_type="tracking branch"
147 short_refname=${refname##refs/remotes/}
148 echo >&2 "*** Push-update of tracking branch, $refname"
149 echo >&2 "*** - no email generated."
150 exit 0
151 ;;
152 *)
153 # Anything else (is there anything else?)
154 echo >&2 "*** Unknown type of update to $refname ($rev_type)"
155 echo >&2 "*** - no email generated"
156 exit 1
157 ;;
158 esac
159
160 # Check if we've got anyone to send to
161 if [ -z "$recipients" ]; then
fdfeb87c
JM
162 case "$refname_type" in
163 "annotated tag")
164 config_name="hooks.announcelist"
165 ;;
166 *)
167 config_name="hooks.mailinglist"
168 ;;
169 esac
170 echo >&2 "*** $config_name is not set so no email will be sent"
4557e0de
AP
171 echo >&2 "*** for $refname update $oldrev->$newrev"
172 exit 0
173 fi
174
175 # Email parameters
4557e0de
AP
176 # The email subject will contain the best description of the ref
177 # that we can build from the parameters
178 describe=$(git describe $rev 2>/dev/null)
179 if [ -z "$describe" ]; then
180 describe=$rev
181 fi
182
183 generate_email_header
184
185 # Call the correct body generation function
186 fn_name=general
187 case "$refname_type" in
188 "tracking branch"|branch)
189 fn_name=branch
190 ;;
191 "annotated tag")
192 fn_name=atag
193 ;;
194 esac
195 generate_${change_type}_${fn_name}_email
196
197 generate_email_footer
198}
199
200generate_email_header()
201{
202 # --- Email (all stdout will be the email)
203 # Generate header
204 cat <<-EOF
4557e0de 205 To: $recipients
b5e233ec 206 Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
4557e0de
AP
207 X-Git-Refname: $refname
208 X-Git-Reftype: $refname_type
209 X-Git-Oldrev: $oldrev
210 X-Git-Newrev: $newrev
211
212 This is an automated email from the git hooks/post-receive script. It was
213 generated because a ref change was pushed to the repository containing
214 the project "$projectdesc".
215
216 The $refname_type, $short_refname has been ${change_type}d
217 EOF
218}
219
220generate_email_footer()
221{
71bd81ad 222 SPACE=" "
4557e0de
AP
223 cat <<-EOF
224
225
226 hooks/post-receive
71bd81ad 227 --${SPACE}
4557e0de
AP
228 $projectdesc
229 EOF
230}
231
232# --------------- Branches
233
234#
235# Called for the creation of a branch
236#
237generate_create_branch_email()
238{
239 # This is a new branch and so oldrev is not valid
240 echo " at $newrev ($newrev_type)"
241 echo ""
242
243 echo $LOGBEGIN
4471649f 244 show_new_revisions
4557e0de
AP
245 echo $LOGEND
246}
247
248#
249# Called for the change of a pre-existing branch
250#
251generate_update_branch_email()
252{
253 # Consider this:
254 # 1 --- 2 --- O --- X --- 3 --- 4 --- N
255 #
256 # O is $oldrev for $refname
257 # N is $newrev for $refname
258 # X is a revision pointed to by some other ref, for which we may
259 # assume that an email has already been generated.
260 # In this case we want to issue an email containing only revisions
261 # 3, 4, and N. Given (almost) by
262 #
22fa97d4 263 # git rev-list N ^O --not --all
4557e0de
AP
264 #
265 # The reason for the "almost", is that the "--not --all" will take
266 # precedence over the "N", and effectively will translate to
267 #
22fa97d4 268 # git rev-list N ^O ^X ^N
4557e0de 269 #
22fa97d4
DM
270 # So, we need to build up the list more carefully. git rev-parse
271 # will generate a list of revs that may be fed into git rev-list.
15a2f530
GP
272 # We can get it to make the "--not --all" part and then filter out
273 # the "^N" with:
4557e0de 274 #
22fa97d4 275 # git rev-parse --not --all | grep -v N
4557e0de 276 #
22fa97d4 277 # Then, using the --stdin switch to git rev-list we have effectively
4557e0de
AP
278 # manufactured
279 #
22fa97d4 280 # git rev-list N ^O ^X
4557e0de
AP
281 #
282 # This leaves a problem when someone else updates the repository
15a2f530
GP
283 # while this script is running. Their new value of the ref we're
284 # working on would be included in the "--not --all" output; and as
285 # our $newrev would be an ancestor of that commit, it would exclude
286 # all of our commits. What we really want is to exclude the current
287 # value of $refname from the --not list, rather than N itself. So:
4557e0de 288 #
22fa97d4 289 # git rev-parse --not --all | grep -v $(git rev-parse $refname)
4557e0de 290 #
15a2f530 291 # Get's us to something pretty safe (apart from the small time
22fa97d4 292 # between refname being read, and git rev-parse running - for that,
15a2f530 293 # I give up)
4557e0de
AP
294 #
295 #
296 # Next problem, consider this:
297 # * --- B --- * --- O ($oldrev)
298 # \
299 # * --- X --- * --- N ($newrev)
300 #
15a2f530
GP
301 # That is to say, there is no guarantee that oldrev is a strict
302 # subset of newrev (it would have required a --force, but that's
303 # allowed). So, we can't simply say rev-list $oldrev..$newrev.
304 # Instead we find the common base of the two revs and list from
305 # there.
4557e0de 306 #
15a2f530
GP
307 # As above, we need to take into account the presence of X; if
308 # another branch is already in the repository and points at some of
309 # the revisions that we are about to output - we don't want them.
22fa97d4 310 # The solution is as before: git rev-parse output filtered.
4557e0de 311 #
15a2f530 312 # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
4557e0de
AP
313 #
314 # Tags pushed into the repository generate nice shortlog emails that
315 # summarise the commits between them and the previous tag. However,
316 # those emails don't include the full commit messages that we output
317 # for a branch update. Therefore we still want to output revisions
318 # that have been output on a tag email.
319 #
22fa97d4 320 # Luckily, git rev-parse includes just the tool. Instead of using
15a2f530
GP
321 # "--all" we use "--branches"; this has the added benefit that
322 # "remotes/" will be ignored as well.
323
324 # List all of the revisions that were removed by this update, in a
a75d7b54
FC
325 # fast-forward update, this list will be empty, because rev-list O
326 # ^N is empty. For a non-fast-forward, O ^N is the list of removed
15a2f530 327 # revisions
8e404f82 328 fast_forward=""
4557e0de
AP
329 rev=""
330 for rev in $(git rev-list $newrev..$oldrev)
331 do
332 revtype=$(git cat-file -t "$rev")
333 echo " discards $rev ($revtype)"
334 done
335 if [ -z "$rev" ]; then
336 fast_forward=1
337 fi
338
339 # List all the revisions from baserev to newrev in a kind of
15a2f530
GP
340 # "table-of-contents"; note this list can include revisions that
341 # have already had notification emails and is present to show the
342 # full detail of the change from rolling back the old revision to
343 # the base revision and then forward to the new revision
4557e0de
AP
344 for rev in $(git rev-list $oldrev..$newrev)
345 do
346 revtype=$(git cat-file -t "$rev")
347 echo " via $rev ($revtype)"
348 done
349
a2d6b872 350 if [ "$fast_forward" ]; then
4557e0de
AP
351 echo " from $oldrev ($oldrev_type)"
352 else
15a2f530
GP
353 # 1. Existing revisions were removed. In this case newrev
354 # is a subset of oldrev - this is the reverse of a
355 # fast-forward, a rewind
356 # 2. New revisions were added on top of an old revision,
357 # this is a rewind and addition.
024e5b31 358
15a2f530
GP
359 # (1) certainly happened, (2) possibly. When (2) hasn't
360 # happened, we set a flag to indicate that no log printout
361 # is required.
024e5b31 362
4557e0de 363 echo ""
024e5b31 364
15a2f530
GP
365 # Find the common ancestor of the old and new revisions and
366 # compare it with newrev
024e5b31
AP
367 baserev=$(git merge-base $oldrev $newrev)
368 rewind_only=""
369 if [ "$baserev" = "$newrev" ]; then
370 echo "This update discarded existing revisions and left the branch pointing at"
371 echo "a previous point in the repository history."
372 echo ""
373 echo " * -- * -- N ($newrev)"
374 echo " \\"
375 echo " O -- O -- O ($oldrev)"
376 echo ""
377 echo "The removed revisions are not necessarilly gone - if another reference"
378 echo "still refers to them they will stay in the repository."
379 rewind_only=1
380 else
381 echo "This update added new revisions after undoing existing revisions. That is"
382 echo "to say, the old revision is not a strict subset of the new revision. This"
383 echo "situation occurs when you --force push a change and generate a repository"
384 echo "containing something like this:"
385 echo ""
386 echo " * -- * -- B -- O -- O -- O ($oldrev)"
387 echo " \\"
388 echo " N -- N -- N ($newrev)"
389 echo ""
390 echo "When this happens we assume that you've already had alert emails for all"
391 echo "of the O revisions, and so we here report only the revisions in the N"
392 echo "branch from the common base, B."
393 fi
4557e0de
AP
394 fi
395
396 echo ""
024e5b31
AP
397 if [ -z "$rewind_only" ]; then
398 echo "Those revisions listed above that are new to this repository have"
399 echo "not appeared on any other notification email; so we list those"
400 echo "revisions in full, below."
4557e0de 401
024e5b31
AP
402 echo ""
403 echo $LOGBEGIN
4471649f 404 show_new_revisions
4557e0de 405
15a2f530
GP
406 # XXX: Need a way of detecting whether git rev-list actually
407 # outputted anything, so that we can issue a "no new
408 # revisions added by this update" message
4557e0de 409
024e5b31
AP
410 echo $LOGEND
411 else
412 echo "No new revisions were added by this update."
413 fi
4557e0de 414
15a2f530
GP
415 # The diffstat is shown from the old revision to the new revision.
416 # This is to show the truth of what happened in this change.
417 # There's no point showing the stat from the base to the new
418 # revision because the base is effectively a random revision at this
419 # point - the user will be interested in what this revision changed
420 # - including the undoing of previous revisions in the case of
a75d7b54 421 # non-fast-forward updates.
4557e0de
AP
422 echo ""
423 echo "Summary of changes:"
424 git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
425}
426
427#
428# Called for the deletion of a branch
429#
430generate_delete_branch_email()
431{
432 echo " was $oldrev"
433 echo ""
434 echo $LOGEND
435 git show -s --pretty=oneline $oldrev
436 echo $LOGEND
437}
438
439# --------------- Annotated tags
440
441#
442# Called for the creation of an annotated tag
443#
444generate_create_atag_email()
445{
446 echo " at $newrev ($newrev_type)"
447
448 generate_atag_email
449}
450
451#
452# Called for the update of an annotated tag (this is probably a rare event
453# and may not even be allowed)
454#
455generate_update_atag_email()
456{
457 echo " to $newrev ($newrev_type)"
458 echo " from $oldrev (which is now obsolete)"
459
460 generate_atag_email
461}
462
463#
464# Called when an annotated tag is created or changed
465#
466generate_atag_email()
467{
22fa97d4 468 # Use git for-each-ref to pull out the individual fields from the
15a2f530 469 # tag
4557e0de
AP
470 eval $(git for-each-ref --shell --format='
471 tagobject=%(*objectname)
472 tagtype=%(*objecttype)
473 tagger=%(taggername)
474 tagged=%(taggerdate)' $refname
475 )
476
477 echo " tagging $tagobject ($tagtype)"
478 case "$tagtype" in
479 commit)
15a2f530 480
4557e0de 481 # If the tagged object is a commit, then we assume this is a
15a2f530
GP
482 # release, and so we calculate which tag this tag is
483 # replacing
4557e0de
AP
484 prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
485
486 if [ -n "$prevtag" ]; then
487 echo " replaces $prevtag"
488 fi
489 ;;
490 *)
491 echo " length $(git cat-file -s $tagobject) bytes"
492 ;;
493 esac
494 echo " tagged by $tagger"
495 echo " on $tagged"
496
497 echo ""
498 echo $LOGBEGIN
499
15a2f530
GP
500 # Show the content of the tag message; this might contain a change
501 # log or release notes so is worth displaying.
4557e0de
AP
502 git cat-file tag $newrev | sed -e '1,/^$/d'
503
504 echo ""
505 case "$tagtype" in
506 commit)
15a2f530
GP
507 # Only commit tags make sense to have rev-list operations
508 # performed on them
4557e0de
AP
509 if [ -n "$prevtag" ]; then
510 # Show changes since the previous release
511 git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
512 else
15a2f530
GP
513 # No previous tag, show all the changes since time
514 # began
4557e0de
AP
515 git rev-list --pretty=short $newrev | git shortlog
516 fi
517 ;;
518 *)
15a2f530
GP
519 # XXX: Is there anything useful we can do for non-commit
520 # objects?
4557e0de
AP
521 ;;
522 esac
523
524 echo $LOGEND
525}
526
527#
528# Called for the deletion of an annotated tag
529#
530generate_delete_atag_email()
531{
532 echo " was $oldrev"
533 echo ""
534 echo $LOGEND
535 git show -s --pretty=oneline $oldrev
536 echo $LOGEND
537}
538
539# --------------- General references
540
541#
542# Called when any other type of reference is created (most likely a
543# non-annotated tag)
544#
545generate_create_general_email()
546{
547 echo " at $newrev ($newrev_type)"
548
549 generate_general_email
550}
551
552#
553# Called when any other type of reference is updated (most likely a
554# non-annotated tag)
555#
556generate_update_general_email()
557{
558 echo " to $newrev ($newrev_type)"
559 echo " from $oldrev"
560
561 generate_general_email
562}
563
564#
565# Called for creation or update of any other type of reference
566#
567generate_general_email()
568{
15a2f530
GP
569 # Unannotated tags are more about marking a point than releasing a
570 # version; therefore we don't do the shortlog summary that we do for
571 # annotated tags above - we simply show that the point has been
572 # marked, and print the log message for the marked point for
573 # reference purposes
4557e0de 574 #
15a2f530
GP
575 # Note this section also catches any other reference type (although
576 # there aren't any) and deals with them in the same way.
4557e0de
AP
577
578 echo ""
579 if [ "$newrev_type" = "commit" ]; then
580 echo $LOGBEGIN
9225d7be 581 git show --no-color --root -s --pretty=medium $newrev
4557e0de
AP
582 echo $LOGEND
583 else
15a2f530
GP
584 # What can we do here? The tag marks an object that is not
585 # a commit, so there is no log for us to display. It's
22fa97d4 586 # probably not wise to output git cat-file as it could be a
15a2f530 587 # binary blob. We'll just say how big it is
4557e0de
AP
588 echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
589 fi
590}
591
592#
593# Called for the deletion of any other type of reference
594#
595generate_delete_general_email()
596{
597 echo " was $oldrev"
598 echo ""
599 echo $LOGEND
600 git show -s --pretty=oneline $oldrev
601 echo $LOGEND
602}
603
4471649f
PH
604
605# --------------- Miscellaneous utilities
606
607#
608# Show new revisions as the user would like to see them in the email.
609#
610show_new_revisions()
611{
612 # This shows all log entries that are not already covered by
613 # another ref - i.e. commits that are now accessible from this
614 # ref that were previously not accessible
615 # (see generate_update_branch_email for the explanation of this
616 # command)
617
618 # Revision range passed to rev-list differs for new vs. updated
619 # branches.
620 if [ "$change_type" = create ]
621 then
622 # Show all revisions exclusive to this (new) branch.
623 revspec=$newrev
624 else
625 # Branch update; show revisions not part of $oldrev.
626 revspec=$oldrev..$newrev
627 fi
628
e5f5050e
PN
629 other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
630 grep -F -v $refname)
631 git rev-parse --not $other_branches |
b0a7d111
PH
632 if [ -z "$custom_showrev" ]
633 then
634 git rev-list --pretty --stdin $revspec
635 else
636 git rev-list --stdin $revspec |
637 while read onerev
638 do
639 eval $(printf "$custom_showrev" $onerev)
640 done
641 fi
4471649f
PH
642}
643
644
d1637a07
JM
645send_mail()
646{
647 if [ -n "$envelopesender" ]; then
648 /usr/sbin/sendmail -t -f "$envelopesender"
649 else
650 /usr/sbin/sendmail -t
651 fi
652}
653
4557e0de
AP
654# ---------------------------- main()
655
656# --- Constants
4557e0de
AP
657LOGBEGIN="- Log -----------------------------------------------------------------"
658LOGEND="-----------------------------------------------------------------------"
659
660# --- Config
661# Set GIT_DIR either from the working directory, or from the environment
662# variable.
663GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
664if [ -z "$GIT_DIR" ]; then
665 echo >&2 "fatal: post-receive: GIT_DIR not set"
666 exit 1
667fi
668
c855195c 669projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
15a2f530
GP
670# Check if the description is unchanged from it's default, and shorten it to
671# a more manageable length if it is
4557e0de
AP
672if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
673then
674 projectdesc="UNNAMED PROJECT"
675fi
676
22fa97d4
DM
677recipients=$(git config hooks.mailinglist)
678announcerecipients=$(git config hooks.announcelist)
679envelopesender=$(git config hooks.envelopesender)
680emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
b0a7d111 681custom_showrev=$(git config hooks.showrev)
4557e0de
AP
682
683# --- Main loop
15a2f530
GP
684# Allow dual mode: run from the command line just like the update hook, or
685# if no arguments are given then run as a hook script
4557e0de
AP
686if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
687 # Output to the terminal in command line mode - if someone wanted to
15a2f530
GP
688 # resend an email; they could redirect the output to sendmail
689 # themselves
4557e0de
AP
690 PAGER= generate_email $2 $3 $1
691else
4557e0de
AP
692 while read oldrev newrev refname
693 do
d1637a07 694 generate_email $oldrev $newrev $refname | send_mail
4557e0de
AP
695 done
696fi