Skip to content

Commit 829a686

Browse files
andyparkinsJunio C Hamano
authored andcommitted
Heavily expanded update hook to send more useful emails than the old hook
I know it's only an example, but having this might save someone else the trouble of writing an enhanced version for themselves. It basically does the same job as the old update hook, but with these differences: * The recipients list is read from the repository config file from hooks.mailinglist * Updating unannotated tags can be allowed by setting hooks.allowunannotated * Announcement emails (via annotated tag creation) can be sent to a different mailing list by setting hooks.announcelist * Output email is more verbose and generates specific content depending on whether the ref is a tag, an annotated tag, a branch, or a tracking branch * The email is easier to filter; the subject line is prefixed with [SCM] and a project description pulled from the "description" file * It catches (and displays differently) branch updates that are performed with a --force Obviously, it's nothing that clever - it's the update hook I use on my repositories but I've tried to keep it general, and tried to make the output always relevant to the type of update. Signed-off-by: Andy Parkins <andyparkins@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent a69aba6 commit 829a686

File tree

1 file changed

+268
-72
lines changed

1 file changed

+268
-72
lines changed

templates/hooks--update

Lines changed: 268 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,285 @@
11
#!/bin/sh
22
#
33
# An example hook script to mail out commit update information.
4-
# It also blocks tags that aren't annotated.
4+
# It can also blocks tags that aren't annotated.
55
# Called by git-receive-pack with arguments: refname sha1-old sha1-new
66
#
7-
# To enable this hook:
8-
# (1) change the recipient e-mail address
9-
# (2) make this file executable by "chmod +x update".
7+
# To enable this hook, make this file executable by "chmod +x update".
108
#
9+
# Config
10+
# ------
11+
# hooks.mailinglist
12+
# This is the list that all pushes will go to; leave it blank to not send
13+
# emails frequently. The log email will list every log entry in full between
14+
# the old ref value and the new ref value.
15+
# hooks.announcelist
16+
# This is the list that all pushes of annotated tags will go to. Leave it
17+
# blank to just use the mailinglist field. The announce emails list the
18+
# short log summary of the changes since the last annotated tag
19+
# hooks.allowunannotated
20+
# This boolean sets whether unannotated tags will be allowed into the
21+
# repository. By default they won't be.
22+
#
23+
# Notes
24+
# -----
25+
# All emails have their subjects prefixed with "[SCM]" to aid filtering.
26+
# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
27+
# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and info.
1128

12-
project=$(cat $GIT_DIR/description)
13-
recipients="commit-list@somewhere.com commit-list@somewhereelse.com"
14-
15-
ref_type=$(git cat-file -t "$3")
16-
17-
# Only allow annotated tags in a shared repo
18-
# Remove this code to treat dumb tags the same as everything else
19-
case "$1","$ref_type" in
20-
refs/tags/*,commit)
21-
echo "*** Un-annotated tags are not allowed in this repo" >&2
22-
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
23-
exit 1;;
24-
refs/tags/*,tag)
25-
echo "### Pushing version '${1##refs/tags/}' to the masses" >&2
26-
# recipients="release-announce@somwehere.com announce@somewhereelse.com"
27-
;;
28-
esac
29+
# --- Constants
30+
EMAILPREFIX="[SCM] "
31+
LOGBEGIN="- Log -----------------------------------------------------------------"
32+
LOGEND="-----------------------------------------------------------------------"
33+
DATEFORMAT="%F %R %z"
34+
35+
# --- Command line
36+
refname="$1"
37+
oldrev="$2"
38+
newrev="$3"
39+
40+
# --- Safety check
41+
if [ -z "$GIT_DIR" ]; then
42+
echo "Don't run this script from the command line." >&2
43+
echo " (if you want, you could supply GIT_DIR then run" >&2
44+
echo " $0 <ref> <oldrev> <newrev>)" >&2
45+
exit 1
46+
fi
47+
48+
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
49+
echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
50+
exit 1
51+
fi
52+
53+
# --- Config
54+
projectdesc=$(cat $GIT_DIR/description)
55+
recipients=$(git-repo-config hooks.mailinglist)
56+
announcerecipients=$(git-repo-config hooks.announcelist)
57+
allowunannotated=$(git-repo-config --bool hooks.allowunannotated)
2958

30-
# set this to 'cat' to get a very detailed listing.
31-
# short only kicks in when an annotated tag is added
32-
short='git shortlog'
33-
34-
# see 'date --help' for info on how to write this
35-
# The default is a human-readable iso8601-like format with minute
36-
# precision ('2006-01-25 15:58 +0100' for example)
37-
date_format="%F %R %z"
38-
39-
(if expr "$2" : '0*$' >/dev/null
40-
then
41-
# new ref
42-
case "$1" in
43-
refs/tags/*)
44-
# a pushed and annotated tag (usually) means a new version
45-
tag="${1##refs/tags/}"
46-
if [ "$ref_type" = tag ]; then
47-
eval $(git cat-file tag $3 | \
48-
sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p')
49-
date=$(date --date="1970-01-01 00:00:00 +0000 $ts seconds" +"$date_format")
50-
echo "Tag '$tag' created by $tagger at $date"
51-
git cat-file tag $3 | sed -n '5,$p'
52-
echo
59+
# --- Check types
60+
newrev_type=$(git-cat-file -t "$newrev")
61+
62+
case "$refname","$newrev_type" in
63+
refs/tags/*,commit)
64+
# un-annoted tag
65+
refname_type="tag"
66+
short_refname=${refname##refs/tags/}
67+
if [ $allowunannotated != "true" ]; then
68+
echo "*** The un-annotated tag, $short_refname is not allowed in this repository" >&2
69+
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
70+
exit 1
5371
fi
54-
prev=$(git describe "$3^" | sed 's/-g.*//')
55-
# the first tag in a repo will yield no $prev
56-
if [ -z "$prev" ]; then
57-
echo "Changes since the dawn of time:"
58-
git rev-list --pretty $3 | $short
59-
else
60-
echo "Changes since $prev:"
61-
git rev-list --pretty $prev..$3 | $short
62-
echo ---
63-
git diff --stat $prev..$3
64-
echo ---
72+
;;
73+
refs/tags/*,tag)
74+
# annotated tag
75+
refname_type="annotated tag"
76+
short_refname=${refname##refs/tags/}
77+
# change recipients
78+
if [ -n "$announcerecipients" ]; then
79+
recipients="$announcerecipients"
6580
fi
6681
;;
82+
refs/heads/*,commit)
83+
# branch
84+
refname_type="branch"
85+
short_refname=${refname##refs/heads/}
86+
;;
87+
refs/remotes/*,commit)
88+
# tracking branch
89+
refname_type="tracking branch"
90+
short_refname=${refname##refs/remotes/}
91+
# Should this even be allowed?
92+
echo "*** Push-update of tracking branch, $refname. No email generated." >&2
93+
exit 0
94+
;;
95+
*)
96+
# Anything else (is there anything else?)
97+
echo "*** Update hook: unknown type of update, \"$newrev_type\", to ref $refname" >&2
98+
exit 1
99+
;;
100+
esac
101+
102+
# Check if we've got anyone to send to
103+
if [ -z "$recipients" ]; then
104+
# If the email isn't sent, then at least give the user some idea of what command
105+
# would generate the email at a later date
106+
echo "*** No recipients found - no email will be sent, but the push will continue" >&2
107+
echo "*** for $0 $1 $2 $3" >&2
108+
exit 0
109+
fi
110+
111+
# --- Email parameters
112+
committer=$(git show --pretty=full -s $newrev | grep "^Commit: " | sed -e "s/^Commit: //")
113+
describe=$(git describe $newrev 2>/dev/null)
114+
if [ -z "$describe" ]; then
115+
describe=$newrev
116+
fi
67117

68-
refs/heads/*)
69-
branch="${1##refs/heads/}"
70-
echo "New branch '$branch' available with the following commits:"
71-
git-rev-list --pretty "$3" $(git-rev-parse --not --all)
118+
# --- Email (all stdout will be the email)
119+
(
120+
# Generate header
121+
cat <<-EOF
122+
From: $committer
123+
To: $recipients
124+
Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname now at $describe
125+
X-Git-Refname: $refname
126+
X-Git-Reftype: $refname_type
127+
X-Git-Oldrev: $oldrev
128+
X-Git-Newrev: $newrev
129+
130+
Hello,
131+
132+
This is an automated email from the git hooks/update script, it was
133+
generated because a ref change was pushed to the repository.
134+
135+
Updating $refname_type, $short_refname,
136+
EOF
137+
138+
case "$refname_type" in
139+
"tracking branch"|branch)
140+
if expr "$oldrev" : '0*$' >/dev/null
141+
then
142+
# If the old reference is "0000..0000" then this is a new branch
143+
# and so oldrev is not valid
144+
echo " as a new $refname_type"
145+
echo " to $newrev ($newrev_type)"
146+
echo ""
147+
echo $LOGBEGIN
148+
# This shows all log entries that are not already covered by
149+
# another ref - i.e. commits that are now accessible from this
150+
# ref that were previously not accessible
151+
git-rev-list --pretty $newref $(git-rev-parse --not --all)
152+
echo $LOGEND
153+
else
154+
# oldrev is valid
155+
oldrev_type=$(git-cat-file -t "$oldrev")
156+
157+
# Now the problem is for cases like this:
158+
# * --- * --- * --- * (oldrev)
159+
# \
160+
# * --- * --- * (newrev)
161+
# i.e. there is no guarantee that newrev is a strict subset
162+
# of oldrev - (would have required a force, but that's allowed).
163+
# So, we can't simply say rev-list $oldrev..$newrev. Instead
164+
# we find the common base of the two revs and list from there
165+
baserev=$(git-merge-base $oldrev $newrev)
166+
167+
# Commit with a parent
168+
for rev in $(git-rev-list $newrev ^$baserev)
169+
do
170+
revtype=$(git-cat-file -t "$rev")
171+
echo " via $rev ($revtype)"
172+
done
173+
if [ "$baserev" = "$oldrev" ]; then
174+
echo " from $oldrev ($oldrev_type)"
175+
else
176+
echo " based on $baserev"
177+
echo " from $oldrev ($oldrev_type)"
178+
echo ""
179+
echo "This ref update crossed a branch point; i.e. the old rev is not a strict subset"
180+
echo "of the new rev. This occurs, when you --force push a change in a situation"
181+
echo "like this:"
182+
echo ""
183+
echo " * -- * -- B -- O -- O -- O ($oldrev)"
184+
echo " \\"
185+
echo " N -- N -- N ($newrev)"
186+
echo ""
187+
echo "Therefore, we assume that you've already had alert emails for all of the O"
188+
echo "revisions, and now give you all the revisions in the N branch from the common"
189+
echo "base, B ($baserev), up to the new revision."
190+
fi
191+
echo ""
192+
echo $LOGBEGIN
193+
git-rev-list --pretty $newrev ^$baserev
194+
echo $LOGEND
195+
echo ""
196+
echo "Diffstat:"
197+
git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$baserev
198+
fi
72199
;;
73-
esac
74-
else
75-
base=$(git-merge-base "$2" "$3")
76-
case "$base" in
77-
"$2")
78-
git diff --stat "$3" "^$base"
79-
echo
80-
echo "New commits:"
200+
"annotated tag")
201+
# Should we allow changes to annotated tags?
202+
if expr "$oldrev" : '0*$' >/dev/null
203+
then
204+
# If the old reference is "0000..0000" then this is a new atag
205+
# and so oldrev is not valid
206+
echo " to $newrev ($newrev_type)"
207+
else
208+
echo " to $newrev ($newrev_type)"
209+
echo " from $oldrev"
210+
fi
211+
212+
# If this tag succeeds another, then show which tag it replaces
213+
prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//')
214+
if [ -n "$prevtag" ]; then
215+
echo " replaces $prevtag"
216+
fi
217+
218+
# Read the tag details
219+
eval $(git cat-file tag $newrev | \
220+
sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p')
221+
tagged=$(date --date="1970-01-01 00:00:00 +0000 $ts seconds" +"$DATEFORMAT")
222+
223+
echo " tagged by $tagger"
224+
echo " on $tagged"
225+
226+
echo ""
227+
echo $LOGBEGIN
228+
echo ""
229+
230+
if [ -n "$prevtag" ]; then
231+
git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
232+
else
233+
git rev-list --pretty=short $newrev | git shortlog
234+
fi
235+
236+
echo $LOGEND
237+
echo ""
81238
;;
82239
*)
83-
echo "Rebased ref, commits from common ancestor:"
240+
# By default, unannotated tags aren't allowed in; if
241+
# they are though, it's debatable whether we would even want an
242+
# email to be generated; however, I don't want to add another config
243+
# option just for that.
244+
#
245+
# Unannotated tags are more about marking a point than releasing
246+
# a version; therefore we don't do the shortlog summary that we
247+
# do for annotated tags above - we simply show that the point has
248+
# been marked, and print the log message for the marked point for
249+
# reference purposes
250+
#
251+
# Note this section also catches any other reference type (although
252+
# there aren't any) and deals with them in the same way.
253+
if expr "$oldrev" : '0*$' >/dev/null
254+
then
255+
# If the old reference is "0000..0000" then this is a new tag
256+
# and so oldrev is not valid
257+
echo " as a new $refname_type"
258+
echo " to $newrev ($newrev_type)"
259+
else
260+
echo " to $newrev ($newrev_type)"
261+
echo " from $oldrev"
262+
fi
263+
echo ""
264+
echo $LOGBEGIN
265+
git-show --no-color --root -s $newrev
266+
echo $LOGEND
267+
echo ""
84268
;;
85-
esac
86-
git-rev-list --pretty "$3" "^$base"
87-
fi) |
88-
mail -s "$project: Changes to '${1##refs/heads/}'" $recipients
269+
esac
270+
271+
# Footer
272+
cat <<-EOF
273+
274+
hooks/update
275+
---
276+
Git Source Code Management System
277+
$0 $1 \\
278+
$2 \\
279+
$3
280+
EOF
281+
#) | cat >&2
282+
) | /usr/sbin/sendmail -t
283+
284+
# --- Finished
89285
exit 0

0 commit comments

Comments
 (0)