##// END OF EJS Templates
phases: fix performance regression with Python 2...
phases: fix performance regression with Python 2 Unlike Python 3, xrange doesn't support efficient "in" and uses a linear time scan instead. Expand the condition to handle it fast. Differential Revision: https://phab.mercurial-scm.org/D9072

File last commit:

r45824:8cce9f77 default
r46127:29a259be default
Show More
patchbomb.py
979 lines | 30.9 KiB | text/x-python | PythonLexer
Martin Geisler
patchbomb: add copyright and license header
r8252 # patchbomb.py - sending Mercurial changesets as patch emails
#
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
#
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Martin Geisler
patchbomb: add copyright and license header
r8252
Dirkjan Ochtman
extensions: change descriptions for hook-providing extensions...
r8935 '''command to send changesets as (a series of) patch emails
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Martin Geisler
patchbomb: word-wrap help texts at 70 characters
r7997 The series is started off with a "[PATCH 0 of N]" introduction, which
describes the series as a whole.
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 Each patch email has a Subject line of "[PATCH M of N] ...", using the
first line of the changeset description as the subject text. The
message contains two or three body parts:
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Martin Geisler
patchbomb: use a list instead of indented paragraphs
r9300 - The changeset description.
- [Optional] The result of running diffstat on the patch.
Martin Geisler
Use hg role in help strings
r10973 - The patch itself, as generated by :hg:`export`.
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 Each message refers to the first in the series using the In-Reply-To
and References headers, so they will show up as a sequence in threaded
mail and news readers, and in mail archives.
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Martin Geisler
patchbomb, help/hgweb: do not refer to config files as hgrc files
r13838 To configure other defaults, add a section like this to your
configuration file::
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
[email]
from = My Name <my@email>
to = recipient1, recipient2, ...
cc = cc1, cc2, ...
bcc = bcc1, bcc2, ...
Cédric Duval
patchbomb: Reply-To support...
r11150 reply-to = address1, address2, ...
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Christian Ebert
patchbomb: document [patchbomb] config section for addresses
r10284 Use ``[patchbomb]`` as configuration section name if you need to
override global ``[email]`` address settings.
Martin Geisler
Use hg role in help strings
r10973 Then you can use the :hg:`email` command to mail a series of
changesets as a patchbomb.
Dirkjan Ochtman
convert comments to docstrings in a bunch of extensions
r6666
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 You can also either configure the method option in the email section
to be a sendmail compatible mailer or fill out the [smtp] section so
that the patchbomb extension can automatically send patchbombs
directly from the commandline. See the [email] and [smtp] sections in
hgrc(5) for details.
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487
Bryan O'Sullivan
patchbomb: treat empty address list as no addresses...
r27697 By default, :hg:`email` will prompt for a ``To`` or ``CC`` header if
you do not supply one via configuration or the command line. You can
override this to never prompt by configuring an empty value::
[email]
cc =
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 You can control the default inclusion of an introduction message with the
``patchbomb.intro`` configuration option. The configuration is always
overwritten by command line flags like --intro and --desc::
[patchbomb]
intro=auto # include introduction message if more than 1 patch (default)
intro=never # never include an introduction message
intro=always # always include an introduction message
Pierre-Yves David
patchbomb: introduce a 'patchbomb.confirm' option...
r23488
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 You can specify a template for flags to be added in subject prefixes. Flags
specified by --flag option are exported as ``{flags}`` keyword::
[patchbomb]
flagtemplate = "{separate(' ',
ifeq(branch, 'default', '', branch|upper),
flags)}"
Pierre-Yves David
patchbomb: introduce a 'patchbomb.confirm' option...
r23488 You can set patchbomb to always ask for confirmation by setting
``patchbomb.confirm`` to true.
Martin Geisler
patchbomb: wrapped docstrings at 78 characters
r9071 '''
timeless
patchbomb: use absolute_import
r28415 from __future__ import absolute_import
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Augie Fackler
patchbomb: use email.encoders instead of email.Encoders...
r39067 import email.encoders as emailencoders
Pulkit Goyal
patchbomb: use email.mime.base instead of email.MIMEBase...
r38491 import email.mime.base as emimebase
Pulkit Goyal
patchbomb: use email.mime.multipart instead of email.MIMEMultipart...
r38490 import email.mime.multipart as emimemultipart
Pulkit Goyal
py3: use email.utils module instead of email.Utils...
r36466 import email.utils as eutil
timeless
patchbomb: use absolute_import
r28415 import errno
import os
import socket
Augie Fackler
python2.4: fix imports of sub-packages of the email package...
r19810
Yuya Nishihara
py3: move up symbol imports to enforce import-checker rules...
r29205 from mercurial.i18n import _
Gregory Szorc
py3: manually import pycompat.open into files that need it...
r43355 from mercurial.pycompat import open
timeless
patchbomb: use absolute_import
r28415 from mercurial import (
cmdutil,
commands,
Pulkit Goyal
py3: use encoding.strtolocal() to convert str to bytes...
r36467 encoding,
timeless
patchbomb: use absolute_import
r28415 error,
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 formatter,
timeless
patchbomb: use absolute_import
r28415 hg,
mail,
node as nodemod,
patch,
Pulkit Goyal
py3: handle keyword arguments correctly in hgext/patchbomb.py...
r35035 pycompat,
Yuya Nishihara
registrar: move cmdutil.command to registrar module (API)...
r32337 registrar,
timeless
patchbomb: use absolute_import
r28415 scmutil,
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 templater,
timeless
patchbomb: use absolute_import
r28415 util,
)
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 from mercurial.utils import dateutil
Augie Fackler
formatting: blacken the codebase...
r43346
timeless
pycompat: switch to util.stringio for py3 compat
r28861 stringio = util.stringio
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309 cmdtable = {}
Yuya Nishihara
registrar: move cmdutil.command to registrar module (API)...
r32337 command = registrar.command(cmdtable)
Boris Feld
configitems: register the 'patchbomb.bundletype' config
r34113
configtable = {}
configitem = registrar.configitem(configtable)
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'bundletype', default=None,
Boris Feld
configitems: register the 'patchbomb.bundletype' config
r34113 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'bcc', default=None,
Boris Feld
configitems: register the 'patchbomb.bcc' config
r34761 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'cc', default=None,
Boris Feld
configitems: register the 'patchbomb.cc' config
r34762 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'confirm', default=False,
Boris Feld
configitems: register the 'patchbomb.confirm' config
r34114 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'flagtemplate', default=None,
Boris Feld
configitems: register the 'patchbomb.flagtemplate' config
r34115 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'from', default=None,
Boris Feld
configitems: register the 'patchbomb.from' config
r34116 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'intro', default=b'auto',
Boris Feld
configitems: register the 'patchbomb.intro' config
r34117 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'publicurl', default=None,
Boris Feld
configitems: register the 'patchbomb.publicurl' config
r34118 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'reply-to', default=None,
Boris Feld
configitems: register the 'patchbomb.reply-to' config
r34763 )
Augie Fackler
formatting: blacken the codebase...
r43346 configitem(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'patchbomb', b'to', default=None,
Yuya Nishihara
configitems: register 'email.to' and 'patchbomb.to'
r34912 )
Boris Feld
configitems: register the 'patchbomb.bundletype' config
r34113
Augie Fackler
extensions: change magic "shipped with hg" string...
r29841 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
Augie Fackler
extensions: document that `testedwith = 'internal'` is special...
r25186 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 testedwith = b'ships-with-hg-core'
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: add experimental config of a "pullurl" and export it...
r26546 def _addpullheader(seq, ctx):
"""Add a header pointing to a public URL where the changeset is available
"""
repo = ctx.repo()
# experimental config: patchbomb.publicurl
# waiting for some logic that check that the changeset are available on the
# destination before patchbombing anything.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 publicurl = repo.ui.config(b'patchbomb', b'publicurl')
Augie Fackler
patchbomb: look for non-empty publicurl, not a non-None one...
r32825 if publicurl:
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 return b'Available At %s\n# hg pull %s -r %s' % (
Augie Fackler
formatting: blacken the codebase...
r43346 publicurl,
publicurl,
ctx,
)
Pierre-Yves David
patchbomb: add experimental config of a "pullurl" and export it...
r26546 return None
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: add experimental config of a "pullurl" and export it...
r26546 def uisetup(ui):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cmdutil.extraexport.append(b'pullurl')
cmdutil.extraexportmap[b'pullurl'] = _addpullheader
Pierre-Yves David
patchbomb: add experimental config of a "pullurl" and export it...
r26546
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
repovfs: add a ward to check if locks are properly taken...
r33436 def reposetup(ui, repo):
if not repo.local():
return
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo._wlockfreeprefix.add(b'last-email.txt')
Pierre-Yves David
patchbomb: add experimental config of a "pullurl" and export it...
r26546
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 def prompt(ui, prompt, default=None, rest=b':'):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if default:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 prompt += b' [%s]' % default
Matt Mackall
patchbomb: drop loop in prompt...
r15166 return ui.prompt(prompt + rest, default)
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 def introwanted(ui, opts, number):
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 '''is an introductory message apparently wanted?'''
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 introconfig = ui.config(b'patchbomb', b'intro')
if opts.get(b'intro') or opts.get(b'desc'):
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 intro = True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif introconfig == b'always':
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 intro = True
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif introconfig == b'never':
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 intro = False
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif introconfig == b'auto':
Martin von Zweigbergk
cleanup: some Yoda conditions, this patch removes...
r40065 intro = number > 1
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 else:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.write_err(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'warning: invalid patchbomb.intro value "%s"\n') % introconfig
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write_err(_(b'(should be one of always, never, auto)\n'))
Martin von Zweigbergk
cleanup: some Yoda conditions, this patch removes...
r40065 intro = number > 1
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 return intro
Cédric Duval
patchbomb: --desc implies --intro...
r10734
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 def _formatflags(ui, repo, rev, flags):
"""build flag string optionally by template"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 tmpl = ui.config(b'patchbomb', b'flagtemplate')
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 if not tmpl:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b' '.join(flags)
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 out = util.stringio()
Martin von Zweigbergk
templatespec: create a factory function for each type there is...
r45824 spec = formatter.literal_templatespec(templater.unquotestring(tmpl))
Yuya Nishihara
formatter: pass in template spec to templateformatter as argument...
r43369 with formatter.templateformatter(ui, out, b'patchbombflag', {}, spec) as fm:
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 fm.startitem()
fm.context(ctx=repo[rev])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fm.write(b'flags', b'%s', fm.formatlist(flags, name=b'flag'))
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 return out.getvalue()
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
patchbomb: pass around ui and revs that are needed for flag template...
r31186 def _formatprefix(ui, repo, rev, flags, idx, total, numbered):
Yuya Nishihara
patchbomb: factor out function that builds a prefix string to patch subject...
r31183 """build prefix to patch subject"""
Yuya Nishihara
patchbomb: add config knob to generate flags by template (issue5354)...
r31187 flag = _formatflags(ui, repo, rev, flags)
Yuya Nishihara
patchbomb: factor out function that builds a prefix string to patch subject...
r31183 if flag:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 flag = b' ' + flag
Yuya Nishihara
patchbomb: factor out function that builds a prefix string to patch subject...
r31183
if not numbered:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b'[PATCH%s]' % flag
Yuya Nishihara
patchbomb: factor out function that builds a prefix string to patch subject...
r31183 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 tlen = len(b"%d" % total)
return b'[PATCH %0*d of %d%s]' % (tlen, idx, total, flag)
Yuya Nishihara
patchbomb: factor out function that builds a prefix string to patch subject...
r31183
Augie Fackler
formatting: blacken the codebase...
r43346
def makepatch(
ui,
repo,
rev,
patchlines,
opts,
_charsets,
idx,
total,
numbered,
patchname=None,
):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
desc = []
node = None
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 body = b''
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Martin Geisler
patchbomb: rename argument to avoid shadowing patch module
r12199 for line in patchlines:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if line.startswith(b'#'):
if line.startswith(b'# Node ID'):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 node = line.split()[-1]
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if line.startswith(b'diff -r') or line.startswith(b'diff --git'):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 break
desc.append(line)
if not patchname and not node:
raise ValueError
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'attach') and not opts.get(b'body'):
Augie Fackler
formatting: blacken the codebase...
r43346 body = (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'\n'.join(desc[1:]).strip()
or b'Patch subject is complete summary.'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 body += b'\n\n\n'
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'plain'):
while patchlines and patchlines[0].startswith(b'# '):
Martin Geisler
patchbomb: rename argument to avoid shadowing patch module
r12199 patchlines.pop(0)
if patchlines:
patchlines.pop(0)
while patchlines and not patchlines[0].strip():
patchlines.pop(0)
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Henning Schild
patch: remove unused git parameter from patch.diffstat()...
r30407 ds = patch.diffstat(patchlines)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'diffstat'):
body += ds + b'\n\n'
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 addattachment = opts.get(b'attach') or opts.get(b'inline')
if not addattachment or opts.get(b'body'):
body += b'\n'.join(patchlines)
Angel Ezquerra
patchbomb: add --body flag to send patches as inline message body text...
r16307
if addattachment:
Pulkit Goyal
patchbomb: use email.mime.multipart instead of email.MIMEMultipart...
r38490 msg = emimemultipart.MIMEMultipart()
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if body:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get(b'test')))
Augie Fackler
formatting: blacken the codebase...
r43346 p = mail.mimetextpatch(
Denis Laxalde
mail: use a native string for "subtype" value...
r44026 b'\n'.join(patchlines), 'x-patch', opts.get(b'test')
Augie Fackler
formatting: blacken the codebase...
r43346 )
timeless
patchbomb: use absolute_import
r28415 binnode = nodemod.bin(node)
timeless
Generally replace "file name" with "filename" in help and comments.
r8761 # if node is mq patch, it will have the patch file's name as a tag
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if not patchname:
Augie Fackler
formatting: blacken the codebase...
r43346 patchtags = [
t
for t in repo.nodetags(binnode)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if t.endswith(b'.patch') or t.endswith(b'.diff')
Augie Fackler
formatting: blacken the codebase...
r43346 ]
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if patchtags:
patchname = patchtags[0]
elif total > 1:
Augie Fackler
formatting: blacken the codebase...
r43346 patchname = cmdutil.makefilename(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo[node], b'%b-%n.patch', seqno=idx, total=total
Augie Fackler
formatting: blacken the codebase...
r43346 )
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 patchname = cmdutil.makefilename(repo[node], b'%b.patch')
Augie Fackler
patchbomb: use native strings when determining attachment disposition...
r39073 disposition = r'inline'
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'attach'):
Augie Fackler
patchbomb: use native strings when determining attachment disposition...
r39073 disposition = r'attachment'
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 p['Content-Disposition'] = (
disposition + '; filename=' + encoding.strfromlocal(patchname)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 msg.attach(p)
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = mail.mimetextpatch(body, display=opts.get(b'test'))
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Augie Fackler
formatting: blacken the codebase...
r43346 prefix = _formatprefix(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, repo, rev, opts.get(b'flag'), idx, total, numbered
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 subj = desc[0].strip().rstrip(b'. ')
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 if not numbered:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 subj = b' '.join([prefix, opts.get(b'subject') or subj])
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 subj = b' '.join([prefix, subj])
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get(b'test'))
msg['X-Mercurial-Node'] = pycompat.sysstr(node)
msg['X-Mercurial-Series-Index'] = '%i' % idx
msg['X-Mercurial-Series-Total'] = '%i' % total
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 return msg, subj, ds
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: extract 'getpatches' closure in its own function...
r23210 def _getpatches(repo, revs, **opts):
"""return a list of patches for a list of revisions
Each patch in the list is itself a list of lines.
"""
ui = repo.ui
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 prev = repo[b'.'].rev()
Yuya Nishihara
patchbomb: factor out scmutil.revrange() calls...
r24568 for r in revs:
Pierre-Yves David
patchbomb: extract 'getpatches' closure in its own function...
r23210 if r == prev and (repo[None].files() or repo[None].deleted()):
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 ui.warn(_(b'warning: working directory has uncommitted changes\n'))
timeless
pycompat: switch to util.stringio for py3 compat
r28861 output = stringio()
Augie Fackler
formatting: blacken the codebase...
r43346 cmdutil.exportfile(
repo, [r], output, opts=patch.difffeatureopts(ui, opts, git=True)
)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield output.getvalue().split(b'\n')
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: extract 'getbundle' closure in its own function...
r23211 def _getbundle(repo, dest, **opts):
"""return a bundle containing changesets missing in "dest"
The `opts` keyword-arguments are the same as the one accepted by the
`bundle` command.
The bundle is a returned as a single in-memory binary blob.
"""
ui = repo.ui
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 tmpdir = pycompat.mkdtemp(prefix=b'hg-email-bundle-')
tmpfn = os.path.join(tmpdir, b'bundle')
btype = ui.config(b'patchbomb', b'bundletype')
Pierre-Yves David
patchbomb: add a 'bundletype' config under 'patchbomb'...
r26563 if btype:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 opts['type'] = btype
Pierre-Yves David
patchbomb: extract 'getbundle' closure in its own function...
r23211 try:
commands.bundle(ui, repo, tmpfn, dest, **opts)
Bryan O'Sullivan
patchbomb: replace file I/O with util.readfile
r27767 return util.readfile(tmpfn)
Pierre-Yves David
patchbomb: extract 'getbundle' closure in its own function...
r23211 finally:
try:
os.unlink(tmpfn)
except OSError:
pass
os.rmdir(tmpdir)
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: extract 'getdescription' closure in its own function...
r23212 def _getdescription(repo, defaultbody, sender, **opts):
"""obtain the body of the introduction message and return it
This is also used for the body of email with an attached bundle.
The body can be obtained either from the command line option or entered by
the user through the editor.
"""
ui = repo.ui
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 if opts.get('desc'):
body = open(opts.get('desc')).read()
Pierre-Yves David
patchbomb: extract 'getdescription' closure in its own function...
r23212 else:
Augie Fackler
formatting: blacken the codebase...
r43346 ui.write(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'\nWrite the introductory message for the patch series.\n\n')
Augie Fackler
formatting: blacken the codebase...
r43346 )
body = ui.edit(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 defaultbody, sender, repopath=repo.path, action=b'patchbombbody'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
patchbomb: extract 'getdescription' closure in its own function...
r23212 # Save series description in case sendmail fails
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msgfile = repo.vfs(b'last-email.txt', b'wb')
Pierre-Yves David
patchbomb: extract 'getdescription' closure in its own function...
r23212 msgfile.write(body)
msgfile.close()
return body
Pierre-Yves David
patchbomb: extract 'getbundle' closure in its own function...
r23211
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 def _getbundlemsgs(repo, sender, bundle, **opts):
"""Get the full email for sending a given bundle
This function returns a list of "email" tuples (subject, content, None).
The list is always one message long in that case.
"""
ui = repo.ui
_charsets = mail._charsets(ui)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 subj = opts.get('subject') or prompt(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, b'Subject:', b'A bundle for your repository'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 body = _getdescription(repo, b'', sender, **opts)
Pulkit Goyal
patchbomb: use email.mime.multipart instead of email.MIMEMultipart...
r38490 msg = emimemultipart.MIMEMultipart()
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 if body:
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
datapart = emimebase.MIMEBase('application', 'x-mercurial-bundle')
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 datapart.set_payload(bundle)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 bundlename = b'%s.hg' % opts.get('bundlename', b'bundle')
Augie Fackler
formatting: blacken the codebase...
r43346 datapart.add_header(
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 'Content-Disposition',
'attachment',
Augie Fackler
formatting: blacken the codebase...
r43346 filename=encoding.strfromlocal(bundlename),
)
Augie Fackler
patchbomb: use email.encoders instead of email.Encoders...
r39067 emailencoders.encode_base64(datapart)
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 msg.attach(datapart)
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 return [(msg, subj, None)]
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
patchbomb: pass around ui and revs that are needed for flag template...
r31186 def _makeintro(repo, sender, revs, patches, **opts):
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214 """make an introduction email, asking the user for content if needed
email is returned as (subject, body, cumulative-diffstat)"""
ui = repo.ui
_charsets = mail._charsets(ui)
Yuya Nishihara
patchbomb: pass around ui and revs that are needed for flag template...
r31186 # use the last revision which is likely to be a bookmarked head
Augie Fackler
formatting: blacken the codebase...
r43346 prefix = _formatprefix(
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 ui, repo, revs.last(), opts.get('flag'), 0, len(patches), numbered=True
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 subj = opts.get('subject') or prompt(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, b'(optional) Subject: ', rest=prefix, default=b''
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214 if not subj:
Augie Fackler
formatting: blacken the codebase...
r43346 return None # skip intro if the user doesn't bother
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 subj = prefix + b' ' + subj
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 body = b''
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 if opts.get('diffstat'):
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214 # generate a cumulative diffstat of the whole patch series
diffstat = patch.diffstat(sum(patches, []))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 body = b'\n' + diffstat
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214 else:
diffstat = None
body = _getdescription(repo, body, sender, **opts)
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214 return (msg, subj, diffstat)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
patchbomb: build patch texts by _getpatchmsgs()...
r31185 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts):
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 """return a list of emails from a list of patches
This involves introduction message creation if necessary.
This function returns a list of "email" tuples (subject, content, None).
"""
Pulkit Goyal
py3: handle keyword arguments correctly in hgext/patchbomb.py...
r35035 bytesopts = pycompat.byteskwargs(opts)
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 ui = repo.ui
_charsets = mail._charsets(ui)
Yuya Nishihara
patchbomb: build patch texts by _getpatchmsgs()...
r31185 patches = list(_getpatches(repo, revs, **opts))
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 msgs = []
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(_(b'this patch series consists of %d patches.\n\n') % len(patches))
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215
# build the intro message, or skip it if the user declines
Pulkit Goyal
py3: handle keyword arguments correctly in hgext/patchbomb.py...
r35035 if introwanted(ui, bytesopts, len(patches)):
Yuya Nishihara
patchbomb: pass around ui and revs that are needed for flag template...
r31186 msg = _makeintro(repo, sender, revs, patches, **opts)
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 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
Yuya Nishihara
patchbomb: pass around ui and revs that are needed for flag template...
r31186 assert len(revs) == len(patches)
for i, (r, p) in enumerate(zip(revs, patches)):
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 if patchnames:
name = patchnames[i]
Augie Fackler
formatting: blacken the codebase...
r43346 msg = makepatch(
ui,
repo,
r,
p,
bytesopts,
_charsets,
i + 1,
len(patches),
numbered,
name,
)
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 msgs.append(msg)
return msgs
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
patchbomb: extract 'getoutgoing' closure into its own function...
r23486 def _getoutgoing(repo, dest, revs):
'''Return the revisions present locally but not in dest'''
ui = repo.ui
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 url = ui.expandpath(dest or b'default-push', dest or b'default')
Pierre-Yves David
patchbomb: extract 'getoutgoing' closure into its own function...
r23486 url = hg.parseurl(url)[0]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.status(_(b'comparing with %s\n') % util.hidepassword(url))
Pierre-Yves David
patchbomb: extract 'getoutgoing' closure into its own function...
r23486
Yuya Nishihara
patchbomb: factor out scmutil.revrange() calls...
r24568 revs = [r for r in revs if r >= 0]
Pierre-Yves David
patchbomb: extract 'getoutgoing' closure into its own function...
r23486 if not revs:
Boris Feld
patchbomb: use 'tiprev' when appropriate...
r35692 revs = [repo.changelog.tiprev()]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 revs = repo.revs(b'outgoing(%s) and ::%ld', dest or b'', revs)
Pierre-Yves David
patchbomb: extract 'getoutgoing' closure into its own function...
r23486 if not revs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.status(_(b"no changes found\n"))
Yuya Nishihara
patchbomb: return outgoing revs as a smartset...
r24567 return revs
Pierre-Yves David
patchbomb: extract 'getoutgoing' closure into its own function...
r23486
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
patchbomb: extract function for generating message-id...
r39153 def _msgid(node, timestamp):
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 try:
hostname = encoding.strfromlocal(encoding.environ[b'HGHOSTNAME'])
except KeyError:
hostname = socket.getfqdn()
return '<%s.%d@%s>' % (node, timestamp, hostname)
Augie Fackler
patchbomb: extract function for generating message-id...
r39153
Augie Fackler
formatting: blacken the codebase...
r43346
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309 emailopts = [
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (b'', b'body', None, _(b'send patches as inline message text (default)')),
(b'a', b'attach', None, _(b'send patches as attachments')),
(b'i', b'inline', None, _(b'send patches as inline attachments')),
Augie Fackler
formatting: blacken the codebase...
r43346 (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'bcc',
[],
_(b'email addresses of blind carbon copy recipients'),
_(b'EMAIL'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (b'c', b'cc', [], _(b'email addresses of copy recipients'), _(b'EMAIL')),
(b'', b'confirm', None, _(b'ask for confirmation before sending')),
(b'd', b'diffstat', None, _(b'add diffstat output to messages')),
Augie Fackler
formatting: blacken the codebase...
r43346 (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'date',
b'',
_(b'use the given date as the sending date'),
_(b'DATE'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'desc',
b'',
_(b'use the given file as the series description'),
_(b'FILE'),
),
(b'f', b'from', b'', _(b'email address of sender'), _(b'EMAIL')),
(b'n', b'test', None, _(b'print messages that would be sent')),
(
b'm',
b'mbox',
b'',
_(b'write messages to mbox file instead of sending them'),
_(b'FILE'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'reply-to',
[],
_(b'email addresses replies should be sent to'),
_(b'EMAIL'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (
b's',
b'subject',
b'',
_(b'subject of first message (intro or single patch)'),
_(b'TEXT'),
),
(
b'',
b'in-reply-to',
b'',
_(b'message identifier to reply to'),
_(b'MSGID'),
),
(b'', b'flag', [], _(b'flags to add in subject prefixes'), _(b'FLAG')),
(b't', b'to', [], _(b'email addresses of recipients'), _(b'EMAIL')),
Augie Fackler
formatting: blacken the codebase...
r43346 ]
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309
Augie Fackler
formatting: blacken the codebase...
r43346 @command(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'email',
Augie Fackler
formatting: blacken the codebase...
r43346 [
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (b'g', b'git', None, _(b'use git extended diff format')),
(b'', b'plain', None, _(b'omit hg patch header')),
Augie Fackler
formatting: blacken the codebase...
r43346 (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'o',
b'outgoing',
Augie Fackler
formatting: blacken the codebase...
r43346 None,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'send changes not found in the target repository'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'b',
b'bundle',
Augie Fackler
formatting: blacken the codebase...
r43346 None,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'send changes not in target as a binary bundle'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'B',
b'bookmark',
b'',
_(b'send changes only reachable by given bookmark'),
_(b'BOOKMARK'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'bundlename',
b'bundle',
_(b'name of the bundle attachment file'),
_(b'NAME'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (b'r', b'rev', [], _(b'a revision to send'), _(b'REV')),
Augie Fackler
formatting: blacken the codebase...
r43346 (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'force',
Augie Fackler
formatting: blacken the codebase...
r43346 None,
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'run even when remote repository is unrelated '
b'(with -b/--bundle)'
Augie Fackler
formatting: blacken the codebase...
r43346 ),
),
(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'',
b'base',
Augie Fackler
formatting: blacken the codebase...
r43346 [],
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'a base changeset to specify instead of a destination '
b'(with -b/--bundle)'
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'REV'),
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (
b'',
b'intro',
None,
_(b'send an introduction email for a single patch'),
),
Augie Fackler
formatting: blacken the codebase...
r43346 ]
+ emailopts
+ cmdutil.remoteopts,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'hg email [OPTION]... [DEST]...'),
Augie Fackler
formatting: blacken the codebase...
r43346 helpcategory=command.CATEGORY_IMPORT_EXPORT,
)
timeless
patchbomb: rename email function...
r27150 def email(ui, repo, *revs, **opts):
John Goerzen
Slight refining to help text in patchbomb.py
r4283 '''send changesets by email
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Martin Geisler
Use our custom hg reStructuredText role some more...
r11193 By default, diffs are sent in the format generated by
:hg:`export`, one per message. The series starts with a "[PATCH 0
of N]" introduction, which describes the series as a whole.
Vadim Gelfer
add documentation for email command.
r1672
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 Each patch email has a Subject line of "[PATCH M of N] ...", using
the first line of the changeset description as the subject text.
The message contains two or three parts. First, the changeset
timeless
patchbomb: move command option help from the extension (patchbomb) to the command (email)
r12749 description.
With the -d/--diffstat option, if the diffstat program is
installed, the result of running diffstat on the patch is inserted.
Finally, the patch itself, as generated by :hg:`export`.
Julian Cowley
patchbomb: -c is not an alias for the --confirm option...
r17880 With the -d/--diffstat or --confirm options, you will be presented
timeless
patchbomb: move command option help from the extension (patchbomb) to the command (email)
r12749 with a final summary of all messages and asked for confirmation before
the messages are sent.
Brendan Cully
Add --outgoing option to patchbomb
r4262
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 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
Angel Ezquerra
patchbomb: add --body flag to send patches as inline message body text...
r16307 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.
Martin Geisler
patchbomb: describe --attach and --inline options in help
r8472
David Demelier
patchbomb: add -B option to select a bookmark...
r32639 With -B/--bookmark changesets reachable by the given bookmark are
selected.
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 With -o/--outgoing, emails will be generated for patches not found
in the destination repository (or only those which are ancestors
of the specified revisions if any are provided)
John Goerzen
Improve documentation for patchbomb and email
r4280
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 With -b/--bundle, changesets are selected as for --outgoing, but a
single email containing a binary Mercurial bundle as an attachment
Pierre-Yves David
patchbomb: add a 'bundletype' config under 'patchbomb'...
r26563 will be sent. Use the ``patchbomb.bundletype`` config option to
control the bundle type as with :hg:`bundle --type`.
John Goerzen
Improve documentation for patchbomb and email
r4280
timeless
patchbomb: move command option help from the extension (patchbomb) to the command (email)
r12749 With -m/--mbox, instead of previewing each patchbomb message in a
pager or sending the messages directly, it will create a UNIX
mailbox file with the patch emails. This mailbox file can be
previewed with any mail user agent which supports UNIX mbox
files.
With -n/--test, all steps will run, but mail will not be sent.
You will be prompted for an email recipient address, a subject and
an introductory message describing the patches of your patchbomb.
Yuya Nishihara
patchbomb: use modern pager to display -n/--test result (BC)...
r31489 Then when all is done, patchbomb messages are displayed.
timeless
patchbomb: move command option help from the extension (patchbomb) to the command (email)
r12749
Nicolas Dumazet
patchbomb: save introductory message in .hg/last-email.txt
r13198 In case email sending fails, you will find a backup of your series
Pierre-Yves David
patchbomb: introduce a 'patchbomb.confirm' option...
r23488 introductory message in ``.hg/last-email.txt``.
The default behavior of this command can be customized through
configuration. (See :hg:`help patchbomb` for details)
Nicolas Dumazet
patchbomb: save introductory message in .hg/last-email.txt
r13198
Christian Ebert
patchbomb: reST syntax for literal blocks in help text
r9289 Examples::
John Goerzen
Improve documentation for patchbomb and email
r4280
Christian Ebert
patchbomb: reST syntax for literal blocks in help text
r9289 hg email -r 3000 # send patch 3000 only
hg email -r 3000 -r 3001 # send patches 3000 and 3001
hg email -r 3000:3005 # send patches 3000 through 3005
hg email 3000 # send patch 3000 (deprecated)
John Goerzen
Improve documentation for patchbomb and email
r4280
Christian Ebert
patchbomb: reST syntax for literal blocks in help text
r9289 hg email -o # send all patches not in default
hg email -o DEST # send all patches not in DEST
hg email -o -r 3000 # send all ancestors of 3000 not in default
hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
John Goerzen
Improve documentation for patchbomb and email
r4280
David Demelier
patchbomb: add -B option to select a bookmark...
r32639 hg email -B feature # send all ancestors of feature bookmark
Christian Ebert
patchbomb: reST syntax for literal blocks in help text
r9289 hg email -b # send bundle of all patches not in default
hg email -b DEST # send bundle of all patches not in DEST
hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
John Goerzen
Improve documentation for patchbomb and email
r4280
timeless
patchbomb: move command option help from the extension (patchbomb) to the command (email)
r12749 hg email -o -m mbox && # generate an mbox file...
mutt -R -f mbox # ... and view it with mutt
hg email -o -m mbox && # generate an mbox file ...
Martin Geisler
patchbomb: fix stray backslash in docstring...
r12839 formail -s sendmail \\ # ... and use formail to send from the mbox
timeless
patchbomb: move command option help from the extension (patchbomb) to the command (email)
r12749 -bm -t < mbox # ... using sendmail
Martin Geisler
patchbomb: wrap docstrings at 70 characters
r9269 Before using this command, you will need to enable email in your
hgrc. See the [email] section in hgrc(5) for details.
Brendan Cully
Add --outgoing option to patchbomb
r4262 '''
Pulkit Goyal
py3: handle keyword arguments correctly in hgext/patchbomb.py...
r35035 opts = pycompat.byteskwargs(opts)
Brendan Cully
Add --outgoing option to patchbomb
r4262
Christian Ebert
patchbomb: mime-encode headers and parts not containing patches...
r7115 _charsets = mail._charsets(ui)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 bundle = opts.get(b'bundle')
date = opts.get(b'date')
mbox = opts.get(b'mbox')
outgoing = opts.get(b'outgoing')
rev = opts.get(b'rev')
bookmark = opts.get(b'bookmark')
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not (opts.get(b'test') or mbox):
Christian Ebert
Catch smtp exceptions
r5472 # really sending
Bryan O'Sullivan
patchbomb: Validate email config before we start prompting for info.
r4489 mail.validateconfig(ui)
David Demelier
patchbomb: add -B option to select a bookmark...
r32639 if not (revs or rev or outgoing or bundle or bookmark):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(
_(b'specify at least one changeset with -B, -r or -o')
)
Bryan O'Sullivan
patchbomb: Fail early if no revs given to email
r4493
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if outgoing and bundle:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.Abort(
_(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"--outgoing mode always on with --bundle;"
b" do not re-specify --outgoing"
Augie Fackler
formatting: blacken the codebase...
r43346 )
)
Martin von Zweigbergk
patchbomb: use cmdutil.check_at_most_one_arg()...
r44348 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
John Goerzen
Add ability to send bundles to patchbomb extension
r4278
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if outgoing or bundle:
Brendan Cully
Add --outgoing option to patchbomb
r4262 if len(revs) > 1:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"too many destinations"))
Jordi Gutiérrez Hermoso
style: kill ersatz if-else ternary operators...
r24306 if revs:
dest = revs[0]
else:
dest = None
Brendan Cully
Add --outgoing option to patchbomb
r4262 revs = []
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if rev:
Brendan Cully
Add --outgoing option to patchbomb
r4262 if revs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'use only one form to specify the revision'))
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 revs = rev
David Demelier
patchbomb: add -B option to select a bookmark...
r32639 elif bookmark:
if bookmark not in repo._bookmarks:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
David Demelier
scmutil: move repair.stripbmrevset as scmutil.bookmarkrevs (API)
r38146 revs = scmutil.bookmarkrevs(repo, bookmark)
Brendan Cully
Add --outgoing option to patchbomb
r4262
Yuya Nishihara
patchbomb: factor out scmutil.revrange() calls...
r24568 revs = scmutil.revrange(repo, revs)
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if outgoing:
Yuya Nishihara
patchbomb: factor out scmutil.revrange() calls...
r24568 revs = _getoutgoing(repo, dest, revs)
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if bundle:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 opts[b'revs'] = [b"%d" % r for r in revs]
Brendan Cully
Add --outgoing option to patchbomb
r4262
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 # check if revision exist on the public destination
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 publicurl = repo.ui.config(b'patchbomb', b'publicurl')
Augie Fackler
patchbomb: look for non-empty publicurl, not a non-None one...
r32825 if publicurl:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 repo.ui.debug(b'checking that revision exist in the public repo\n')
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 try:
publicpeer = hg.peer(repo, {}, publicurl)
except error.RepoError:
Augie Fackler
formatting: blacken the codebase...
r43346 repo.ui.write_err(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'unable to access public repo: %s\n') % publicurl
Augie Fackler
formatting: blacken the codebase...
r43346 )
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 raise
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not publicpeer.capable(b'known'):
repo.ui.debug(b'skipping existence checks: public repo too old\n')
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 else:
out = [repo[r] for r in revs]
known = publicpeer.known(h.node() for h in out)
missing = []
for idx, h in enumerate(out):
if not known[idx]:
missing.append(h)
if missing:
Martin von Zweigbergk
cleanup: some Yoda conditions, this patch removes...
r40065 if len(missing) > 1:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = _(b'public "%s" is missing %s and %i others')
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 msg %= (publicurl, missing[0], len(missing) - 1)
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = _(b'public url %s is missing %s')
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 msg %= (publicurl, missing[0])
Gregory Szorc
patchbomb: resolve revs before evaluating %ld revset...
r36428 missingrevs = [ctx.rev() for ctx in missing]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 revhint = b' '.join(
b'-r %s' % h for h in repo.set(b'heads(%ld)', missingrevs)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 hint = _(b"use 'hg push %s %s'") % (publicurl, revhint)
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 raise error.Abort(msg, hint=hint)
Brendan Cully
Add --outgoing option to patchbomb
r4262 # start
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if date:
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 start_time = dateutil.parsedate(date)
Bryan O'Sullivan
patchbomb: add --date option
r4566 else:
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 start_time = dateutil.makedate()
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
def genmsgid(id):
Augie Fackler
patchbomb: extract function for generating message-id...
r39153 return _msgid(id[:20], int(start_time[0]))
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Matt Mackall
patchbomb: mark ancient option deprecated...
r25825 # deprecated config: patchbomb.from
Augie Fackler
formatting: blacken the codebase...
r43346 sender = (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 opts.get(b'from')
or ui.config(b'email', b'from')
or ui.config(b'patchbomb', b'from')
or prompt(ui, b'From', ui.username())
Augie Fackler
formatting: blacken the codebase...
r43346 )
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Yuya Nishihara
patchbomb: drop internal option for pbranch extension (API)...
r31184 if bundle:
Pulkit Goyal
py3: handle keyword arguments correctly in hgext/patchbomb.py...
r35035 stropts = pycompat.strkwargs(opts)
bundledata = _getbundle(repo, dest, **stropts)
bundleopts = stropts.copy()
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 bundleopts.pop('bundle', None) # already processed
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
John Goerzen
Add ability to send bundles to patchbomb extension
r4278 else:
Pulkit Goyal
py3: handle keyword arguments correctly in hgext/patchbomb.py...
r35035 msgs = _getpatchmsgs(repo, sender, revs, **pycompat.strkwargs(opts))
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 showaddrs = []
Greg Ward
patchbomb: simplify some contorted logic and odd variable names.
r15162 def getaddrs(header, ask=False, default=None):
configkey = header.lower()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 opt = header.replace(b'-', b'_').lower()
Greg Ward
patchbomb: simplify some contorted logic and odd variable names.
r15162 addrs = opts.get(opt)
Cédric Duval
patchbomb: Reply-To support...
r11150 if addrs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 showaddrs.append(b'%s: %s' % (header, b', '.join(addrs)))
return mail.addrlistencode(ui, addrs, _charsets, opts.get(b'test'))
Marti Raudsepp
patchbomb: fix parsing of multiple addresses, allow multiple addrs in --to/cc/bcc...
r9947
Greg Ward
patchbomb: simplify some contorted logic and odd variable names.
r15162 # not on the command line: fallback to config and then maybe ask
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 addr = ui.config(b'email', configkey) or ui.config(
b'patchbomb', configkey
Augie Fackler
formatting: blacken the codebase...
r43346 )
Bryan O'Sullivan
patchbomb: treat empty address list as no addresses...
r27697 if not addr:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 specified = ui.hasconfig(b'email', configkey) or ui.hasconfig(
b'patchbomb', configkey
Augie Fackler
formatting: blacken the codebase...
r43346 )
Bryan O'Sullivan
patchbomb: treat empty address list as no addresses...
r27697 if not specified and ask:
addr = prompt(ui, header, default=default)
Greg Ward
patchbomb: simplify some contorted logic and odd variable names.
r15162 if addr:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 showaddrs.append(b'%s: %s' % (header, addr))
return mail.addrlistencode(ui, [addr], _charsets, opts.get(b'test'))
Augie Fackler
patchbomb: make getaddrs function easier to work with...
r32826 elif default:
return mail.addrlistencode(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui, [default], _charsets, opts.get(b'test')
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
patchbomb: make getaddrs function easier to work with...
r32826 return []
Marti Raudsepp
patchbomb: fix parsing of multiple addresses, allow multiple addrs in --to/cc/bcc...
r9947
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 to = getaddrs(b'To', ask=True)
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 if not to:
# we can get here in non-interactive mode
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'no recipient addresses provided'))
cc = getaddrs(b'Cc', ask=True, default=b'')
bcc = getaddrs(b'Bcc')
replyto = getaddrs(b'Reply-To')
Christian Ebert
optionally send blind carbon copies...
r2679
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 confirm = ui.configbool(b'patchbomb', b'confirm')
confirm |= bool(opts.get(b'diffstat') or opts.get(b'confirm'))
Pierre-Yves David
patchbomb: introduce a 'patchbomb.confirm' option...
r23488
if confirm:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(_(b'\nFinal summary:\n\n'), label=b'patchbomb.finalsummary')
ui.write((b'From: %s\n' % sender), label=b'patchbomb.from')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 for addr in showaddrs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(b'%s\n' % addr, label=b'patchbomb.to')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 for m, subj, ds in msgs:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write((b'Subject: %s\n' % subj), label=b'patchbomb.subject')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 if ds:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(ds, label=b'patchbomb.diffstats')
ui.write(b'\n')
Augie Fackler
formatting: blacken the codebase...
r43346 if ui.promptchoice(
Martin von Zweigbergk
cleanup: join string literals that are already on one line...
r43387 _(b'are you sure you want to send (yn)?$$ &Yes $$ &No')
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'patchbomb canceled'))
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(b'\n')
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parent = opts.get(b'in_reply_to') or None
Cédric Duval
patchbomb: do not assume the presence of angle brackets around msg-id...
r8826 # angle brackets may be omitted, they're not semantically part of the msg-id
if parent is not None:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 parent = encoding.strfromlocal(parent)
if not parent.startswith('<'):
parent = '<' + parent
if not parent.endswith('>'):
parent += '>'
Cédric Duval
patchbomb: do not assume the presence of angle brackets around msg-id...
r8826
Pulkit Goyal
py3: convert bytes to str using encoding.strfromlocal...
r36468 sender_addr = eutil.parseaddr(encoding.strfromlocal(sender))[1]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 sender = mail.addressencode(ui, sender, _charsets, opts.get(b'test'))
Matt Mackall
patchbomb: undo backout and fix bugs in the earlier patch
r5973 sendmail = None
Augie Fackler
patchbomb: always use message-id of first patch for series-id...
r21726 firstpatch = None
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 progress = ui.makeprogress(
_(b'sending'), unit=_(b'emails'), total=len(msgs)
)
Yuya Nishihara
patchbomb: show progress when sending emails or writing mbox
r12265 for i, (m, subj, ds) in enumerate(msgs):
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 try:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
Augie Fackler
patchbomb: always use message-id of first patch for series-id...
r21726 if not firstpatch:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 firstpatch = m['Message-Id']
m['X-Mercurial-Series-Id'] = firstpatch
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 except TypeError:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['Message-Id'] = genmsgid('patchbomb')
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 if parent:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['In-Reply-To'] = parent
m['References'] = parent
if not parent or 'X-Mercurial-Node' not in m:
parent = m['Message-Id']
Cédric Duval
patchbomb: with --in-reply-to, still thread message under first in series...
r8514
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version().decode()
m['Date'] = eutil.formatdate(start_time[0], localtime=True)
Volker Kleinfeld
patchbomb does not handle email time stamp plattform independent
r2443
Christian Ebert
patchbomb: fix timezone offset in message date header...
r4027 start_time = (start_time[0] + 1, start_time[1])
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['From'] = sender
m['To'] = ', '.join(to)
Christian Ebert
patchbomb: add linebreaks after colons (coding style)
r5785 if cc:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['Cc'] = ', '.join(cc)
Christian Ebert
patchbomb: add linebreaks after colons (coding style)
r5785 if bcc:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['Bcc'] = ', '.join(bcc)
Cédric Duval
patchbomb: Reply-To support...
r11150 if replyto:
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 m['Reply-To'] = ', '.join(replyto)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if opts.get(b'test'):
ui.status(_(b'displaying '), subj, b' ...\n')
ui.pager(b'email')
Denis Laxalde
patchbomb: use mail.Generator alias for py2/py3 compat
r43427 generator = mail.Generator(ui, mangle_from_=False)
Vadim Gelfer
patchbomb: ignore exception if pager quits.
r1871 try:
Denis Laxalde
patchbomb: fix wrong argument type when calling mail generator.flatten()
r44028 generator.flatten(m, False)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.write(b'\n')
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except IOError as inst:
Vadim Gelfer
patchbomb: ignore exception if pager quits.
r1871 if inst.errno != errno.EPIPE:
raise
Mads Kiilerich
mail: mbox handling as a part of mail handling, refactored from patchbomb
r15560 else:
if not sendmail:
Gregory Szorc
mail: unsupport smtp.verifycert (BC)...
r29285 sendmail = mail.connect(ui, mbox=mbox)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.status(_(b'sending '), subj, b' ...\n')
Martin von Zweigbergk
patchbomb: use progress helper...
r38422 progress.update(i, item=subj)
Mads Kiilerich
mail: mbox handling as a part of mail handling, refactored from patchbomb
r15560 if not mbox:
# Exim does not remove the Bcc field
Denis Laxalde
py3: use native strings when forming email headers in patchbomb...
r43978 del m['Bcc']
timeless
pycompat: switch to util.stringio for py3 compat
r28861 fp = stringio()
Denis Laxalde
patchbomb: use mail.Generator alias for py2/py3 compat
r43427 generator = mail.Generator(fp, mangle_from_=False)
Denis Laxalde
patchbomb: fix wrong argument type when calling mail generator.flatten()
r44028 generator.flatten(m, False)
Augie Fackler
patchbomb: python 3 really wants those email addresses in unicode...
r39062 alldests = to + bcc + cc
sendmail(sender_addr, alldests, fp.getvalue())
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Martin von Zweigbergk
patchbomb: use progress helper...
r38422 progress.complete()