X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/94aad4db1b67bd54891b86f60c771d8b2988afb6..fe623377defcfb3a80ce30b2b4c8f62d9999818f:/admin/forward-diffs.py diff --git a/admin/forward-diffs.py b/admin/forward-diffs.py index 105533487..67c7d8a53 100755 --- a/admin/forward-diffs.py +++ b/admin/forward-diffs.py @@ -1,5 +1,5 @@ #!/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. @@ -20,23 +20,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/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. ## -## package1 email1 -## package2 email2,email3 +## overmaint = like maintfile, but takes precedence over it. ### Code: @@ -48,8 +65,107 @@ import smtplib import datetime import os -usage="""usage: %prog <-m maintfile> <-l logfile> <-s sender> [--sendmail] -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 ) @@ -57,21 +173,54 @@ parser.add_option( "-m", dest="maintfile", default=None, help="file listing packages and maintainers") parser.add_option( "-l", dest="logfile", default=None, help="file to append output to") +parser.add_option( "-o", dest="overmaintfile", default=None, + help="override file listing packages and maintainers") +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=False, + 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, + action="store_true", help="debug only, do not send mail") + ( opts, args ) = parser.parse_args() -if not opts.logfile: - parser.error('No logfile specified') if not opts.maintfile: parser.error('No maintfile specified') -if not opts.sender: - parser.error('No sender specified') +if not opts.packagedir: + parser.error('No packagedir specified') + +if not os.path.isdir(opts.packagedir): + 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') + + +## Create the maintfile. +if opts.create: + scan_dir( opts.packagedir, opts.maintfile ) + sys.exit() try: @@ -87,23 +236,39 @@ except Exception as err: lfile.write('Error opening maintfile: %s\n' % str(err)) sys.exit(1) -## Each element is package: maint1, maint2, ... +## 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() +if opts.overmaintfile: + try: + ofile = open( opts.overmaintfile, 'r' ) + except Exception as err: + lfile.write('Error opening overmaintfile: %s\n' % str(err)) + sys.exit(1) + + for line in ofile: + if re.match( '#| *$', line ): continue + (pfile, maint) = line.split() + maints[pfile] = maint.split(',') + + ofile.close() + + stdin = sys.stdin text = stdin.read() -resent_via = 'GNU ELPA diff forwarder' +resent_via = 'GNU Emacs diff forwarder' message = email.message_from_string( text ) @@ -114,59 +279,104 @@ lfile.write('Message-ID: %s\n' % message['message-id']) lfile.write('From: %s\n' % msg_from) if resent_via == message['x-resent-via']: - lfile.write('Mail loop; aborting') + lfile.write('Mail loop; aborting\n') sys.exit(1) start = False -packs_seen = [] +pfiles_seen = [] maints_seen = [] for line in text.splitlines(): - if re.match( '^modified:$', line ): + if re.match( 'modified:$', line ): start = True continue if not start: continue - if re.match( '^ *$', line ): break - + ## An empty line or a line with non-empty first character. + if re.match( '( *$|[^ ])', line ): break - reg = re.match( '^packages/([^/]+)', line.strip() ) - if not reg: break + if opts.prefix: + reg = re.match( '%s([^ ]+)' % opts.prefix, line.strip() ) + if not reg: 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') + ## 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 pfile in maints: - if not pack 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) if maint in maints_seen: - lfile.write('Already seen this maintainer') + lfile.write('Already seen this maintainer\n') continue maints_seen.append(maint) + if maint == "nomail": + lfile.write('Not resending, no mail is requested\n') + continue + + if maint == msg_from: - lfile.write('Not resending, since maintainer = committer') + lfile.write('Not resending, since maintainer = committer\n') continue @@ -179,6 +389,9 @@ for line in text.splitlines(): if opts.sendmail else 'smtp') ) + if opts.debug: continue + + if opts.sendmail: s = os.popen("/usr/sbin/sendmail -i -f %s %s" % (opts.sender, maint), "w")