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