comparison hgext/patchbomb.py @ 12200:aebb39d45500

patchbomb: let diffstat prompt only once with complete summary This changes the behaviour of --diffstat. Before the user was asked for confirmation of each patch with its description and diffstat, and a final summary. Now there is only one prompt right before sending with a final summary which does not include the patch descriptions, but the message details and the diffstats: Final summary: From: sender To: recipient(s) Cc: (if present) Bcc: (if present) Reply-To: (if present) Subject: [patch 0 of x [flags]] intro (if present) a | 28 ++++++++++++++++++++++++++++ b | 15 +++++++++++++++ Subject: [patch 1 of x [flags]] subject a | 28 ++++++++++++++++++++++++++++ [ ... ] are you sure you want to send (yn)?
author Christian Ebert <blacktrash@gmx.net>
date Fri, 10 Sep 2010 15:32:14 +0200
parents 17d604e522b4
children 5bfa45651cf6
comparison
equal deleted inserted replaced
12199:17d604e522b4 12200:aebb39d45500
20 20
21 Each message refers to the first in the series using the In-Reply-To 21 Each message refers to the first in the series using the In-Reply-To
22 and References headers, so they will show up as a sequence in threaded 22 and References headers, so they will show up as a sequence in threaded
23 mail and news readers, and in mail archives. 23 mail and news readers, and in mail archives.
24 24
25 With the -d/--diffstat option, you will be prompted for each changeset 25 With the -d/--diffstat option, you will be presented with a final
26 with a diffstat summary and the changeset summary, so you can be sure 26 summary of all messages and asked for confirmation before the messages
27 you are sending the right changes. 27 are sent.
28 28
29 To configure other defaults, add a section like this to your hgrc 29 To configure other defaults, add a section like this to your hgrc
30 file:: 30 file::
31 31
32 [email] 32 [email]
92 return r 92 return r
93 if default is not None: 93 if default is not None:
94 return default 94 return default
95 ui.warn(_('Please enter a valid value.\n')) 95 ui.warn(_('Please enter a valid value.\n'))
96 96
97 def cdiffstat(ui, summary, patchlines):
98 s = patch.diffstat(patchlines)
99 if summary:
100 ui.write(summary, '\n')
101 ui.write(s, '\n')
102 if ui.promptchoice(_('does the diffstat above look okay (yn)?'),
103 (_('&Yes'), _('&No'))):
104 raise util.Abort(_('diffstat rejected'))
105 return s
106
107 def introneeded(opts, number): 97 def introneeded(opts, number):
108 '''is an introductory message required?''' 98 '''is an introductory message required?'''
109 return number > 1 or opts.get('intro') or opts.get('desc') 99 return number > 1 or opts.get('intro') or opts.get('desc')
110 100
111 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, 101 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total,
138 if patchlines: 128 if patchlines:
139 patchlines.pop(0) 129 patchlines.pop(0)
140 while patchlines and not patchlines[0].strip(): 130 while patchlines and not patchlines[0].strip():
141 patchlines.pop(0) 131 patchlines.pop(0)
142 132
133 ds = patch.diffstat(patchlines)
143 if opts.get('diffstat'): 134 if opts.get('diffstat'):
144 body += cdiffstat(ui, '\n'.join(desc), patchlines) + '\n\n' 135 body += ds + '\n\n'
145 136
146 if opts.get('attach') or opts.get('inline'): 137 if opts.get('attach') or opts.get('inline'):
147 msg = email.MIMEMultipart.MIMEMultipart() 138 msg = email.MIMEMultipart.MIMEMultipart()
148 if body: 139 if body:
149 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) 140 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
179 else: 170 else:
180 tlen = len(str(total)) 171 tlen = len(str(total))
181 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj) 172 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
182 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) 173 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
183 msg['X-Mercurial-Node'] = node 174 msg['X-Mercurial-Node'] = node
184 return msg, subj 175 return msg, subj, ds
185 176
186 def patchbomb(ui, repo, *revs, **opts): 177 def patchbomb(ui, repo, *revs, **opts):
187 '''send changesets by email 178 '''send changesets by email
188 179
189 By default, diffs are sent in the format generated by 180 By default, diffs are sent in the format generated by
349 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches)) 340 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
350 subj += ' ' + (opts.get('subject') or 341 subj += ' ' + (opts.get('subject') or
351 prompt(ui, 'Subject: ', rest=subj)) 342 prompt(ui, 'Subject: ', rest=subj))
352 343
353 body = '' 344 body = ''
354 if opts.get('diffstat'): 345 ds = patch.diffstat(jumbo)
355 d = cdiffstat(ui, _('Final summary:\n'), jumbo) 346 if ds and opts.get('diffstat'):
356 if d: 347 body = '\n' + ds
357 body = '\n' + d
358 348
359 body = getdescription(body, sender) 349 body = getdescription(body, sender)
360 msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) 350 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
361 msg['Subject'] = mail.headencode(ui, subj, _charsets, 351 msg['Subject'] = mail.headencode(ui, subj, _charsets,
362 opts.get('test')) 352 opts.get('test'))
363 353
364 msgs.insert(0, (msg, subj)) 354 msgs.insert(0, (msg, subj, ds))
365 return msgs 355 return msgs
366 356
367 def getbundlemsgs(bundle): 357 def getbundlemsgs(bundle):
368 subj = (opts.get('subject') 358 subj = (opts.get('subject')
369 or prompt(ui, 'Subject:', 'A bundle for your repository')) 359 or prompt(ui, 'Subject:', 'A bundle for your repository'))
378 datapart.add_header('Content-Disposition', 'attachment', 368 datapart.add_header('Content-Disposition', 'attachment',
379 filename=bundlename) 369 filename=bundlename)
380 email.Encoders.encode_base64(datapart) 370 email.Encoders.encode_base64(datapart)
381 msg.attach(datapart) 371 msg.attach(datapart)
382 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) 372 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
383 return [(msg, subj)] 373 return [(msg, subj, None)]
384 374
385 sender = (opts.get('from') or ui.config('email', 'from') or 375 sender = (opts.get('from') or ui.config('email', 'from') or
386 ui.config('patchbomb', 'from') or 376 ui.config('patchbomb', 'from') or
387 prompt(ui, 'From', ui.username())) 377 prompt(ui, 'From', ui.username()))
388 378
391 elif bundle: 381 elif bundle:
392 msgs = getbundlemsgs(getbundle(dest)) 382 msgs = getbundlemsgs(getbundle(dest))
393 else: 383 else:
394 msgs = getpatchmsgs(list(getpatches(revs))) 384 msgs = getpatchmsgs(list(getpatches(revs)))
395 385
386 showaddrs = []
387
396 def getaddrs(opt, prpt=None, default=None): 388 def getaddrs(opt, prpt=None, default=None):
397 addrs = opts.get(opt.replace('-', '_')) 389 addrs = opts.get(opt.replace('-', '_'))
390 if opt != 'reply-to':
391 showaddr = '%s:' % opt.capitalize()
392 else:
393 showaddr = 'Reply-To:'
394
398 if addrs: 395 if addrs:
396 showaddrs.append('%s %s' % (showaddr, ', '.join(addrs)))
399 return mail.addrlistencode(ui, addrs, _charsets, 397 return mail.addrlistencode(ui, addrs, _charsets,
400 opts.get('test')) 398 opts.get('test'))
401 399
402 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or '' 400 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or ''
403 if not addrs and prpt: 401 if not addrs and prpt:
404 addrs = prompt(ui, prpt, default) 402 addrs = prompt(ui, prpt, default)
403
404 if addrs:
405 showaddr = '%s %s' % (showaddr, addrs)
406 showaddrs.append(showaddr)
405 407
406 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test')) 408 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
407 409
408 to = getaddrs('to', 'To') 410 to = getaddrs('to', 'To')
409 cc = getaddrs('cc', 'Cc', '') 411 cc = getaddrs('cc', 'Cc', '')
410 bcc = getaddrs('bcc') 412 bcc = getaddrs('bcc')
411 replyto = getaddrs('reply-to') 413 replyto = getaddrs('reply-to')
414
415 if opts.get('diffstat'):
416 ui.write(_('\nFinal summary:\n\n'))
417 ui.write('From: %s\n' % sender)
418 for addr in showaddrs:
419 ui.write('%s\n' % addr)
420 for m, subj, ds in msgs:
421 ui.write('Subject: %s\n' % subj)
422 if ds:
423 ui.write(ds)
424 ui.write('\n')
425 if ui.promptchoice(_('are you sure you want to send (yn)?'),
426 (_('&Yes'), _('&No'))):
427 raise util.Abort(_('patchbomb canceled'))
412 428
413 ui.write('\n') 429 ui.write('\n')
414 430
415 parent = opts.get('in_reply_to') or None 431 parent = opts.get('in_reply_to') or None
416 # angle brackets may be omitted, they're not semantically part of the msg-id 432 # angle brackets may be omitted, they're not semantically part of the msg-id
423 first = True 439 first = True
424 440
425 sender_addr = email.Utils.parseaddr(sender)[1] 441 sender_addr = email.Utils.parseaddr(sender)[1]
426 sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) 442 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
427 sendmail = None 443 sendmail = None
428 for m, subj in msgs: 444 for m, subj, ds in msgs:
429 try: 445 try:
430 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) 446 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
431 except TypeError: 447 except TypeError:
432 m['Message-Id'] = genmsgid('patchbomb') 448 m['Message-Id'] = genmsgid('patchbomb')
433 if parent: 449 if parent: