changeset 1669:91d40fc959f0

turn patchbomb script into an extension module. command name is now 'hg email'.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Tue, 31 Jan 2006 08:06:35 -0800
parents daff3ef0de8d
children fe19c54ee403
files contrib/patchbomb hgext/patchbomb.py
diffstat 2 files changed, 264 insertions(+), 276 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/patchbomb	Mon Jan 30 19:34:35 2006 +1300
+++ /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	Tue Jan 31 08:06:35 2006 -0800
@@ -0,0 +1,264 @@
+# 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
+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'''
+    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]...")
+    }