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