comparison hgext/patchbomb.py @ 7354:cad454f295b0

patchbomb: extract a bunch of nested functions - clarifies dependencies on variables - extracts potentially useful utility functions - no need for separate confirm() function - error message style conformance - PEP 8 conformance
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Wed, 12 Nov 2008 14:36:16 +0100
parents 982b55ec80be
children b0fa5dbd9cdd
comparison
equal deleted inserted replaced
7353:982b55ec80be 7354:cad454f295b0
79 79
80 def close(self): 80 def close(self):
81 self.container.append(''.join(self.lines).split('\n')) 81 self.container.append(''.join(self.lines).split('\n'))
82 self.lines = [] 82 self.lines = []
83 83
84 def prompt(ui, prompt, default=None, rest=': ', empty_ok=False):
85 if not ui.interactive:
86 return default
87 if default:
88 prompt += ' [%s]' % default
89 prompt += rest
90 while True:
91 r = ui.prompt(prompt, default=default)
92 if r:
93 return r
94 if default is not None:
95 return default
96 if empty_ok:
97 return r
98 ui.warn(_('Please enter a valid value.\n'))
99
100 def cdiffstat(ui, summary, patchlines):
101 s = patch.diffstat(patchlines)
102 if s:
103 if summary:
104 ui.write(summary, '\n')
105 ui.write(s, '\n')
106 ans = prompt(ui, _('Does the diffstat above look okay? '), 'y')
107 if not ans.lower().startswith('y'):
108 raise util.Abort(_('diffstat rejected'))
109 elif s is None:
110 ui.warn(_('no diffstat information available\n'))
111 s = ''
112 return s
113
114 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
115
116 desc = []
117 node = None
118 body = ''
119
120 for line in patch:
121 if line.startswith('#'):
122 if line.startswith('# Node ID'):
123 node = line.split()[-1]
124 continue
125 if line.startswith('diff -r') or line.startswith('diff --git'):
126 break
127 desc.append(line)
128
129 if not patchname and not node:
130 raise ValueError
131
132 if opts.get('attach'):
133 body = ('\n'.join(desc[1:]).strip() or
134 'Patch subject is complete summary.')
135 body += '\n\n\n'
136
137 if opts.get('plain'):
138 while patch and patch[0].startswith('# '):
139 patch.pop(0)
140 if patch:
141 patch.pop(0)
142 while patch and not patch[0].strip():
143 patch.pop(0)
144
145 if opts.get('diffstat'):
146 body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n'
147
148 if opts.get('attach') or opts.get('inline'):
149 msg = email.MIMEMultipart.MIMEMultipart()
150 if body:
151 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
152 p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test'))
153 binnode = bin(node)
154 # if node is mq patch, it will have patch file name as tag
155 if not patchname:
156 patchtags = [t for t in repo.nodetags(binnode)
157 if t.endswith('.patch') or t.endswith('.diff')]
158 if patchtags:
159 patchname = patchtags[0]
160 elif total > 1:
161 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
162 binnode, idx, total)
163 else:
164 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
165 disposition = 'inline'
166 if opts.get('attach'):
167 disposition = 'attachment'
168 p['Content-Disposition'] = disposition + '; filename=' + patchname
169 msg.attach(p)
170 else:
171 body += '\n'.join(patch)
172 msg = mail.mimetextpatch(body, display=opts.get('test'))
173
174 subj = desc[0].strip().rstrip('. ')
175 if total == 1:
176 subj = '[PATCH] ' + (opts.get('subject') or subj)
177 else:
178 tlen = len(str(total))
179 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
180 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
181 msg['X-Mercurial-Node'] = node
182 return msg, subj
183
84 def patchbomb(ui, repo, *revs, **opts): 184 def patchbomb(ui, repo, *revs, **opts):
85 '''send changesets by email 185 '''send changesets by email
86 186
87 By default, diffs are sent in the format generated by hg export, 187 By default, diffs are sent in the format generated by hg export,
88 one per message. The series starts with a "[PATCH 0 of N]" 188 one per message. The series starts with a "[PATCH 0 of N]"
123 Before using this command, you will need to enable email in your hgrc. 223 Before using this command, you will need to enable email in your hgrc.
124 See the [email] section in hgrc(5) for details. 224 See the [email] section in hgrc(5) for details.
125 ''' 225 '''
126 226
127 _charsets = mail._charsets(ui) 227 _charsets = mail._charsets(ui)
128
129 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
130 if not ui.interactive:
131 return default
132 if default:
133 prompt += ' [%s]' % default
134 prompt += rest
135 while True:
136 r = ui.prompt(prompt, default=default)
137 if r:
138 return r
139 if default is not None:
140 return default
141 if empty_ok:
142 return r
143 ui.warn(_('Please enter a valid value.\n'))
144
145 def confirm(s, denial):
146 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
147 raise util.Abort(denial)
148
149 def cdiffstat(summary, patchlines):
150 s = patch.diffstat(patchlines)
151 if s:
152 if summary:
153 ui.write(summary, '\n')
154 ui.write(s, '\n')
155 confirm(_('Does the diffstat above look okay'),
156 _('diffstat rejected'))
157 elif s is None:
158 ui.warn(_('No diffstat information available.\n'))
159 s = ''
160 return s
161
162 def makepatch(patch, idx, total, patchname=None):
163 desc = []
164 node = None
165 body = ''
166 for line in patch:
167 if line.startswith('#'):
168 if line.startswith('# Node ID'):
169 node = line.split()[-1]
170 continue
171 if line.startswith('diff -r') or line.startswith('diff --git'):
172 break
173 desc.append(line)
174 if not patchname and not node:
175 raise ValueError
176
177 if opts.get('attach'):
178 body = ('\n'.join(desc[1:]).strip() or
179 'Patch subject is complete summary.')
180 body += '\n\n\n'
181
182 if opts.get('plain'):
183 while patch and patch[0].startswith('# '):
184 patch.pop(0)
185 if patch:
186 patch.pop(0)
187 while patch and not patch[0].strip():
188 patch.pop(0)
189 if opts.get('diffstat'):
190 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
191 if opts.get('attach') or opts.get('inline'):
192 msg = email.MIMEMultipart.MIMEMultipart()
193 if body:
194 msg.attach(mail.mimeencode(ui, body, _charsets,
195 opts.get('test')))
196 p = mail.mimetextpatch('\n'.join(patch), 'x-patch',
197 opts.get('test'))
198 binnode = bin(node)
199 # if node is mq patch, it will have patch file name as tag
200 if not patchname:
201 patchtags = [t for t in repo.nodetags(binnode)
202 if t.endswith('.patch') or t.endswith('.diff')]
203 if patchtags:
204 patchname = patchtags[0]
205 elif total > 1:
206 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
207 binnode, idx, total)
208 else:
209 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
210 disposition = 'inline'
211 if opts.get('attach'):
212 disposition = 'attachment'
213 p['Content-Disposition'] = disposition + '; filename=' + patchname
214 msg.attach(p)
215 else:
216 body += '\n'.join(patch)
217 msg = mail.mimetextpatch(body, display=opts.get('test'))
218
219 subj = desc[0].strip().rstrip('. ')
220 if total == 1:
221 subj = '[PATCH] ' + (opts.get('subject') or subj)
222 else:
223 tlen = len(str(total))
224 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
225 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
226 msg['X-Mercurial-Node'] = node
227 return msg, subj
228 228
229 def outgoing(dest, revs): 229 def outgoing(dest, revs):
230 '''Return the revisions present locally but not in dest''' 230 '''Return the revisions present locally but not in dest'''
231 dest = ui.expandpath(dest or 'default-push', dest or 'default') 231 dest = ui.expandpath(dest or 'default-push', dest or 'default')
232 revs = [repo.lookup(rev) for rev in revs] 232 revs = [repo.lookup(rev) for rev in revs]
310 name = None 310 name = None
311 for p, i in zip(patches, xrange(len(patches))): 311 for p, i in zip(patches, xrange(len(patches))):
312 jumbo.extend(p) 312 jumbo.extend(p)
313 if patchnames: 313 if patchnames:
314 name = patchnames[i] 314 name = patchnames[i]
315 msgs.append(makepatch(p, i + 1, len(patches), name)) 315 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
316 len(patches), name)
317 msgs.append(msg)
316 318
317 if len(patches) > 1: 319 if len(patches) > 1:
318 tlen = len(str(len(patches))) 320 tlen = len(str(len(patches)))
319 321
320 subj = '[PATCH %0*d of %d] %s' % ( 322 subj = '[PATCH %0*d of %d] %s' % (
321 tlen, 0, len(patches), 323 tlen, 0, len(patches),
322 opts.get('subject') or 324 opts.get('subject') or
323 prompt('Subject:', 325 prompt(ui, 'Subject:',
324 rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches)))) 326 rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches))))
325 327
326 body = '' 328 body = ''
327 if opts.get('diffstat'): 329 if opts.get('diffstat'):
328 d = cdiffstat(_('Final summary:\n'), jumbo) 330 d = cdiffstat(ui, _('Final summary:\n'), jumbo)
329 if d: 331 if d:
330 body = '\n' + d 332 body = '\n' + d
331 333
332 body = getdescription(body, sender) 334 body = getdescription(body, sender)
333 msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) 335 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
337 msgs.insert(0, (msg, subj)) 339 msgs.insert(0, (msg, subj))
338 return msgs 340 return msgs
339 341
340 def getbundlemsgs(bundle): 342 def getbundlemsgs(bundle):
341 subj = (opts.get('subject') 343 subj = (opts.get('subject')
342 or prompt('Subject:', default='A bundle for your repository')) 344 or prompt(ui, 'Subject:', 'A bundle for your repository'))
343 345
344 body = getdescription('', sender) 346 body = getdescription('', sender)
345 msg = email.MIMEMultipart.MIMEMultipart() 347 msg = email.MIMEMultipart.MIMEMultipart()
346 if body: 348 if body:
347 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) 349 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
354 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) 356 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
355 return [(msg, subj)] 357 return [(msg, subj)]
356 358
357 sender = (opts.get('from') or ui.config('email', 'from') or 359 sender = (opts.get('from') or ui.config('email', 'from') or
358 ui.config('patchbomb', 'from') or 360 ui.config('patchbomb', 'from') or
359 prompt('From', ui.username())) 361 prompt(ui, 'From', ui.username()))
360 362
361 patches = opts.get('patches') 363 patches = opts.get('patches')
362 if patches: 364 if patches:
363 msgs = getpatchmsgs(patches, opts.get('patchnames')) 365 msgs = getpatchmsgs(patches, opts.get('patchnames'))
364 elif opts.get('bundle'): 366 elif opts.get('bundle'):
372 msgs = getpatchmsgs(patches) 374 msgs = getpatchmsgs(patches)
373 375
374 def getaddrs(opt, prpt, default = None): 376 def getaddrs(opt, prpt, default = None):
375 addrs = opts.get(opt) or (ui.config('email', opt) or 377 addrs = opts.get(opt) or (ui.config('email', opt) or
376 ui.config('patchbomb', opt) or 378 ui.config('patchbomb', opt) or
377 prompt(prpt, default = default)).split(',') 379 prompt(ui, prpt, default)).split(',')
378 return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test')) 380 return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test'))
379 for a in addrs if a.strip()] 381 for a in addrs if a.strip()]
380 382
381 to = getaddrs('to', 'To') 383 to = getaddrs('to', 'To')
382 cc = getaddrs('cc', 'Cc', '') 384 cc = getaddrs('cc', 'Cc', '')