diff options
Diffstat (limited to 'hgext/patchbomb.py')
-rw-r--r-- | hgext/patchbomb.py | 197 |
1 files changed, 95 insertions, 102 deletions
diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py index 7ac8e27..dfc3cb0 100644 --- a/hgext/patchbomb.py +++ b/hgext/patchbomb.py @@ -45,28 +45,36 @@ directly from the commandline. See the [email] and [smtp] sections in hgrc(5) for details. ''' -import os, errno, socket, tempfile, cStringIO +import os, errno, socket, tempfile, cStringIO, time import email.MIMEMultipart, email.MIMEBase import email.Utils, email.Encoders, email.Generator -from mercurial import cmdutil, commands, hg, mail, patch, util +from mercurial import cmdutil, commands, hg, mail, patch, util, discovery from mercurial import scmutil from mercurial.i18n import _ from mercurial.node import bin cmdtable = {} command = cmdutil.command(cmdtable) -testedwith = 'internal' def prompt(ui, prompt, default=None, rest=':'): + if not ui.interactive() and default is None: + raise util.Abort(_("%s Please enter a valid value" % (prompt + rest))) if default: prompt += ' [%s]' % default - return ui.prompt(prompt + rest, default) + prompt += rest + while True: + r = ui.prompt(prompt, default=default) + if r: + return r + if default is not None: + return default + ui.warn(_('Please enter a valid value.\n')) -def introwanted(opts, number): - '''is an introductory message apparently wanted?''' +def introneeded(opts, number): + '''is an introductory message required?''' return number > 1 or opts.get('intro') or opts.get('desc') -def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, +def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, patchname=None): desc = [] @@ -85,7 +93,7 @@ def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, if not patchname and not node: raise ValueError - if opts.get('attach') and not opts.get('body'): + if opts.get('attach'): body = ('\n'.join(desc[1:]).strip() or 'Patch subject is complete summary.') body += '\n\n\n' @@ -102,16 +110,11 @@ def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, if opts.get('diffstat'): body += ds + '\n\n' - addattachment = opts.get('attach') or opts.get('inline') - if not addattachment or opts.get('body'): - body += '\n'.join(patchlines) - - if addattachment: + if opts.get('attach') or opts.get('inline'): msg = email.MIMEMultipart.MIMEMultipart() if body: msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) - p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', - opts.get('test')) + p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', opts.get('test')) binnode = bin(node) # if node is mq patch, it will have the patch file's name as a tag if not patchname: @@ -121,8 +124,7 @@ def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, patchname = patchtags[0] elif total > 1: patchname = cmdutil.makefilename(repo, '%b-%n.patch', - binnode, seqno=idx, - total=total) + binnode, seqno=idx, total=total) else: patchname = cmdutil.makefilename(repo, '%b.patch', binnode) disposition = 'inline' @@ -131,6 +133,7 @@ def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, p['Content-Disposition'] = disposition + '; filename=' + patchname msg.attach(p) else: + body += '\n'.join(patchlines) msg = mail.mimetextpatch(body, display=opts.get('test')) flag = ' '.join(opts.get('flag')) @@ -138,7 +141,7 @@ def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, flag = ' ' + flag subj = desc[0].strip().rstrip('. ') - if not numbered: + if not introneeded(opts, total): subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj) else: tlen = len(str(total)) @@ -148,7 +151,6 @@ def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, return msg, subj, ds emailopts = [ - ('', 'body', None, _('send patches as inline message text (default)')), ('a', 'attach', None, _('send patches as attachments')), ('i', 'inline', None, _('send patches as inline attachments')), ('', 'bcc', [], _('email addresses of blind carbon copy recipients')), @@ -206,9 +208,7 @@ def patchbomb(ui, repo, *revs, **opts): By default the patch is included as text in the email body for easy reviewing. Using the -a/--attach option will instead create an attachment for the patch. With -i/--inline an inline attachment - will be created. You can include a patch both as text in the email - body and as a regular or an inline attachment by combining the - -a/--attach or -i/--inline with the --body option. + will be created. With -o/--outgoing, emails will be generated for patches not found in the destination repository (or only those which are ancestors @@ -273,18 +273,18 @@ def patchbomb(ui, repo, *revs, **opts): def getoutgoing(dest, revs): '''Return the revisions present locally but not in dest''' - url = ui.expandpath(dest or 'default-push', dest or 'default') - url = hg.parseurl(url)[0] - ui.status(_('comparing with %s\n') % util.hidepassword(url)) - - revs = [r for r in scmutil.revrange(repo, revs) if r >= 0] - if not revs: - revs = [len(repo) - 1] - revs = repo.revs('outgoing(%s) and ::%ld', dest or '', revs) - if not revs: + dest = ui.expandpath(dest or 'default-push', dest or 'default') + dest, branches = hg.parseurl(dest) + revs, checkout = hg.addbranchrevs(repo, repo, branches, revs) + other = hg.peer(repo, opts, dest) + ui.status(_('comparing with %s\n') % util.hidepassword(dest)) + common, _anyinc, _heads = discovery.findcommonincoming(repo, other) + nodes = revs and map(repo.lookup, revs) or revs + o = repo.changelog.findmissing(common, heads=nodes) + if not o: ui.status(_("no changes found\n")) return [] - return [str(r) for r in revs] + return [str(repo.changelog.rev(r)) for r in o] def getpatches(revs): for r in scmutil.revrange(repo, revs): @@ -305,7 +305,7 @@ def patchbomb(ui, repo, *revs, **opts): finally: try: os.unlink(tmpfn) - except OSError: + except: pass os.rmdir(tmpdir) @@ -352,66 +352,51 @@ def patchbomb(ui, repo, *revs, **opts): ui.write(_('\nWrite the introductory message for the ' 'patch series.\n\n')) body = ui.edit(body, sender) - # Save series description in case sendmail fails + # Save serie description in case sendmail fails msgfile = repo.opener('last-email.txt', 'wb') msgfile.write(body) msgfile.close() return body def getpatchmsgs(patches, patchnames=None): + jumbo = [] msgs = [] - ui.write(_('this patch series consists of %d patches.\n\n') + ui.write(_('This patch series consists of %d patches.\n\n') % len(patches)) - # build the intro message, or skip it if the user declines - if introwanted(opts, len(patches)): - msg = makeintro(patches) - if msg: - msgs.append(msg) - - # are we going to send more than one message? - numbered = len(msgs) + len(patches) > 1 - - # now generate the actual patch messages name = None for i, p in enumerate(patches): + jumbo.extend(p) if patchnames: name = patchnames[i] msg = makepatch(ui, repo, p, opts, _charsets, i + 1, - len(patches), numbered, name) + len(patches), name) msgs.append(msg) - return msgs - - def makeintro(patches): - tlen = len(str(len(patches))) + if introneeded(opts, len(patches)): + tlen = len(str(len(patches))) - flag = opts.get('flag') or '' - if flag: - flag = ' ' + ' '.join(flag) - prefix = '[PATCH %0*d of %d%s]' % (tlen, 0, len(patches), flag) - - subj = (opts.get('subject') or - prompt(ui, '(optional) Subject: ', rest=prefix, default='')) - if not subj: - return None # skip intro if the user doesn't bother + flag = ' '.join(opts.get('flag')) + if flag: + subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag) + else: + subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches)) + subj += ' ' + (opts.get('subject') or + prompt(ui, 'Subject: ', rest=subj)) - subj = prefix + ' ' + subj + body = '' + ds = patch.diffstat(jumbo) + if ds and opts.get('diffstat'): + body = '\n' + ds - body = '' - if opts.get('diffstat'): - # generate a cumulative diffstat of the whole patch series - diffstat = patch.diffstat(sum(patches, [])) - body = '\n' + diffstat - else: - diffstat = None + body = getdescription(body, sender) + msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) + msg['Subject'] = mail.headencode(ui, subj, _charsets, + opts.get('test')) - body = getdescription(body, sender) - msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) - msg['Subject'] = mail.headencode(ui, subj, _charsets, - opts.get('test')) - return (msg, subj, diffstat) + msgs.insert(0, (msg, subj, ds)) + return msgs def getbundlemsgs(bundle): subj = (opts.get('subject') @@ -444,33 +429,29 @@ def patchbomb(ui, repo, *revs, **opts): showaddrs = [] - def getaddrs(header, ask=False, default=None): - configkey = header.lower() - opt = header.replace('-', '_').lower() - addrs = opts.get(opt) + def getaddrs(opt, prpt=None, default=None): + addrs = opts.get(opt.replace('-', '_')) + if opt != 'reply-to': + showaddr = '%s:' % opt.capitalize() + else: + showaddr = 'Reply-To:' + if addrs: - showaddrs.append('%s: %s' % (header, ', '.join(addrs))) + showaddrs.append('%s %s' % (showaddr, ', '.join(addrs))) return mail.addrlistencode(ui, addrs, _charsets, opts.get('test')) - # not on the command line: fallback to config and then maybe ask - addr = (ui.config('email', configkey) or - ui.config('patchbomb', configkey) or - '') - if not addr and ask: - addr = prompt(ui, header, default=default) - if addr: - showaddrs.append('%s: %s' % (header, addr)) - return mail.addrlistencode(ui, [addr], _charsets, opts.get('test')) - else: - return default + addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or '' + if not addrs and prpt: + addrs = prompt(ui, prpt, default) + + if addrs: + showaddrs.append('%s %s' % (showaddr, addrs)) + return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test')) - to = getaddrs('To', ask=True) - if not to: - # we can get here in non-interactive mode - raise util.Abort(_('no recipient addresses provided')) - cc = getaddrs('Cc', ask=True, default='') or [] - bcc = getaddrs('Bcc') or [] - replyto = getaddrs('Reply-To') + to = getaddrs('to', 'To') + cc = getaddrs('cc', 'Cc', '') + bcc = getaddrs('bcc') + replyto = getaddrs('reply-to') if opts.get('diffstat') or opts.get('confirm'): ui.write(_('\nFinal summary:\n\n')) @@ -526,7 +507,7 @@ def patchbomb(ui, repo, *revs, **opts): if replyto: m['Reply-To'] = ', '.join(replyto) if opts.get('test'): - ui.status(_('displaying '), subj, ' ...\n') + ui.status(_('Displaying '), subj, ' ...\n') ui.flush() if 'PAGER' in os.environ and not ui.plain(): fp = util.popen(os.environ['PAGER'], 'w') @@ -541,18 +522,30 @@ def patchbomb(ui, repo, *revs, **opts): raise if fp is not ui: fp.close() + elif mbox: + ui.status(_('Writing '), subj, ' ...\n') + ui.progress(_('writing'), i, item=subj, total=len(msgs)) + fp = open(mbox, 'In-Reply-To' in m and 'ab+' or 'wb+') + generator = email.Generator.Generator(fp, mangle_from_=True) + # Should be time.asctime(), but Windows prints 2-characters day + # of month instead of one. Make them print the same thing. + date = time.strftime('%a %b %d %H:%M:%S %Y', + time.localtime(start_time[0])) + fp.write('From %s %s\n' % (sender_addr, date)) + generator.flatten(m, 0) + fp.write('\n\n') + fp.close() else: if not sendmail: - sendmail = mail.connect(ui, mbox=mbox) - ui.status(_('sending '), subj, ' ...\n') + sendmail = mail.connect(ui) + ui.status(_('Sending '), subj, ' ...\n') ui.progress(_('sending'), i, item=subj, total=len(msgs)) - if not mbox: - # Exim does not remove the Bcc field - del m['Bcc'] + # Exim does not remove the Bcc field + del m['Bcc'] fp = cStringIO.StringIO() generator = email.Generator.Generator(fp, mangle_from_=False) generator.flatten(m, 0) - sendmail(sender_addr, to + bcc + cc, fp.getvalue()) + sendmail(sender, to + bcc + cc, fp.getvalue()) ui.progress(_('writing'), None) ui.progress(_('sending'), None) |