X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/ba023f3ca6abf5f2c19c2f188e2719aa60c0584b..e3826c87adb74a98da3ae7922cad6c529d1177d7:/admin/forward-diffs.py diff --git a/admin/forward-diffs.py b/admin/forward-diffs.py index 9de81e231..c0c330d23 100755 --- a/admin/forward-diffs.py +++ b/admin/forward-diffs.py @@ -1,9 +1,10 @@ #!/usr/bin/python -### forward-diffs.py --- forward emacs-elpa-diffs mails to maintainers +### forward-diffs.py --- forward emacs-diffs mails to maintainers -## Copyright (C) 2012 Free Software Foundation, Inc. +## Copyright (C) 2012-2014 Free Software Foundation, Inc. ## Author: Glenn Morris +## Maintainer: emacs-devel@gnu.org ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -20,27 +21,40 @@ ### Commentary: -## Forward emails from the emacs-elpa-diffs mailing list to the +## Forward emails from an emacs-diffs style mailing list to the ## maintainer(s) of the modified files. -## Example usage from procmail (all arguments are compulsory): +## Two modes of operation: + +## 1) Create the maintfile (really this is just an optimization): +## forward-diffs.py --create -p packagesdir -m maintfile + +## You can start with an empty maintfile and normal operation in 2) +## will append information as needed. + +## 2) Call from eg procmail to forward diffs. Example usage: ## :0c ## * ^TO_emacs-elpa-diffs@gnu\.org -## | forward-diffs.py -m maintfile -l logfile -s sender +## | forward-diffs.py -p packagedir -m maintfile -l logfile \ +## -o overmaint -s sender ## where +## packagedir = /path/to/packages ## sender = your email address ## logfile = file to write log to (you might want to rotate/compress/examine it) -## maintfile = file listing packages and their maintainers, with format: +## maintfile = file listing files and their maintainers, with format: ## -## package1 email1 -## package2 email2,email3 +## package1/file1 email1 +## package2/file2 email2,email3 +## package3 email4 ## ## Use "nomail" for the email field to not send a mail. +## An entry that is a directory applies to all files in that directory +## that do not have specific maintainers. ## -## overmaintfile = like maintfile, but takes precedence over it. +## overmaint = like maintfile, but takes precedence over it. ### Code: @@ -52,9 +66,107 @@ import smtplib import datetime import os -usage="""usage: %prog <-m maintfile> <-l logfile> <-s sender> - <-p /path/to/packages> [-o overmaintfile] [--sendmail] [--debug] -Take a GNU ELPA diff on stdin, and forward it to the maintainer(s).""" + +## Scan FILE for Author or Maintainer (preferred) headers. +## Return a list of all email addresses found in MAINTS. +def scan_file(file, maints): + + try: + fd = open( file, 'r') + except Exception as err: + lfile.write('Error opening file %s: %s\n' % (file, str(err))) + return 1 + + ## Max number of lines to scan looking for a maintainer. + ## (20 seems to be the highest at present). + max_lines = 50 + nline = 0 + cont = 0 + type = "" + + for line in fd: + + nline += 1 + + if ( nline > max_lines ): break + + ## Try and de-obfuscate. Worth it? + line = re.sub( '(?i) AT ', '@', line ) + line = re.sub( '(?i) DOT ', '.', line ) + + if cont: # continued header? + reg = re.match( ('%s[ \t]+[^:]*??' % prefix), line, re.I ) + if not reg: # not a continued header + cont = 0 + prefix = "" + if ( type == "maint" ): break + type = "" + + ## Check for one header immediately after another. + if not cont: + reg = re.match( '([^ ]+)? *(Author|Maintainer)s?: .*??', line, re.I ) + + + if not reg: continue + + if cont: + email = reg.group(1) + maints.append(email) + else: + cont = 1 + prefix = reg.group(1) or "" + type = reg.group(2) + email = reg.group(3) + type = "maint" if re.search( 'Maintainer', type, re.I ) else "auth" + ## maints = [] does the wrong thing. + if type == "maint": del maints[:] + maints.append(email) + + fd.close() + + +## Scan all the files under dir for maintainer information. +## Write to stdout, or optional argument outfile (which is overwritten). +def scan_dir(dir, outfile=None): + + dir = re.sub( '/+$', '', dir) + '/' # ensure trailing / + + if not os.path.isdir(dir): + sys.stderr.write('No such directory: %s\n' % dir) + sys.exit(1) + + fd = 0 + if outfile: + try: + fd = open( outfile, 'w' ) + except Exception as err: + sys.stderr.write("Error opening `%s': %s\n" % (outfile, str(err))) + sys.exit(1) + + + for dirpath, dirnames, filenames in os.walk(dir): + for file in filenames: + path = os.path.join(dirpath, file) + maints = [] + scan_file(path, maints) + ## This would skip printing empty maints. + ## That would mean we would scan the file each time for no reason. + ## But empty maintainers are an error at present. + if not maints: continue + path = re.sub( '^%s' % dir, '', path ) + string = "%-50s %s\n" % (path, ",".join(maints)) + if fd: + fd.write(string) + else: + print string, + + if fd: fd.close() + + +usage="""usage: %prog <-p /path/to/packages> <-m maintfile> + <-l logfile -s sender|--create> [-o overmaintfile] [--prefix prefix] + [--sendmail] [--debug] +Take an emacs-diffs mail on stdin, and forward it to the maintainer(s).""" parser = optparse.OptionParser() parser.set_usage ( usage ) @@ -68,6 +180,16 @@ parser.add_option( "-p", dest="packagedir", default=None, help="path to packages directory") parser.add_option( "-s", dest="sender", default=None, help="sender address for forwards") +parser.add_option( "--create", dest="create", default=False, + action="store_true", help="create maintfile") +parser.add_option( "--no-scan", dest="noscan", default=True, + action="store_true", + help="don't scan for maintainers; implies --no-update") +parser.add_option( "--no-update", dest="noupdate", default=False, + action="store_true", + help="do not update the maintfile") +parser.add_option( "--prefix", dest="prefix", default="packages/", + help="prefix to remove from modified file name [default: %default]") parser.add_option( "--sendmail", dest="sendmail", default=False, action="store_true", help="use sendmail rather than smtp") parser.add_option( "--debug", dest="debug", default=False, @@ -76,8 +198,6 @@ parser.add_option( "--debug", dest="debug", default=False, ( opts, args ) = parser.parse_args() -if not opts.logfile: - parser.error('No logfile specified') if not opts.maintfile: parser.error('No maintfile specified') @@ -85,15 +205,19 @@ if not opts.maintfile: if not opts.packagedir: parser.error('No packagedir specified') -if not opts.sender: - parser.error('No sender specified') - - if not os.path.isdir(opts.packagedir): - sys.stderr.write('Error reading packagedir\n') + sys.stderr.write('No such directory: %s\n' % opts.packagedir) sys.exit(1) +if not opts.create: + if not opts.logfile: + parser.error('No logfile specified') + + if not opts.sender: + parser.error('No sender specified') + + try: lfile = open( opts.logfile, 'a' ) except Exception as err: @@ -107,13 +231,20 @@ except Exception as err: lfile.write('Error opening maintfile: %s\n' % str(err)) sys.exit(1) -## Each element is package: maint1, maint2, ... +## Create the maintfile. +if opts.create: + scan_dir( opts.packagedir, opts.maintfile ) + sys.exit() + + +## Each element is package/file: maint1, maint2, ... maints = {} for line in mfile: - if re.match( '^#|^ *$', line ): continue - (pack, maint) = line.split() - maints[pack] = maint.split(',') + if re.match( '#| *$', line ): continue + ## FIXME error here if empty maintainer. + (pfile, maint) = line.split() + maints[pfile] = maint.split(',') mfile.close() @@ -126,9 +257,9 @@ if opts.overmaintfile: sys.exit(1) for line in ofile: - if re.match( '^#|^ *$', line ): continue - (pack, maint) = line.split() - maints[pack] = maint.split(',') + if re.match( '#| *$', line ): continue + (pfile, maint) = line.split() + maints[pfile] = maint.split(',') ofile.close() @@ -138,7 +269,7 @@ stdin = sys.stdin text = stdin.read() -resent_via = 'GNU ELPA diff forwarder' +resent_via = 'GNU Emacs diff forwarder' message = email.message_from_string( text ) @@ -154,41 +285,100 @@ if resent_via == message['x-resent-via']: start = False -packs_seen = [] +pfiles_seen = [] maints_seen = [] for line in text.splitlines(): - if re.match( '^modified:$', line ): + # Look for and process things that look like (Git): + # + # Summary of changes: + # packages/vlf/vlf.el | 2 +- + # 1 files changed, 1 insertions(+), 1 deletions(-) + # + # or things that look like (Git): + # + # --- + # packages/vlf/vlf.el | 2 +- + # 1 files changed, 1 insertions(+), 1 deletions(-) + + #BZR: if re.match( 'modified:$', line ): + if re.match( '---|Summary of changes:$', line ): start = True continue if not start: continue - if re.match( '^ *$', line ): break - - - reg = re.match( '^packages/([^/]+)', line.strip() ) - if not reg: break - + ## An empty line or a line with non-empty first character. + if re.match( '( *$|[^ ])', line ): break + # Any line that doesn't match the diffstat format (Git). + if not re.match( ' [^ ]+ +\| ', line ): + lfile.write('Stop scanning at: %s\n' % line) + break + + if opts.prefix: + #BZR: reg = re.match( '%s([^ ]+)' % opts.prefix, line.strip() ) + reg = re.match( ' %s([^ ]+)' % opts.prefix, line ) + if not reg: + lfile.write('Skip: %s\n' % line) + continue + pfile = reg.group(1) + else: + pfile = line.strip() - pack = reg.group(1) - lfile.write('Package: %s\n' % pack) + lfile.write('File: %s\n' % pfile) - if pack in packs_seen: - lfile.write('Already seen this package\n') + ## Should not be possible for files (rather than packages)... + if pfile in pfiles_seen: + lfile.write('Already seen this file\n') continue - packs_seen.append(pack) + pfiles_seen.append(pfile) - if not pack in maints: + if not pfile in maints: + lfile.write('Unknown maintainer\n') + + if not opts.noscan: + + lfile.write('Scanning file...\n') + thismaint = [] + thisfile = os.path.join( opts.packagedir, pfile ) + # scan_file( thisfile, thismaint ) + + if thismaint: + maints[pfile] = thismaint + + ## Append maintainer to file. + if not opts.noupdate: + try: + mfile = open( opts.maintfile, 'a' ) + string = "%-50s %s\n" % (pfile, ",".join(thismaint)) + mfile.write(string) + mfile.close() + lfile.write('Appended to maintfile\n') + except Exception as err: + lfile.write('Error appending to maintfile: %s\n' % + str(err)) + + ## Didn't scan, or scanning did not work. + ## Look for a directory maintainer. + if not pfile in maints: + lfile.write('No file maintainer, trying directories...\n') + while True: + (pfile, tail) = os.path.split(pfile) + if not pfile: break + if pfile in maints: break + + + if not pfile in maints: + lfile.write('No maintainer, skipping\n') continue - for maint in maints[pack]: + for maint in maints[pfile]: lfile.write('Maint: %s\n' % maint)