##// END OF EJS Templates
notify: add debug output. do not fail if no config file....
Vadim Gelfer -
r2329:90368f89 default
parent child Browse files
Show More
@@ -1,267 +1,276 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 # sources = serve # notify if source of incoming changes in this list
44 44 # # (serve == ssh or http, push, pull, bundle)
45 45 # [email]
46 46 # from = user@host.com # email address to send as if none given
47 47 # [web]
48 48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
49 49 #
50 50 # notify config file has same format as regular hgrc. it has two
51 51 # sections so you can express subscriptions in whatever way is handier
52 52 # for you.
53 53 #
54 54 # [usersubs]
55 55 # # key is subscriber email, value is ","-separated list of glob patterns
56 56 # user@host = pattern
57 57 #
58 58 # [reposubs]
59 59 # # key is glob pattern, value is ","-separated list of subscriber emails
60 60 # pattern = user@host
61 61 #
62 62 # glob patterns are matched against path to repo root.
63 63 #
64 64 # if you like, you can put notify config file in repo that users can
65 65 # push changes to, they can manage their own subscriptions.
66 66
67 67 from mercurial.demandload import *
68 68 from mercurial.i18n import gettext as _
69 69 from mercurial.node import *
70 70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
71 71 demandload(globals(), 'fnmatch socket time')
72 72
73 73 # template for single changeset can include email headers.
74 74 single_template = '''
75 75 Subject: changeset in {webroot}: {desc|firstline|strip}
76 76 From: {author}
77 77
78 78 changeset {node|short} in {root}
79 79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 80 description:
81 81 \t{desc|tabindent|strip}
82 82 '''.lstrip()
83 83
84 84 # template for multiple changesets should not contain email headers,
85 85 # because only first set of headers will be used and result will look
86 86 # strange.
87 87 multiple_template = '''
88 88 changeset {node|short} in {root}
89 89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 90 summary: {desc|firstline}
91 91 '''
92 92
93 93 deftemplates = {
94 94 'changegroup': multiple_template,
95 95 }
96 96
97 97 class notifier(object):
98 98 '''email notification class.'''
99 99
100 100 def __init__(self, ui, repo, hooktype):
101 101 self.ui = ui
102 self.ui.readconfig(self.ui.config('notify', 'config'))
102 cfg = self.ui.config('notify', 'config')
103 if cfg:
104 self.ui.readconfig(cfg)
103 105 self.repo = repo
104 106 self.stripcount = int(self.ui.config('notify', 'strip', 0))
105 107 self.root = self.strip(self.repo.root)
106 108 self.domain = self.ui.config('notify', 'domain')
107 109 self.sio = templater.stringio()
108 110 self.subs = self.subscribers()
109 111
110 112 mapfile = self.ui.config('notify', 'style')
111 113 template = (self.ui.config('notify', hooktype) or
112 114 self.ui.config('notify', 'template'))
113 115 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
114 116 self.sio)
115 117 if not mapfile and not template:
116 118 template = deftemplates.get(hooktype) or single_template
117 119 if template:
118 120 template = templater.parsestring(template, quoted=False)
119 121 self.t.use_template(template)
120 122
121 123 def strip(self, path):
122 124 '''strip leading slashes from local path, turn into web-safe path.'''
123 125
124 126 path = util.pconvert(path)
125 127 count = self.stripcount
126 128 while count > 0:
127 129 c = path.find('/')
128 130 if c == -1:
129 131 break
130 132 path = path[c+1:]
131 133 count -= 1
132 134 return path
133 135
134 136 def fixmail(self, addr):
135 137 '''try to clean up email addresses.'''
136 138
137 139 addr = templater.email(addr.strip())
138 140 a = addr.find('@localhost')
139 141 if a != -1:
140 142 addr = addr[:a]
141 143 if '@' not in addr:
142 144 return addr + '@' + self.domain
143 145 return addr
144 146
145 147 def subscribers(self):
146 148 '''return list of email addresses of subscribers to this repo.'''
147 149
148 150 subs = {}
149 151 for user, pats in self.ui.configitems('usersubs'):
150 152 for pat in pats.split(','):
151 153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
152 154 subs[self.fixmail(user)] = 1
153 155 for pat, users in self.ui.configitems('reposubs'):
154 156 if fnmatch.fnmatch(self.repo.root, pat):
155 157 for user in users.split(','):
156 158 subs[self.fixmail(user)] = 1
157 159 subs = subs.keys()
158 160 subs.sort()
159 161 return subs
160 162
161 163 def url(self, path=None):
162 164 return self.ui.config('web', 'baseurl') + (path or self.root)
163 165
164 166 def node(self, node):
165 167 '''format one changeset.'''
166 168
167 169 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
168 170 baseurl=self.ui.config('web', 'baseurl'),
169 171 root=self.repo.root,
170 172 webroot=self.root)
171 173
172 174 def skipsource(self, source):
173 175 '''true if incoming changes from this source should be skipped.'''
174 176 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
175 177 return source not in ok_sources
176 178
177 179 def send(self, node, count):
178 180 '''send message.'''
179 181
180 182 p = email.Parser.Parser()
181 183 self.sio.seek(0)
182 184 msg = p.parse(self.sio)
183 185
184 186 def fix_subject():
185 187 '''try to make subject line exist and be useful.'''
186 188
187 189 subject = msg['Subject']
188 190 if not subject:
189 191 if count > 1:
190 192 subject = _('%s: %d new changesets') % (self.root, count)
191 193 else:
192 194 changes = self.repo.changelog.read(node)
193 195 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
194 196 subject = '%s: %s' % (self.root, s)
195 197 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
196 198 if maxsubject and len(subject) > maxsubject:
197 199 subject = subject[:maxsubject-3] + '...'
198 200 del msg['Subject']
199 201 msg['Subject'] = subject
200 202
201 203 def fix_sender():
202 204 '''try to make message have proper sender.'''
203 205
204 206 sender = msg['From']
205 207 if not sender:
206 208 sender = self.ui.config('email', 'from') or self.ui.username()
207 209 if '@' not in sender or '@localhost' in sender:
208 210 sender = self.fixmail(sender)
209 211 del msg['From']
210 212 msg['From'] = sender
211 213
212 214 fix_subject()
213 215 fix_sender()
214 216
215 217 msg['X-Hg-Notification'] = 'changeset ' + short(node)
216 218 if not msg['Message-Id']:
217 219 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
218 220 (short(node), int(time.time()),
219 221 hash(self.repo.root), socket.getfqdn()))
220 222 msg['To'] = ', '.join(self.subs)
221 223
222 224 msgtext = msg.as_string(0)
223 225 if self.ui.configbool('notify', 'test', True):
224 226 self.ui.write(msgtext)
225 227 if not msgtext.endswith('\n'):
226 228 self.ui.write('\n')
227 229 else:
230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 (len(self.subs), count))
228 232 mail = self.ui.sendmail()
229 233 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
230 234
231 235 def diff(self, node, ref):
232 236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
233 237 if maxdiff == 0:
234 238 return
235 239 fp = templater.stringio()
236 240 prev = self.repo.changelog.parents(node)[0]
237 241 commands.dodiff(fp, self.ui, self.repo, prev, ref)
238 242 difflines = fp.getvalue().splitlines(1)
239 243 if maxdiff > 0 and len(difflines) > maxdiff:
240 244 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
241 245 (len(difflines), maxdiff))
242 246 difflines = difflines[:maxdiff]
243 247 elif difflines:
244 248 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
245 249 self.sio.write(*difflines)
246 250
247 251 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
248 252 '''send email notifications to interested subscribers.
249 253
250 254 if used as changegroup hook, send one email for all changesets in
251 255 changegroup. else send one email per changeset.'''
252 256 n = notifier(ui, repo, hooktype)
253 if not n.subs or n.skipsource(source):
257 if not n.subs:
258 ui.debug(_('notify: no subscribers to this repo\n'))
259 return
260 if n.skipsource(source):
261 ui.debug(_('notify: changes have source "%s" - skipping\n') %
262 source)
254 263 return
255 264 node = bin(node)
256 265 if hooktype == 'changegroup':
257 266 start = repo.changelog.rev(node)
258 267 end = repo.changelog.count()
259 268 count = end - start
260 269 for rev in xrange(start, end):
261 270 n.node(repo.changelog.node(rev))
262 271 n.diff(node, repo.changelog.tip())
263 272 else:
264 273 count = 1
265 274 n.node(node)
266 275 n.diff(node, node)
267 276 n.send(node, count)
General Comments 0
You need to be logged in to leave comments. Login now