Commit | Line | Data |
---|---|---|
b4bddc06 CM |
1 | __copyright__ = """ |
2 | Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com> | |
3 | ||
4 | This program is free software; you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License version 2 as | |
6 | published by the Free Software Foundation. | |
7 | ||
8 | This program is distributed in the hope that it will be useful, | |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | GNU General Public License for more details. | |
12 | ||
13 | You should have received a copy of the GNU General Public License | |
14 | along with this program; if not, write to the Free Software | |
15 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
16 | """ | |
17 | ||
18 | import sys, os, re, time, smtplib, email.Utils | |
19 | from optparse import OptionParser, make_option | |
20 | from time import gmtime, strftime | |
21 | ||
22 | from stgit.commands.common import * | |
23 | from stgit.utils import * | |
24 | from stgit import stack, git | |
25 | from stgit.config import config | |
26 | ||
27 | ||
28 | help = 'send a patch or series of patches by e-mail' | |
9a316368 | 29 | usage = """%prog [options] [<patch>]""" |
b4bddc06 | 30 | |
9a316368 CM |
31 | options = [make_option('-a', '--all', |
32 | help = 'e-mail all the applied patches', | |
33 | action = 'store_true'), | |
b4bddc06 CM |
34 | make_option('-r', '--range', |
35 | metavar = '[PATCH1][:[PATCH2]]', | |
36 | help = 'e-mail patches between PATCH1 and PATCH2'), | |
9a316368 CM |
37 | make_option('-t', '--template', metavar = 'FILE', |
38 | help = 'use FILE as the message template'), | |
b4bddc06 CM |
39 | make_option('-f', '--first', metavar = 'FILE', |
40 | help = 'send FILE as the first message'), | |
41 | make_option('-s', '--sleep', type = 'int', metavar = 'SECONDS', | |
42 | help = 'sleep for SECONDS between e-mails sending'), | |
43 | make_option('--refid', | |
44 | help = 'Use REFID as the reference id')] | |
45 | ||
46 | ||
47 | def __parse_addresses(string): | |
48 | """Return a two elements tuple: (from, [to]) | |
49 | """ | |
50 | def __addr_list(string): | |
51 | return re.split('.*?([\w\.]+@[\w\.]+)', string)[1:-1:2] | |
52 | ||
53 | from_addr_list = [] | |
54 | to_addr_list = [] | |
55 | for line in string.split('\n'): | |
56 | if re.match('from:\s+', line, re.I): | |
57 | from_addr_list += __addr_list(line) | |
58 | elif re.match('(to|cc|bcc):\s+', line, re.I): | |
59 | to_addr_list += __addr_list(line) | |
60 | ||
61 | if len(from_addr_list) != 1: | |
62 | raise CmdException, 'No "From" address' | |
63 | if len(to_addr_list) == 0: | |
64 | raise CmdException, 'No "To/Cc/Bcc" addresses' | |
65 | ||
66 | return (from_addr_list[0], to_addr_list) | |
67 | ||
68 | def __send_message(smtpserver, from_addr, to_addr_list, msg, sleep): | |
69 | """Send the message using the given SMTP server | |
70 | """ | |
71 | try: | |
72 | s = smtplib.SMTP(smtpserver) | |
73 | except Exception, err: | |
74 | raise CmdException, str(err) | |
75 | ||
76 | s.set_debuglevel(0) | |
77 | try: | |
78 | s.sendmail(from_addr, to_addr_list, msg) | |
79 | # give recipients a chance of receiving patches in the correct order | |
80 | time.sleep(sleep) | |
81 | except Exception, err: | |
82 | raise CmdException, str(err) | |
83 | ||
84 | s.quit() | |
85 | ||
86 | def __build_first(tmpl, total_nr, msg_id): | |
87 | """Build the first message (series description) to be sent via SMTP | |
88 | """ | |
89 | headers_end = 'Message-Id: %s\n' % (msg_id) | |
90 | total_nr_str = str(total_nr) | |
91 | ||
92 | tmpl_dict = {'endofheaders': headers_end, | |
93 | 'date': email.Utils.formatdate(localtime = True), | |
94 | 'totalnr': total_nr_str} | |
95 | ||
96 | try: | |
97 | msg = tmpl % tmpl_dict | |
98 | except KeyError, err: | |
99 | raise CmdException, 'Unknown patch template variable: %s' \ | |
100 | % err | |
101 | except TypeError: | |
102 | raise CmdException, 'Only "%(name)s" variables are ' \ | |
103 | 'supported in the patch template' | |
104 | ||
105 | return msg | |
106 | ||
107 | ||
108 | def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id = None): | |
109 | """Build the message to be sent via SMTP | |
110 | """ | |
111 | p = crt_series.get_patch(patch) | |
112 | ||
113 | descr = p.get_description().strip() | |
114 | descr_lines = descr.split('\n') | |
115 | ||
116 | short_descr = descr_lines[0].rstrip() | |
117 | long_descr = reduce(lambda x, y: x + '\n' + y, | |
118 | descr_lines[1:], '').lstrip() | |
119 | ||
120 | headers_end = 'Message-Id: %s\n' % (msg_id) | |
121 | if ref_id: | |
122 | headers_end += "In-Reply-To: %s\n" % (ref_id) | |
123 | headers_end += "References: %s\n" % (ref_id) | |
124 | ||
125 | total_nr_str = str(total_nr) | |
126 | patch_nr_str = str(patch_nr).zfill(len(total_nr_str)) | |
127 | ||
128 | tmpl_dict = {'patch': patch, | |
129 | 'shortdescr': short_descr, | |
130 | 'longdescr': long_descr, | |
131 | 'endofheaders': headers_end, | |
132 | 'diff': git.diff(rev1 = git_id('%s/bottom' % patch), | |
133 | rev2 = git_id('%s/top' % patch)), | |
134 | 'diffstat': git.diffstat(rev1 = git_id('%s/bottom'%patch), | |
135 | rev2 = git_id('%s/top' % patch)), | |
136 | 'date': email.Utils.formatdate(localtime = True), | |
137 | 'patchnr': patch_nr_str, | |
138 | 'totalnr': total_nr_str, | |
139 | 'authname': p.get_authname(), | |
140 | 'authemail': p.get_authemail(), | |
141 | 'authdate': p.get_authdate(), | |
142 | 'commname': p.get_commname(), | |
143 | 'commemail': p.get_commemail()} | |
144 | for key in tmpl_dict: | |
145 | if not tmpl_dict[key]: | |
146 | tmpl_dict[key] = '' | |
147 | ||
148 | try: | |
149 | msg = tmpl % tmpl_dict | |
150 | except KeyError, err: | |
151 | raise CmdException, 'Unknown patch template variable: %s' \ | |
152 | % err | |
153 | except TypeError: | |
154 | raise CmdException, 'Only "%(name)s" variables are ' \ | |
155 | 'supported in the patch template' | |
156 | ||
157 | return msg | |
158 | ||
159 | ||
160 | def func(parser, options, args): | |
161 | """Send the patches by e-mail using the patchmail.tmpl file as | |
162 | a template | |
163 | """ | |
9a316368 | 164 | if len(args) > 1: |
b4bddc06 CM |
165 | parser.error('incorrect number of arguments') |
166 | ||
167 | if not config.has_option('stgit', 'smtpserver'): | |
168 | raise CmdException, 'smtpserver not defined' | |
169 | smtpserver = config.get('stgit', 'smtpserver') | |
170 | ||
171 | applied = crt_series.get_applied() | |
172 | ||
9a316368 CM |
173 | if len(args) == 1: |
174 | if args[0] in applied: | |
175 | patches = [args[0]] | |
176 | else: | |
177 | raise CmdException, 'Patch "%s" not applied' % args[0] | |
178 | elif options.all: | |
179 | patches = applied | |
180 | elif options.range: | |
b4bddc06 CM |
181 | boundaries = options.range.split(':') |
182 | if len(boundaries) == 1: | |
183 | start = boundaries[0] | |
184 | stop = boundaries[0] | |
185 | elif len(boundaries) == 2: | |
186 | if boundaries[0] == '': | |
187 | start = applied[0] | |
188 | else: | |
189 | start = boundaries[0] | |
190 | if boundaries[1] == '': | |
191 | stop = applied[-1] | |
192 | else: | |
193 | stop = boundaries[1] | |
194 | else: | |
195 | raise CmdException, 'incorrect parameters to "--range"' | |
196 | ||
197 | if start in applied: | |
198 | start_idx = applied.index(start) | |
199 | else: | |
200 | raise CmdException, 'Patch "%s" not applied' % start | |
201 | if stop in applied: | |
202 | stop_idx = applied.index(stop) + 1 | |
203 | else: | |
204 | raise CmdException, 'Patch "%s" not applied' % stop | |
205 | ||
206 | if start_idx >= stop_idx: | |
207 | raise CmdException, 'Incorrect patch range order' | |
9a316368 CM |
208 | |
209 | patches = applied[start_idx:stop_idx] | |
b4bddc06 | 210 | else: |
9a316368 | 211 | raise CmdException, 'Incorrect options. Unknown patches to send' |
b4bddc06 | 212 | |
b4bddc06 | 213 | total_nr = len(patches) |
9a316368 CM |
214 | if total_nr == 0: |
215 | raise CmdException, 'No patches to send' | |
b4bddc06 CM |
216 | |
217 | ref_id = options.refid | |
218 | ||
219 | if options.sleep != None: | |
220 | sleep = options.sleep | |
221 | else: | |
222 | sleep = 2 | |
223 | ||
224 | # send the first message (if any) | |
225 | if options.first: | |
226 | tmpl = file(options.first).read() | |
227 | from_addr, to_addr_list = __parse_addresses(tmpl) | |
228 | ||
229 | msg_id = email.Utils.make_msgid('stgit') | |
230 | msg = __build_first(tmpl, total_nr, msg_id) | |
231 | ||
232 | # subsequent e-mails are seen as replies to the first one | |
233 | ref_id = msg_id | |
234 | ||
235 | print 'Sending file "%s"...' % options.first, | |
236 | sys.stdout.flush() | |
237 | ||
238 | __send_message(smtpserver, from_addr, to_addr_list, msg, sleep) | |
239 | ||
240 | print 'done' | |
241 | ||
242 | # send the patches | |
243 | if options.template: | |
244 | tfile = options.template | |
245 | else: | |
246 | tfile = os.path.join(git.base_dir, 'patchmail.tmpl') | |
247 | tmpl = file(tfile).read() | |
248 | ||
249 | from_addr, to_addr_list = __parse_addresses(tmpl) | |
250 | ||
251 | for (p, patch_nr) in zip(patches, range(1, len(patches) + 1)): | |
252 | msg_id = email.Utils.make_msgid('stgit') | |
253 | msg = __build_message(tmpl, p, patch_nr, total_nr, msg_id, ref_id) | |
254 | # subsequent e-mails are seen as replies to the first one | |
255 | if not ref_id: | |
256 | ref_id = msg_id | |
257 | ||
258 | print 'Sending patch "%s"...' % p, | |
259 | sys.stdout.flush() | |
260 | ||
261 | __send_message(smtpserver, from_addr, to_addr_list, msg, sleep) | |
262 | ||
263 | print 'done' |