##// END OF EJS Templates
convert-repo: fix up octopus merge conversion
convert-repo: fix up octopus merge conversion

File last commit:

r1226:f3837564 default
r1389:9b3ef6f3 default
Show More
patchbomb
270 lines | 8.3 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
patchbomb: add TLS and SMTP AUTH support....
r1226 # tls = yes # or omit if not needed
# username = user # if SMTP authentication required
# password = password # if SMTP authentication required - PLAINTEXT
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 smtplib
import socket
import sys
import tempfile
import time
Bryan O'Sullivan
patchbomb: continue if we can't import readline.
r1204 try:
# readline gives raw_input editing capabilities, but is not
# present on windows
import readline
except ImportError: pass
Bryan O'Sullivan
Add patchbomb script.
r875 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
Thomas Arendsen Hein
Variable 'body' was missing in patchbomb script.
r1135 body = ''
Bryan O'Sullivan
Add patchbomb script.
r875 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
mpm@selenic.com
patchbomb: eliminate silly complete summary message...
r1118
#body = ('\n'.join(desc[1:]).strip() or
# 'Patch subject is complete summary.')
#body += '\n\n\n'
Bryan O'Sullivan
Polish patchbomb script....
r877 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 = []
Bryan O'Sullivan
Get patchbomb working with tip again.
r1032 commands.export(ui, repo, *args, **{'output': exportee(patches),
'text': None})
Bryan O'Sullivan
Add patchbomb script.
r875
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)))
bos@serpentine.internal.keyresearch.com
patchbomb: fix up confusion between strings and lists of strings.
r1154
def getaddrs(opt, prpt, default = None):
addrs = opts[opt] or (ui.config('patchbomb', opt) or
prompt(prpt, default = default)).split(',')
return [a.strip() for a in addrs if a.strip()]
to = getaddrs('to', 'To')
cc = getaddrs('cc', 'Cc', '')
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')
Thomas Arendsen Hein
Make diffstat optional for patchbomb script.
r1136 if opts['diffstat']:
d = cdiffstat('Final summary:\n', jumbo)
if d: msg.attach(MIMEText(d))
Bryan O'Sullivan
Add patchbomb script.
r875
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
patchbomb: add TLS and SMTP AUTH support....
r1226 if ui.configbool('smtp', 'tls'):
s.ehlo()
s.starttls()
s.ehlo()
username = ui.config('smtp', 'username')
password = ui.config('smtp', 'password')
if username and password:
s.login(username, password)
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)