summaryrefslogtreecommitdiff
path: root/hgext/patchbomb.py
diff options
context:
space:
mode:
Diffstat (limited to 'hgext/patchbomb.py')
-rw-r--r--hgext/patchbomb.py197
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)