##// END OF EJS Templates
Emacs support: numerous changes....
Emacs support: numerous changes. Most SCM commands now work in derived buffers (e.g. diff viewing buffers) as well as buffers backed by files. diff and log now work properly on repositories and files. Commit support is more solid. Doc strings are better.

File last commit:

r998:c37dd58a default
r1003:6dfc9cc7 default
Show More
patchbomb
248 lines | 7.6 KiB | text/plain | TextLexer
Bryan O'Sullivan
Add patchbomb script.
r875 #!/usr/bin/python
#
# Interactive script for sending a collection of Mercurial changesets
# as a series of patch emails.
#
# The series is started off with a "[PATCH 0 of N]" introduction,
# which describes the series as a whole.
#
# 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:
#
# The remainder of the changeset description.
#
Bryan O'Sullivan
Polish patchbomb script....
r877 # [Optional] If the diffstat program is installed, the result of
# running diffstat on the patch.
Bryan O'Sullivan
Add patchbomb script.
r875 #
# The patch itself, as generated by "hg export".
#
# Each message refers to all of its predecessors 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.
#
# For each changeset, you will be prompted with a diffstat summary and
# the changeset summary, so you can be sure you are sending the right
# changes.
#
# It is best to run this script with the "-n" (test only) flag before
Bryan O'Sullivan
Polish patchbomb script....
r877 # firing it up "for real", in which case it will use your pager to
# display each of the messages that it would send.
Bryan O'Sullivan
Add patchbomb script.
r875 #
# To configure a default mail host, add a section like this to your
# hgrc file:
#
# [smtp]
# host = my_mail_host
# port = 1025
Bryan O'Sullivan
Polish patchbomb script....
r877 #
# To configure other defaults, add a section like this to your hgrc
# file:
#
# [patchbomb]
# from = My Name <my@email>
# to = recipient1, recipient2, ...
# cc = cc1, cc2, ...
Bryan O'Sullivan
Add patchbomb script.
r875
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from mercurial import commands
from mercurial import fancyopts
from mercurial import hg
from mercurial import ui
import os
import popen2
import readline
import smtplib
import socket
import sys
import tempfile
import time
def diffstat(patch):
fd, name = tempfile.mkstemp()
try:
p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
try:
for line in patch: print >> p.tochild, line
p.tochild.close()
if p.wait(): return
fp = os.fdopen(fd, 'r')
stat = []
for line in fp: stat.append(line.lstrip())
last = stat.pop()
stat.insert(0, last)
stat = ''.join(stat)
if stat.startswith('0 files'): raise ValueError
return stat
except: raise
finally:
try: os.unlink(name)
except: pass
def patchbomb(ui, repo, *revs, **opts):
def prompt(prompt, default = None, rest = ': ', empty_ok = False):
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 if default: prompt += ' [%s]' % default
prompt += rest
while True:
Bryan O'Sullivan
Add patchbomb script.
r875 r = raw_input(prompt)
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 if r: return r
if default is not None: return default
if empty_ok: return r
Bryan O'Sullivan
Polish patchbomb script....
r877 ui.warn('Please enter a valid value.\n')
Bryan O'Sullivan
Add patchbomb script.
r875
def confirm(s):
if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
raise ValueError
def cdiffstat(summary, patch):
s = diffstat(patch)
if s:
if summary:
ui.write(summary, '\n')
ui.write(s, '\n')
confirm('Does the diffstat above look okay')
return s
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 def makepatch(patch, idx, total):
Bryan O'Sullivan
Add patchbomb script.
r875 desc = []
node = None
for line in patch:
if line.startswith('#'):
if line.startswith('# Node ID'): node = line.split()[-1]
continue
if line.startswith('diff -r'): break
desc.append(line)
if not node: raise ValueError
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 body = ('\n'.join(desc[1:]).strip() or
'Patch subject is complete summary.')
Bryan O'Sullivan
Polish patchbomb script....
r877 body += '\n\n\n'
if opts['diffstat']:
body += cdiffstat('\n'.join(desc), patch) + '\n\n'
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 body += '\n'.join(patch)
msg = MIMEText(body)
Bryan O'Sullivan
Add patchbomb script.
r875 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
if subj.endswith('.'): subj = subj[:-1]
msg['Subject'] = subj
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 msg['X-Mercurial-Node'] = node
Bryan O'Sullivan
Add patchbomb script.
r875 return msg
start_time = int(time.time())
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 def genmsgid(id):
Bryan O'Sullivan
Add patchbomb script.
r875 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
patches = []
class exportee:
def __init__(self, container):
self.lines = []
self.container = container
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 self.name = 'email'
Bryan O'Sullivan
Add patchbomb script.
r875
def write(self, data):
self.lines.append(data)
def close(self):
self.container.append(''.join(self.lines).split('\n'))
self.lines = []
commands.export(ui, repo, *args, **{'output': exportee(patches)})
jumbo = []
msgs = []
ui.write('This patch series consists of %d patches.\n\n' % len(patches))
for p, i in zip(patches, range(len(patches))):
jumbo.extend(p)
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 msgs.append(makepatch(p, i + 1, len(patches)))
Bryan O'Sullivan
Add patchbomb script.
r875
ui.write('\nWrite the introductory message for the patch series.\n\n')
Bryan O'Sullivan
Polish patchbomb script....
r877 sender = (opts['from'] or ui.config('patchbomb', 'from') or
prompt('From', ui.username()))
Bryan O'Sullivan
Add patchbomb script.
r875
msg = MIMEMultipart()
msg['Subject'] = '[PATCH 0 of %d] %s' % (
len(patches),
Bryan O'Sullivan
Polish patchbomb script....
r877 opts['subject'] or
Bryan O'Sullivan
Add patchbomb script.
r875 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
Bryan O'Sullivan
Fix handling of addresses in hgrc.
r997 to = opts['to'] or ui.config('patchbomb', 'to') or prompt('To')
to = [t.strip() for t in to.split(',')]
Bryan O'Sullivan
Polish patchbomb script....
r877 cc = (opts['cc'] or ui.config('patchbomb', 'cc') or
Bryan O'Sullivan
Fix handling of addresses in hgrc.
r997 prompt('Cc', default = ''))
Bryan O'Sullivan
contrib/patchbomb: Fix buglet with empty cc list.
r998 cc = (cc and [c.strip() for c in cc.split(',')]) or []
Bryan O'Sullivan
Add patchbomb script.
r875
ui.write('Finish with ^D or a dot on a line by itself.\n\n')
body = []
while True:
try: l = raw_input()
except EOFError: break
if l == '.': break
body.append(l)
msg.attach(MIMEText('\n'.join(body) + '\n'))
ui.write('\n')
d = cdiffstat('Final summary:\n', jumbo)
if d: msg.attach(MIMEText(d))
msgs.insert(0, msg)
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 if not opts['test']:
s = smtplib.SMTP()
s.connect(host = ui.config('smtp', 'host', 'mail'),
port = int(ui.config('smtp', 'port', 25)))
Bryan O'Sullivan
Add patchbomb script.
r875
parent = None
tz = time.strftime('%z')
for m in msgs:
try:
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
Bryan O'Sullivan
Add patchbomb script.
r875 except TypeError:
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 m['Message-Id'] = genmsgid('patchbomb')
Bryan O'Sullivan
Add patchbomb script.
r875 if parent:
m['In-Reply-To'] = parent
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 else:
parent = m['Message-Id']
Bryan O'Sullivan
Polish patchbomb script....
r877 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
Bryan O'Sullivan
Add patchbomb script.
r875 start_time += 1
m['From'] = sender
m['To'] = ', '.join(to)
if cc: m['Cc'] = ', '.join(cc)
ui.status('Sending ', m['Subject'], ' ...\n')
if opts['test']:
fp = os.popen(os.getenv('PAGER', 'more'), 'w')
fp.write(m.as_string(0))
fp.write('\n')
fp.close()
else:
s.sendmail(sender, to + cc, m.as_string(0))
Bryan O'Sullivan
Get patchbomb script to not use MIME attachments....
r876 if not opts['test']:
s.close()
Bryan O'Sullivan
Add patchbomb script.
r875
if __name__ == '__main__':
optspec = [('c', 'cc', [], 'email addresses of copy recipients'),
Bryan O'Sullivan
Polish patchbomb script....
r877 ('d', 'diffstat', None, 'add diffstat output to messages'),
('f', 'from', '', 'email address of sender'),
Bryan O'Sullivan
Add patchbomb script.
r875 ('n', 'test', None, 'print messages that would be sent'),
Bryan O'Sullivan
Polish patchbomb script....
r877 ('s', 'subject', '', 'subject of introductory message'),
Bryan O'Sullivan
Add patchbomb script.
r875 ('t', 'to', [], 'email addresses of recipients')]
options = {}
try:
args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec,
options)
except fancyopts.getopt.GetoptError, inst:
u = ui.ui()
u.warn('error: %s' % inst)
sys.exit(1)
u = ui.ui(options["verbose"], options["debug"], options["quiet"],
not options["noninteractive"])
repo = hg.repository(ui = u)
patchbomb(u, repo, *args, **options)