comparison hgext/notify.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children 8ff1ecfadcd1
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
171 171
172 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for 172 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
173 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should 173 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
174 # be specifying the version(s) of Mercurial they are tested with, or 174 # be specifying the version(s) of Mercurial they are tested with, or
175 # leave the attribute unspecified. 175 # leave the attribute unspecified.
176 testedwith = 'ships-with-hg-core' 176 testedwith = b'ships-with-hg-core'
177 177
178 configtable = {} 178 configtable = {}
179 configitem = registrar.configitem(configtable) 179 configitem = registrar.configitem(configtable)
180 180
181 configitem( 181 configitem(
182 'notify', 'changegroup', default=None, 182 b'notify', b'changegroup', default=None,
183 ) 183 )
184 configitem( 184 configitem(
185 'notify', 'config', default=None, 185 b'notify', b'config', default=None,
186 ) 186 )
187 configitem( 187 configitem(
188 'notify', 'diffstat', default=True, 188 b'notify', b'diffstat', default=True,
189 ) 189 )
190 configitem( 190 configitem(
191 'notify', 'domain', default=None, 191 b'notify', b'domain', default=None,
192 ) 192 )
193 configitem( 193 configitem(
194 'notify', 'messageidseed', default=None, 194 b'notify', b'messageidseed', default=None,
195 ) 195 )
196 configitem( 196 configitem(
197 'notify', 'fromauthor', default=None, 197 b'notify', b'fromauthor', default=None,
198 ) 198 )
199 configitem( 199 configitem(
200 'notify', 'incoming', default=None, 200 b'notify', b'incoming', default=None,
201 ) 201 )
202 configitem( 202 configitem(
203 'notify', 'maxdiff', default=300, 203 b'notify', b'maxdiff', default=300,
204 ) 204 )
205 configitem( 205 configitem(
206 'notify', 'maxdiffstat', default=-1, 206 b'notify', b'maxdiffstat', default=-1,
207 ) 207 )
208 configitem( 208 configitem(
209 'notify', 'maxsubject', default=67, 209 b'notify', b'maxsubject', default=67,
210 ) 210 )
211 configitem( 211 configitem(
212 'notify', 'mbox', default=None, 212 b'notify', b'mbox', default=None,
213 ) 213 )
214 configitem( 214 configitem(
215 'notify', 'merge', default=True, 215 b'notify', b'merge', default=True,
216 ) 216 )
217 configitem( 217 configitem(
218 'notify', 'outgoing', default=None, 218 b'notify', b'outgoing', default=None,
219 ) 219 )
220 configitem( 220 configitem(
221 'notify', 'sources', default='serve', 221 b'notify', b'sources', default=b'serve',
222 ) 222 )
223 configitem( 223 configitem(
224 'notify', 'showfunc', default=None, 224 b'notify', b'showfunc', default=None,
225 ) 225 )
226 configitem( 226 configitem(
227 'notify', 'strip', default=0, 227 b'notify', b'strip', default=0,
228 ) 228 )
229 configitem( 229 configitem(
230 'notify', 'style', default=None, 230 b'notify', b'style', default=None,
231 ) 231 )
232 configitem( 232 configitem(
233 'notify', 'template', default=None, 233 b'notify', b'template', default=None,
234 ) 234 )
235 configitem( 235 configitem(
236 'notify', 'test', default=True, 236 b'notify', b'test', default=True,
237 ) 237 )
238 238
239 # template for single changeset can include email headers. 239 # template for single changeset can include email headers.
240 single_template = b''' 240 single_template = b'''
241 Subject: changeset in {webroot}: {desc|firstline|strip} 241 Subject: changeset in {webroot}: {desc|firstline|strip}
255 details: {baseurl}{webroot}?cmd=changeset;node={node|short} 255 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
256 summary: {desc|firstline} 256 summary: {desc|firstline}
257 ''' 257 '''
258 258
259 deftemplates = { 259 deftemplates = {
260 'changegroup': multiple_template, 260 b'changegroup': multiple_template,
261 } 261 }
262 262
263 263
264 class notifier(object): 264 class notifier(object):
265 '''email notification class.''' 265 '''email notification class.'''
266 266
267 def __init__(self, ui, repo, hooktype): 267 def __init__(self, ui, repo, hooktype):
268 self.ui = ui 268 self.ui = ui
269 cfg = self.ui.config('notify', 'config') 269 cfg = self.ui.config(b'notify', b'config')
270 if cfg: 270 if cfg:
271 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs']) 271 self.ui.readconfig(cfg, sections=[b'usersubs', b'reposubs'])
272 self.repo = repo 272 self.repo = repo
273 self.stripcount = int(self.ui.config('notify', 'strip')) 273 self.stripcount = int(self.ui.config(b'notify', b'strip'))
274 self.root = self.strip(self.repo.root) 274 self.root = self.strip(self.repo.root)
275 self.domain = self.ui.config('notify', 'domain') 275 self.domain = self.ui.config(b'notify', b'domain')
276 self.mbox = self.ui.config('notify', 'mbox') 276 self.mbox = self.ui.config(b'notify', b'mbox')
277 self.test = self.ui.configbool('notify', 'test') 277 self.test = self.ui.configbool(b'notify', b'test')
278 self.charsets = mail._charsets(self.ui) 278 self.charsets = mail._charsets(self.ui)
279 self.subs = self.subscribers() 279 self.subs = self.subscribers()
280 self.merge = self.ui.configbool('notify', 'merge') 280 self.merge = self.ui.configbool(b'notify', b'merge')
281 self.showfunc = self.ui.configbool('notify', 'showfunc') 281 self.showfunc = self.ui.configbool(b'notify', b'showfunc')
282 self.messageidseed = self.ui.config('notify', 'messageidseed') 282 self.messageidseed = self.ui.config(b'notify', b'messageidseed')
283 if self.showfunc is None: 283 if self.showfunc is None:
284 self.showfunc = self.ui.configbool('diff', 'showfunc') 284 self.showfunc = self.ui.configbool(b'diff', b'showfunc')
285 285
286 mapfile = None 286 mapfile = None
287 template = self.ui.config('notify', hooktype) or self.ui.config( 287 template = self.ui.config(b'notify', hooktype) or self.ui.config(
288 'notify', 'template' 288 b'notify', b'template'
289 ) 289 )
290 if not template: 290 if not template:
291 mapfile = self.ui.config('notify', 'style') 291 mapfile = self.ui.config(b'notify', b'style')
292 if not mapfile and not template: 292 if not mapfile and not template:
293 template = deftemplates.get(hooktype) or single_template 293 template = deftemplates.get(hooktype) or single_template
294 spec = logcmdutil.templatespec(template, mapfile) 294 spec = logcmdutil.templatespec(template, mapfile)
295 self.t = logcmdutil.changesettemplater(self.ui, self.repo, spec) 295 self.t = logcmdutil.changesettemplater(self.ui, self.repo, spec)
296 296
298 '''strip leading slashes from local path, turn into web-safe path.''' 298 '''strip leading slashes from local path, turn into web-safe path.'''
299 299
300 path = util.pconvert(path) 300 path = util.pconvert(path)
301 count = self.stripcount 301 count = self.stripcount
302 while count > 0: 302 while count > 0:
303 c = path.find('/') 303 c = path.find(b'/')
304 if c == -1: 304 if c == -1:
305 break 305 break
306 path = path[c + 1 :] 306 path = path[c + 1 :]
307 count -= 1 307 count -= 1
308 return path 308 return path
310 def fixmail(self, addr): 310 def fixmail(self, addr):
311 '''try to clean up email addresses.''' 311 '''try to clean up email addresses.'''
312 312
313 addr = stringutil.email(addr.strip()) 313 addr = stringutil.email(addr.strip())
314 if self.domain: 314 if self.domain:
315 a = addr.find('@localhost') 315 a = addr.find(b'@localhost')
316 if a != -1: 316 if a != -1:
317 addr = addr[:a] 317 addr = addr[:a]
318 if '@' not in addr: 318 if b'@' not in addr:
319 return addr + '@' + self.domain 319 return addr + b'@' + self.domain
320 return addr 320 return addr
321 321
322 def subscribers(self): 322 def subscribers(self):
323 '''return list of email addresses of subscribers to this repo.''' 323 '''return list of email addresses of subscribers to this repo.'''
324 subs = set() 324 subs = set()
325 for user, pats in self.ui.configitems('usersubs'): 325 for user, pats in self.ui.configitems(b'usersubs'):
326 for pat in pats.split(','): 326 for pat in pats.split(b','):
327 if '#' in pat: 327 if b'#' in pat:
328 pat, revs = pat.split('#', 1) 328 pat, revs = pat.split(b'#', 1)
329 else: 329 else:
330 revs = None 330 revs = None
331 if fnmatch.fnmatch(self.repo.root, pat.strip()): 331 if fnmatch.fnmatch(self.repo.root, pat.strip()):
332 subs.add((self.fixmail(user), revs)) 332 subs.add((self.fixmail(user), revs))
333 for pat, users in self.ui.configitems('reposubs'): 333 for pat, users in self.ui.configitems(b'reposubs'):
334 if '#' in pat: 334 if b'#' in pat:
335 pat, revs = pat.split('#', 1) 335 pat, revs = pat.split(b'#', 1)
336 else: 336 else:
337 revs = None 337 revs = None
338 if fnmatch.fnmatch(self.repo.root, pat): 338 if fnmatch.fnmatch(self.repo.root, pat):
339 for user in users.split(','): 339 for user in users.split(b','):
340 subs.add((self.fixmail(user), revs)) 340 subs.add((self.fixmail(user), revs))
341 return [ 341 return [
342 (mail.addressencode(self.ui, s, self.charsets, self.test), r) 342 (mail.addressencode(self.ui, s, self.charsets, self.test), r)
343 for s, r in sorted(subs) 343 for s, r in sorted(subs)
344 ] 344 ]
348 if not self.merge and len(ctx.parents()) > 1: 348 if not self.merge and len(ctx.parents()) > 1:
349 return False 349 return False
350 self.t.show( 350 self.t.show(
351 ctx, 351 ctx,
352 changes=ctx.changeset(), 352 changes=ctx.changeset(),
353 baseurl=self.ui.config('web', 'baseurl'), 353 baseurl=self.ui.config(b'web', b'baseurl'),
354 root=self.repo.root, 354 root=self.repo.root,
355 webroot=self.root, 355 webroot=self.root,
356 **props 356 **props
357 ) 357 )
358 return True 358 return True
359 359
360 def skipsource(self, source): 360 def skipsource(self, source):
361 '''true if incoming changes from this source should be skipped.''' 361 '''true if incoming changes from this source should be skipped.'''
362 ok_sources = self.ui.config('notify', 'sources').split() 362 ok_sources = self.ui.config(b'notify', b'sources').split()
363 return source not in ok_sources 363 return source not in ok_sources
364 364
365 def send(self, ctx, count, data): 365 def send(self, ctx, count, data):
366 '''send message.''' 366 '''send message.'''
367 367
369 subs = set() 369 subs = set()
370 for sub, spec in self.subs: 370 for sub, spec in self.subs:
371 if spec is None: 371 if spec is None:
372 subs.add(sub) 372 subs.add(sub)
373 continue 373 continue
374 revs = self.repo.revs('%r and %d:', spec, ctx.rev()) 374 revs = self.repo.revs(b'%r and %d:', spec, ctx.rev())
375 if len(revs): 375 if len(revs):
376 subs.add(sub) 376 subs.add(sub)
377 continue 377 continue
378 if len(subs) == 0: 378 if len(subs) == 0:
379 self.ui.debug( 379 self.ui.debug(
380 'notify: no subscribers to selected repo ' 'and revset\n' 380 b'notify: no subscribers to selected repo ' b'and revset\n'
381 ) 381 )
382 return 382 return
383 383
384 p = emailparser.Parser() 384 p = emailparser.Parser()
385 try: 385 try:
406 # reinstate custom headers 406 # reinstate custom headers
407 for k, v in headers: 407 for k, v in headers:
408 msg[k] = v 408 msg[k] = v
409 409
410 msg[r'Date'] = encoding.strfromlocal( 410 msg[r'Date'] = encoding.strfromlocal(
411 dateutil.datestr(format="%a, %d %b %Y %H:%M:%S %1%2") 411 dateutil.datestr(format=b"%a, %d %b %Y %H:%M:%S %1%2")
412 ) 412 )
413 413
414 # try to make subject line exist and be useful 414 # try to make subject line exist and be useful
415 if not subject: 415 if not subject:
416 if count > 1: 416 if count > 1:
417 subject = _('%s: %d new changesets') % (self.root, count) 417 subject = _(b'%s: %d new changesets') % (self.root, count)
418 else: 418 else:
419 s = ctx.description().lstrip().split('\n', 1)[0].rstrip() 419 s = ctx.description().lstrip().split(b'\n', 1)[0].rstrip()
420 subject = '%s: %s' % (self.root, s) 420 subject = b'%s: %s' % (self.root, s)
421 maxsubject = int(self.ui.config('notify', 'maxsubject')) 421 maxsubject = int(self.ui.config(b'notify', b'maxsubject'))
422 if maxsubject: 422 if maxsubject:
423 subject = stringutil.ellipsis(subject, maxsubject) 423 subject = stringutil.ellipsis(subject, maxsubject)
424 msg[r'Subject'] = encoding.strfromlocal( 424 msg[r'Subject'] = encoding.strfromlocal(
425 mail.headencode(self.ui, subject, self.charsets, self.test) 425 mail.headencode(self.ui, subject, self.charsets, self.test)
426 ) 426 )
427 427
428 # try to make message have proper sender 428 # try to make message have proper sender
429 if not sender: 429 if not sender:
430 sender = self.ui.config('email', 'from') or self.ui.username() 430 sender = self.ui.config(b'email', b'from') or self.ui.username()
431 if '@' not in sender or '@localhost' in sender: 431 if b'@' not in sender or b'@localhost' in sender:
432 sender = self.fixmail(sender) 432 sender = self.fixmail(sender)
433 msg[r'From'] = encoding.strfromlocal( 433 msg[r'From'] = encoding.strfromlocal(
434 mail.addressencode(self.ui, sender, self.charsets, self.test) 434 mail.addressencode(self.ui, sender, self.charsets, self.test)
435 ) 435 )
436 436
437 msg[r'X-Hg-Notification'] = r'changeset %s' % ctx 437 msg[r'X-Hg-Notification'] = r'changeset %s' % ctx
438 if not msg[r'Message-Id']: 438 if not msg[r'Message-Id']:
439 msg[r'Message-Id'] = messageid(ctx, self.domain, self.messageidseed) 439 msg[r'Message-Id'] = messageid(ctx, self.domain, self.messageidseed)
440 msg[r'To'] = encoding.strfromlocal(', '.join(sorted(subs))) 440 msg[r'To'] = encoding.strfromlocal(b', '.join(sorted(subs)))
441 441
442 msgtext = encoding.strtolocal(msg.as_string()) 442 msgtext = encoding.strtolocal(msg.as_string())
443 if self.test: 443 if self.test:
444 self.ui.write(msgtext) 444 self.ui.write(msgtext)
445 if not msgtext.endswith('\n'): 445 if not msgtext.endswith(b'\n'):
446 self.ui.write('\n') 446 self.ui.write(b'\n')
447 else: 447 else:
448 self.ui.status( 448 self.ui.status(
449 _('notify: sending %d subscribers %d changes\n') 449 _(b'notify: sending %d subscribers %d changes\n')
450 % (len(subs), count) 450 % (len(subs), count)
451 ) 451 )
452 mail.sendmail( 452 mail.sendmail(
453 self.ui, 453 self.ui,
454 stringutil.email(msg[r'From']), 454 stringutil.email(msg[r'From']),
457 mbox=self.mbox, 457 mbox=self.mbox,
458 ) 458 )
459 459
460 def diff(self, ctx, ref=None): 460 def diff(self, ctx, ref=None):
461 461
462 maxdiff = int(self.ui.config('notify', 'maxdiff')) 462 maxdiff = int(self.ui.config(b'notify', b'maxdiff'))
463 prev = ctx.p1().node() 463 prev = ctx.p1().node()
464 if ref: 464 if ref:
465 ref = ref.node() 465 ref = ref.node()
466 else: 466 else:
467 ref = ctx.node() 467 ref = ctx.node()
468 diffopts = patch.diffallopts(self.ui) 468 diffopts = patch.diffallopts(self.ui)
469 diffopts.showfunc = self.showfunc 469 diffopts.showfunc = self.showfunc
470 chunks = patch.diff(self.repo, prev, ref, opts=diffopts) 470 chunks = patch.diff(self.repo, prev, ref, opts=diffopts)
471 difflines = ''.join(chunks).splitlines() 471 difflines = b''.join(chunks).splitlines()
472 472
473 if self.ui.configbool('notify', 'diffstat'): 473 if self.ui.configbool(b'notify', b'diffstat'):
474 maxdiffstat = int(self.ui.config('notify', 'maxdiffstat')) 474 maxdiffstat = int(self.ui.config(b'notify', b'maxdiffstat'))
475 s = patch.diffstat(difflines) 475 s = patch.diffstat(difflines)
476 # s may be nil, don't include the header if it is 476 # s may be nil, don't include the header if it is
477 if s: 477 if s:
478 if maxdiffstat >= 0 and s.count("\n") > maxdiffstat + 1: 478 if maxdiffstat >= 0 and s.count(b"\n") > maxdiffstat + 1:
479 s = s.split("\n") 479 s = s.split(b"\n")
480 msg = _('\ndiffstat (truncated from %d to %d lines):\n\n') 480 msg = _(b'\ndiffstat (truncated from %d to %d lines):\n\n')
481 self.ui.write(msg % (len(s) - 2, maxdiffstat)) 481 self.ui.write(msg % (len(s) - 2, maxdiffstat))
482 self.ui.write("\n".join(s[:maxdiffstat] + s[-2:])) 482 self.ui.write(b"\n".join(s[:maxdiffstat] + s[-2:]))
483 else: 483 else:
484 self.ui.write(_('\ndiffstat:\n\n%s') % s) 484 self.ui.write(_(b'\ndiffstat:\n\n%s') % s)
485 485
486 if maxdiff == 0: 486 if maxdiff == 0:
487 return 487 return
488 elif maxdiff > 0 and len(difflines) > maxdiff: 488 elif maxdiff > 0 and len(difflines) > maxdiff:
489 msg = _('\ndiffs (truncated from %d to %d lines):\n\n') 489 msg = _(b'\ndiffs (truncated from %d to %d lines):\n\n')
490 self.ui.write(msg % (len(difflines), maxdiff)) 490 self.ui.write(msg % (len(difflines), maxdiff))
491 difflines = difflines[:maxdiff] 491 difflines = difflines[:maxdiff]
492 elif difflines: 492 elif difflines:
493 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines)) 493 self.ui.write(_(b'\ndiffs (%d lines):\n\n') % len(difflines))
494 494
495 self.ui.write("\n".join(difflines)) 495 self.ui.write(b"\n".join(difflines))
496 496
497 497
498 def hook(ui, repo, hooktype, node=None, source=None, **kwargs): 498 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
499 '''send email notifications to interested subscribers. 499 '''send email notifications to interested subscribers.
500 500
503 503
504 n = notifier(ui, repo, hooktype) 504 n = notifier(ui, repo, hooktype)
505 ctx = repo.unfiltered()[node] 505 ctx = repo.unfiltered()[node]
506 506
507 if not n.subs: 507 if not n.subs:
508 ui.debug('notify: no subscribers to repository %s\n' % n.root) 508 ui.debug(b'notify: no subscribers to repository %s\n' % n.root)
509 return 509 return
510 if n.skipsource(source): 510 if n.skipsource(source):
511 ui.debug('notify: changes have source "%s" - skipping\n' % source) 511 ui.debug(b'notify: changes have source "%s" - skipping\n' % source)
512 return 512 return
513 513
514 ui.pushbuffer() 514 ui.pushbuffer()
515 data = '' 515 data = b''
516 count = 0 516 count = 0
517 author = '' 517 author = b''
518 if hooktype == 'changegroup' or hooktype == 'outgoing': 518 if hooktype == b'changegroup' or hooktype == b'outgoing':
519 for rev in repo.changelog.revs(start=ctx.rev()): 519 for rev in repo.changelog.revs(start=ctx.rev()):
520 if n.node(repo[rev]): 520 if n.node(repo[rev]):
521 count += 1 521 count += 1
522 if not author: 522 if not author:
523 author = repo[rev].user() 523 author = repo[rev].user()
524 else: 524 else:
525 data += ui.popbuffer() 525 data += ui.popbuffer()
526 ui.note( 526 ui.note(
527 _('notify: suppressing notification for merge %d:%s\n') 527 _(b'notify: suppressing notification for merge %d:%s\n')
528 % (rev, repo[rev].hex()[:12]) 528 % (rev, repo[rev].hex()[:12])
529 ) 529 )
530 ui.pushbuffer() 530 ui.pushbuffer()
531 if count: 531 if count:
532 n.diff(ctx, repo['tip']) 532 n.diff(ctx, repo[b'tip'])
533 elif ctx.rev() in repo: 533 elif ctx.rev() in repo:
534 if not n.node(ctx): 534 if not n.node(ctx):
535 ui.popbuffer() 535 ui.popbuffer()
536 ui.note( 536 ui.note(
537 _('notify: suppressing notification for merge %d:%s\n') 537 _(b'notify: suppressing notification for merge %d:%s\n')
538 % (ctx.rev(), ctx.hex()[:12]) 538 % (ctx.rev(), ctx.hex()[:12])
539 ) 539 )
540 return 540 return
541 count += 1 541 count += 1
542 n.diff(ctx) 542 n.diff(ctx)
543 if not author: 543 if not author:
544 author = ctx.user() 544 author = ctx.user()
545 545
546 data += ui.popbuffer() 546 data += ui.popbuffer()
547 fromauthor = ui.config('notify', 'fromauthor') 547 fromauthor = ui.config(b'notify', b'fromauthor')
548 if author and fromauthor: 548 if author and fromauthor:
549 data = '\n'.join(['From: %s' % author, data]) 549 data = b'\n'.join([b'From: %s' % author, data])
550 550
551 if count: 551 if count:
552 n.send(ctx, count, data) 552 n.send(ctx, count, data)
553 553
554 554
557 host = domain 557 host = domain
558 else: 558 else:
559 host = encoding.strtolocal(socket.getfqdn()) 559 host = encoding.strtolocal(socket.getfqdn())
560 if messageidseed: 560 if messageidseed:
561 messagehash = hashlib.sha512(ctx.hex() + messageidseed) 561 messagehash = hashlib.sha512(ctx.hex() + messageidseed)
562 messageid = '<hg.%s@%s>' % (messagehash.hexdigest()[:64], host) 562 messageid = b'<hg.%s@%s>' % (messagehash.hexdigest()[:64], host)
563 else: 563 else:
564 messageid = '<hg.%s.%d.%d@%s>' % ( 564 messageid = b'<hg.%s.%d.%d@%s>' % (
565 ctx, 565 ctx,
566 int(time.time()), 566 int(time.time()),
567 hash(ctx.repo().root), 567 hash(ctx.repo().root),
568 host, 568 host,
569 ) 569 )