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.
11 # This is Girocco-customized version. No matter what is said below, it has
13 # * Calling with arguments is same as giving them on stdin.
14 # * Optional fourth parameter is project name, used at most places instead
16 # * Optional fifth parameter is email sender.
17 # * Optional sixth parameter is extra e-mail header to add.
18 # * If MAIL_SH_OTHER_BRANCHES is set it is taken as branch tips of already seen
19 # revisions when running show_new_revisions and mail.sh skips its own
20 # computation. If it starts with '@' the rest is the hash of a blob that
21 # contains the information (one tip per line).
22 # If MAIL_SH_OTHER_BRANCHES is not set, guess at what they are.
23 # MAIL_SH_OTHER_BRANCHES is ignored if updates are piped in via stdin.
25 # * Default subject prefix is site name.
26 # * Unsubscribe instructions in email footer.
27 # * Default showrev includes gitweb link and show -C.
28 # * Nicer subject line.
29 # * Limit mail size to 256kb.
32 # This hook is stored in the contrib/hooks directory. Your distribution
33 # will have put this somewhere standard. You should make this script
34 # executable then link to it in the repository you would like to use it in.
35 # For example, on debian the hook is stored in
36 # /usr/share/git-core/contrib/hooks/post-receive-email:
38 # chmod a+x post-receive-email
39 # cd /path/to/your/repository.git
40 # ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
42 # This hook script assumes it is enabled on the central repository of a
43 # project, with all users pushing only to it and not between each other. It
44 # will still work if you don't operate in that style, but it would become
45 # possible for the email to be from someone other than the person doing the
51 # This is the list that all pushes will go to; leave it blank to not send
52 # emails for every ref update.
54 # If true new revisions are shown in reverse order (i.e. oldest to newest)
56 # If true do not include the actual diff when showing new commits (the
57 # summary information will still be shown). Default is false.
59 # This is the list that all pushes of annotated tags will go to. Leave it
60 # blank to default to the mailinglist field. The announce emails lists
61 # the short log summary of the changes since the last annotated tag.
62 # hooks.envelopesender
63 # If set then the -f option is passed to sendmail to allow the envelope
64 # sender address to be set
66 # All emails have their subjects prefixed with this prefix, or "[SCM]"
67 # if emailprefix is unset, to aid filtering
69 # The shell command used to format each revision in the email, with
70 # "%s" replaced with the commit id. Defaults to "git rev-list -1
71 # --pretty %s", displaying the commit id, author, date and log
72 # message. To list full patches separated by a blank line, you
73 # could set this to "git show -C %s; echo".
74 # To list a gitweb/cgit URL *and* a full patch for each change set, use this:
75 # "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo"
76 # Be careful if "..." contains things that will be expanded by shell "eval"
81 # All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
82 # "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
83 # give information for debugging.
86 # ---------------------------- Functions
89 # Function to output arguments without interpretation followed by \n
97 # Function to run git diff-tree with select warnings redirected to stdout
101 [ -n "$differrtmp" ] ||
102 differrtmp
="$(mktemp -u "${TMPDIR:-/tmp}/difftree-$$
-XXXXXX")"
105 git diff-tree
"$@" 2>"$differrtmp" || _gdtec
=$?
106 ! [ -s "$differrtmp" ] ||
107 # omit anything that mentions a ".renameLimit" config item
108 <"$differrtmp" LC_ALL
=C
grep -v -i -F '.renameLimit' ||
:
114 # Function to read a tag's (the single argument) header fields into
115 # tagobject the "object" value
116 # tagtype the "type" value
117 # tagtag the "tag" value
118 # taggername from "tagger" value before first '<'
119 # taggerdate from "tagger" value after '>' but formatted into a date string
120 # taggeremail from "tagger" value between '<' and '>'
121 # On return all fields will be set (to empty if failure) and the three tagger
122 # fields will only be set to non-empty if a "tagger" header field is present.
123 # If the object does not exist or is not a tag the function returns failure.
132 if _tagfields
="$(git cat-file tag "$1" | LC_ALL=C awk -F '[ ]' '
133 function quotesh(sv) {
134 gsub(/'\''/, "'\'\\\\\'\''", sv)
135 return "'\''" sv "'\''"
138 sub(/^[ \t]+/, "", sv)
139 sub(/[ \t]+$/, "", sv)
143 sub(/^[^ ]* /, "", sv)
147 if ($1 == "object
") to=hval($0)
148 if ($1 == "type") ty=hval($0)
149 if ($1 == "tag
") tt=hval($0)
150 if ($1 == "tagger
") tr=hval($0)
153 if (tt == "" || to == "" || ty == "") exit 1
154 print "tagobject
=" quotesh(to)
155 print "tagtype
=" quotesh(ty)
156 print "tagtag
=" quotesh(tt)
157 if (match(tr, /^[ \t]*[^ \t<][^<]*/)) {
158 tn=substr(tr, RSTART, RLENGTH)
159 tr=substr(tr, RSTART + RLENGTH)
162 print "taggername
=" quotesh(tn)
163 if (match(tr, /^<.*>/)) {
165 te=substr(tr, RSTART+1, RLENGTH-2)
166 tr=substr(tr, RSTART + RLENGTH)
169 print "taggeremail
=" quotesh(te)
170 if (match(tr, /^[ \t]*[0-9]+([ \t]+[-+]?[0-9][0-9]([0-9][0-9]([0-9][0-9])?)?)[ \t]*$/))
173 print "taggerdate
=" quotesh(td)
179 [ -z "$taggerdate" ] || taggerdate
="$(strftime "$sdatefmt" $taggerdate)"
186 # Function to prepare for email generation. This decides what type
187 # of update this is and whether an email should even be generated.
192 oldrev
="$(git rev-parse --revs-only "$1" --)" ||
:
193 newrev
="$(git rev-parse --revs-only "$2" --)" ||
:
194 [ -n "$oldrev" ] && [ -n "$newrev" ] && [ "$oldrev" != "$newrev" ] ||
return 1
195 scratch
="${newrev#????????????????}"
196 newrev16
="${newrev%$scratch}"
201 # 0000->1234 (create)
202 # 1234->2345 (update)
203 # 2345->0000 (delete)
204 if [ -n "$oldrev" ] && [ "${oldrev#*[!0]}" = "$oldrev" ]
208 if [ -n "$newrev" ] && [ "${newrev#*[!0]}" = "$newrev" ]
216 # --- Get the revision types
217 newrev_type
="$(git cat-file -t "$newrev" 2>/dev/null)" ||
:
218 oldrev_type
="$(git cat-file -t "$oldrev" 2>/dev/null)" ||
:
219 case "$change_type" in
222 rev_type
="$newrev_type"
226 rev_type
="$oldrev_type"
230 # The revision type tells us what type the commit is, combined with
231 # the location of the ref we can decide between
236 case "$refname:$rev_type" in
240 short_refname
="${refname#refs/tags/}"
244 refname_type
="annotated tag"
245 short_refname
="${refname#refs/tags/}"
247 if [ -n "$announcerecipients" ]; then
248 recipients
="$announcerecipients"
251 refs
/heads
/*:commit|refs
/heads
/*:tag
)
253 refname_type
="branch"
254 short_refname
="${refname#refs/heads/}"
256 refs
/remotes
/*:commit|refs
/remotes
/*:tag
)
258 refname_type
="tracking branch"
259 short_refname
="${refname#refs/remotes/}"
260 echol
>&2 "*** Push-update of tracking branch, $refname"
261 echol
>&2 "*** - no email generated."
264 refs
/mob
/*:commit|refs
/mob
/*:tag
)
266 refname_type
="personal mob ref"
267 short_refname
="${refname#refs/mob/}"
268 echol
>&2 "*** Push-update of personal mob ref, $refname"
269 echol
>&2 "*** - no email generated."
273 # Anything else (is there anything else?)
274 echol
>&2 "*** Unknown type of update to $refname ($rev_type)"
275 echol
>&2 "*** - no email generated"
280 # Check if we've got anyone to send to
281 if [ -z "$recipients" ]; then
282 case "$refname_type" in
284 config_name
="neither hooks.announcelist nor hooks.mailinglist is"
287 config_name
="hooks.mailinglist is not"
290 echol
>&2 "*** $config_name set so no email will be sent"
291 echol
>&2 "*** for $refname update $oldrev->$newrev"
299 # Top level email generation function. This calls the appropriate
300 # body-generation routine after outputting the common header.
302 # Note this function doesn't actually generate any email output, that is
303 # taken care of by the functions it calls:
304 # - generate_email_header
305 # - generate_create_XXXX_email
306 # - generate_update_XXXX_email
307 # - generate_delete_XXXX_email
308 # - generate_email_footer
310 # Note also that this function cannot 'exit' from the script; when this
311 # function is running (in hook script mode), the send_mail() function
312 # is already executing in another process, connected via a pipe, and
313 # if this function exits without, whatever has been generated to that
314 # point will be sent as an email... even if nothing has been generated.
319 # The email subject will contain the best description of the ref
320 # that we can build from the parameters
321 describe
="$(git describe $rev 2>/dev/null)" ||
:
322 if [ -z "$describe" ]; then
326 generate_email_header
328 # Call the correct body generation function
330 case "$refname_type" in
331 "tracking branch"|branch
)
338 generate_
${change_type}_
${fn_name}_email
340 generate_email_footer
343 generate_email_header
()
345 # --- Email (all stdout will be the email)
347 if [ -n "$emailsender" ]; then
348 echol
"From: $emailsender"
352 Subject: ${emailprefix}$projectname $refname_type $short_refname ${change_type}d: $describe
354 Content-Type: text/plain; charset=utf-8; format=fixed
355 Content-Transfer-Encoding: 8bit
357 [ -z "$refname" ] || echol
"References: <$refname@$projurl>"
358 [ -n "$cfg_suppress_x_girocco" ] || echol
"X-Girocco: $cfg_gitweburl"
359 [ -z "$emailextraheader" ] || echol
"$emailextraheader"
361 X-Git-Refname: $refname
362 X-Git-Reftype: $refname_type
363 X-Git-Oldrev: $oldrev
364 X-Git-Newrev: $newrev
365 Auto-Submitted: auto-generated
367 This is an automated email generated because a ref change occurred in the
368 git repository for project $projectname.
370 The $refname_type, $short_refname has been ${change_type}d
374 generate_email_footer
()
380 ${cfg_name:-mail.sh} automatic notification. Contact project admin $projectowner
381 if you want to unsubscribe, or site admin ${cfg_admin:+$cfg_admin }if you receive
388 # --------------- Branches
391 # Called for the creation of a branch
393 generate_create_branch_email
()
395 # This is a new branch and so oldrev is not valid
396 echol
" at $newrev ($newrev_type)"
399 show_new_revisions_eval
='
400 echol "Those revisions in this branch that are new to this repository"
401 echol "have not appeared on any other notification email; so we list"
402 echol "those revisions in full, below."
408 if [ -n "$LAST_SHOWN_REVISION" ]; then
411 echol
"No new revisions were added by this branch."
414 # If any revisions were shown by show_new_revisions then we show
415 # a diffstat from the last shown revisions's parent (or the empty tree
416 # if the last revision shown is a root revision) to the new revision
417 # provided the last shown revision is not a merge commit and there
418 # were no more boundary commits encountered by show_new_revisions than
419 # the last shown revision has parents.
420 # This is to show the truth of what happened in this change.
422 if [ -n "$LAST_SHOWN_REVISION" ] && [ -n "$LAST_SHOWN_NPARENTS" ] && [ -n "$LAST_SHOWN_NBOUNDARY" ] &&
423 [ "$LAST_SHOWN_NPARENTS" -le 1 ] && [ "$LAST_SHOWN_NBOUNDARY" -le "$LAST_SHOWN_NPARENTS" ]; then
424 if [ "$LAST_SHOWN_NPARENTS" -eq 0 ]; then
425 # Note that since Git 1.5.5 the empty tree object is ALWAYS available
426 # whether or not it's actually present in the repository.
428 # Set oldrev to the result of $(git hash-object -t tree --stdin </dev/null)
429 oldrev
="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
431 # Set oldrev to the parent of the last shown revision
432 oldrev
="$LAST_SHOWN_REVISION^"
435 echol
"Summary of changes:"
436 git_diff_tree
--no-color --stat=72 --summary -B --find-copies-harder "$oldrev" "$newrev" --
441 # Called for the change of a pre-existing branch
443 generate_update_branch_email
()
446 # 1 --- 2 --- O --- X --- 3 --- 4 --- N
448 # O is $oldrev for $refname
449 # N is $newrev for $refname
450 # X is a revision pointed to by some other ref, for which we may
451 # assume that an email has already been generated.
452 # In this case we want to issue an email containing only revisions
453 # 3, 4, and N. Given (almost) by
455 # git rev-list N ^O --not --all
457 # The reason for the "almost", is that the "--not --all" will take
458 # precedence over the "N", and effectively will translate to
460 # git rev-list N ^O ^X ^N
462 # So, we need to build up the list more carefully. git rev-parse
463 # will generate a list of revs that may be fed into git rev-list.
464 # We can get it to make the "--not --all" part and then filter out
467 # git rev-parse --not --all | grep -v N
469 # Then, using the --stdin switch to git rev-list we have effectively
472 # git rev-list N ^O ^X
474 # This leaves a problem when someone else updates the repository
475 # while this script is running. Their new value of the ref we're
476 # working on would be included in the "--not --all" output; and as
477 # our $newrev would be an ancestor of that commit, it would exclude
478 # all of our commits. What we really want is to exclude the current
479 # value of $refname from the --not list, rather than N itself. So:
481 # git rev-parse --not --all | grep -v $(git rev-parse $refname)
483 # Get's us to something pretty safe (apart from the small time
484 # between refname being read, and git rev-parse running - for that,
488 # Next problem, consider this:
489 # * --- B --- * --- O ($oldrev)
491 # * --- X --- * --- N ($newrev)
493 # That is to say, there is no guarantee that oldrev is a strict
494 # subset of newrev (it would have required a --force, but that's
495 # allowed). So, we can't simply say rev-list $oldrev..$newrev.
496 # Instead we find the common base of the two revs and list from
499 # As above, we need to take into account the presence of X; if
500 # another branch is already in the repository and points at some of
501 # the revisions that we are about to output - we don't want them.
502 # The solution is as before: git rev-parse output filtered.
504 # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
506 # Tags pushed into the repository generate nice shortlog emails that
507 # summarise the commits between them and the previous tag. However,
508 # those emails don't include the full commit messages that we output
509 # for a branch update. Therefore we still want to output revisions
510 # that have been output on a tag email.
512 # Luckily, git rev-parse includes just the tool. Instead of using
513 # "--all" we use "--branches"; this has the added benefit that
514 # "remotes/" will be ignored as well.
516 # List all of the revisions that were removed by this update, in a
517 # fast-forward update, this list will be empty, because rev-list O
518 # ^N is empty. For a non-fast-forward, O ^N is the list of removed
522 for rev in $
(git rev-list
"$newrev..$oldrev" --)
524 revtype
="$(git cat-file -t "$rev")" ||
:
525 echol
" discards $rev ($revtype)"
527 if [ -z "$rev" ]; then
531 # List all the revisions from baserev to newrev in a kind of
532 # "table-of-contents"; note this list can include revisions that
533 # have already had notification emails and is present to show the
534 # full detail of the change from rolling back the old revision to
535 # the base revision and then forward to the new revision
536 for rev in $
(git rev-list
"$oldrev..$newrev" --)
538 revtype
="$(git cat-file -t "$rev")" ||
:
539 echol
" via $rev ($revtype)"
542 if [ "$fast_forward" ]; then
543 echol
" from $oldrev ($oldrev_type)"
545 # 1. Existing revisions were removed. In this case newrev
546 # is a subset of oldrev - this is the reverse of a
547 # fast-forward, a rewind
548 # 2. New revisions were added on top of an old revision,
549 # this is a rewind and addition.
551 # (1) certainly happened, (2) possibly. When (2) hasn't
552 # happened, we set a flag to indicate that no log printout
557 # Find the common ancestor of the old and new revisions and
558 # compare it with newrev
559 baserev
=$
(git merge-base
$oldrev $newrev)
561 if [ "$baserev" = "$newrev" ]; then
562 echol
"This update discarded existing revisions and left the branch pointing at"
563 echol
"a previous point in the repository history."
565 echol
" * -- * -- N ($newrev)"
567 echol
" O -- O -- O ($oldrev)"
569 echol
"The removed revisions are not necessarily gone - if another reference"
570 echol
"still refers to them they will stay in the repository."
573 echol
"This update added new revisions after undoing existing revisions. That is"
574 echol
"to say, the old revision is not a strict subset of the new revision. This"
575 echol
"situation occurs when you --force push a change and generate a repository"
576 echol
"containing something like this:"
578 echol
" * -- * -- B -- O -- O -- O ($oldrev)"
580 echol
" N -- N -- N ($newrev)"
582 echol
"When this happens we assume that you've already had alert emails for all"
583 echol
"of the O revisions, and so we here report only the revisions in the N"
584 echol
"branch from the common base, B."
590 if [ -z "$rewind_only" ]; then
591 show_new_revisions_eval
='
592 echol "Those revisions listed above that are new to this repository have"
593 echol "not appeared on any other notification email; so we list those"
594 echol "revisions in full, below."
601 if [ -n "$LAST_SHOWN_REVISION" ]; then
604 echol
"No new revisions were added by this update."
607 # The diffstat is shown from the old revision to the new revision.
608 # This is to show the truth of what happened in this change.
609 # There's no point showing the stat from the base to the new
610 # revision because the base is effectively a random revision at this
611 # point - the user will be interested in what this revision changed
612 # - including the undoing of previous revisions in the case of
613 # non-fast-forward updates.
615 echol
"Summary of changes:"
616 git_diff_tree
--no-color --stat=72 --summary -B --find-copies-harder "$oldrev..$newrev" --
620 # Called for the deletion of a branch
622 generate_delete_branch_email
()
627 git_diff_tree
--no-color --date="$datefmt" -s --abbrev-commit --abbrev=$habbr --always --encoding=UTF-8
--pretty=oneline
"$oldrev" --
631 # --------------- Annotated tags
634 # Called for the creation of an annotated tag
636 generate_create_atag_email
()
638 echol
" at $newrev ($newrev_type)"
644 # Called for the update of an annotated tag (this is probably a rare event
645 # and may not even be allowed)
647 generate_update_atag_email
()
649 echol
" to $newrev ($newrev_type)"
650 echol
" from $oldrev (which is now obsolete)"
656 # Called when an annotated tag is created or changed
658 generate_atag_email
()
660 # Use read_tag_fields to pull out the individual fields from the
661 # tag and git cat-file --batch-check to fully peel the tag if needed
664 read_tag_fields
"$tagrev"
665 if [ "$tagtype" = "tag" ]; then
671 read -r _po _pt junk
<<-EOT || :
672 $(git cat-file --batch-check"${var_have_git_185:+=%(objectname) %(objecttype)}" <<-ETX || :
677 if [ -n "$_po" ] && [ -n "$_pt" ] && [ "$_pt" != "missing" ] && [ "$_po" != "missing" ]; then
684 peeledobject
="$tagobject"
685 peeledtype
="$tagtype"
688 echol
" tagging $peeledobject ($peeledtype)"
689 case "$peeledtype" in
692 # If the tagged object is a commit, then we assume this is a
693 # release, and so we calculate which tag this tag is
695 prevtag
="$(git describe --abbrev=0 "$peeledobject^
" 2>/dev/null)" ||
:
697 if [ -n "$prevtag" ]; then
698 echol
" replaces $prevtag"
702 echol
" length $(git cat-file -s "$peeledobject" 2>/dev/null) bytes"
705 echol
" tagged by $taggername"
706 echol
" on $taggerdate"
714 echol
"Object: $tagobject ($tagtype)"
715 [ -z "$taggername" ] ||
716 echol
"Tagger: $taggername"
717 [ -z "$taggerdate" ] ||
718 echol
"Date: $taggerdate"
719 echol
"URL: <$projurl/$tagrev16>"
722 # Show the content of the tag message; this might contain a change
723 # log or release notes so is worth displaying.
724 git cat-file tag
"$tagrev" | LC_ALL
=C
sed -e '1,/^$/d;s/^/ /;'
726 [ "$tagtype" = "tag" ]
729 scratch
="${tagrev#????????????????}"
730 tagrev16
="${tagrev%$scratch}"
731 read_tag_fields
"$tagrev" ||
break
734 case "$peeledtype" in
736 # Only commit tags make sense to have rev-list operations
738 if [ -n "$prevtag" ]; then
739 # Show changes since the previous release
740 git shortlog
"$prevtag..$newrev" --
742 # No previous tag, show all the changes since time
744 git shortlog
"$newrev" --
748 # XXX: Is there anything useful we can do for non-commit
757 # Called for the deletion of an annotated tag
759 generate_delete_atag_email
()
764 git_diff_tree
--no-color --date="$datefmt" -s --abbrev-commit --abbrev=$habbr --always --encoding=UTF-8
--pretty=oneline
"$oldrev" --
768 # --------------- General references
771 # Called when any other type of reference is created (most likely a
774 generate_create_general_email
()
776 echol
" at $newrev ($newrev_type)"
778 generate_general_email
782 # Called when any other type of reference is updated (most likely a
785 generate_update_general_email
()
787 echol
" to $newrev ($newrev_type)"
788 echol
" from $oldrev"
790 generate_general_email
794 # Called for creation or update of any other type of reference
796 generate_general_email
()
798 # Unannotated tags are more about marking a point than releasing a
799 # version; therefore we don't do the shortlog summary that we do for
800 # annotated tags above - we simply show that the point has been
801 # marked, and print the log message for the marked point for
804 # Note this section also catches any other reference type (although
805 # there aren't any) and deals with them in the same way.
808 if [ "$newrev_type" = "commit" ]; then
809 if [ -n "$(git rev-list --no-walk --merges "$newrev" --)" ]; then
810 pfmt12
="$pfmt1$pfmt1m$pfmt2"
812 pfmt12
="$pfmt1$pfmt2"
815 git_diff_tree
--no-color --date="$datefmt" --root -s --always --encoding=UTF-8
--format="$pfmt12$projurlesc/$newrev16$pfmt3" --abbrev=$habbr "$newrev" --
818 # What can we do here? The tag marks an object that is not
819 # a commit, so there is no log for us to display. It's
820 # probably not wise to output git cat-file as it could be a
821 # binary blob. We'll just say how big it is
822 echol
"$newrev is a $newrev_type, and is $(git cat-file -s "$newrev") bytes long."
827 # Called for the deletion of any other type of reference
829 generate_delete_general_email
()
834 git_diff_tree
--no-color --date="$datefmt" -s --abbrev-commit --abbrev=$habbr --always --encoding=UTF-8
--pretty=oneline
"$oldrev" --
839 # --------------- Miscellaneous utilities
842 # Show new revisions as the user would like to see them in the email.
844 # On return LAST_SHOWN_REVISION will be set to non-empty if any revisions shown
845 # and LAST_SHOWN_NPARENTS will be the number of parents it has (possibly 0)
846 # and LAST_SHOWN_NBOUNDARY will be the total number of boundary commits seen (possibly 0)
847 # So if $LAST_SHOWN_NBOUNDARY is greater than $LAST_SHOWN_NPARENTS then the
848 # portion of the graph shown by show_new_revisions (excluding $LAST_SHOWN_REVISION)
849 # includes at least one merge commit that had at least one parent excluded.
850 # If show_new_revisions_eval is non-empty, than just before the first output
851 # gets shown it will be eval'd and then set to "".
855 # This shows all log entries that are not already covered by
856 # another ref - i.e. commits that are now accessible from this
857 # ref that were previously not accessible
858 # (see generate_update_branch_email for the explanation of this
862 LAST_SHOWN_NBOUNDARY
=0
864 # Revision range passed to rev-list differs for new vs. updated
866 if [ "$change_type" = create
]
868 # Show all revisions exclusive to this (new) branch.
871 # Branch update; show revisions not part of $oldrev.
872 revspec
="$oldrev..$newrev"
875 if [ "${MAIL_SH_OTHER_BRANCHES+set}" = "set" ]; then
876 case "$MAIL_SH_OTHER_BRANCHES" in
878 othertips
="git cat-file blob '${MAIL_SH_OTHER_BRANCHES#@}' 2>/dev/null"
881 othertips
='printf "%s\n" $MAIL_SH_OTHER_BRANCHES'
885 othertips
='git for-each-ref --format="%(refname)" refs/heads |
886 LC_ALL=C awk -v "refname=$refname" "\$1 != refname"'
888 # if [ -z "$custom_showrev" ]
890 # git rev-list --pretty --stdin "$revspec" --
892 while read onerev mark pcnt
&& [ -n "$onerev" ] && [ -n "$mark" ] && [ -n "$pcnt" ]
894 if [ "$mark" = "-" ]; then
895 LAST_SHOWN_NBOUNDARY
=$
(( $LAST_SHOWN_NBOUNDARY + 1 ))
898 if [ -n "$show_new_revisions_eval" ]; then
899 eval "$show_new_revisions_eval"
900 show_new_revisions_eval
=
902 if [ -z "$reverseopt" ] ||
[ -z "$LAST_SHOWN_REVISION" ]; then
903 LAST_SHOWN_REVISION
="$onerev"
904 LAST_SHOWN_NPARENTS
="$pcnt"
906 if [ -n "$custom_showrev" ]; then
907 eval $
(printf "$custom_showrev" $onerev)
909 if [ "${summaryonly:-false}" = "false" ]; then
910 if [ ${pcnt:-1} -gt 1 ]; then
912 pfmt12
="$pfmt1$pfmt1m$pfmt2"
914 opts
="-p --stat=72 --summary"
915 pfmt12
="$pfmt1$pfmt2"
918 if [ ${pcnt:-1} -gt 1 ]; then
920 pfmt12
="$pfmt1$pfmt1m$pfmt2"
922 opts
="--stat=72 --summary"
923 pfmt12
="$pfmt1$pfmt2"
926 scratch
="${onerev#????????????????}"
927 onerev16
="${onerev%$scratch}"
928 git_diff_tree
--no-color --date="$datefmt" $opts --always --encoding=UTF-8
--format="$pfmt12$projurlesc/$onerev16$pfmt3" --abbrev=$habbr -B --find-copies-harder --root "$onerev" --
932 $(eval "$othertips" |
933 git cat-file --batch-check"${var_have_git_185:+=%(objectname)}" |
934 LC_ALL=C sed -e '/ missing$/d' -e 's/^\([^ ][^ ]*\).*$/\1/' -e 's/^/^/' |
935 git --no-pager log --stdin --no-color $reverseopt --boundary --format=tformat:"%H %m %p" "$revspec" -- |
936 LC_ALL=C awk '{print $1 " " $2 " " NF-2}')
945 while IFS
= read -r line
; do
946 # Include size of RFC 822 trailing CR+LF on each line
947 size
=$
(($size+${#line}+2))
948 if [ $size -gt $1 ]; then
949 echol
"...e-mail trimmed, exceeded size limit."
965 if [ -n "$cfg_sender" ]; then
966 "${cfg_sendmail_bin:-/usr/sbin/sendmail}" -i -t -f "$cfg_sender"
968 "${cfg_sendmail_bin:-/usr/sbin/sendmail}" -i -t
972 # ---------------------------- main()
975 LOGBEGIN
="- Log -----------------------------------------------------------------"
976 LOGEND
="-----------------------------------------------------------------------"
979 ! [ -f @basedir@
/shlib.sh
] ||
! [ -r @basedir@
/shlib.sh
] || . @basedir@
/shlib.sh
981 # Git formatting to use
985 # strftime formatting to use (empty is default rfc2822 format)
988 # This is --pretty=medium in four parts with a URL: line added
989 # The pfmt1m value must be inserted after the pfmt1 value but only for merges
990 # The URL must be inserted between pfmt2 and pfmt3
991 pfmt1
='format:commit %H%n'
993 pfmt2
='Author: %an <%ae>%nDate: %ad%nURL: <'
994 pfmt3
='>%n%n%w(0,4,4)%s%n%n%b'
996 GIT_CONFIG_PARAMETERS
="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }'core.abbrev=$habbr'"
997 export GIT_CONFIG_PARAMETERS
999 # Set GIT_DIR either from the working directory, or from the environment
1001 GIT_DIR
="$(git rev-parse --git-dir 2>/dev/null)" ||
:
1002 if [ -z "$GIT_DIR" ]; then
1003 echol
>&2 "fatal: mail.sh: GIT_DIR not set"
1006 # Get GIT_COMMON_DIR for Git >= 2.5.0
1007 GCD_DIR
="$(cd "$GIT_DIR" && unset GIT_DIR && git rev-parse --git-common-dir 2>/dev/null)" ||
:
1008 if [ -z "$GCD_DIR" ] ||
[ "$GCD_DIR" = "--git-common-dir" ]; then
1012 # Might be relative to GIT_DIR so cd there first
1013 GCD_DIR
="$(cd "$GIT_DIR" && cd "$GCD_DIR" && pwd)"
1017 ! [ -s "$GCD_DIR/description" ] || projectdesc
="$(LC_ALL=C sed -ne '1p' "$GCD_DIR/description
")"
1018 # Check if the description is unchanged from it's default, and shorten it to
1019 # a more manageable length if it is
1020 case "$projectdesc" in ""|
"Unnamed repository"*)
1021 projectdesc
="UNNAMED PROJECT"
1024 # If --stdout is first argument send all output there instead of mailing
1025 if [ "$1" = "--stdout" ]; then
1027 cfg_sendmail_bin
="sendmail_to_stdout"
1030 projectname
="${4%.git}"
1031 if [ -n "$projectname" ]; then
1032 projectname
="$projectname.git"
1033 projectowner
="$(git config gitweb.owner 2>/dev/null)" ||
:
1035 projectname
="$(cd "$GIT_DIR" && pwd)"
1036 projectname
="${projectname%/.git}"
1037 projectname
="$(basename "$projectname")"
1039 projectboth
="$projectname (\"$projectdesc\")"
1040 projurl
="$cfg_gitweburl/$projectname"
1041 projurlesc
="$(printf '%s\n' "$projurl" | sed -e 's/%/%%/g')"
1044 emailextraheader
="$6"
1046 recipients
="$(git config hooks.mailinglist 2>/dev/null)" ||
:
1047 summaryonly
="$(git config --bool hooks.summaryonly 2>/dev/null)" ||
:
1049 [ "$(git config --bool hooks.reverseorder 2>/dev/null || :)" != "true" ] || reverseopt
=--reverse
1050 announcerecipients
="$(git config hooks.announcelist)" ||
:
1051 envelopesender
="$(git config hooks.envelopesender)" ||
:
1052 emailprefix
="$(git config hooks.emailprefix || echol "[${cfg_name:-mail.sh}] ")" ||
:
1053 custom_showrev
="$(git config hooks.showrev)" ||
:
1055 differrtmp
="$(mktemp -u "${TMPDIR:-/tmp}/difftree-$$
-XXXXXX")"
1058 # Allow dual mode: run from the command line just like the update hook, or
1059 # if no arguments are given then run as a hook script
1060 # If --stdout is first argument send all output there instead (handled above)
1061 # Optional 4th (projectname), 5th (sender) and 6th (extra header) arguments
1062 max_email_size
="$(( ${cfg_mailsh_sizelimit:-256} * 1024 ))"
1063 [ "${max_email_size:-0}" -ge "10240" ] || max_email_size
="10240" # 10K minimum
1064 if [ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ]; then
1065 # Handle a single update rather than a batch on stdin
1066 # Output will still be sent to sendmail unless --stdout is used
1067 # Same 3 args as update hook (<refname> <old> <new>)
1068 if prep_for_email
$2 $3 $1; then
1069 PAGER
= generate_email | size_limit
"$max_email_size" | send_mail
1072 # MAIL_SH_OTHER_BRANCHES cannot possibly be valid for multiple updates
1073 unset MAIL_SH_OTHER_BRANCHES
1074 # Same input as pre-receive hook (each line is <old> <new> <refname>)
1075 while read oldrev newrev refname
1077 prep_for_email
$oldrev $newrev $refname ||
continue
1078 PAGER
= generate_email | size_limit
"$max_email_size" | send_mail