-#!/bin/sh
+#!/bin/mksh
#
+# Copyright © 2016, 2020
+# mirabilos <m@mirbsd.org>
# Copyright © 2013
# Thorsten “mirabilos” Glaser <thorsten.glaser@teckids.org>
-# Copyright © 2011, 2012, 2013, 2014
+# Copyright © 2011, 2012, 2013, 2014, 2015, 2018, 2019
# Thorsten “mirabilos” Glaser <t.glaser@tarent.de>
+# Copyright © 2016
+# martin f krafft (madduck)
#
# Copyright (c) 2007 Andy Parkins
#
#
# An example hook script to mail out commit update information.
#
-# NOTE: This script is no longer under active development. There
-# is another script, git-multimail, which is more capable and
-# configurable and is largely backwards-compatible with this script;
-# please see "contrib/hooks/multimail/". For instructions on how to
-# migrate from post-receive-email to git-multimail, please see
-# "README.migrate-from-post-receive-email" in that directory.
+# This script is not under active development by the git team, with
+# git-multimail being suggested as replacement. It has downsides,
+# of course; this script is maintained as a fork, by mirabilos, for
+# 100% compatibility with the old version, plus bugfixes (encoding)
+# and new features (more and better diffs shown; cURL triggers and
+# “KGB” bot support, etc.) and optional FusionForge integration.
#
# This hook sends emails listing new revisions to the repository
# introduced by the change being reported. The rule is that (for
# chmod a+x post-receive-email
# cd /path/to/your/repository.git
# ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
+# # or
+# ln -sf /etc/gforge/plugins/scmgit/post-receive-email.sh hooks/post-receive
+# # but that can only be used for a Forge-controlled repository
#
-# This hook script needs the “en_US.UTF-8” locale set up on the server.
-# The FusionForge version works with a POSIX shell.
+# This hook script needs the “C.UTF-8” locale set up (or change below).
+# Other requirementa are: The MirBSD Korn Shell (mksh) as /bin/mksh;
+# PHP with the mbstring extension (preferred) or Perl 5; curl, if you
+# wish to use the curltrigger functionality.
#
# This hook script assumes it is enabled on the central repository of a
# project, with all users pushing only to it and not between each other. It
# ------
# hooks.mailinglist
# This is the list that all pushes will go to; leave it blank to not send
-# emails for every ref update.
+# emails for every ref update. This fills the To header, so use comma for
+# multiple recipients (but please only use addresses, not full names).
# hooks.announcelist
-# This is the list that all pushes of annotated tags will go to. Leave it
-# blank to default to the mailinglist field. The announce emails lists
+# This is the list(s) that all pushes of annotated tags will go to. Leave
+# it blank to default to the mailinglist field. The announce emails list
# the short log summary of the changes since the last annotated tag.
# hooks.replyto
-# If set, replies are requested to be directed to this eMail address.
+# If set, replies are requested to be directed to this eMail address(es).
# hooks.envelopesender
# If set then the -f option is passed to sendmail to allow the envelope
-# sender address to be set. This is also used as Header From.
+# sender to be set (one address only). This is also used as From header.
# hooks.emailprefix
# If set, e.g. to '[SCM] ', all eMails have their subjects prefixed
# with this string to aid filtering.
# hooks.diffopts
# Alternate options for the git diff-tree invocation that shows changes.
# Default is "-p --stat --summary --find-copies-harder". Remove -p from
-# those options to skil including a unified diff of changes in addition
+# those options to skip including a unified diff of changes in addition
# to the summary output. See hooks.showrev for more discussion on this.
# hooks.kgbconffile
# Full path to a configuration file for kgb-client, or unset/empty.
# If set, kgb-client will be invoked.
+# hooks.curltrigger
+# List (expanded by shell “for”, so be careful) of URIs to trigger on
+# each push to be accessed with cURL.
#
# Notes
# -----
nl='
'
unset LANGUAGE LC_ALL
-LANG=C LC_ADDRESS=C LC_COLLATE=C LC_CTYPE=en_US.UTF-8 \
+export LANG=C LC_ADDRESS=C LC_COLLATE=C LC_CTYPE=C.UTF-8 \
LC_IDENTIFICATION=C LC_MEASUREMENT=C LC_MESSAGES=C \
LC_MONETARY=C LC_NAME=C LC_NUMERIC=C LC_PAPER=C \
LC_TELEPHONE=C LC_TIME=C
-export LANG LC_ADDRESS LC_COLLATE LC_CTYPE \
- LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES \
- LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
- LC_TELEPHONE LC_TIME
# ---------------------------- Functions
# Email parameters
# The email subject will contain the best description of the ref
# that we can build from the parameters
- describe=$(git show --no-patch --format="%h %s" $rev 2>/dev/null)
+ describe=$(git describe $rev 2>/dev/null)
if [ -z "$describe" ]; then
describe=$rev
fi
generate_email_header()
{
+ local routing_headers subj senc failc=0
+ local repolabel
+
# --- Email (all stdout will be the email)
# Generate header
routing_headers="To: $recipients"
- test -n "$envelopesender" && routing_headers="From: $envelopesender${nl}$routing_headers"
- test -n "$replyto" && routing_headers="$routing_headers${nl}Reply-To: $replyto"
+ [[ -n $envelopesender ]] && routing_headers="From: $envelopesender${nl}$routing_headers"
+ [[ -n $replyto ]] && routing_headers="$routing_headers${nl}Reply-To: $replyto"
+
+ subj="Subject: ${emailprefix}$shortdesc $refname_type $short_refname ${change_type}d. $describe"
+
+ # Try to encode. Strongly prefer PHP over Perl and Python, sadly;
+ # evidence provided in Debian bugs #787511, #787512, #787513,
+ # #819155, #879204, #879205, #881955, #907891 though.
+ senc=$(print -nr -- "$subj" | php -r '
+ mb_internal_encoding("UTF-8");
+ echo mb_encode_mimeheader(file_get_contents("php://stdin"),
+ "UTF-8", "Q", "\012");
+ ' 2>/dev/null) || { (( failc |= 1 )); senc=; }
+ [[ $senc = Subject:?("$nl")' '* ]] || { (( failc |= 2 )); senc=; }
+ [[ -n $senc ]] || senc=$(print -nr -- "$subj" | perl \
+ -C7 -0777 -Mutf8 -MEncode -e "
+ print encode('MIME-Q', <>);
+ " 2>/dev/null) || { (( failc |= 4 )); senc=; }
+ [[ $senc = Subject:?("$nl")' '* ]] || { (( failc |= 8 )); senc=; }
+
+ if [[ -z $senc ]]; then
+ print -u2 "E: Subject empty, RFC2047 MIME encoding failed"
+ if ! whence -p php >/dev/null 2>&1; then
+ : show only the Perl-related error messages below
+ elif (( failc & 1 )); then
+ print -u2 "N: PHP encoding failed, perhaps install php-mbstring."
+ elif (( failc & 2 )); then
+ print -u2 "N: PHP encoding produced bad output, WTF‽"
+ fi
+ if (( failc & 4 )); then
+ print -u2 "N: Perl encoding failed, WTF‽ Also, install PHP."
+ elif (( failc & 8 )); then
+ print -u2 "N: Perl encoding produced bad output, install PHP!"
+ fi
+ senc="Subject: post-receive-email: no Subject, RFC2047 MIME encoding failed"
+ fi
+
+ if [[ $shortdesc = "$projectdesc" ]]; then
+ repolabel=$PWD
+ else
+ repolabel=$shortdesc
+ fi
cat <<-EOF
MIME-Version: 1.0
X-Git-Reftype: $refname_type
X-Git-Oldrev: $oldrev
X-Git-Newrev: $newrev
- $(perl -Mutf8 -MEncode -e "print encode('MIME-Q', 'Subject: ${emailprefix}$shortdesc $refname_type $short_refname ${change_type}d. $describe');")
+ $senc
Auto-Submitted: auto-generated
- This is an automated email from the git hooks/post-receive script. It was
- generated because a ref change was pushed to the repository containing
- the project "$projectdesc".
+ This is an automated email from the git hooks/post-receive script.
+ It was generated because a ref change was pushed to the repository
+ "$repolabel" containing the project
+ "$projectdesc".
The $refname_type, $short_refname has been ${change_type}d
EOF
echo " at $newrev ($newrev_type)"
echo ""
+ local newrevs=$(show_new_revisions)
+
+ if [[ -z $newrevs ]]; then
+ echo "No new revisions were added by this update."
+ return 0
+ fi
+
+ echo "Below, we list all revisions that are new to this repository and"
+ echo "have not appeared on any other notification email. The diff below"
+ echo "represents the summarised changes from the first known parent:"
+
+ echo ""
echo $LOGBEGIN
- show_new_revisions
+ print -r -- "$newrevs"
echo $LOGEND
+ # experimental summarised changes diff
+ echo ""
+ echo "Summary of changes:"
+ git rev-parse --not "${other_branches[@]}" | \
+ git rev-list --parents --topo-order --reverse --stdin $newrev | \
+ head -n 1 |&
+ read -p commit parent otherparents
+ git -c core.quotePath=false diff-tree $diffopts \
+ "${parent:-$(:|git hash-object -t tree --stdin)}" "$newrev"
}
#
fi
fi
- echo ""
- if [ -z "$rewind_only" ]; then
- echo "Those revisions listed above that are new to this repository have"
- echo "not appeared on any other notification email; so we list those"
- echo "revisions in full, below."
+ local newrevs=
+ [[ -n $rewind_only ]] || newrevs=$(show_new_revisions)
- echo ""
- echo $LOGBEGIN
- show_new_revisions
-
- # XXX: Need a way of detecting whether git rev-list actually
- # outputted anything, so that we can issue a "no new
- # revisions added by this update" message
-
- echo $LOGEND
- else
+ echo ""
+ if [[ -z $newrevs ]]; then
echo "No new revisions were added by this update."
+ return 0
fi
+ echo "Those revisions listed above that are new to this repository have"
+ echo "not appeared on any other notification email; so we list those"
+ echo "revisions in full, below."
+
+ echo ""
+ echo $LOGBEGIN
+ print -r -- "$newrevs"
+ echo $LOGEND
+
# The diffstat is shown from the old revision to the new revision.
# This is to show the truth of what happened in this change.
# There's no point showing the stat from the base to the new
# non-fast-forward updates.
echo ""
echo "Summary of changes:"
- git diff-tree $diffopts $oldrev..$newrev
+ git -c core.quotePath=false diff-tree $diffopts $oldrev..$newrev
}
#
# (see generate_update_branch_email for the explanation of this
# command)
+ local revspec onerev
+
# Revision range passed to rev-list differs for new vs. updated
# branches.
if [ "$change_type" = create ]
revspec=$oldrev..$newrev
fi
- other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ |
- grep -F -v $refname)
- git rev-parse --not $other_branches |
+ git rev-parse --not "${other_branches[@]}" |
if [ -z "$custom_showrev" ]
then
git rev-list --pretty --stdin $revspec
send_mail()
{
- send_mail_x=$(cat; echo x)
+ local x=$(cat; echo x)
- send_mail_x=${send_mail_x%x}
- test -n "$send_mail_x" || return 1
+ x=${x%x}
+ [[ -n $x ]] || return 1
- printf '%s' "$send_mail_x" | if [ -n "$envelopesender" ]; then
+ print -nr -- "$x" | if [ -n "$envelopesender" ]; then
/usr/sbin/sendmail -t -f "$envelopesender"
else
/usr/sbin/sendmail -t
fi
- unset send_mail_x
}
# ---------------------------- main()
custom_showrev=$(git config hooks.showrev)
maxlines=$(git config hooks.emailmaxlines)
diffopts=$(git config hooks.diffopts)
-: ${diffopts:="-p --stat --summary --find-copies-harder"}
+: "${diffopts:="-p --stat --summary --find-copies-harder"}"
kgbconffile=$(git config hooks.kgbconffile)
replyto=$(git config hooks.replyto)
shortdesc=$(git config hooks.shortdesc)
-: ${shortdesc:=$projectdesc}
+: "${shortdesc:=$projectdesc}"
# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
# themselves
prep_for_email $2 $3 $1 && PAGER= generate_email
else
- ilines=
- while read oldrev newrev refname
- do
- ilines="$ilines$oldrev $newrev $refname$nl"
+ # get a list of all branches
+ set -A other_branches -- $(git for-each-ref \
+ --format='%(refname)' refs/heads/)
+ set -A ilines
+ while IFS= read -r line; do
+ # store for later
+ ilines+=("$line")
+ # check if refname is a branch name
+ refname=${line##* }
+ [[ $refname = refs/heads/* ]] || continue
+ # filter out from other_branches if matching
+ i=-1 j=${#other_branches[@]}
+ while (( ++i < j )); do
+ [[ ${other_branches[i]} = "$refname" ]] || continue
+ unset other_branches[i]
+ break
+ done
+ set -A other_branches -- "${other_branches[@]}"
+ done
+ # process one by one for eMails
+ for line in "${ilines[@]}"; do
+ oldrev=${line%% *}
+ newrev=${line#* }
+ newrev=${newrev%% *}
+ refname=${line##* }
prep_for_email $oldrev $newrev $refname || continue
generate_email $maxlines | send_mail
+ # append to list of branches for which eMails have been sent
+ [[ $refname = refs/heads/* ]] && other_branches+=("$refname")
done
- if test -n "$kgbconffile" && test -s "$kgbconffile"; then
- printf '%s' "$ilines" | kgb-client \
+ if [[ -n $kgbconffile && -s $kgbconffile ]]; then
+ for line in "${ilines[@]}"; do
+ print -r -- "$line"
+ done | kgb-client \
--conf "$kgbconffile" --repository git --git-reflog -
fi
+ set -o noglob
+ for uri in $(git config hooks.curltrigger); do
+ print -r -- "‣‣‣ $uri"
+ curl "$uri"
+ done
fi