comparison contrib/patchbomb @ 876:14cfaaec2e8e

Get patchbomb script to not use MIME attachments. Adding patches as attachments makes it difficult or impossible for some mail clients to quote them effectively.
author Bryan O'Sullivan <bos@serpentine.com>
date Tue, 09 Aug 2005 20:53:50 -0800
parents d3f836bf6cc1
children 25430c523677
comparison
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'),