##// END OF EJS Templates
journal: new experimental extension...
journal: new experimental extension Records bookmark locations and shows you where bookmarks were located in the past. This is the first in a planned series of locations to be recorded; a future patch will add working copy (dirstate) tracking, and remote bookmarks will be supported as well, so the journal storage format should be fairly generic to support those use-cases.

File last commit:

r29285:63a37491 default
r29443:cf092a3d default
Show More
patchbomb.py
724 lines | 26.6 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
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
timeless
patchbomb: rename email function...
r27150 import email as emailmod
timeless
patchbomb: use absolute_import
r28415 import errno
import os
import socket
import tempfile
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 _
timeless
patchbomb: use absolute_import
r28415 from mercurial import (
cmdutil,
commands,
error,
hg,
mail,
node as nodemod,
patch,
scmutil,
util,
)
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 = {}
command = cmdutil.command(cmdtable)
Augie Fackler
extensions: document that `testedwith = 'internal'` is special...
r25186 # Note for extension authors: ONLY specify testedwith = 'internal' for
# 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
hgext: mark all first-party extensions as such
r16743 testedwith = 'internal'
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309
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.
pullurl = repo.ui.config('patchbomb', 'publicurl')
if pullurl is not None:
return ('Available At %s\n'
'# hg pull %s -r %s' % (pullurl, pullurl, ctx))
return None
def uisetup(ui):
cmdutil.extraexport.append('pullurl')
cmdutil.extraexportmap['pullurl'] = _addpullheader
Alexander Solovyov
patchbomb: accept default if it is empty string...
r9648 def prompt(ui, prompt, default=None, rest=':'):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if default:
prompt += ' [%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
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?'''
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 introconfig = ui.config('patchbomb', 'intro', 'auto')
if opts.get('intro') or opts.get('desc'):
intro = True
elif introconfig == 'always':
intro = True
elif introconfig == 'never':
intro = False
elif introconfig == 'auto':
intro = 1 < number
else:
ui.write_err(_('warning: invalid patchbomb.intro value "%s"\n')
% introconfig)
ui.write_err(_('(should be one of always, never, auto)\n'))
intro = 1 < number
return intro
Cédric Duval
patchbomb: --desc implies --intro...
r10734
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered,
Martin Geisler
patchbomb: rename argument to avoid shadowing patch module
r12199 patchname=None):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
desc = []
node = None
body = ''
Martin Geisler
patchbomb: rename argument to avoid shadowing patch module
r12199 for line in patchlines:
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if line.startswith('#'):
if line.startswith('# Node ID'):
node = line.split()[-1]
continue
if line.startswith('diff -r') or line.startswith('diff --git'):
break
desc.append(line)
if not patchname and not node:
raise ValueError
Angel Ezquerra
patchbomb: add --body flag to send patches as inline message body text...
r16307 if opts.get('attach') and not opts.get('body'):
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 body = ('\n'.join(desc[1:]).strip() or
'Patch subject is complete summary.')
body += '\n\n\n'
if opts.get('plain'):
Martin Geisler
patchbomb: rename argument to avoid shadowing patch module
r12199 while patchlines and patchlines[0].startswith('# '):
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
Idan Kamara
patchbomb: pass --git argument to diffstat
r14397 ds = patch.diffstat(patchlines, git=opts.get('git'))
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if opts.get('diffstat'):
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 body += ds + '\n\n'
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354
Angel Ezquerra
patchbomb: add --body flag to send patches as inline message body text...
r16307 addattachment = opts.get('attach') or opts.get('inline')
if not addattachment or opts.get('body'):
body += '\n'.join(patchlines)
if addattachment:
timeless
patchbomb: rename email function...
r27150 msg = emailmod.MIMEMultipart.MIMEMultipart()
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 if body:
msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
Brodie Rao
cleanup: eradicate long lines
r16683 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch',
opts.get('test'))
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:
patchtags = [t for t in repo.nodetags(binnode)
if t.endswith('.patch') or t.endswith('.diff')]
if patchtags:
patchname = patchtags[0]
elif total > 1:
Matt Mackall
cmdutil: make_filename -> makefilename
r14290 patchname = cmdutil.makefilename(repo, '%b-%n.patch',
Brodie Rao
cleanup: eradicate long lines
r16683 binnode, seqno=idx,
total=total)
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 else:
Matt Mackall
cmdutil: make_filename -> makefilename
r14290 patchname = cmdutil.makefilename(repo, '%b.patch', binnode)
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 disposition = 'inline'
if opts.get('attach'):
disposition = 'attachment'
p['Content-Disposition'] = disposition + '; filename=' + patchname
msg.attach(p)
else:
msg = mail.mimetextpatch(body, display=opts.get('test'))
Nicolas Dumazet
patchbomb: add --flag to put flags in subject prefixes...
r9346 flag = ' '.join(opts.get('flag'))
if flag:
flag = ' ' + flag
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 subj = desc[0].strip().rstrip('. ')
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 if not numbered:
Nicolas Dumazet
patchbomb: add --flag to put flags in subject prefixes...
r9346 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 else:
tlen = len(str(total))
Nicolas Dumazet
patchbomb: add --flag to put flags in subject prefixes...
r9346 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
msg['X-Mercurial-Node'] = node
Pierre-Yves David
patchbomb: includes series information in the header...
r21282 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
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
prev = repo['.'].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()):
ui.warn(_('warning: working directory has '
'uncommitted changes\n'))
timeless
pycompat: switch to util.stringio for py3 compat
r28861 output = stringio()
Pierre-Yves David
patchbomb: extract 'getpatches' closure in its own function...
r23210 cmdutil.export(repo, [r], fp=output,
Siddharth Agarwal
patchbomb: don't honor whitespace and format-changing diffopts (BC)...
r23450 opts=patch.difffeatureopts(ui, opts, git=True))
Pierre-Yves David
patchbomb: extract 'getpatches' closure in its own function...
r23210 yield output.getvalue().split('\n')
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
tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
tmpfn = os.path.join(tmpdir, 'bundle')
Pierre-Yves David
patchbomb: add a 'bundletype' config under 'patchbomb'...
r26563 btype = ui.config('patchbomb', 'bundletype')
if btype:
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)
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
if opts.get('desc'):
body = open(opts.get('desc')).read()
else:
ui.write(_('\nWrite the introductory message for the '
'patch series.\n\n'))
body = ui.edit(defaultbody, sender)
# Save series description in case sendmail fails
Angel Ezquerra
localrepo: remove all external users of localrepo.opener...
r23877 msgfile = repo.vfs('last-email.txt', '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
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)
subj = (opts.get('subject')
or prompt(ui, 'Subject:', 'A bundle for your repository'))
body = _getdescription(repo, '', sender, **opts)
timeless
patchbomb: rename email function...
r27150 msg = emailmod.MIMEMultipart.MIMEMultipart()
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 if body:
msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
timeless
patchbomb: rename email function...
r27150 datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 datapart.set_payload(bundle)
bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
datapart.add_header('Content-Disposition', 'attachment',
filename=bundlename)
timeless
patchbomb: rename email function...
r27150 emailmod.Encoders.encode_base64(datapart)
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 msg.attach(datapart)
msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
return [(msg, subj, None)]
Pierre-Yves David
patchbomb: extract 'makeintro' closure into its own function...
r23214 def _makeintro(repo, sender, patches, **opts):
"""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)
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
subj = prefix + ' ' + subj
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(repo, body, sender, **opts)
msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
msg['Subject'] = mail.headencode(ui, subj, _charsets,
opts.get('test'))
return (msg, subj, diffstat)
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 def _getpatchmsgs(repo, sender, patches, patchnames=None, **opts):
"""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).
"""
ui = repo.ui
_charsets = mail._charsets(ui)
msgs = []
ui.write(_('this patch series consists of %d patches.\n\n')
% len(patches))
# build the intro message, or skip it if the user declines
Pierre-Yves David
patchbomb: add a 'patchbomb.intro' option...
r23487 if introwanted(ui, opts, len(patches)):
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 msg = _makeintro(repo, sender, patches, **opts)
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):
if patchnames:
name = patchnames[i]
msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
len(patches), numbered, name)
msgs.append(msg)
return msgs
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
url = ui.expandpath(dest or 'default-push', dest or 'default')
url = hg.parseurl(url)[0]
ui.status(_('comparing with %s\n') % util.hidepassword(url))
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:
revs = [len(repo) - 1]
revs = repo.revs('outgoing(%s) and ::%ld', dest or '', revs)
if not revs:
ui.status(_("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
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309 emailopts = [
Angel Ezquerra
patchbomb: add --body flag to send patches as inline message body text...
r16307 ('', 'body', None, _('send patches as inline message text (default)')),
Adrian Buehlmann
patchbomb: use cmdutil.command decorator
r14309 ('a', 'attach', None, _('send patches as attachments')),
('i', 'inline', None, _('send patches as inline attachments')),
('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
('c', 'cc', [], _('email addresses of copy recipients')),
('', 'confirm', None, _('ask for confirmation before sending')),
('d', 'diffstat', None, _('add diffstat output to messages')),
('', 'date', '', _('use the given date as the sending date')),
('', 'desc', '', _('use the given file as the series description')),
('f', 'from', '', _('email address of sender')),
('n', 'test', None, _('print messages that would be sent')),
('m', 'mbox', '', _('write messages to mbox file instead of sending them')),
('', 'reply-to', [], _('email addresses replies should be sent to')),
('s', 'subject', '', _('subject of first message (intro or single patch)')),
('', 'in-reply-to', '', _('message identifier to reply to')),
('', 'flag', [], _('flags to add in subject prefixes')),
('t', 'to', [], _('email addresses of recipients'))]
@command('email',
[('g', 'git', None, _('use git extended diff format')),
('', 'plain', None, _('omit hg patch header')),
('o', 'outgoing', None,
_('send changes not found in the target repository')),
('b', 'bundle', None, _('send changes not in target as a binary bundle')),
('', 'bundlename', 'bundle',
_('name of the bundle attachment file'), _('NAME')),
('r', 'rev', [], _('a revision to send'), _('REV')),
('', 'force', None, _('run even when remote repository is unrelated '
'(with -b/--bundle)')),
('', 'base', [], _('a base changeset to specify instead of a destination '
'(with -b/--bundle)'), _('REV')),
('', 'intro', None, _('send an introduction email for a single patch')),
] + emailopts + commands.remoteopts,
_('hg email [OPTION]... [DEST]...'))
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
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.
Then when all is done, patchbomb messages are displayed. If the
PAGER environment variable is set, your pager will be fired up once
for each patchbomb message, so you can verify everything is alright.
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
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 '''
Christian Ebert
patchbomb: mime-encode headers and parts not containing patches...
r7115 _charsets = mail._charsets(ui)
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 bundle = opts.get('bundle')
date = opts.get('date')
mbox = opts.get('mbox')
outgoing = opts.get('outgoing')
rev = opts.get('rev')
# internal option used by pbranches
patches = opts.get('patches')
if not (opts.get('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)
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 if not (revs or rev or outgoing or bundle or patches):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('specify at least one changeset with -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:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("--outgoing mode always on with --bundle;"
Christian Ebert
patchbomb: break lines > 80 chars (coding style)
r5746 " do not re-specify --outgoing"))
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:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("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:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('use only one form to specify the revision'))
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 revs = rev
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:
Yuya Nishihara
patchbomb: return outgoing revs as a smartset...
r24567 opts['revs'] = [str(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
publicurl = repo.ui.config('patchbomb', 'publicurl')
if publicurl is not None:
repo.ui.debug('checking that revision exist in the public repo')
try:
publicpeer = hg.peer(repo, {}, publicurl)
except error.RepoError:
repo.ui.write_err(_('unable to access public repo: %s\n')
% publicurl)
raise
if not publicpeer.capable('known'):
repo.ui.debug('skipping existence checks: public repo too old')
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:
if 1 < len(missing):
msg = _('public "%s" is missing %s and %i others')
msg %= (publicurl, missing[0], len(missing) - 1)
else:
msg = _('public url %s is missing %s')
msg %= (publicurl, missing[0])
timeless
patchbomb: fix public-is-missing hint...
r28907 revhint = ' '.join('-r %s' % h
Pierre-Yves David
patchbomb: check that targets exist at the publicurl...
r26626 for h in repo.set('heads(%ld)', missing))
timeless
patchbomb: use single quotes around command hint...
r28981 hint = _("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:
start_time = util.parsedate(date)
Bryan O'Sullivan
patchbomb: add --date option
r4566 else:
start_time = util.makedate()
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
def genmsgid(id):
Christian Ebert
patchbomb: fix timezone offset in message date header...
r4027 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Matt Mackall
patchbomb: mark ancient option deprecated...
r25825 # deprecated config: patchbomb.from
Christian Ebert
patchbomb: consistently use opts.get
r5818 sender = (opts.get('from') or ui.config('email', 'from') or
Vadim Gelfer
rename [patchbomb] section to [email] section in hgrc. old name still ok.
r2198 ui.config('patchbomb', 'from') or
Dirkjan Ochtman
patchbomb: extract a bunch of nested functions...
r7354 prompt(ui, 'From', ui.username()))
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Peter Arrenbrecht
patchbomb: make `hg email` reusable for other patch sources...
r7353 if patches:
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 msgs = _getpatchmsgs(repo, sender, patches, opts.get('patchnames'),
**opts)
Christian Ebert
patchbomb: reduce number of opts.get calls...
r11413 elif bundle:
Pierre-Yves David
patchbomb: extract 'getbundlemsgs' closure in its own function...
r23213 bundledata = _getbundle(repo, dest, **opts)
bundleopts = opts.copy()
bundleopts.pop('bundle', None) # already processed
msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
John Goerzen
Add ability to send bundles to patchbomb extension
r4278 else:
Pierre-Yves David
patchbomb: extract 'getpatchmsgs' closure into its own function...
r23215 _patches = list(_getpatches(repo, revs, **opts))
msgs = _getpatchmsgs(repo, sender, _patches, **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()
opt = header.replace('-', '_').lower()
addrs = opts.get(opt)
Cédric Duval
patchbomb: Reply-To support...
r11150 if addrs:
Greg Ward
patchbomb: simplify some contorted logic and odd variable names.
r15162 showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
Christian Ebert
patchbomb: consistent code style in getaddrs()
r12264 return mail.addrlistencode(ui, addrs, _charsets, opts.get('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
addr = (ui.config('email', configkey) or
Bryan O'Sullivan
patchbomb: treat empty address list as no addresses...
r27697 ui.config('patchbomb', configkey))
if not addr:
specified = (ui.hasconfig('email', configkey) or
ui.hasconfig('patchbomb', configkey))
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:
showaddrs.append('%s: %s' % (header, addr))
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
else:
return default
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 to = getaddrs('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
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('no recipient addresses provided'))
Greg Ward
patchbomb: make it easy for the user to decline sending an intro message....
r15164 cc = getaddrs('Cc', ask=True, default='') or []
bcc = getaddrs('Bcc') or []
Greg Ward
patchbomb: simplify some contorted logic and odd variable names.
r15162 replyto = getaddrs('Reply-To')
Christian Ebert
optionally send blind carbon copies...
r2679
Pierre-Yves David
patchbomb: introduce a 'patchbomb.confirm' option...
r23488 confirm = ui.configbool('patchbomb', 'confirm')
confirm |= bool(opts.get('diffstat') or opts.get('confirm'))
if confirm:
Pierre-Yves David
patchbomb: add label and color to the confirm output...
r23173 ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
ui.write(('From: %s\n' % sender), label='patchbomb.from')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 for addr in showaddrs:
Pierre-Yves David
patchbomb: add label and color to the confirm output...
r23173 ui.write('%s\n' % addr, label='patchbomb.to')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 for m, subj, ds in msgs:
Pierre-Yves David
patchbomb: add label and color to the confirm output...
r23173 ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 if ds:
Pierre-Yves David
patchbomb: add label and color to the confirm output...
r23173 ui.write(ds, label='patchbomb.diffstats')
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200 ui.write('\n')
Matt Mackall
ui: merge prompt text components into a singe string...
r19226 if ui.promptchoice(_('are you sure you want to send (yn)?'
'$$ &Yes $$ &No')):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('patchbomb canceled'))
Christian Ebert
patchbomb: let diffstat prompt only once with complete summary...
r12200
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 ui.write('\n')
Henrik Stuart <henrik.stuart at edlund.dk>
patchbomb: Support initial in-reply-to header...
r8025 parent = opts.get('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:
if not parent.startswith('<'):
parent = '<' + parent
if not parent.endswith('>'):
parent += '>'
timeless
patchbomb: rename email function...
r27150 sender_addr = emailmod.Utils.parseaddr(sender)[1]
Christian Ebert
patchbomb: mime-encode headers and parts not containing patches...
r7115 sender = mail.addressencode(ui, sender, _charsets, opts.get('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
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:
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:
firstpatch = m['Message-Id']
Augie Fackler
patchbomb: reorder header insertions to clarify code...
r21727 m['X-Mercurial-Series-Id'] = firstpatch
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 except TypeError:
m['Message-Id'] = genmsgid('patchbomb')
if parent:
m['In-Reply-To'] = parent
Benoit Allard
email: add References field in the header...
r7413 m['References'] = parent
Thomas Arendsen Hein
patchbomb: respect --in-reply-to for all mails if no intro message is sent...
r17859 if not parent or 'X-Mercurial-Node' not in m:
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 parent = m['Message-Id']
Cédric Duval
patchbomb: with --in-reply-to, still thread message under first in series...
r8514
Henrik Stuart
patchbomb: add user agent header to mails
r8160 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
timeless
patchbomb: rename email function...
r27150 m['Date'] = emailmod.Utils.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])
Vadim Gelfer
turn patchbomb script into an extension module....
r1669 m['From'] = sender
m['To'] = ', '.join(to)
Christian Ebert
patchbomb: add linebreaks after colons (coding style)
r5785 if cc:
m['Cc'] = ', '.join(cc)
if bcc:
m['Bcc'] = ', '.join(bcc)
Cédric Duval
patchbomb: Reply-To support...
r11150 if replyto:
m['Reply-To'] = ', '.join(replyto)
Christian Ebert
patchbomb: consistently use opts.get
r5818 if opts.get('test'):
Martin Geisler
patchbomb: lowercase status messages
r16931 ui.status(_('displaying '), subj, ' ...\n')
Patrick Mezard
patchbomb: flush ui before delegating to pager.
r4596 ui.flush()
Yuya Nishihara
patchbomb: respect HGPLAIN when piping --test output to PAGER...
r11183 if 'PAGER' in os.environ and not ui.plain():
Dirkjan Ochtman
replace usage of os.popen() with util.popen()...
r6548 fp = util.popen(os.environ['PAGER'], 'w')
Patrick Mezard
patchbomb: page patchbomb messages only if PAGER is defined....
r4599 else:
fp = ui
timeless
patchbomb: rename email function...
r27150 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
Vadim Gelfer
patchbomb: ignore exception if pager quits.
r1871 try:
Benoit Boissinot
patchbomb: Fix mangling of lines beginning with From...
r6447 generator.flatten(m, 0)
Vadim Gelfer
patchbomb: ignore exception if pager quits.
r1871 fp.write('\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
Patrick Mezard
patchbomb: page patchbomb messages only if PAGER is defined....
r4599 if fp is not ui:
fp.close()
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)
Martin Geisler
patchbomb: lowercase status messages
r16931 ui.status(_('sending '), subj, ' ...\n')
av6
patchbomb: specify unit for ui.progress when sending emails
r28474 ui.progress(_('sending'), i, item=subj, total=len(msgs),
unit=_('emails'))
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
del m['Bcc']
timeless
pycompat: switch to util.stringio for py3 compat
r28861 fp = stringio()
timeless
patchbomb: rename email function...
r27150 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
Benoit Boissinot
patchbomb: Fix mangling of lines beginning with From...
r6447 generator.flatten(m, 0)
Mads Kiilerich
patchbomb: minor refactoring of mbox functionality, preparing for move...
r15559 sendmail(sender_addr, to + bcc + cc, fp.getvalue())
Vadim Gelfer
turn patchbomb script into an extension module....
r1669
Yuya Nishihara
patchbomb: show progress when sending emails or writing mbox
r12265 ui.progress(_('writing'), None)
ui.progress(_('sending'), None)