]>
code.delx.au - gnu-emacs-elpa/blob - admin/forward-diffs.py
2 ### forward-diffs.py --- forward emacs-diffs mails to maintainers
4 ## Copyright (C) 2012-2014 Free Software Foundation, Inc.
6 ## Author: Glenn Morris <rgm@gnu.org>
7 ## Maintainer: emacs-devel@gnu.org
9 ## This program is free software; you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published by
11 ## the Free Software Foundation, either version 3 of the License, or
12 ## (at your option) any later version.
14 ## This program is distributed in the hope that it will be useful,
15 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ## GNU General Public License for more details.
19 ## You should have received a copy of the GNU General Public License
20 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
24 ## Forward emails from an emacs-diffs style mailing list to the
25 ## maintainer(s) of the modified files.
27 ## Two modes of operation:
29 ## 1) Create the maintfile (really this is just an optimization):
30 ## forward-diffs.py --create -p packagesdir -m maintfile
32 ## You can start with an empty maintfile and normal operation in 2)
33 ## will append information as needed.
35 ## 2) Call from eg procmail to forward diffs. Example usage:
38 ## * ^TO_emacs-elpa-diffs@gnu\.org
39 ## | forward-diffs.py -p packagedir -m maintfile -l logfile \
40 ## -o overmaint -s sender
44 ## packagedir = /path/to/packages
45 ## sender = your email address
46 ## logfile = file to write log to (you might want to rotate/compress/examine it)
47 ## maintfile = file listing files and their maintainers, with format:
49 ## package1/file1 email1
50 ## package2/file2 email2,email3
53 ## Use "nomail" for the email field to not send a mail.
54 ## An entry that is a directory applies to all files in that directory
55 ## that do not have specific maintainers.
57 ## overmaint = like maintfile, but takes precedence over it.
70 ## Scan FILE for Author or Maintainer (preferred) headers.
71 ## Return a list of all email addresses found in MAINTS.
72 def scan_file(file, maints
):
76 except Exception as err
:
77 lfile
.write('Error opening file %s: %s\n' % (file, str(err
)))
80 ## Max number of lines to scan looking for a maintainer.
81 ## (20 seems to be the highest at present).
91 if ( nline
> max_lines
): break
93 ## Try and de-obfuscate. Worth it?
94 line
= re
.sub( '(?i) AT ', '@', line
)
95 line
= re
.sub( '(?i) DOT ', '.', line
)
97 if cont
: # continued header?
98 reg
= re
.match( ('%s[ \t]+[^:]*?<?([\w.-]+@[\w.-]+)>?' % prefix
), line
, re
.I
)
99 if not reg
: # not a continued header
102 if ( type == "maint" ): break
105 ## Check for one header immediately after another.
107 reg
= re
.match( '([^ ]+)? *(Author|Maintainer)s?: .*?<?([\w.-]+@[\w.-]+)>?', line
, re
.I
)
117 prefix
= reg
.group(1) or ""
120 type = "maint" if re
.search( 'Maintainer', type, re
.I
) else "auth"
121 ## maints = [] does the wrong thing.
122 if type == "maint": del maints
[:]
128 ## Scan all the files under dir for maintainer information.
129 ## Write to stdout, or optional argument outfile (which is overwritten).
130 def scan_dir(dir, outfile
=None):
132 dir = re
.sub( '/+$', '', dir) + '/' # ensure trailing /
134 if not os
.path
.isdir(dir):
135 sys
.stderr
.write('No such directory: %s\n' % dir)
141 fd
= open( outfile
, 'w' )
142 except Exception as err
:
143 sys
.stderr
.write("Error opening `%s': %s\n" % (outfile
, str(err
)))
147 for dirpath
, dirnames
, filenames
in os
.walk(dir):
148 for file in filenames
:
149 path
= os
.path
.join(dirpath
, file)
151 scan_file(path
, maints
)
152 ## This would skip printing empty maints.
153 ## That would mean we would scan the file each time for no reason.
154 ## But empty maintainers are an error at present.
155 if not maints
: continue
156 path
= re
.sub( '^%s' % dir, '', path
)
157 string
= "%-50s %s\n" % (path
, ",".join(maints
))
166 usage
="""usage: %prog <-p /path/to/packages> <-m maintfile>
167 <-l logfile -s sender|--create> [-o overmaintfile] [--prefix prefix]
168 [--sendmail] [--debug]
169 Take an emacs-diffs mail on stdin, and forward it to the maintainer(s)."""
171 parser
= optparse
.OptionParser()
172 parser
.set_usage ( usage
)
173 parser
.add_option( "-m", dest
="maintfile", default
=None,
174 help="file listing packages and maintainers")
175 parser
.add_option( "-l", dest
="logfile", default
=None,
176 help="file to append output to")
177 parser
.add_option( "-o", dest
="overmaintfile", default
=None,
178 help="override file listing packages and maintainers")
179 parser
.add_option( "-p", dest
="packagedir", default
=None,
180 help="path to packages directory")
181 parser
.add_option( "-s", dest
="sender", default
=None,
182 help="sender address for forwards")
183 parser
.add_option( "--create", dest
="create", default
=False,
184 action
="store_true", help="create maintfile")
185 parser
.add_option( "--no-scan", dest
="noscan", default
=True,
187 help="don't scan for maintainers; implies --no-update")
188 parser
.add_option( "--no-update", dest
="noupdate", default
=False,
190 help="do not update the maintfile")
191 parser
.add_option( "--prefix", dest
="prefix", default
="packages/",
192 help="prefix to remove from modified file name [default: %default]")
193 parser
.add_option( "--sendmail", dest
="sendmail", default
=False,
194 action
="store_true", help="use sendmail rather than smtp")
195 parser
.add_option( "--debug", dest
="debug", default
=False,
196 action
="store_true", help="debug only, do not send mail")
199 ( opts
, args
) = parser
.parse_args()
202 if not opts
.maintfile
:
203 parser
.error('No maintfile specified')
205 if not opts
.packagedir
:
206 parser
.error('No packagedir specified')
208 if not os
.path
.isdir(opts
.packagedir
):
209 sys
.stderr
.write('No such directory: %s\n' % opts
.packagedir
)
215 parser
.error('No logfile specified')
218 parser
.error('No sender specified')
222 lfile
= open( opts
.logfile
, 'a' )
223 except Exception as err
:
224 sys
.stderr
.write('Error opening logfile: %s\n' % str(err
))
229 mfile
= open( opts
.maintfile
, 'r' )
230 except Exception as err
:
231 lfile
.write('Error opening maintfile: %s\n' % str(err
))
234 ## Create the maintfile.
236 scan_dir( opts
.packagedir
, opts
.maintfile
)
240 ## Each element is package/file: maint1, maint2, ...
244 if re
.match( '#| *$', line
): continue
245 ## FIXME error here if empty maintainer.
246 (pfile
, maint
) = line
.split()
247 maints
[pfile
] = maint
.split(',')
252 if opts
.overmaintfile
:
254 ofile
= open( opts
.overmaintfile
, 'r' )
255 except Exception as err
:
256 lfile
.write('Error opening overmaintfile: %s\n' % str(err
))
260 if re
.match( '#| *$', line
): continue
261 (pfile
, maint
) = line
.split()
262 maints
[pfile
] = maint
.split(',')
272 resent_via
= 'GNU Emacs diff forwarder'
274 message
= email
.message_from_string( text
)
276 (msg_name
, msg_from
) = email
.utils
.parseaddr( message
['from'] )
278 lfile
.write('\nDate: %s\n' % str(datetime
.datetime
.now()))
279 lfile
.write('Message-ID: %s\n' % message
['message-id'])
280 lfile
.write('From: %s\n' % msg_from
)
282 if resent_via
== message
['x-resent-via']:
283 lfile
.write('Mail loop; aborting\n')
291 for line
in text
.splitlines():
293 # Look for and process things that look like (Git):
295 # Summary of changes:
296 # packages/vlf/vlf.el | 2 +-
297 # 1 files changed, 1 insertions(+), 1 deletions(-)
299 # or things that look like (Git):
302 # packages/vlf/vlf.el | 2 +-
303 # 1 files changed, 1 insertions(+), 1 deletions(-)
305 #BZR: if re.match( 'modified:$', line ):
306 if re
.match( '---|Summary of changes:$', line
):
310 if not start
: continue
312 ## An empty line or a line with non-empty first character.
313 if re
.match( '( *$|[^ ])', line
): break
314 # Any line that doesn't match the diffstat format (Git).
315 if not re
.match( ' [^ ]+ +\| ', line
):
316 lfile
.write('Stop scanning at: %s\n' % line
)
320 #BZR: reg = re.match( '%s([^ ]+)' % opts.prefix, line.strip() )
321 reg
= re
.match( ' %s([^ ]+)' % opts
.prefix
, line
)
323 lfile
.write('Skip: %s\n' % line
)
330 lfile
.write('File: %s\n' % pfile
)
332 ## Should not be possible for files (rather than packages)...
333 if pfile
in pfiles_seen
:
334 lfile
.write('Already seen this file\n')
337 pfiles_seen
.append(pfile
)
340 if not pfile
in maints
:
342 lfile
.write('Unknown maintainer\n')
346 lfile
.write('Scanning file...\n')
348 thisfile
= os
.path
.join( opts
.packagedir
, pfile
)
349 # scan_file( thisfile, thismaint )
352 maints
[pfile
] = thismaint
354 ## Append maintainer to file.
355 if not opts
.noupdate
:
357 mfile
= open( opts
.maintfile
, 'a' )
358 string
= "%-50s %s\n" % (pfile
, ",".join(thismaint
))
361 lfile
.write('Appended to maintfile\n')
362 except Exception as err
:
363 lfile
.write('Error appending to maintfile: %s\n' %
366 ## Didn't scan, or scanning did not work.
367 ## Look for a directory maintainer.
368 if not pfile
in maints
:
369 lfile
.write('No file maintainer, trying directories...\n')
371 (pfile
, tail
) = os
.path
.split(pfile
)
373 if pfile
in maints
: break
376 if not pfile
in maints
:
377 lfile
.write('No maintainer, skipping\n')
381 for maint
in maints
[pfile
]:
383 lfile
.write('Maint: %s\n' % maint
)
386 if maint
in maints_seen
:
387 lfile
.write('Already seen this maintainer\n')
390 maints_seen
.append(maint
)
393 if maint
== "nomail":
394 lfile
.write('Not resending, no mail is requested\n')
398 if maint
== msg_from
:
399 lfile
.write('Not resending, since maintainer = committer\n')
404 forward
.add_header('X-Resent-Via', resent_via
)
405 forward
.add_header('Resent-To', maint
)
406 forward
.add_header('Resent-From', opts
.sender
)
408 lfile
.write('Resending via %s...\n' % ('sendmail'
409 if opts
.sendmail
else 'smtp') )
412 if opts
.debug
: continue
416 s
= os
.popen("/usr/sbin/sendmail -i -f %s %s" %
417 (opts
.sender
, maint
), "w")
418 s
.write(forward
.as_string())
421 lfile
.write('Sendmail exit status: %s\n' % status
)
426 s
= smtplib
.SMTP('localhost')
427 except Exception as err
:
428 lfile
.write('Error opening smtp: %s\n' % str(err
))
432 s
.sendmail(opts
.sender
, maint
, forward
.as_string())
433 except Exception as err
:
434 lfile
.write('Error sending smtp: %s\n' % str(err
))
438 ### forward-diffs.py ends here