--- a/contrib/patchbomb Wed Feb 01 08:46:24 2006 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +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 <my@email>
-# 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['plain']:
- while patch and patch[0].startswith('# '): patch.pop(0)
- if patch: patch.pop(0)
- while patch and not patch[0].strip(): patch.pop(0)
- 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),
- 'switch_parent': False,
- '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'),
- ('', 'plain', None, 'omit hg patch header'),
- ('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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/patchbomb.py Wed Feb 01 08:50:45 2006 +0100
@@ -0,0 +1,275 @@
+# Command 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 <my@email>
+# to = recipient1, recipient2, ...
+# cc = cc1, cc2, ...
+
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from mercurial import commands
+from mercurial import hg
+from mercurial import ui
+from mercurial.i18n import gettext as _
+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):
+ '''send changesets as a series of patch emails
+
+ The series starts 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. First, the rest of
+ the changeset description. Next, (optionally) if the diffstat
+ program is installed, the result of running diffstat on the patch.
+ Finally, the patch itself, as generated by "hg export".'''
+ 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['plain']:
+ while patch and patch[0].startswith('# '): patch.pop(0)
+ if patch: patch.pop(0)
+ while patch and not patch[0].strip(): patch.pop(0)
+ 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, *revs, **{'output': exportee(patches),
+ 'switch_parent': False,
+ '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()
+
+cmdtable = {
+ 'email':
+ (patchbomb,
+ [('c', 'cc', [], 'email addresses of copy recipients'),
+ ('d', 'diffstat', None, 'add diffstat output to messages'),
+ ('f', 'from', '', 'email address of sender'),
+ ('', 'plain', None, 'omit hg patch header'),
+ ('n', 'test', None, 'print messages that would be sent'),
+ ('s', 'subject', '', 'subject of introductory message'),
+ ('t', 'to', [], 'email addresses of recipients')],
+ "hg email [OPTION]... [REV]...")
+ }