diff --git a/contrib/patchbomb b/contrib/patchbomb deleted file mode 100755 --- a/contrib/patchbomb +++ /dev/null @@ -1,270 +0,0 @@ -#!/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. -# -# [Optional] If the diffstat program is installed, the result of -# running diffstat on the patch. -# -# 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 -# firing it up "for real", in which case it will use your pager to -# display each of the messages that it would send. -# -# To configure a default mail host, add a section like this to your -# hgrc file: -# -# [smtp] -# host = my_mail_host -# port = 1025 -# tls = yes # or omit if not needed -# username = user # if SMTP authentication required -# password = password # if SMTP authentication required - PLAINTEXT -# -# To configure other defaults, add a section like this to your hgrc -# file: -# -# [patchbomb] -# from = My Name -# to = recipient1, recipient2, ... -# cc = cc1, cc2, ... - -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 - -try: - # readline gives raw_input editing capabilities, but is not - # present on windows - import readline -except ImportError: pass - -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): - if default: prompt += ' [%s]' % default - prompt += rest - while True: - r = raw_input(prompt) - if r: return r - if default is not None: return default - if empty_ok: return r - ui.warn('Please enter a valid value.\n') - - 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 - - def makepatch(patch, idx, total): - desc = [] - node = None - body = '' - 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 - - #body = ('\n'.join(desc[1:]).strip() or - # 'Patch subject is complete summary.') - #body += '\n\n\n' - - if opts['diffstat']: - body += cdiffstat('\n'.join(desc), patch) + '\n\n' - body += '\n'.join(patch) - msg = MIMEText(body) - subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) - if subj.endswith('.'): subj = subj[:-1] - msg['Subject'] = subj - msg['X-Mercurial-Node'] = node - return msg - - start_time = int(time.time()) - - def genmsgid(id): - return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) - - patches = [] - - class exportee: - def __init__(self, container): - self.lines = [] - self.container = container - self.name = 'email' - - 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), - 'text': None}) - - 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) - msgs.append(makepatch(p, i + 1, len(patches))) - - ui.write('\nWrite the introductory message for the patch series.\n\n') - - sender = (opts['from'] or ui.config('patchbomb', 'from') or - prompt('From', ui.username())) - - msg = MIMEMultipart() - msg['Subject'] = '[PATCH 0 of %d] %s' % ( - len(patches), - opts['subject'] or - prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches))) - - 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', '') - - 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') - - if opts['diffstat']: - d = cdiffstat('Final summary:\n', jumbo) - if d: msg.attach(MIMEText(d)) - - msgs.insert(0, msg) - - if not opts['test']: - s = smtplib.SMTP() - s.connect(host = ui.config('smtp', 'host', 'mail'), - port = int(ui.config('smtp', 'port', 25))) - 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) - parent = None - tz = time.strftime('%z') - for m in msgs: - try: - m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) - except TypeError: - m['Message-Id'] = genmsgid('patchbomb') - if parent: - m['In-Reply-To'] = parent - else: - parent = m['Message-Id'] - m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz - 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)) - if not opts['test']: - s.close() - -if __name__ == '__main__': - optspec = [('c', 'cc', [], 'email addresses of copy recipients'), - ('d', 'diffstat', None, 'add diffstat output to messages'), - ('f', 'from', '', 'email address of sender'), - ('n', 'test', None, 'print messages that would be sent'), - ('s', 'subject', '', 'subject of introductory message'), - ('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)