##// END OF EJS Templates
patchbomb: consistently use opts.get
Christian Ebert -
r5818:77775ae8 default
parent child Browse files
Show More
@@ -1,458 +1,459 b''
1 # Command for sending a collection of Mercurial changesets as a series
1 # Command for sending a collection of Mercurial changesets as a series
2 # of patch emails.
2 # of patch emails.
3 #
3 #
4 # The series is started off with a "[PATCH 0 of N]" introduction,
4 # The series is started off with a "[PATCH 0 of N]" introduction,
5 # which describes the series as a whole.
5 # which describes the series as a whole.
6 #
6 #
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
7 # Each patch email has a Subject line of "[PATCH M of N] ...", using
8 # the first line of the changeset description as the subject text.
8 # the first line of the changeset description as the subject text.
9 # The message contains two or three body parts:
9 # The message contains two or three body parts:
10 #
10 #
11 # The remainder of the changeset description.
11 # The remainder of the changeset description.
12 #
12 #
13 # [Optional] If the diffstat program is installed, the result of
13 # [Optional] If the diffstat program is installed, the result of
14 # running diffstat on the patch.
14 # running diffstat on the patch.
15 #
15 #
16 # The patch itself, as generated by "hg export".
16 # The patch itself, as generated by "hg export".
17 #
17 #
18 # Each message refers to all of its predecessors using the In-Reply-To
18 # Each message refers to all of its predecessors using the In-Reply-To
19 # and References headers, so they will show up as a sequence in
19 # and References headers, so they will show up as a sequence in
20 # threaded mail and news readers, and in mail archives.
20 # threaded mail and news readers, and in mail archives.
21 #
21 #
22 # For each changeset, you will be prompted with a diffstat summary and
22 # For each changeset, you will be prompted with a diffstat summary and
23 # the changeset summary, so you can be sure you are sending the right
23 # the changeset summary, so you can be sure you are sending the right
24 # changes.
24 # changes.
25 #
25 #
26 # To enable this extension:
26 # To enable this extension:
27 #
27 #
28 # [extensions]
28 # [extensions]
29 # hgext.patchbomb =
29 # hgext.patchbomb =
30 #
30 #
31 # To configure other defaults, add a section like this to your hgrc
31 # To configure other defaults, add a section like this to your hgrc
32 # file:
32 # file:
33 #
33 #
34 # [email]
34 # [email]
35 # from = My Name <my@email>
35 # from = My Name <my@email>
36 # to = recipient1, recipient2, ...
36 # to = recipient1, recipient2, ...
37 # cc = cc1, cc2, ...
37 # cc = cc1, cc2, ...
38 # bcc = bcc1, bcc2, ...
38 # bcc = bcc1, bcc2, ...
39 #
39 #
40 # Then you can use the "hg email" command to mail a series of changesets
40 # Then you can use the "hg email" command to mail a series of changesets
41 # as a patchbomb.
41 # as a patchbomb.
42 #
42 #
43 # To avoid sending patches prematurely, it is a good idea to first run
43 # To avoid sending patches prematurely, it is a good idea to first run
44 # the "email" command with the "-n" option (test only). You will be
44 # the "email" command with the "-n" option (test only). You will be
45 # prompted for an email recipient address, a subject an an introductory
45 # prompted for an email recipient address, a subject an an introductory
46 # message describing the patches of your patchbomb. Then when all is
46 # message describing the patches of your patchbomb. Then when all is
47 # done, patchbomb messages are displayed. If PAGER environment variable
47 # done, patchbomb messages are displayed. If PAGER environment variable
48 # is set, your pager will be fired up once for each patchbomb message, so
48 # is set, your pager will be fired up once for each patchbomb message, so
49 # you can verify everything is alright.
49 # you can verify everything is alright.
50 #
50 #
51 # The "-m" (mbox) option is also very useful. Instead of previewing
51 # The "-m" (mbox) option is also very useful. Instead of previewing
52 # each patchbomb message in a pager or sending the messages directly,
52 # each patchbomb message in a pager or sending the messages directly,
53 # it will create a UNIX mailbox file with the patch emails. This
53 # it will create a UNIX mailbox file with the patch emails. This
54 # mailbox file can be previewed with any mail user agent which supports
54 # mailbox file can be previewed with any mail user agent which supports
55 # UNIX mbox files, i.e. with mutt:
55 # UNIX mbox files, i.e. with mutt:
56 #
56 #
57 # % mutt -R -f mbox
57 # % mutt -R -f mbox
58 #
58 #
59 # When you are previewing the patchbomb messages, you can use `formail'
59 # When you are previewing the patchbomb messages, you can use `formail'
60 # (a utility that is commonly installed as part of the procmail package),
60 # (a utility that is commonly installed as part of the procmail package),
61 # to send each message out:
61 # to send each message out:
62 #
62 #
63 # % formail -s sendmail -bm -t < mbox
63 # % formail -s sendmail -bm -t < mbox
64 #
64 #
65 # That should be all. Now your patchbomb is on its way out.
65 # That should be all. Now your patchbomb is on its way out.
66
66
67 import os, errno, socket, tempfile
67 import os, errno, socket, tempfile
68 import email.MIMEMultipart, email.MIMEText, email.MIMEBase
68 import email.MIMEMultipart, email.MIMEText, email.MIMEBase
69 import email.Utils, email.Encoders
69 import email.Utils, email.Encoders
70 from mercurial import cmdutil, commands, hg, mail, ui, patch, util
70 from mercurial import cmdutil, commands, hg, mail, ui, patch, util
71 from mercurial.i18n import _
71 from mercurial.i18n import _
72 from mercurial.node import *
72 from mercurial.node import *
73
73
74 def patchbomb(ui, repo, *revs, **opts):
74 def patchbomb(ui, repo, *revs, **opts):
75 '''send changesets by email
75 '''send changesets by email
76
76
77 By default, diffs are sent in the format generated by hg export,
77 By default, diffs are sent in the format generated by hg export,
78 one per message. The series starts with a "[PATCH 0 of N]"
78 one per message. The series starts with a "[PATCH 0 of N]"
79 introduction, which describes the series as a whole.
79 introduction, which describes the series as a whole.
80
80
81 Each patch email has a Subject line of "[PATCH M of N] ...", using
81 Each patch email has a Subject line of "[PATCH M of N] ...", using
82 the first line of the changeset description as the subject text.
82 the first line of the changeset description as the subject text.
83 The message contains two or three body parts. First, the rest of
83 The message contains two or three body parts. First, the rest of
84 the changeset description. Next, (optionally) if the diffstat
84 the changeset description. Next, (optionally) if the diffstat
85 program is installed, the result of running diffstat on the patch.
85 program is installed, the result of running diffstat on the patch.
86 Finally, the patch itself, as generated by "hg export".
86 Finally, the patch itself, as generated by "hg export".
87
87
88 With --outgoing, emails will be generated for patches not
88 With --outgoing, emails will be generated for patches not
89 found in the destination repository (or only those which are
89 found in the destination repository (or only those which are
90 ancestors of the specified revisions if any are provided)
90 ancestors of the specified revisions if any are provided)
91
91
92 With --bundle, changesets are selected as for --outgoing,
92 With --bundle, changesets are selected as for --outgoing,
93 but a single email containing a binary Mercurial bundle as an
93 but a single email containing a binary Mercurial bundle as an
94 attachment will be sent.
94 attachment will be sent.
95
95
96 Examples:
96 Examples:
97
97
98 hg email -r 3000 # send patch 3000 only
98 hg email -r 3000 # send patch 3000 only
99 hg email -r 3000 -r 3001 # send patches 3000 and 3001
99 hg email -r 3000 -r 3001 # send patches 3000 and 3001
100 hg email -r 3000:3005 # send patches 3000 through 3005
100 hg email -r 3000:3005 # send patches 3000 through 3005
101 hg email 3000 # send patch 3000 (deprecated)
101 hg email 3000 # send patch 3000 (deprecated)
102
102
103 hg email -o # send all patches not in default
103 hg email -o # send all patches not in default
104 hg email -o DEST # send all patches not in DEST
104 hg email -o DEST # send all patches not in DEST
105 hg email -o -r 3000 # send all ancestors of 3000 not in default
105 hg email -o -r 3000 # send all ancestors of 3000 not in default
106 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
106 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
107
107
108 hg email -b # send bundle of all patches not in default
108 hg email -b # send bundle of all patches not in default
109 hg email -b DEST # send bundle of all patches not in DEST
109 hg email -b DEST # send bundle of all patches not in DEST
110 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
110 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
111 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
111 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
112
112
113 Before using this command, you will need to enable email in your hgrc.
113 Before using this command, you will need to enable email in your hgrc.
114 See the [email] section in hgrc(5) for details.
114 See the [email] section in hgrc(5) for details.
115 '''
115 '''
116
116
117 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
117 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
118 if not ui.interactive:
118 if not ui.interactive:
119 return default
119 return default
120 if default:
120 if default:
121 prompt += ' [%s]' % default
121 prompt += ' [%s]' % default
122 prompt += rest
122 prompt += rest
123 while True:
123 while True:
124 r = ui.prompt(prompt, default=default)
124 r = ui.prompt(prompt, default=default)
125 if r:
125 if r:
126 return r
126 return r
127 if default is not None:
127 if default is not None:
128 return default
128 return default
129 if empty_ok:
129 if empty_ok:
130 return r
130 return r
131 ui.warn(_('Please enter a valid value.\n'))
131 ui.warn(_('Please enter a valid value.\n'))
132
132
133 def confirm(s, denial):
133 def confirm(s, denial):
134 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
134 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
135 raise util.Abort(denial)
135 raise util.Abort(denial)
136
136
137 def cdiffstat(summary, patchlines):
137 def cdiffstat(summary, patchlines):
138 s = patch.diffstat(patchlines)
138 s = patch.diffstat(patchlines)
139 if s:
139 if s:
140 if summary:
140 if summary:
141 ui.write(summary, '\n')
141 ui.write(summary, '\n')
142 ui.write(s, '\n')
142 ui.write(s, '\n')
143 confirm(_('Does the diffstat above look okay'),
143 confirm(_('Does the diffstat above look okay'),
144 _('diffstat rejected'))
144 _('diffstat rejected'))
145 elif s is None:
145 elif s is None:
146 ui.warn(_('No diffstat information available.\n'))
146 ui.warn(_('No diffstat information available.\n'))
147 s = ''
147 s = ''
148 return s
148 return s
149
149
150 def makepatch(patch, idx, total):
150 def makepatch(patch, idx, total):
151 desc = []
151 desc = []
152 node = None
152 node = None
153 body = ''
153 body = ''
154 for line in patch:
154 for line in patch:
155 if line.startswith('#'):
155 if line.startswith('#'):
156 if line.startswith('# Node ID'):
156 if line.startswith('# Node ID'):
157 node = line.split()[-1]
157 node = line.split()[-1]
158 continue
158 continue
159 if line.startswith('diff -r') or line.startswith('diff --git'):
159 if line.startswith('diff -r') or line.startswith('diff --git'):
160 break
160 break
161 desc.append(line)
161 desc.append(line)
162 if not node:
162 if not node:
163 raise ValueError
163 raise ValueError
164
164
165 #body = ('\n'.join(desc[1:]).strip() or
165 #body = ('\n'.join(desc[1:]).strip() or
166 # 'Patch subject is complete summary.')
166 # 'Patch subject is complete summary.')
167 #body += '\n\n\n'
167 #body += '\n\n\n'
168
168
169 if opts['plain']:
169 if opts.get('plain'):
170 while patch and patch[0].startswith('# '):
170 while patch and patch[0].startswith('# '):
171 patch.pop(0)
171 patch.pop(0)
172 if patch:
172 if patch:
173 patch.pop(0)
173 patch.pop(0)
174 while patch and not patch[0].strip():
174 while patch and not patch[0].strip():
175 patch.pop(0)
175 patch.pop(0)
176 if opts['diffstat']:
176 if opts.get('diffstat'):
177 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
177 body += cdiffstat('\n'.join(desc), patch) + '\n\n'
178 if opts['attach']:
178 if opts.get('attach'):
179 msg = email.MIMEMultipart.MIMEMultipart()
179 msg = email.MIMEMultipart.MIMEMultipart()
180 if body:
180 if body:
181 msg.attach(email.MIMEText.MIMEText(body, 'plain'))
181 msg.attach(email.MIMEText.MIMEText(body, 'plain'))
182 p = email.MIMEText.MIMEText('\n'.join(patch), 'x-patch')
182 p = email.MIMEText.MIMEText('\n'.join(patch), 'x-patch')
183 binnode = bin(node)
183 binnode = bin(node)
184 # if node is mq patch, it will have patch file name as tag
184 # if node is mq patch, it will have patch file name as tag
185 patchname = [t for t in repo.nodetags(binnode)
185 patchname = [t for t in repo.nodetags(binnode)
186 if t.endswith('.patch') or t.endswith('.diff')]
186 if t.endswith('.patch') or t.endswith('.diff')]
187 if patchname:
187 if patchname:
188 patchname = patchname[0]
188 patchname = patchname[0]
189 elif total > 1:
189 elif total > 1:
190 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
190 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
191 binnode, idx, total)
191 binnode, idx, total)
192 else:
192 else:
193 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
193 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
194 p['Content-Disposition'] = 'inline; filename=' + patchname
194 p['Content-Disposition'] = 'inline; filename=' + patchname
195 msg.attach(p)
195 msg.attach(p)
196 else:
196 else:
197 body += '\n'.join(patch)
197 body += '\n'.join(patch)
198 msg = email.MIMEText.MIMEText(body)
198 msg = email.MIMEText.MIMEText(body)
199
199
200 subj = desc[0].strip().rstrip('. ')
200 subj = desc[0].strip().rstrip('. ')
201 if total == 1:
201 if total == 1:
202 subj = '[PATCH] ' + (opts['subject'] or subj)
202 subj = '[PATCH] ' + (opts.get('subject') or subj)
203 else:
203 else:
204 tlen = len(str(total))
204 tlen = len(str(total))
205 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
205 subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
206 msg['Subject'] = subj
206 msg['Subject'] = subj
207 msg['X-Mercurial-Node'] = node
207 msg['X-Mercurial-Node'] = node
208 return msg
208 return msg
209
209
210 def outgoing(dest, revs):
210 def outgoing(dest, revs):
211 '''Return the revisions present locally but not in dest'''
211 '''Return the revisions present locally but not in dest'''
212 dest = ui.expandpath(dest or 'default-push', dest or 'default')
212 dest = ui.expandpath(dest or 'default-push', dest or 'default')
213 revs = [repo.lookup(rev) for rev in revs]
213 revs = [repo.lookup(rev) for rev in revs]
214 other = hg.repository(ui, dest)
214 other = hg.repository(ui, dest)
215 ui.status(_('comparing with %s\n') % dest)
215 ui.status(_('comparing with %s\n') % dest)
216 o = repo.findoutgoing(other)
216 o = repo.findoutgoing(other)
217 if not o:
217 if not o:
218 ui.status(_("no changes found\n"))
218 ui.status(_("no changes found\n"))
219 return []
219 return []
220 o = repo.changelog.nodesbetween(o, revs or None)[0]
220 o = repo.changelog.nodesbetween(o, revs or None)[0]
221 return [str(repo.changelog.rev(r)) for r in o]
221 return [str(repo.changelog.rev(r)) for r in o]
222
222
223 def getbundle(dest):
223 def getbundle(dest):
224 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
224 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
225 tmpfn = os.path.join(tmpdir, 'bundle')
225 tmpfn = os.path.join(tmpdir, 'bundle')
226 try:
226 try:
227 commands.bundle(ui, repo, tmpfn, dest, **opts)
227 commands.bundle(ui, repo, tmpfn, dest, **opts)
228 return open(tmpfn, 'rb').read()
228 return open(tmpfn, 'rb').read()
229 finally:
229 finally:
230 try:
230 try:
231 os.unlink(tmpfn)
231 os.unlink(tmpfn)
232 except:
232 except:
233 pass
233 pass
234 os.rmdir(tmpdir)
234 os.rmdir(tmpdir)
235
235
236 if not (opts['test'] or opts['mbox']):
236 if not (opts.get('test') or opts.get('mbox')):
237 # really sending
237 # really sending
238 mail.validateconfig(ui)
238 mail.validateconfig(ui)
239
239
240 if not (revs or opts.get('rev')
240 if not (revs or opts.get('rev')
241 or opts.get('outgoing') or opts.get('bundle')):
241 or opts.get('outgoing') or opts.get('bundle')):
242 raise util.Abort(_('specify at least one changeset with -r or -o'))
242 raise util.Abort(_('specify at least one changeset with -r or -o'))
243
243
244 cmdutil.setremoteconfig(ui, opts)
244 cmdutil.setremoteconfig(ui, opts)
245 if opts.get('outgoing') and opts.get('bundle'):
245 if opts.get('outgoing') and opts.get('bundle'):
246 raise util.Abort(_("--outgoing mode always on with --bundle;"
246 raise util.Abort(_("--outgoing mode always on with --bundle;"
247 " do not re-specify --outgoing"))
247 " do not re-specify --outgoing"))
248
248
249 if opts.get('outgoing') or opts.get('bundle'):
249 if opts.get('outgoing') or opts.get('bundle'):
250 if len(revs) > 1:
250 if len(revs) > 1:
251 raise util.Abort(_("too many destinations"))
251 raise util.Abort(_("too many destinations"))
252 dest = revs and revs[0] or None
252 dest = revs and revs[0] or None
253 revs = []
253 revs = []
254
254
255 if opts.get('rev'):
255 if opts.get('rev'):
256 if revs:
256 if revs:
257 raise util.Abort(_('use only one form to specify the revision'))
257 raise util.Abort(_('use only one form to specify the revision'))
258 revs = opts.get('rev')
258 revs = opts.get('rev')
259
259
260 if opts.get('outgoing'):
260 if opts.get('outgoing'):
261 revs = outgoing(dest, opts.get('rev'))
261 revs = outgoing(dest, opts.get('rev'))
262 if opts.get('bundle'):
262 if opts.get('bundle'):
263 opts['revs'] = revs
263 opts['revs'] = revs
264
264
265 # start
265 # start
266 if opts.get('date'):
266 if opts.get('date'):
267 start_time = util.parsedate(opts['date'])
267 start_time = util.parsedate(opts.get('date'))
268 else:
268 else:
269 start_time = util.makedate()
269 start_time = util.makedate()
270
270
271 def genmsgid(id):
271 def genmsgid(id):
272 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
272 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
273
273
274 def getdescription(body, sender):
274 def getdescription(body, sender):
275 if opts['desc']:
275 if opts.get('desc'):
276 body = open(opts['desc']).read()
276 body = open(opts.get('desc')).read()
277 else:
277 else:
278 ui.write(_('\nWrite the introductory message for the '
278 ui.write(_('\nWrite the introductory message for the '
279 'patch series.\n\n'))
279 'patch series.\n\n'))
280 body = ui.edit(body, sender)
280 body = ui.edit(body, sender)
281 return body
281 return body
282
282
283 def getexportmsgs():
283 def getexportmsgs():
284 patches = []
284 patches = []
285
285
286 class exportee:
286 class exportee:
287 def __init__(self, container):
287 def __init__(self, container):
288 self.lines = []
288 self.lines = []
289 self.container = container
289 self.container = container
290 self.name = 'email'
290 self.name = 'email'
291
291
292 def write(self, data):
292 def write(self, data):
293 self.lines.append(data)
293 self.lines.append(data)
294
294
295 def close(self):
295 def close(self):
296 self.container.append(''.join(self.lines).split('\n'))
296 self.container.append(''.join(self.lines).split('\n'))
297 self.lines = []
297 self.lines = []
298
298
299 commands.export(ui, repo, *revs, **{'output': exportee(patches),
299 commands.export(ui, repo, *revs, **{'output': exportee(patches),
300 'switch_parent': False,
300 'switch_parent': False,
301 'text': None,
301 'text': None,
302 'git': opts.get('git')})
302 'git': opts.get('git')})
303
303
304 jumbo = []
304 jumbo = []
305 msgs = []
305 msgs = []
306
306
307 ui.write(_('This patch series consists of %d patches.\n\n')
307 ui.write(_('This patch series consists of %d patches.\n\n')
308 % len(patches))
308 % len(patches))
309
309
310 for p, i in zip(patches, xrange(len(patches))):
310 for p, i in zip(patches, xrange(len(patches))):
311 jumbo.extend(p)
311 jumbo.extend(p)
312 msgs.append(makepatch(p, i + 1, len(patches)))
312 msgs.append(makepatch(p, i + 1, len(patches)))
313
313
314 if len(patches) > 1:
314 if len(patches) > 1:
315 tlen = len(str(len(patches)))
315 tlen = len(str(len(patches)))
316
316
317 subj = '[PATCH %0*d of %d] %s' % (
317 subj = '[PATCH %0*d of %d] %s' % (
318 tlen, 0, len(patches),
318 tlen, 0, len(patches),
319 opts['subject'] or
319 opts.get('subject') or
320 prompt('Subject:',
320 prompt('Subject:',
321 rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches))))
321 rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches))))
322
322
323 body = ''
323 body = ''
324 if opts['diffstat']:
324 if opts.get('diffstat'):
325 d = cdiffstat(_('Final summary:\n'), jumbo)
325 d = cdiffstat(_('Final summary:\n'), jumbo)
326 if d:
326 if d:
327 body = '\n' + d
327 body = '\n' + d
328
328
329 body = getdescription(body, sender)
329 body = getdescription(body, sender)
330 msg = email.MIMEText.MIMEText(body)
330 msg = email.MIMEText.MIMEText(body)
331 msg['Subject'] = subj
331 msg['Subject'] = subj
332
332
333 msgs.insert(0, msg)
333 msgs.insert(0, msg)
334 return msgs
334 return msgs
335
335
336 def getbundlemsgs(bundle):
336 def getbundlemsgs(bundle):
337 subj = (opts['subject']
337 subj = (opts.get('subject')
338 or prompt('Subject:', default='A bundle for your repository'))
338 or prompt('Subject:', default='A bundle for your repository'))
339
339
340 body = getdescription('', sender)
340 body = getdescription('', sender)
341 msg = email.MIMEMultipart.MIMEMultipart()
341 msg = email.MIMEMultipart.MIMEMultipart()
342 if body:
342 if body:
343 msg.attach(email.MIMEText.MIMEText(body, 'plain'))
343 msg.attach(email.MIMEText.MIMEText(body, 'plain'))
344 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
344 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
345 datapart.set_payload(bundle)
345 datapart.set_payload(bundle)
346 datapart.add_header('Content-Disposition', 'attachment',
346 datapart.add_header('Content-Disposition', 'attachment',
347 filename='bundle.hg')
347 filename='bundle.hg')
348 email.Encoders.encode_base64(datapart)
348 email.Encoders.encode_base64(datapart)
349 msg.attach(datapart)
349 msg.attach(datapart)
350 msg['Subject'] = subj
350 msg['Subject'] = subj
351 return [msg]
351 return [msg]
352
352
353 sender = (opts['from'] or ui.config('email', 'from') or
353 sender = (opts.get('from') or ui.config('email', 'from') or
354 ui.config('patchbomb', 'from') or
354 ui.config('patchbomb', 'from') or
355 prompt('From', ui.username()))
355 prompt('From', ui.username()))
356
356
357 if opts.get('bundle'):
357 if opts.get('bundle'):
358 msgs = getbundlemsgs(getbundle(dest))
358 msgs = getbundlemsgs(getbundle(dest))
359 else:
359 else:
360 msgs = getexportmsgs()
360 msgs = getexportmsgs()
361
361
362 def getaddrs(opt, prpt, default = None):
362 def getaddrs(opt, prpt, default = None):
363 addrs = opts[opt] or (ui.config('email', opt) or
363 addrs = opts.get(opt) or (ui.config('email', opt) or
364 ui.config('patchbomb', opt) or
364 ui.config('patchbomb', opt) or
365 prompt(prpt, default = default)).split(',')
365 prompt(prpt, default = default)).split(',')
366 return [a.strip() for a in addrs if a.strip()]
366 return [a.strip() for a in addrs if a.strip()]
367
367
368 to = getaddrs('to', 'To')
368 to = getaddrs('to', 'To')
369 cc = getaddrs('cc', 'Cc', '')
369 cc = getaddrs('cc', 'Cc', '')
370
370
371 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
371 bcc = opts.get('bcc') or (ui.config('email', 'bcc') or
372 ui.config('patchbomb', 'bcc') or '').split(',')
372 ui.config('patchbomb', 'bcc') or '').split(',')
373 bcc = [a.strip() for a in bcc if a.strip()]
373 bcc = [a.strip() for a in bcc if a.strip()]
374
374
375 ui.write('\n')
375 ui.write('\n')
376
376
377 parent = None
377 parent = None
378
378
379 sender_addr = email.Utils.parseaddr(sender)[1]
379 sender_addr = email.Utils.parseaddr(sender)[1]
380 for m in msgs:
380 for m in msgs:
381 try:
381 try:
382 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
382 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
383 except TypeError:
383 except TypeError:
384 m['Message-Id'] = genmsgid('patchbomb')
384 m['Message-Id'] = genmsgid('patchbomb')
385 if parent:
385 if parent:
386 m['In-Reply-To'] = parent
386 m['In-Reply-To'] = parent
387 else:
387 else:
388 parent = m['Message-Id']
388 parent = m['Message-Id']
389 m['Date'] = util.datestr(date=start_time,
389 m['Date'] = util.datestr(date=start_time,
390 format="%a, %d %b %Y %H:%M:%S", timezone=True)
390 format="%a, %d %b %Y %H:%M:%S", timezone=True)
391
391
392 start_time = (start_time[0] + 1, start_time[1])
392 start_time = (start_time[0] + 1, start_time[1])
393 m['From'] = sender
393 m['From'] = sender
394 m['To'] = ', '.join(to)
394 m['To'] = ', '.join(to)
395 if cc:
395 if cc:
396 m['Cc'] = ', '.join(cc)
396 m['Cc'] = ', '.join(cc)
397 if bcc:
397 if bcc:
398 m['Bcc'] = ', '.join(bcc)
398 m['Bcc'] = ', '.join(bcc)
399 if opts['test']:
399 if opts.get('test'):
400 ui.status('Displaying ', m['Subject'], ' ...\n')
400 ui.status('Displaying ', m['Subject'], ' ...\n')
401 ui.flush()
401 ui.flush()
402 if 'PAGER' in os.environ:
402 if 'PAGER' in os.environ:
403 fp = os.popen(os.environ['PAGER'], 'w')
403 fp = os.popen(os.environ['PAGER'], 'w')
404 else:
404 else:
405 fp = ui
405 fp = ui
406 try:
406 try:
407 fp.write(m.as_string(0))
407 fp.write(m.as_string(0))
408 fp.write('\n')
408 fp.write('\n')
409 except IOError, inst:
409 except IOError, inst:
410 if inst.errno != errno.EPIPE:
410 if inst.errno != errno.EPIPE:
411 raise
411 raise
412 if fp is not ui:
412 if fp is not ui:
413 fp.close()
413 fp.close()
414 elif opts['mbox']:
414 elif opts.get('mbox'):
415 ui.status('Writing ', m['Subject'], ' ...\n')
415 ui.status('Writing ', m['Subject'], ' ...\n')
416 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
416 fp = open(opts.get('mbox'),
417 m.has_key('In-Reply-To') and 'ab+' or 'wb+')
417 date = util.datestr(date=start_time,
418 date = util.datestr(date=start_time,
418 format='%a %b %d %H:%M:%S %Y', timezone=False)
419 format='%a %b %d %H:%M:%S %Y', timezone=False)
419 fp.write('From %s %s\n' % (sender_addr, date))
420 fp.write('From %s %s\n' % (sender_addr, date))
420 fp.write(m.as_string(0))
421 fp.write(m.as_string(0))
421 fp.write('\n\n')
422 fp.write('\n\n')
422 fp.close()
423 fp.close()
423 else:
424 else:
424 ui.status('Sending ', m['Subject'], ' ...\n')
425 ui.status('Sending ', m['Subject'], ' ...\n')
425 # Exim does not remove the Bcc field
426 # Exim does not remove the Bcc field
426 del m['Bcc']
427 del m['Bcc']
427 mail.sendmail(ui, sender, to + bcc + cc, m.as_string(0))
428 mail.sendmail(ui, sender, to + bcc + cc, m.as_string(0))
428
429
429 cmdtable = {
430 cmdtable = {
430 "email":
431 "email":
431 (patchbomb,
432 (patchbomb,
432 [('a', 'attach', None, _('send patches as inline attachments')),
433 [('a', 'attach', None, _('send patches as inline attachments')),
433 ('', 'bcc', [], _('email addresses of blind copy recipients')),
434 ('', 'bcc', [], _('email addresses of blind copy recipients')),
434 ('c', 'cc', [], _('email addresses of copy recipients')),
435 ('c', 'cc', [], _('email addresses of copy recipients')),
435 ('d', 'diffstat', None, _('add diffstat output to messages')),
436 ('d', 'diffstat', None, _('add diffstat output to messages')),
436 ('', 'date', '', _('use the given date as the sending date')),
437 ('', 'date', '', _('use the given date as the sending date')),
437 ('', 'desc', '', _('use the given file as the series description')),
438 ('', 'desc', '', _('use the given file as the series description')),
438 ('g', 'git', None, _('use git extended diff format')),
439 ('g', 'git', None, _('use git extended diff format')),
439 ('f', 'from', '', _('email address of sender')),
440 ('f', 'from', '', _('email address of sender')),
440 ('', 'plain', None, _('omit hg patch header')),
441 ('', 'plain', None, _('omit hg patch header')),
441 ('n', 'test', None, _('print messages that would be sent')),
442 ('n', 'test', None, _('print messages that would be sent')),
442 ('m', 'mbox', '',
443 ('m', 'mbox', '',
443 _('write messages to mbox file instead of sending them')),
444 _('write messages to mbox file instead of sending them')),
444 ('o', 'outgoing', None,
445 ('o', 'outgoing', None,
445 _('send changes not found in the target repository')),
446 _('send changes not found in the target repository')),
446 ('b', 'bundle', None,
447 ('b', 'bundle', None,
447 _('send changes not in target as a binary bundle')),
448 _('send changes not in target as a binary bundle')),
448 ('r', 'rev', [], _('a revision to send')),
449 ('r', 'rev', [], _('a revision to send')),
449 ('s', 'subject', '',
450 ('s', 'subject', '',
450 _('subject of first message (intro or single patch)')),
451 _('subject of first message (intro or single patch)')),
451 ('t', 'to', [], _('email addresses of recipients')),
452 ('t', 'to', [], _('email addresses of recipients')),
452 ('', 'force', None,
453 ('', 'force', None,
453 _('run even when remote repository is unrelated (with -b)')),
454 _('run even when remote repository is unrelated (with -b)')),
454 ('', 'base', [],
455 ('', 'base', [],
455 _('a base changeset to specify instead of a destination (with -b)')),
456 _('a base changeset to specify instead of a destination (with -b)')),
456 ] + commands.remoteopts,
457 ] + commands.remoteopts,
457 _('hg email [OPTION]... [DEST]...'))
458 _('hg email [OPTION]... [DEST]...'))
458 }
459 }
General Comments 0
You need to be logged in to leave comments. Login now