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