##// END OF EJS Templates
use ui.readsections in the notify extension
Alexis S. L. Carvalho -
r3434:bf10cd8b default
parent child Browse files
Show More
@@ -1,280 +1,280 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:commands,patch,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 self.ui.readconfig(cfg)
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.sio = templater.stringio()
111 111 self.subs = self.subscribers()
112 112
113 113 mapfile = self.ui.config('notify', 'style')
114 114 template = (self.ui.config('notify', hooktype) or
115 115 self.ui.config('notify', 'template'))
116 116 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
117 117 self.sio)
118 118 if not mapfile and not template:
119 119 template = deftemplates.get(hooktype) or single_template
120 120 if template:
121 121 template = templater.parsestring(template, quoted=False)
122 122 self.t.use_template(template)
123 123
124 124 def strip(self, path):
125 125 '''strip leading slashes from local path, turn into web-safe path.'''
126 126
127 127 path = util.pconvert(path)
128 128 count = self.stripcount
129 129 while count > 0:
130 130 c = path.find('/')
131 131 if c == -1:
132 132 break
133 133 path = path[c+1:]
134 134 count -= 1
135 135 return path
136 136
137 137 def fixmail(self, addr):
138 138 '''try to clean up email addresses.'''
139 139
140 140 addr = templater.email(addr.strip())
141 141 a = addr.find('@localhost')
142 142 if a != -1:
143 143 addr = addr[:a]
144 144 if '@' not in addr:
145 145 return addr + '@' + self.domain
146 146 return addr
147 147
148 148 def subscribers(self):
149 149 '''return list of email addresses of subscribers to this repo.'''
150 150
151 151 subs = {}
152 152 for user, pats in self.ui.configitems('usersubs'):
153 153 for pat in pats.split(','):
154 154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
155 155 subs[self.fixmail(user)] = 1
156 156 for pat, users in self.ui.configitems('reposubs'):
157 157 if fnmatch.fnmatch(self.repo.root, pat):
158 158 for user in users.split(','):
159 159 subs[self.fixmail(user)] = 1
160 160 subs = subs.keys()
161 161 subs.sort()
162 162 return subs
163 163
164 164 def url(self, path=None):
165 165 return self.ui.config('web', 'baseurl') + (path or self.root)
166 166
167 167 def node(self, node):
168 168 '''format one changeset.'''
169 169
170 170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
171 171 baseurl=self.ui.config('web', 'baseurl'),
172 172 root=self.repo.root,
173 173 webroot=self.root)
174 174
175 175 def skipsource(self, source):
176 176 '''true if incoming changes from this source should be skipped.'''
177 177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
178 178 return source not in ok_sources
179 179
180 180 def send(self, node, count):
181 181 '''send message.'''
182 182
183 183 p = email.Parser.Parser()
184 184 self.sio.seek(0)
185 185 msg = p.parse(self.sio)
186 186
187 187 def fix_subject():
188 188 '''try to make subject line exist and be useful.'''
189 189
190 190 subject = msg['Subject']
191 191 if not subject:
192 192 if count > 1:
193 193 subject = _('%s: %d new changesets') % (self.root, count)
194 194 else:
195 195 changes = self.repo.changelog.read(node)
196 196 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
197 197 subject = '%s: %s' % (self.root, s)
198 198 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
199 199 if maxsubject and len(subject) > maxsubject:
200 200 subject = subject[:maxsubject-3] + '...'
201 201 del msg['Subject']
202 202 msg['Subject'] = subject
203 203
204 204 def fix_sender():
205 205 '''try to make message have proper sender.'''
206 206
207 207 sender = msg['From']
208 208 if not sender:
209 209 sender = self.ui.config('email', 'from') or self.ui.username()
210 210 if '@' not in sender or '@localhost' in sender:
211 211 sender = self.fixmail(sender)
212 212 del msg['From']
213 213 msg['From'] = sender
214 214
215 215 fix_subject()
216 216 fix_sender()
217 217
218 218 msg['X-Hg-Notification'] = 'changeset ' + short(node)
219 219 if not msg['Message-Id']:
220 220 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 221 (short(node), int(time.time()),
222 222 hash(self.repo.root), socket.getfqdn()))
223 223 msg['To'] = ', '.join(self.subs)
224 224
225 225 msgtext = msg.as_string(0)
226 226 if self.ui.configbool('notify', 'test', True):
227 227 self.ui.write(msgtext)
228 228 if not msgtext.endswith('\n'):
229 229 self.ui.write('\n')
230 230 else:
231 231 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 232 (len(self.subs), count))
233 233 mail.sendmail(self.ui, templater.email(msg['From']),
234 234 self.subs, msgtext)
235 235
236 236 def diff(self, node, ref):
237 237 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 238 if maxdiff == 0:
239 239 return
240 240 fp = templater.stringio()
241 241 prev = self.repo.changelog.parents(node)[0]
242 242 patch.diff(self.repo, prev, ref, fp=fp)
243 243 difflines = fp.getvalue().splitlines(1)
244 244 if self.ui.configbool('notify', 'diffstat', True):
245 245 s = patch.diffstat(difflines)
246 246 self.sio.write('\ndiffstat:\n\n' + s)
247 247 if maxdiff > 0 and len(difflines) > maxdiff:
248 248 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
249 249 (len(difflines), maxdiff))
250 250 difflines = difflines[:maxdiff]
251 251 elif difflines:
252 252 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
253 253 self.sio.write(*difflines)
254 254
255 255 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
256 256 '''send email notifications to interested subscribers.
257 257
258 258 if used as changegroup hook, send one email for all changesets in
259 259 changegroup. else send one email per changeset.'''
260 260 n = notifier(ui, repo, hooktype)
261 261 if not n.subs:
262 262 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
263 263 return
264 264 if n.skipsource(source):
265 265 ui.debug(_('notify: changes have source "%s" - skipping\n') %
266 266 source)
267 267 return
268 268 node = bin(node)
269 269 if hooktype == 'changegroup':
270 270 start = repo.changelog.rev(node)
271 271 end = repo.changelog.count()
272 272 count = end - start
273 273 for rev in xrange(start, end):
274 274 n.node(repo.changelog.node(rev))
275 275 n.diff(node, repo.changelog.tip())
276 276 else:
277 277 count = 1
278 278 n.node(node)
279 279 n.diff(node, node)
280 280 n.send(node, count)
General Comments 0
You need to be logged in to leave comments. Login now