##// END OF EJS Templates
notify: don't try to fix addresses if notify.domain is not set
Alexis S. L. Carvalho -
r4094:fbf0e9ac default
parent child Browse files
Show More
@@ -1,282 +1,283 b''
1 1 # notify.py - email notifications for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to email notifications to people when changesets are
9 9 # committed to a repo they subscribe to.
10 10 #
11 11 # default mode is to print messages to stdout, for testing and
12 12 # configuring.
13 13 #
14 14 # to use, configure notify extension and enable in hgrc like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.notify =
18 18 #
19 19 # [hooks]
20 20 # # one email for each incoming changeset
21 21 # incoming.notify = python:hgext.notify.hook
22 22 # # batch emails when many changesets incoming at one time
23 23 # changegroup.notify = python:hgext.notify.hook
24 24 #
25 25 # [notify]
26 26 # # config items go in here
27 27 #
28 28 # config items:
29 29 #
30 30 # REQUIRED:
31 31 # config = /path/to/file # file containing subscriptions
32 32 #
33 33 # OPTIONAL:
34 34 # test = True # print messages to stdout for testing
35 35 # strip = 3 # number of slashes to strip for url paths
36 36 # domain = example.com # domain to use if committer missing domain
37 37 # style = ... # style file to use when formatting email
38 38 # template = ... # template to use when formatting email
39 39 # incoming = ... # template to use when run as incoming hook
40 40 # changegroup = ... # template when run as changegroup hook
41 41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 42 # maxsubject = 67 # truncate subject line longer than this
43 43 # diffstat = True # add a diffstat before the diff content
44 44 # sources = serve # notify if source of incoming changes in this list
45 45 # # (serve == ssh or http, push, pull, bundle)
46 46 # [email]
47 47 # from = user@host.com # email address to send as if none given
48 48 # [web]
49 49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
50 50 #
51 51 # notify config file has same format as regular hgrc. it has two
52 52 # sections so you can express subscriptions in whatever way is handier
53 53 # for you.
54 54 #
55 55 # [usersubs]
56 56 # # key is subscriber email, value is ","-separated list of glob patterns
57 57 # user@host = pattern
58 58 #
59 59 # [reposubs]
60 60 # # key is glob pattern, value is ","-separated list of subscriber emails
61 61 # pattern = user@host
62 62 #
63 63 # glob patterns are matched against path to repo root.
64 64 #
65 65 # if you like, you can put notify config file in repo that users can
66 66 # push changes to, they can manage their own subscriptions.
67 67
68 68 from mercurial.demandload import *
69 69 from mercurial.i18n import gettext as _
70 70 from mercurial.node import *
71 71 demandload(globals(), 'mercurial:patch,cmdutil,templater,util,mail')
72 72 demandload(globals(), 'email.Parser fnmatch socket time')
73 73
74 74 # template for single changeset can include email headers.
75 75 single_template = '''
76 76 Subject: changeset in {webroot}: {desc|firstline|strip}
77 77 From: {author}
78 78
79 79 changeset {node|short} in {root}
80 80 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
81 81 description:
82 82 \t{desc|tabindent|strip}
83 83 '''.lstrip()
84 84
85 85 # template for multiple changesets should not contain email headers,
86 86 # because only first set of headers will be used and result will look
87 87 # strange.
88 88 multiple_template = '''
89 89 changeset {node|short} in {root}
90 90 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
91 91 summary: {desc|firstline}
92 92 '''
93 93
94 94 deftemplates = {
95 95 'changegroup': multiple_template,
96 96 }
97 97
98 98 class notifier(object):
99 99 '''email notification class.'''
100 100
101 101 def __init__(self, ui, repo, hooktype):
102 102 self.ui = ui
103 103 cfg = self.ui.config('notify', 'config')
104 104 if cfg:
105 105 self.ui.readsections(cfg, 'usersubs', 'reposubs')
106 106 self.repo = repo
107 107 self.stripcount = int(self.ui.config('notify', 'strip', 0))
108 108 self.root = self.strip(self.repo.root)
109 109 self.domain = self.ui.config('notify', 'domain')
110 110 self.subs = self.subscribers()
111 111
112 112 mapfile = self.ui.config('notify', 'style')
113 113 template = (self.ui.config('notify', hooktype) or
114 114 self.ui.config('notify', 'template'))
115 115 self.t = cmdutil.changeset_templater(self.ui, self.repo,
116 116 False, None, mapfile, False)
117 117 if not mapfile and not template:
118 118 template = deftemplates.get(hooktype) or single_template
119 119 if template:
120 120 template = templater.parsestring(template, quoted=False)
121 121 self.t.use_template(template)
122 122
123 123 def strip(self, path):
124 124 '''strip leading slashes from local path, turn into web-safe path.'''
125 125
126 126 path = util.pconvert(path)
127 127 count = self.stripcount
128 128 while count > 0:
129 129 c = path.find('/')
130 130 if c == -1:
131 131 break
132 132 path = path[c+1:]
133 133 count -= 1
134 134 return path
135 135
136 136 def fixmail(self, addr):
137 137 '''try to clean up email addresses.'''
138 138
139 139 addr = templater.email(addr.strip())
140 a = addr.find('@localhost')
141 if a != -1:
142 addr = addr[:a]
143 if '@' not in addr:
144 return addr + '@' + self.domain
140 if self.domain:
141 a = addr.find('@localhost')
142 if a != -1:
143 addr = addr[:a]
144 if '@' not in addr:
145 return addr + '@' + self.domain
145 146 return addr
146 147
147 148 def subscribers(self):
148 149 '''return list of email addresses of subscribers to this repo.'''
149 150
150 151 subs = {}
151 152 for user, pats in self.ui.configitems('usersubs'):
152 153 for pat in pats.split(','):
153 154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 155 subs[self.fixmail(user)] = 1
155 156 for pat, users in self.ui.configitems('reposubs'):
156 157 if fnmatch.fnmatch(self.repo.root, pat):
157 158 for user in users.split(','):
158 159 subs[self.fixmail(user)] = 1
159 160 subs = subs.keys()
160 161 subs.sort()
161 162 return subs
162 163
163 164 def url(self, path=None):
164 165 return self.ui.config('web', 'baseurl') + (path or self.root)
165 166
166 167 def node(self, node):
167 168 '''format one changeset.'''
168 169
169 170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
170 171 baseurl=self.ui.config('web', 'baseurl'),
171 172 root=self.repo.root,
172 173 webroot=self.root)
173 174
174 175 def skipsource(self, source):
175 176 '''true if incoming changes from this source should be skipped.'''
176 177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
177 178 return source not in ok_sources
178 179
179 180 def send(self, node, count, data):
180 181 '''send message.'''
181 182
182 183 p = email.Parser.Parser()
183 184 msg = p.parsestr(data)
184 185
185 186 def fix_subject():
186 187 '''try to make subject line exist and be useful.'''
187 188
188 189 subject = msg['Subject']
189 190 if not subject:
190 191 if count > 1:
191 192 subject = _('%s: %d new changesets') % (self.root, count)
192 193 else:
193 194 changes = self.repo.changelog.read(node)
194 195 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
195 196 subject = '%s: %s' % (self.root, s)
196 197 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
197 198 if maxsubject and len(subject) > maxsubject:
198 199 subject = subject[:maxsubject-3] + '...'
199 200 del msg['Subject']
200 201 msg['Subject'] = subject
201 202
202 203 def fix_sender():
203 204 '''try to make message have proper sender.'''
204 205
205 206 sender = msg['From']
206 207 if not sender:
207 208 sender = self.ui.config('email', 'from') or self.ui.username()
208 209 if '@' not in sender or '@localhost' in sender:
209 210 sender = self.fixmail(sender)
210 211 del msg['From']
211 212 msg['From'] = sender
212 213
213 214 fix_subject()
214 215 fix_sender()
215 216
216 217 msg['X-Hg-Notification'] = 'changeset ' + short(node)
217 218 if not msg['Message-Id']:
218 219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
219 220 (short(node), int(time.time()),
220 221 hash(self.repo.root), socket.getfqdn()))
221 222 msg['To'] = ', '.join(self.subs)
222 223
223 224 msgtext = msg.as_string(0)
224 225 if self.ui.configbool('notify', 'test', True):
225 226 self.ui.write(msgtext)
226 227 if not msgtext.endswith('\n'):
227 228 self.ui.write('\n')
228 229 else:
229 230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
230 231 (len(self.subs), count))
231 232 mail.sendmail(self.ui, templater.email(msg['From']),
232 233 self.subs, msgtext)
233 234
234 235 def diff(self, node, ref):
235 236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
236 237 if maxdiff == 0:
237 238 return
238 239 prev = self.repo.changelog.parents(node)[0]
239 240 self.ui.pushbuffer()
240 241 patch.diff(self.repo, prev, ref)
241 242 difflines = self.ui.popbuffer().splitlines(1)
242 243 if self.ui.configbool('notify', 'diffstat', True):
243 244 s = patch.diffstat(difflines)
244 245 # s may be nil, don't include the header if it is
245 246 if s:
246 247 self.ui.write('\ndiffstat:\n\n%s' % s)
247 248 if maxdiff > 0 and len(difflines) > maxdiff:
248 249 self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
249 250 (len(difflines), maxdiff))
250 251 difflines = difflines[:maxdiff]
251 252 elif difflines:
252 253 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
253 254 self.ui.write(*difflines)
254 255
255 256 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
256 257 '''send email notifications to interested subscribers.
257 258
258 259 if used as changegroup hook, send one email for all changesets in
259 260 changegroup. else send one email per changeset.'''
260 261 n = notifier(ui, repo, hooktype)
261 262 if not n.subs:
262 263 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
263 264 return
264 265 if n.skipsource(source):
265 266 ui.debug(_('notify: changes have source "%s" - skipping\n') %
266 267 source)
267 268 return
268 269 node = bin(node)
269 270 ui.pushbuffer()
270 271 if hooktype == 'changegroup':
271 272 start = repo.changelog.rev(node)
272 273 end = repo.changelog.count()
273 274 count = end - start
274 275 for rev in xrange(start, end):
275 276 n.node(repo.changelog.node(rev))
276 277 n.diff(node, repo.changelog.tip())
277 278 else:
278 279 count = 1
279 280 n.node(node)
280 281 n.diff(node, node)
281 282 data = ui.popbuffer()
282 283 n.send(node, count, data)
@@ -1,40 +1,54 b''
1 1 #!/bin/sh
2 2
3 3 cat <<EOF >> $HGRCPATH
4 4 [extensions]
5 5 notify=
6 6
7 7 [hooks]
8 8 incoming.notify = python:hgext.notify.hook
9 9
10 10 [notify]
11 config = $HGTMP/.notify.conf
12 11 sources = pull
13 domain = test.com
14 strip = 3
15 template = Subject: {desc|firstline|strip}\nFrom: {author}\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
16 12 diffstat = False
17 13
18 [web]
19 baseurl = http://test/
20
21 14 [usersubs]
22 15 foo@bar = *
16
17 [reposubs]
18 * = baz
23 19 EOF
24 20
25 21 hg help notify
26 22 hg init a
27 23 echo a > a/a
28 24 echo % commit
29 25 hg --traceback --cwd a commit -Ama -d '0 0'
30 26
31 27 echo % clone
32 28 hg --traceback clone a b
33 29
34 30 echo a >> a/a
35 31 echo % commit
36 32 hg --traceback --cwd a commit -Amb -d '1 0'
37 33
34 echo '% pull (minimal config)'
35 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
36 -e 's/changeset \([0-9a-f]* \)\?in .*test-notif/changeset \1in test-notif/' \
37 -e 's/^details: .*test-notify/details: test-notify/'
38
39 cat <<EOF >> $HGRCPATH
40 [notify]
41 config = $HGTMP/.notify.conf
42 domain = test.com
43 strip = 3
44 template = Subject: {desc|firstline|strip}\nFrom: {author}\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
45
46 [web]
47 baseurl = http://test/
48 EOF
49
38 50 echo % pull
51 hg --cwd b rollback
39 52 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
40 53 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/'
54
@@ -1,33 +1,61 b''
1 1 notify extension - No help text available
2 2
3 3 no commands defined
4 4 % commit
5 5 adding a
6 6 % clone
7 7 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 8 % commit
9 % pull (minimal config)
10 pulling from ../a
11 searching for changes
12 adding changesets
13 adding manifests
14 adding file changes
15 added 1 changesets with 1 changes to 1 files
16 Subject: changeset in test-notify/b: b
17 From: test
18 X-Hg-Notification: changeset 0647d048b600
19 Message-Id:
20 To: baz, foo@bar
21
22 changeset 0647d048b600 in test-notify/b
23 details: test-notify/b?cmd=changeset;node=0647d048b600
24 description:
25 b
26
27 diffs (6 lines):
28
29 diff -r cb9a9f314b8b -r 0647d048b600 a
30 --- a/a Thu Jan 01 00:00:00 1970 +0000
31 +++ b/a Thu Jan 01 00:00:01 1970 +0000
32 @@ -1,1 +1,2 @@ a
33 a
34 +a
35 (run 'hg update' to get a working copy)
9 36 % pull
37 rolling back last transaction
10 38 pulling from ../a
11 39 searching for changes
12 40 adding changesets
13 41 adding manifests
14 42 adding file changes
15 43 added 1 changesets with 1 changes to 1 files
16 44 Subject: b
17 45 From: test@test.com
18 46 X-Hg-Notification: changeset 0647d048b600
19 47 Message-Id:
20 To: foo@bar
48 To: baz@test.com, foo@bar
21 49
22 50 changeset 0647d048b600
23 51 description:
24 52 b
25 53 diffs (6 lines):
26 54
27 55 diff -r cb9a9f314b8b -r 0647d048b600 a
28 56 --- a/a Thu Jan 01 00:00:00 1970 +0000
29 57 +++ b/a Thu Jan 01 00:00:01 1970 +0000
30 58 @@ -1,1 +1,2 @@ a
31 59 a
32 60 +a
33 61 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now