##// END OF EJS Templates
Add a filename for the bundle
John Goerzen -
r4284:a04141f5 default
parent child Browse files
Show More
@@ -1,416 +1,418 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 try:
73 try:
74 # readline gives raw_input editing capabilities, but is not
74 # readline gives raw_input editing capabilities, but is not
75 # present on windows
75 # present on windows
76 import readline
76 import readline
77 except ImportError: pass
77 except ImportError: pass
78
78
79 def patchbomb(ui, repo, *revs, **opts):
79 def patchbomb(ui, repo, *revs, **opts):
80 '''send changesets by email
80 '''send changesets by email
81
81
82 By default, diffs are sent in the format generated by hg export,
82 By default, diffs are sent in the format generated by hg export,
83 one per message. The series starts with a "[PATCH 0 of N]"
83 one per message. The series starts with a "[PATCH 0 of N]"
84 introduction, which describes the series as a whole.
84 introduction, which describes the series as a whole.
85
85
86 Each patch email has a Subject line of "[PATCH M of N] ...", using
86 Each patch email has a Subject line of "[PATCH M of N] ...", using
87 the first line of the changeset description as the subject text.
87 the first line of the changeset description as the subject text.
88 The message contains two or three body parts. First, the rest of
88 The message contains two or three body parts. First, the rest of
89 the changeset description. Next, (optionally) if the diffstat
89 the changeset description. Next, (optionally) if the diffstat
90 program is installed, the result of running diffstat on the patch.
90 program is installed, the result of running diffstat on the patch.
91 Finally, the patch itself, as generated by "hg export".
91 Finally, the patch itself, as generated by "hg export".
92
92
93 With --outgoing, emails will be generated for patches not
93 With --outgoing, emails will be generated for patches not
94 found in the destination repository (or only those which are
94 found in the destination repository (or only those which are
95 ancestors of the specified revisions if any are provided)
95 ancestors of the specified revisions if any are provided)
96
96
97 With --bundle, changesets are selected as for --outgoing,
97 With --bundle, changesets are selected as for --outgoing,
98 but a single email containing a binary Mercurial bundle as an
98 but a single email containing a binary Mercurial bundle as an
99 attachment will be sent.
99 attachment will be sent.
100
100
101 Examples:
101 Examples:
102
102
103 hg email -r 3000 # send patch 3000 only
103 hg email -r 3000 # send patch 3000 only
104 hg email -r 3000 -r 3001 # send patches 3000 and 3001
104 hg email -r 3000 -r 3001 # send patches 3000 and 3001
105 hg email -r 3000:3005 # send patches 3000 through 3005
105 hg email -r 3000:3005 # send patches 3000 through 3005
106 hg email 3000 # send patch 3000 (deprecated)
106 hg email 3000 # send patch 3000 (deprecated)
107
107
108 hg email -o # send all patches not in default
108 hg email -o # send all patches not in default
109 hg email -o DEST # send all patches not in DEST
109 hg email -o DEST # send all patches not in DEST
110 hg email -o -r 3000 # send all ancestors of 3000 not in default
110 hg email -o -r 3000 # send all ancestors of 3000 not in default
111 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
111 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
112
112
113 hg email -b # send bundle of all patches not in default
113 hg email -b # send bundle of all patches not in default
114 hg email -b DEST # send bundle of all patches not in DEST
114 hg email -b DEST # send bundle of all patches not in DEST
115 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
115 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
116 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
116 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
117
117
118 Before using this command, you will need to enable email in your hgrc.
118 Before using this command, you will need to enable email in your hgrc.
119 See the [email] section in hgrc(5) for details.
119 See the [email] section in hgrc(5) for details.
120 '''
120 '''
121
121
122 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
122 def prompt(prompt, default = None, rest = ': ', empty_ok = False):
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 # option handling
226 # option handling
227 commands.setremoteconfig(ui, opts)
227 commands.setremoteconfig(ui, opts)
228 if opts.get('outgoint') and opts.get('bundle'):
228 if opts.get('outgoint') and opts.get('bundle'):
229 raise util.Abort(_("--outgoing mode always on with --bundle; do not re-specify --outgoing"))
229 raise util.Abort(_("--outgoing mode always on with --bundle; do not re-specify --outgoing"))
230
230
231 if opts.get('outgoing') or opts.get('bundle'):
231 if opts.get('outgoing') or opts.get('bundle'):
232 if len(revs) > 1:
232 if len(revs) > 1:
233 raise util.Abort(_("too many destinations"))
233 raise util.Abort(_("too many destinations"))
234 dest = revs and revs[0] or None
234 dest = revs and revs[0] or None
235 revs = []
235 revs = []
236
236
237 if opts.get('rev'):
237 if opts.get('rev'):
238 if revs:
238 if revs:
239 raise util.Abort(_('use only one form to specify the revision'))
239 raise util.Abort(_('use only one form to specify the revision'))
240 revs = opts.get('rev')
240 revs = opts.get('rev')
241
241
242 if opts.get('outgoing'):
242 if opts.get('outgoing'):
243 revs = outgoing(dest, opts.get('rev'))
243 revs = outgoing(dest, opts.get('rev'))
244 if opts.get('bundle'):
244 if opts.get('bundle'):
245 opts['revs'] = revs
245 opts['revs'] = revs
246
246
247 # start
247 # start
248 start_time = util.makedate()
248 start_time = util.makedate()
249
249
250 def genmsgid(id):
250 def genmsgid(id):
251 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
251 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
252
252
253 sender = (opts['from'] or ui.config('email', 'from') or
253 sender = (opts['from'] or ui.config('email', 'from') or
254 ui.config('patchbomb', 'from') or
254 ui.config('patchbomb', 'from') or
255 prompt('From', ui.username()))
255 prompt('From', ui.username()))
256
256
257 def getaddrs(opt, prpt, default = None):
257 def getaddrs(opt, prpt, default = None):
258 addrs = opts[opt] or (ui.config('email', opt) or
258 addrs = opts[opt] or (ui.config('email', opt) or
259 ui.config('patchbomb', opt) or
259 ui.config('patchbomb', opt) or
260 prompt(prpt, default = default)).split(',')
260 prompt(prpt, default = default)).split(',')
261 return [a.strip() for a in addrs if a.strip()]
261 return [a.strip() for a in addrs if a.strip()]
262
262
263 to = getaddrs('to', 'To')
263 to = getaddrs('to', 'To')
264 cc = getaddrs('cc', 'Cc', '')
264 cc = getaddrs('cc', 'Cc', '')
265
265
266 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
266 bcc = opts['bcc'] or (ui.config('email', 'bcc') or
267 ui.config('patchbomb', 'bcc') or '').split(',')
267 ui.config('patchbomb', 'bcc') or '').split(',')
268 bcc = [a.strip() for a in bcc if a.strip()]
268 bcc = [a.strip() for a in bcc if a.strip()]
269
269
270 def getexportmsgs():
270 def getexportmsgs():
271 patches = []
271 patches = []
272
272
273 class exportee:
273 class exportee:
274 def __init__(self, container):
274 def __init__(self, container):
275 self.lines = []
275 self.lines = []
276 self.container = container
276 self.container = container
277 self.name = 'email'
277 self.name = 'email'
278
278
279 def write(self, data):
279 def write(self, data):
280 self.lines.append(data)
280 self.lines.append(data)
281
281
282 def close(self):
282 def close(self):
283 self.container.append(''.join(self.lines).split('\n'))
283 self.container.append(''.join(self.lines).split('\n'))
284 self.lines = []
284 self.lines = []
285
285
286 commands.export(ui, repo, *revs, **{'output': exportee(patches),
286 commands.export(ui, repo, *revs, **{'output': exportee(patches),
287 'switch_parent': False,
287 'switch_parent': False,
288 'text': None,
288 'text': None,
289 'git': opts.get('git')})
289 'git': opts.get('git')})
290
290
291 jumbo = []
291 jumbo = []
292 msgs = []
292 msgs = []
293
293
294 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
294 ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
295
295
296 for p, i in zip(patches, xrange(len(patches))):
296 for p, i in zip(patches, xrange(len(patches))):
297 jumbo.extend(p)
297 jumbo.extend(p)
298 msgs.append(makepatch(p, i + 1, len(patches)))
298 msgs.append(makepatch(p, i + 1, len(patches)))
299
299
300 if len(patches) > 1:
300 if len(patches) > 1:
301 tlen = len(str(len(patches)))
301 tlen = len(str(len(patches)))
302
302
303 subj = '[PATCH %0*d of %d] %s' % (
303 subj = '[PATCH %0*d of %d] %s' % (
304 tlen, 0,
304 tlen, 0,
305 len(patches),
305 len(patches),
306 opts['subject'] or
306 opts['subject'] or
307 prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
307 prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
308 len(patches))))
308 len(patches))))
309
309
310 body = ''
310 body = ''
311 if opts['diffstat']:
311 if opts['diffstat']:
312 d = cdiffstat(_('Final summary:\n'), jumbo)
312 d = cdiffstat(_('Final summary:\n'), jumbo)
313 if d: body = '\n' + d
313 if d: body = '\n' + d
314
314
315 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
315 ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
316 body = ui.edit(body, sender)
316 body = ui.edit(body, sender)
317
317
318 msg = email.MIMEText.MIMEText(body)
318 msg = email.MIMEText.MIMEText(body)
319 msg['Subject'] = subj
319 msg['Subject'] = subj
320
320
321 msgs.insert(0, msg)
321 msgs.insert(0, msg)
322 return msgs
322 return msgs
323
323
324 def getbundlemsgs(bundle):
324 def getbundlemsgs(bundle):
325 subj = opts['subject'] or \
325 subj = opts['subject'] or \
326 prompt('Subject:', default='A bundle for your repository')
326 prompt('Subject:', default='A bundle for your repository')
327 ui.write(_('\nWrite the introductory message for the bundle.\n\n'))
327 ui.write(_('\nWrite the introductory message for the bundle.\n\n'))
328 body = ui.edit('', sender)
328 body = ui.edit('', sender)
329
329
330 msg = email.MIMEMultipart.MIMEMultipart()
330 msg = email.MIMEMultipart.MIMEMultipart()
331 if body:
331 if body:
332 msg.attach(email.MIMEText.MIMEText(body, 'plain'))
332 msg.attach(email.MIMEText.MIMEText(body, 'plain'))
333 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
333 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
334 datapart.set_payload(bundle)
334 datapart.set_payload(bundle)
335 datapart.add_header('Content-Disposition', 'attachment',
336 filename='bundle.hg')
335 email.Encoders.encode_base64(datapart)
337 email.Encoders.encode_base64(datapart)
336 msg.attach(datapart)
338 msg.attach(datapart)
337 msg['Subject'] = subj
339 msg['Subject'] = subj
338 return [msg]
340 return [msg]
339
341
340 if opts.get('bundle'):
342 if opts.get('bundle'):
341 msgs = getbundlemsgs(getbundle(dest))
343 msgs = getbundlemsgs(getbundle(dest))
342 else:
344 else:
343 msgs = getexportmsgs()
345 msgs = getexportmsgs()
344
346
345 ui.write('\n')
347 ui.write('\n')
346
348
347 if not opts['test'] and not opts['mbox']:
349 if not opts['test'] and not opts['mbox']:
348 mailer = mail.connect(ui)
350 mailer = mail.connect(ui)
349 parent = None
351 parent = None
350
352
351 sender_addr = email.Utils.parseaddr(sender)[1]
353 sender_addr = email.Utils.parseaddr(sender)[1]
352 for m in msgs:
354 for m in msgs:
353 try:
355 try:
354 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
356 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
355 except TypeError:
357 except TypeError:
356 m['Message-Id'] = genmsgid('patchbomb')
358 m['Message-Id'] = genmsgid('patchbomb')
357 if parent:
359 if parent:
358 m['In-Reply-To'] = parent
360 m['In-Reply-To'] = parent
359 else:
361 else:
360 parent = m['Message-Id']
362 parent = m['Message-Id']
361 m['Date'] = util.datestr(date=start_time,
363 m['Date'] = util.datestr(date=start_time,
362 format="%a, %d %b %Y %H:%M:%S", timezone=True)
364 format="%a, %d %b %Y %H:%M:%S", timezone=True)
363
365
364 start_time = (start_time[0] + 1, start_time[1])
366 start_time = (start_time[0] + 1, start_time[1])
365 m['From'] = sender
367 m['From'] = sender
366 m['To'] = ', '.join(to)
368 m['To'] = ', '.join(to)
367 if cc: m['Cc'] = ', '.join(cc)
369 if cc: m['Cc'] = ', '.join(cc)
368 if bcc: m['Bcc'] = ', '.join(bcc)
370 if bcc: m['Bcc'] = ', '.join(bcc)
369 if opts['test']:
371 if opts['test']:
370 ui.status('Displaying ', m['Subject'], ' ...\n')
372 ui.status('Displaying ', m['Subject'], ' ...\n')
371 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
373 fp = os.popen(os.getenv('PAGER', 'more'), 'w')
372 try:
374 try:
373 fp.write(m.as_string(0))
375 fp.write(m.as_string(0))
374 fp.write('\n')
376 fp.write('\n')
375 except IOError, inst:
377 except IOError, inst:
376 if inst.errno != errno.EPIPE:
378 if inst.errno != errno.EPIPE:
377 raise
379 raise
378 fp.close()
380 fp.close()
379 elif opts['mbox']:
381 elif opts['mbox']:
380 ui.status('Writing ', m['Subject'], ' ...\n')
382 ui.status('Writing ', m['Subject'], ' ...\n')
381 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
383 fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
382 date = util.datestr(date=start_time,
384 date = util.datestr(date=start_time,
383 format='%a %b %d %H:%M:%S %Y', timezone=False)
385 format='%a %b %d %H:%M:%S %Y', timezone=False)
384 fp.write('From %s %s\n' % (sender_addr, date))
386 fp.write('From %s %s\n' % (sender_addr, date))
385 fp.write(m.as_string(0))
387 fp.write(m.as_string(0))
386 fp.write('\n\n')
388 fp.write('\n\n')
387 fp.close()
389 fp.close()
388 else:
390 else:
389 ui.status('Sending ', m['Subject'], ' ...\n')
391 ui.status('Sending ', m['Subject'], ' ...\n')
390 # Exim does not remove the Bcc field
392 # Exim does not remove the Bcc field
391 del m['Bcc']
393 del m['Bcc']
392 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
394 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
393
395
394 cmdtable = {
396 cmdtable = {
395 'email':
397 'email':
396 (patchbomb,
398 (patchbomb,
397 [('a', 'attach', None, 'send patches as inline attachments'),
399 [('a', 'attach', None, 'send patches as inline attachments'),
398 ('', 'bcc', [], 'email addresses of blind copy recipients'),
400 ('', 'bcc', [], 'email addresses of blind copy recipients'),
399 ('c', 'cc', [], 'email addresses of copy recipients'),
401 ('c', 'cc', [], 'email addresses of copy recipients'),
400 ('d', 'diffstat', None, 'add diffstat output to messages'),
402 ('d', 'diffstat', None, 'add diffstat output to messages'),
401 ('g', 'git', None, _('use git extended diff format')),
403 ('g', 'git', None, _('use git extended diff format')),
402 ('f', 'from', '', 'email address of sender'),
404 ('f', 'from', '', 'email address of sender'),
403 ('', 'plain', None, 'omit hg patch header'),
405 ('', 'plain', None, 'omit hg patch header'),
404 ('n', 'test', None, 'print messages that would be sent'),
406 ('n', 'test', None, 'print messages that would be sent'),
405 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
407 ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
406 ('o', 'outgoing', None, _('send changes not found in the target repository')),
408 ('o', 'outgoing', None, _('send changes not found in the target repository')),
407 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
409 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
408 ('r', 'rev', [], _('a revision to send')),
410 ('r', 'rev', [], _('a revision to send')),
409 ('s', 'subject', '', 'subject of first message (intro or single patch)'),
411 ('s', 'subject', '', 'subject of first message (intro or single patch)'),
410 ('t', 'to', [], 'email addresses of recipients'),
412 ('t', 'to', [], 'email addresses of recipients'),
411 ('', 'force', None, _('run even when remote repository is unrelated (with -b)')),
413 ('', 'force', None, _('run even when remote repository is unrelated (with -b)')),
412 ('', 'base', [],
414 ('', 'base', [],
413 _('a base changeset to specify instead of a destination (with -b)'))]
415 _('a base changeset to specify instead of a destination (with -b)'))]
414 + commands.remoteopts,
416 + commands.remoteopts,
415 "hg email [OPTION]... [DEST]...")
417 "hg email [OPTION]... [DEST]...")
416 }
418 }
General Comments 0
You need to be logged in to leave comments. Login now