##// END OF EJS Templates
Variable 'body' was missing in patchbomb script.
Thomas Arendsen Hein -
r1135:e455d91f default
parent child Browse files
Show More
@@ -1,251 +1,252 b''
1 1 #!/usr/bin/python
2 2 #
3 3 # Interactive script for sending a collection of Mercurial changesets
4 4 # as a series of patch emails.
5 5 #
6 6 # The series is started off with a "[PATCH 0 of N]" introduction,
7 7 # which describes the series as a whole.
8 8 #
9 9 # Each patch email has a Subject line of "[PATCH M of N] ...", using
10 10 # the first line of the changeset description as the subject text.
11 11 # The message contains two or three body parts:
12 12 #
13 13 # The remainder of the changeset description.
14 14 #
15 15 # [Optional] If the diffstat program is installed, the result of
16 16 # running diffstat on the patch.
17 17 #
18 18 # The patch itself, as generated by "hg export".
19 19 #
20 20 # Each message refers to all of its predecessors using the In-Reply-To
21 21 # and References headers, so they will show up as a sequence in
22 22 # threaded mail and news readers, and in mail archives.
23 23 #
24 24 # For each changeset, you will be prompted with a diffstat summary and
25 25 # the changeset summary, so you can be sure you are sending the right
26 26 # changes.
27 27 #
28 28 # It is best to run this script with the "-n" (test only) flag before
29 29 # firing it up "for real", in which case it will use your pager to
30 30 # display each of the messages that it would send.
31 31 #
32 32 # To configure a default mail host, add a section like this to your
33 33 # hgrc file:
34 34 #
35 35 # [smtp]
36 36 # host = my_mail_host
37 37 # port = 1025
38 38 #
39 39 # To configure other defaults, add a section like this to your hgrc
40 40 # file:
41 41 #
42 42 # [patchbomb]
43 43 # from = My Name <my@email>
44 44 # to = recipient1, recipient2, ...
45 45 # cc = cc1, cc2, ...
46 46
47 47 from email.MIMEMultipart import MIMEMultipart
48 48 from email.MIMEText import MIMEText
49 49 from mercurial import commands
50 50 from mercurial import fancyopts
51 51 from mercurial import hg
52 52 from mercurial import ui
53 53 import os
54 54 import popen2
55 55 import readline
56 56 import smtplib
57 57 import socket
58 58 import sys
59 59 import tempfile
60 60 import time
61 61
62 62 def diffstat(patch):
63 63 fd, name = tempfile.mkstemp()
64 64 try:
65 65 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
66 66 try:
67 67 for line in patch: print >> p.tochild, line
68 68 p.tochild.close()
69 69 if p.wait(): return
70 70 fp = os.fdopen(fd, 'r')
71 71 stat = []
72 72 for line in fp: stat.append(line.lstrip())
73 73 last = stat.pop()
74 74 stat.insert(0, last)
75 75 stat = ''.join(stat)
76 76 if stat.startswith('0 files'): raise ValueError
77 77 return stat
78 78 except: raise
79 79 finally:
80 80 try: os.unlink(name)
81 81 except: pass
82 82
83 83 def patchbomb(ui, repo, *revs, **opts):
84 84 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
85 85 if default: prompt += ' [%s]' % default
86 86 prompt += rest
87 87 while True:
88 88 r = raw_input(prompt)
89 89 if r: return r
90 90 if default is not None: return default
91 91 if empty_ok: return r
92 92 ui.warn('Please enter a valid value.\n')
93 93
94 94 def confirm(s):
95 95 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
96 96 raise ValueError
97 97
98 98 def cdiffstat(summary, patch):
99 99 s = diffstat(patch)
100 100 if s:
101 101 if summary:
102 102 ui.write(summary, '\n')
103 103 ui.write(s, '\n')
104 104 confirm('Does the diffstat above look okay')
105 105 return s
106 106
107 107 def makepatch(patch, idx, total):
108 108 desc = []
109 109 node = None
110 body = ''
110 111 for line in patch:
111 112 if line.startswith('#'):
112 113 if line.startswith('# Node ID'): node = line.split()[-1]
113 114 continue
114 115 if line.startswith('diff -r'): break
115 116 desc.append(line)
116 117 if not node: raise ValueError
117 118
118 119 #body = ('\n'.join(desc[1:]).strip() or
119 120 # 'Patch subject is complete summary.')
120 121 #body += '\n\n\n'
121 122
122 123 if opts['diffstat']:
123 124 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
124 125 body += '\n'.join(patch)
125 126 msg = MIMEText(body)
126 127 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
127 128 if subj.endswith('.'): subj = subj[:-1]
128 129 msg['Subject'] = subj
129 130 msg['X-Mercurial-Node'] = node
130 131 return msg
131 132
132 133 start_time = int(time.time())
133 134
134 135 def genmsgid(id):
135 136 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn())
136 137
137 138 patches = []
138 139
139 140 class exportee:
140 141 def __init__(self, container):
141 142 self.lines = []
142 143 self.container = container
143 144 self.name = 'email'
144 145
145 146 def write(self, data):
146 147 self.lines.append(data)
147 148
148 149 def close(self):
149 150 self.container.append(''.join(self.lines).split('\n'))
150 151 self.lines = []
151 152
152 153 commands.export(ui, repo, *args, **{'output': exportee(patches),
153 154 'text': None})
154 155
155 156 jumbo = []
156 157 msgs = []
157 158
158 159 ui.write('This patch series consists of %d patches.\n\n' % len(patches))
159 160
160 161 for p, i in zip(patches, range(len(patches))):
161 162 jumbo.extend(p)
162 163 msgs.append(makepatch(p, i + 1, len(patches)))
163 164
164 165 ui.write('\nWrite the introductory message for the patch series.\n\n')
165 166
166 167 sender = (opts['from'] or ui.config('patchbomb', 'from') or
167 168 prompt('From', ui.username()))
168 169
169 170 msg = MIMEMultipart()
170 171 msg['Subject'] = '[PATCH 0 of %d] %s' % (
171 172 len(patches),
172 173 opts['subject'] or
173 174 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
174 175 to = opts['to'] or ui.config('patchbomb', 'to') or prompt('To')
175 176 to = [t.strip() for t in to.split(',')]
176 177 cc = (opts['cc'] or ui.config('patchbomb', 'cc') or
177 178 prompt('Cc', default = ''))
178 179 cc = (cc and [c.strip() for c in cc.split(',')]) or []
179 180
180 181 ui.write('Finish with ^D or a dot on a line by itself.\n\n')
181 182
182 183 body = []
183 184
184 185 while True:
185 186 try: l = raw_input()
186 187 except EOFError: break
187 188 if l == '.': break
188 189 body.append(l)
189 190
190 191 msg.attach(MIMEText('\n'.join(body) + '\n'))
191 192
192 193 ui.write('\n')
193 194
194 195 d = cdiffstat('Final summary:\n', jumbo)
195 196 if d: msg.attach(MIMEText(d))
196 197
197 198 msgs.insert(0, msg)
198 199
199 200 if not opts['test']:
200 201 s = smtplib.SMTP()
201 202 s.connect(host = ui.config('smtp', 'host', 'mail'),
202 203 port = int(ui.config('smtp', 'port', 25)))
203 204
204 205 parent = None
205 206 tz = time.strftime('%z')
206 207 for m in msgs:
207 208 try:
208 209 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
209 210 except TypeError:
210 211 m['Message-Id'] = genmsgid('patchbomb')
211 212 if parent:
212 213 m['In-Reply-To'] = parent
213 214 else:
214 215 parent = m['Message-Id']
215 216 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz
216 217 start_time += 1
217 218 m['From'] = sender
218 219 m['To'] = ', '.join(to)
219 220 if cc: m['Cc'] = ', '.join(cc)
220 221 ui.status('Sending ', m['Subject'], ' ...\n')
221 222 if opts['test']:
222 223 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
223 224 fp.write(m.as_string(0))
224 225 fp.write('\n')
225 226 fp.close()
226 227 else:
227 228 s.sendmail(sender, to + cc, m.as_string(0))
228 229 if not opts['test']:
229 230 s.close()
230 231
231 232 if __name__ == '__main__':
232 233 optspec = [('c', 'cc', [], 'email addresses of copy recipients'),
233 234 ('d', 'diffstat', None, 'add diffstat output to messages'),
234 235 ('f', 'from', '', 'email address of sender'),
235 236 ('n', 'test', None, 'print messages that would be sent'),
236 237 ('s', 'subject', '', 'subject of introductory message'),
237 238 ('t', 'to', [], 'email addresses of recipients')]
238 239 options = {}
239 240 try:
240 241 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec,
241 242 options)
242 243 except fancyopts.getopt.GetoptError, inst:
243 244 u = ui.ui()
244 245 u.warn('error: %s' % inst)
245 246 sys.exit(1)
246 247
247 248 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
248 249 not options["noninteractive"])
249 250 repo = hg.repository(ui = u)
250 251
251 252 patchbomb(u, repo, *args, **options)
General Comments 0
You need to be logged in to leave comments. Login now