hgext/patchbomb.py
changeset 12200 aebb39d45500
parent 12199 17d604e522b4
child 12201 5bfa45651cf6
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: