contrib/patchbomb
changeset 876 14cfaaec2e8e
parent 875 d3f836bf6cc1
child 877 25430c523677
equal deleted inserted replaced
875:d3f836bf6cc1 876:14cfaaec2e8e
    72         try: os.unlink(name)
    72         try: os.unlink(name)
    73         except: pass
    73         except: pass
    74 
    74 
    75 def patchbomb(ui, repo, *revs, **opts):
    75 def patchbomb(ui, repo, *revs, **opts):
    76     def prompt(prompt, default = None, rest = ': ', empty_ok = False):
    76     def prompt(prompt, default = None, rest = ': ', empty_ok = False):
    77         try:
    77         if default: prompt += ' [%s]' % default
    78             if default: prompt += ' [%s]' % default
    78         prompt += rest
    79             prompt += rest
    79         while True:
    80             r = raw_input(prompt)
    80             r = raw_input(prompt)
    81             if not r and not empty_ok: raise EOFError
    81             if r: return r
    82             return r
    82             if default is not None: return default
    83         except EOFError:
    83             if empty_ok: return r
    84             if default is None: raise
    84             print >> sys.stderr, 'Please enter a valid value.'
    85             return default
       
    86 
    85 
    87     def confirm(s):
    86     def confirm(s):
    88         if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
    87         if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
    89             raise ValueError
    88             raise ValueError
    90 
    89 
    95                 ui.write(summary, '\n')
    94                 ui.write(summary, '\n')
    96                 ui.write(s, '\n')
    95                 ui.write(s, '\n')
    97             confirm('Does the diffstat above look okay')
    96             confirm('Does the diffstat above look okay')
    98         return s
    97         return s
    99 
    98 
   100     def make_patch(patch, idx, total):
    99     def makepatch(patch, idx, total):
   101         desc = []
   100         desc = []
   102         node = None
   101         node = None
   103         for line in patch:
   102         for line in patch:
   104             if line.startswith('#'):
   103             if line.startswith('#'):
   105                 if line.startswith('# Node ID'): node = line.split()[-1]
   104                 if line.startswith('# Node ID'): node = line.split()[-1]
   106                 continue
   105                 continue
   107             if line.startswith('diff -r'): break
   106             if line.startswith('diff -r'): break
   108             desc.append(line)
   107             desc.append(line)
   109         if not node: raise ValueError
   108         if not node: raise ValueError
   110         msg = MIMEMultipart()
   109         body = ('\n'.join(desc[1:]).strip() or
   111         msg['X-Mercurial-Node'] = node
   110                 'Patch subject is complete summary.')
       
   111         body += '\n\n\n' + cdiffstat('\n'.join(desc), patch) + '\n\n'
       
   112         body += '\n'.join(patch)
       
   113         msg = MIMEText(body)
   112         subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
   114         subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
   113         if subj.endswith('.'): subj = subj[:-1]
   115         if subj.endswith('.'): subj = subj[:-1]
   114         msg['Subject'] = subj
   116         msg['Subject'] = subj
   115         body = '\n'.join(desc[1:]).strip() + '\n'
   117         msg['X-Mercurial-Node'] = node
   116         summary = subj
       
   117         if body != '\n':
       
   118             msg.attach(MIMEText(body))
       
   119             summary += '\n\n' + body
       
   120         else:
       
   121             summary += '\n'
       
   122         d = cdiffstat(summary, patch)
       
   123         if d: msg.attach(MIMEText(d))
       
   124         p = MIMEText('\n'.join(patch), 'x-patch')
       
   125         p['Content-Disposition'] = commands.make_filename(repo, None,
       
   126                                                           'inline; filename=%b-%n.patch',
       
   127                                                           seqno = idx)
       
   128         msg.attach(p)
       
   129         return msg
   118         return msg
   130 
   119 
   131     start_time = int(time.time())
   120     start_time = int(time.time())
   132 
   121 
   133     def make_msgid(id):
   122     def genmsgid(id):
   134         return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
   123         return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
   135 
   124 
   136     patches = []
   125     patches = []
   137 
   126 
   138     class exportee:
   127     class exportee:
   139         def __init__(self, container):
   128         def __init__(self, container):
   140             self.lines = []
   129             self.lines = []
   141             self.container = container
   130             self.container = container
       
   131             self.name = 'email'
   142 
   132 
   143         def write(self, data):
   133         def write(self, data):
   144             self.lines.append(data)
   134             self.lines.append(data)
   145 
   135 
   146         def close(self):
   136         def close(self):
   154 
   144 
   155     ui.write('This patch series consists of %d patches.\n\n' % len(patches))
   145     ui.write('This patch series consists of %d patches.\n\n' % len(patches))
   156 
   146 
   157     for p, i in zip(patches, range(len(patches))):
   147     for p, i in zip(patches, range(len(patches))):
   158         jumbo.extend(p)
   148         jumbo.extend(p)
   159         msgs.append(make_patch(p, i + 1, len(patches)))
   149         msgs.append(makepatch(p, i + 1, len(patches)))
   160 
   150 
   161     ui.write('\nWrite the introductory message for the patch series.\n\n')
   151     ui.write('\nWrite the introductory message for the patch series.\n\n')
   162 
   152 
   163     sender = opts['sender'] or prompt('From', ui.username())
   153     sender = opts['sender'] or prompt('From', ui.username())
   164 
   154 
   186     d = cdiffstat('Final summary:\n', jumbo)
   176     d = cdiffstat('Final summary:\n', jumbo)
   187     if d: msg.attach(MIMEText(d))
   177     if d: msg.attach(MIMEText(d))
   188 
   178 
   189     msgs.insert(0, msg)
   179     msgs.insert(0, msg)
   190 
   180 
   191     s = smtplib.SMTP()
   181     if not opts['test']:
   192     s.connect(host = ui.config('smtp', 'host', 'mail'),
   182         s = smtplib.SMTP()
   193               port = int(ui.config('smtp', 'port', 25)))
   183         s.connect(host = ui.config('smtp', 'host', 'mail'),
   194 
   184                   port = int(ui.config('smtp', 'port', 25)))
   195     refs = []
   185 
   196     parent = None
   186     parent = None
   197     tz = time.strftime('%z')
   187     tz = time.strftime('%z')
   198     for m in msgs:
   188     for m in msgs:
   199         try:
   189         try:
   200             m['Message-Id'] = make_msgid(m['X-Mercurial-Node'])
   190             m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
   201         except TypeError:
   191         except TypeError:
   202             m['Message-Id'] = make_msgid('patchbomb')
   192             m['Message-Id'] = genmsgid('patchbomb')
   203         if parent:
   193         if parent:
   204             m['In-Reply-To'] = parent
   194             m['In-Reply-To'] = parent
   205         parent = m['Message-Id']
   195         else:
   206         if len(refs) > 1:
   196             parent = m['Message-Id']
   207             m['References'] = ' '.join(refs[:-1])
       
   208         refs.append(parent)
       
   209         m['Date'] = time.strftime('%a, %m %b %Y %T ', time.localtime(start_time)) + tz
   197         m['Date'] = time.strftime('%a, %m %b %Y %T ', time.localtime(start_time)) + tz
   210         start_time += 1
   198         start_time += 1
   211         m['From'] = sender
   199         m['From'] = sender
   212         m['To'] = ', '.join(to)
   200         m['To'] = ', '.join(to)
   213         if cc: m['Cc'] = ', '.join(cc)
   201         if cc: m['Cc'] = ', '.join(cc)
   217             fp.write(m.as_string(0))
   205             fp.write(m.as_string(0))
   218             fp.write('\n')
   206             fp.write('\n')
   219             fp.close()
   207             fp.close()
   220         else:
   208         else:
   221             s.sendmail(sender, to + cc, m.as_string(0))
   209             s.sendmail(sender, to + cc, m.as_string(0))
   222     s.close()
   210     if not opts['test']:
       
   211         s.close()
   223 
   212 
   224 if __name__ == '__main__':
   213 if __name__ == '__main__':
   225     optspec = [('c', 'cc', [], 'email addresses of copy recipients'),
   214     optspec = [('c', 'cc', [], 'email addresses of copy recipients'),
   226                ('n', 'test', None, 'print messages that would be sent'),
   215                ('n', 'test', None, 'print messages that would be sent'),
   227                ('s', 'sender', '', 'email address of sender'),
   216                ('s', 'sender', '', 'email address of sender'),