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', '') |