]> code.delx.au - gnu-emacs-elpa/blob - admin/forward-diffs.py
Change it to work on the basis of files rather than packages
[gnu-emacs-elpa] / admin / forward-diffs.py
1 #!/usr/bin/python
2 ### forward-diffs.py --- forward emacs-elpa-diffs mails to maintainers
3
4 ## Copyright (C) 2012 Free Software Foundation, Inc.
5
6 ## Author: Glenn Morris <rgm@gnu.org>
7
8 ## This program is free software; you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published by
10 ## the Free Software Foundation, either version 3 of the License, or
11 ## (at your option) any later version.
12
13 ## This program is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
17
18 ## You should have received a copy of the GNU General Public License
19 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 ### Commentary:
22
23 ## Forward emails from the emacs-elpa-diffs mailing list to the
24 ## maintainer(s) of the modified files.
25
26 ## Two modes of operation:
27
28 ## 1) Create the maintfile (really this is just an optimization):
29 ## forward-diffs.py --create -p packagesdir -m maintfile
30
31 ## 2) Call from eg procmail to forward diffs. Example usage:
32
33 ## :0c
34 ## * ^TO_emacs-elpa-diffs@gnu\.org
35 ## | forward-diffs.py -p packagedir -m maintfile -l logfile -s sender
36
37 ## where
38
39 ## packagedir = /path/to/packages
40 ## sender = your email address
41 ## logfile = file to write log to (you might want to rotate/compress/examine it)
42 ## maintfile = file listing files and their maintainers, with format:
43 ##
44 ## package1/file1 email1
45 ## package2/file2 email2,email3
46 ##
47 ## Use "nomail" for the email field to not send a mail.
48 ##
49 ## overmaintfile = like maintfile, but takes precedence over it.
50
51 ### Code:
52
53 import optparse
54 import sys
55 import re
56 import email
57 import smtplib
58 import datetime
59 import os
60
61
62 ## Scan FILE for Author or Maintainer (preferred) headers.
63 ## Return a list of all email addresses found in MAINTS.
64 def scan_file(file, maints):
65
66 try:
67 fd = open( file, 'r')
68 except Exception as err:
69 lfile.write('Error opening file %s: %s\n' % (file, str(err)))
70 return 1
71
72 ## Max number of lines to scan looking for a maintainer.
73 ## (20 seems to be the highest at present).
74 max_lines = 50
75 nline = 0
76 cont = 0
77 type = ""
78
79 for line in fd:
80
81 nline += 1
82
83 if ( nline > max_lines ): break
84
85 ## Try and de-obfuscate. Worth it?
86 line = re.sub( '(?i) AT ', '@', line )
87 line = re.sub( '(?i) DOT ', '.', line )
88
89 if cont: # continued header?
90 reg = re.match( ('%s[ \t]+[^:]*?<?([\w.-]+@[\w.-]+)>?' % prefix), line, re.I )
91 if not reg: # not a continued header
92 cont = 0
93 prefix = ""
94 if ( type == "maint" ): break
95 type = ""
96
97 ## Check for one header immediately after another.
98 if not cont:
99 reg = re.match( '([^ ]+)? *(Author|Maintainer)s?: .*?<?([\w.-]+@[\w.-]+)>?', line, re.I )
100
101
102 if not reg: continue
103
104 if cont:
105 email = reg.group(1)
106 maints.append(email)
107 else:
108 cont = 1
109 prefix = reg.group(1) or ""
110 type = reg.group(2)
111 email = reg.group(3)
112 type = "maint" if re.search( 'Maintainer', type, re.I ) else "auth"
113 ## maints = [] does the wrong thing.
114 if type == "maint": del maints[:]
115 maints.append(email)
116
117 fd.close()
118
119
120 ## Scan all the files under dir for maintainer information.
121 ## Write to stdout, or optional argument outfile (which is overwritten).
122 def scan_dir(dir, outfile=None):
123
124 dir = re.sub( '/+$', '', dir) + '/' # ensure trailing /
125
126 if not os.path.isdir(dir):
127 sys.stderr.write('No such directory: %s\n' % dir)
128 sys.exit(1)
129
130 fd = 0
131 if outfile:
132 try:
133 fd = open( outfile, 'w' )
134 except Exception as err:
135 sys.stderr.write("Error opening `%s': %s\n" % (outfile, str(err)))
136 sys.exit(1)
137
138
139 for dirpath, dirnames, filenames in os.walk(dir):
140 for file in filenames:
141 path = os.path.join(dirpath, file)
142 maints = []
143 scan_file(path, maints)
144 ## This would skip printing empty maints.
145 ## That would mean we would scan the file each time for no reason.
146 ## if not maints: continue
147 path = re.sub( '^%s' % dir, '', path )
148 string = "%-50s %s\n" % (path, ",".join(maints))
149 if fd:
150 fd.write(string)
151 else:
152 print string,
153
154 if fd: fd.close()
155
156
157 usage="""usage: %prog <-p /path/to/packages> <-m maintfile>
158 <-l logfile -s sender|--create> [-o overmaintfile] [--sendmail] [--debug]
159 Take a GNU ELPA diff on stdin, and forward it to the maintainer(s)."""
160
161 parser = optparse.OptionParser()
162 parser.set_usage ( usage )
163 parser.add_option( "-m", dest="maintfile", default=None,
164 help="file listing packages and maintainers")
165 parser.add_option( "-l", dest="logfile", default=None,
166 help="file to append output to")
167 parser.add_option( "-o", dest="overmaintfile", default=None,
168 help="override file listing packages and maintainers")
169 parser.add_option( "-p", dest="packagedir", default=None,
170 help="path to packages directory")
171 parser.add_option( "-s", dest="sender", default=None,
172 help="sender address for forwards")
173 parser.add_option( "--create", dest="create", default=False,
174 action="store_true", help="create maintfile")
175 parser.add_option( "--sendmail", dest="sendmail", default=False,
176 action="store_true", help="use sendmail rather than smtp")
177 parser.add_option( "--debug", dest="debug", default=False,
178 action="store_true", help="debug only, do not send mail")
179
180
181 ( opts, args ) = parser.parse_args()
182
183
184 if not opts.maintfile:
185 parser.error('No maintfile specified')
186
187 if not opts.packagedir:
188 parser.error('No packagedir specified')
189
190 if not os.path.isdir(opts.packagedir):
191 sys.stderr.write('No such directory: %s\n' % opts.packagedir)
192 sys.exit(1)
193
194
195 if not opts.create:
196 if not opts.logfile:
197 parser.error('No logfile specified')
198
199 if not opts.sender:
200 parser.error('No sender specified')
201
202
203 ## Create the maintfile.
204 if opts.create:
205 scan_dir( opts.packagedir, opts.maintfile )
206 sys.exit()
207
208
209 try:
210 lfile = open( opts.logfile, 'a' )
211 except Exception as err:
212 sys.stderr.write('Error opening logfile: %s\n' % str(err))
213 sys.exit(1)
214
215
216 try:
217 mfile = open( opts.maintfile, 'r' )
218 except Exception as err:
219 lfile.write('Error opening maintfile: %s\n' % str(err))
220 sys.exit(1)
221
222 ## Each element is package/file: maint1, maint2, ...
223 maints = {}
224
225 for line in mfile:
226 if re.match( '#| *$', line ): continue
227 (pfile, maint) = line.split()
228 maints[pfile] = maint.split(',')
229
230 mfile.close()
231
232
233 if opts.overmaintfile:
234 try:
235 ofile = open( opts.overmaintfile, 'r' )
236 except Exception as err:
237 lfile.write('Error opening overmaintfile: %s\n' % str(err))
238 sys.exit(1)
239
240 for line in ofile:
241 if re.match( '#| *$', line ): continue
242 (pfile, maint) = line.split()
243 maints[pfile] = maint.split(',')
244
245 ofile.close()
246
247
248 stdin = sys.stdin
249
250 text = stdin.read()
251
252
253 resent_via = 'GNU ELPA diff forwarder'
254
255 message = email.message_from_string( text )
256
257 (msg_name, msg_from) = email.utils.parseaddr( message['from'] )
258
259 lfile.write('\nDate: %s\n' % str(datetime.datetime.now()))
260 lfile.write('Message-ID: %s\n' % message['message-id'])
261 lfile.write('From: %s\n' % msg_from)
262
263 if resent_via == message['x-resent-via']:
264 lfile.write('Mail loop; aborting\n')
265 sys.exit(1)
266
267
268 start = False
269 pfiles_seen = []
270 maints_seen = []
271
272 for line in text.splitlines():
273
274 if re.match( 'modified:$', line ):
275 start = True
276 continue
277
278 if not start: continue
279
280 if re.match( ' *$', line ): break
281
282
283 reg = re.match( 'packages/([^ ]+)', line.strip() )
284 if not reg: break
285
286
287 pfile = reg.group(1)
288
289 lfile.write('File: %s\n' % pfile)
290
291 ## Should not be possible for files (rather than packages)...
292 if pfile in pfiles_seen:
293 lfile.write('Already seen this file\n')
294 continue
295
296 pfiles_seen.append(pfile)
297
298
299 if not pfile in maints:
300 lfile.write('Unknown maintainer\n')
301 continue
302
303
304 for maint in maints[pfile]:
305
306 lfile.write('Maint: %s\n' % maint)
307
308
309 if maint in maints_seen:
310 lfile.write('Already seen this maintainer\n')
311 continue
312
313 maints_seen.append(maint)
314
315
316 if maint == "nomail":
317 lfile.write('Not resending, no mail is requested\n')
318 continue
319
320
321 if maint == msg_from:
322 lfile.write('Not resending, since maintainer = committer\n')
323 continue
324
325
326 forward = message
327 forward.add_header('X-Resent-Via', resent_via)
328 forward.add_header('Resent-To', maint)
329 forward.add_header('Resent-From', opts.sender)
330
331 lfile.write('Resending via %s...\n' % ('sendmail'
332 if opts.sendmail else 'smtp') )
333
334
335 if opts.debug: continue
336
337
338 if opts.sendmail:
339 s = os.popen("/usr/sbin/sendmail -i -f %s %s" %
340 (opts.sender, maint), "w")
341 s.write(forward.as_string())
342 status = s.close()
343 if status:
344 lfile.write('Sendmail exit status: %s\n' % status)
345
346 else:
347
348 try:
349 s = smtplib.SMTP('localhost')
350 except Exception as err:
351 lfile.write('Error opening smtp: %s\n' % str(err))
352 sys.exit(1)
353
354 try:
355 s.sendmail(opts.sender, maint, forward.as_string())
356 except Exception as err:
357 lfile.write('Error sending smtp: %s\n' % str(err))
358
359 s.quit()
360
361 ### forward-diffs.py ends here