From 2a712f54b4466726f3d54af2e076f278967046f3 Mon Sep 17 00:00:00 2001 From: allaert Date: Tue, 6 Apr 2004 21:16:21 +0200 Subject: [PATCH] copie du fichier de egon a la place du vieux celui de egon est sarge complient darcs-hash:20040406191621-9f550-81433bf3087ed581abea2c669a5a823b392d769f.gz --- syncmail | 296 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 210 insertions(+), 86 deletions(-) diff --git a/syncmail b/syncmail index 03297357..daa96667 100755 --- a/syncmail +++ b/syncmail @@ -1,5 +1,9 @@ #! /usr/bin/python +# Copyright (c) 2002, 2003, Barry Warsaw, Fred Drake, and contributors +# All rights reserved. +# See the accompanying LICENSE file for details. + # NOTE: Until SourceForge installs a modern version of Python on the cvs # servers, this script MUST be compatible with Python 1.5.2. @@ -34,8 +38,8 @@ Usage: Where options are: --cvsroot= - Use as the environment variable CVSROOT. Otherwise this - variable must exist in the environment. + Use as the environment variable CVSROOT. Otherwise this + variable must exist in the environment. --context=# -C # @@ -44,16 +48,32 @@ Where options are: -c Produce a context diff (default). + -m hostname + --mailhost hostname + The hostname of an available SMTP server. The default is + 'localhost'. + -u Produce a unified diff (smaller). + -S TEXT + --subject-prefix=TEXT + Prepend TEXT to the email subject line. + + -R ADDR + --reply-to=ADDR + Add a "Reply-To: ADDR" header to the email message. + + --charset=charset + Add a encoding header to message. + --quiet / -q Don't print as much status to stdout. --fromhost=hostname -f hostname The hostname that email messages appear to be coming from. The From: - header will of the outgoing message will look like user@hostname. By + header of the outgoing message will look like user@hostname. By default, hostname is the machine's fully qualified domain name. --help / -h @@ -71,6 +91,8 @@ The rest of the command line arguments are: email-addrs At least one email address. """ +__version__ = '1.2' + import os import sys import re @@ -97,12 +119,12 @@ except ImportError: else: fqdn = 'localhost.localdomain' return fqdn - + from cStringIO import StringIO -# Which SMTP server to do we connect to? Empty string means localhost. -MAILHOST = '' +# Which SMTP server to do we connect to? +MAILHOST = 'localhost' MAILPORT = 25 # Diff trimming stuff @@ -110,9 +132,6 @@ DIFF_HEAD_LINES = 20 DIFF_TAIL_LINES = 20 DIFF_TRUNCATE_IF_LARGER = 1000 -EMPTYSTRING = '' -SPACE = ' ' -DOT = '.' COMMASPACE = ', ' PROGRAM = sys.argv[0] @@ -121,8 +140,7 @@ BINARY_EXPLANATION_LINES = [ "(This appears to be a binary file; contents omitted.)\n" ] -REVCRE = re.compile("^(NONE|[0-9.]+)$") -NOVERSION = "Couldn't generate diff; no version number found in filespec: %s" +NOVERSION = "Couldn't generate diff; no version number found for file: %s" BACKSLASH = "Couldn't generate diff: backslash in filespec's filename: %s" @@ -135,25 +153,20 @@ def usage(code, msg=''): -def calculate_diff(filespec, contextlines): - file, oldrev, newrev = string.split(filespec, ',') - # Make sure we can find a CVS version number - if not REVCRE.match(oldrev): - return NOVERSION % filespec - if not REVCRE.match(newrev): - return NOVERSION % filespec +def calculate_diff(entry, contextlines): + file = entry.name + oldrev = entry.revision + newrev = entry.new_revision - if string.find(file, '\\') <> -1: - # I'm sorry, a file name that contains a backslash is just too much. - # XXX if someone wants to figure out how to escape the backslashes in - # a safe way to allow filenames containing backslashes, this is the - # place to do it. --Zooko 2002-03-17 - return BACKSLASH % filespec + # Make sure we can find a CVS version number + if oldrev is None and newrev is None: + return NOVERSION % file if string.find(file, "'") <> -1: # Those crazy users put single-quotes in their file names! Now we # have to escape everything that is meaningful inside double-quotes. - filestr = string.replace(file, '`', '\`') + filestr = string.replace(file, '\\', '\\\\') + filestr = string.replace(filestr, '`', '\`') filestr = string.replace(filestr, '"', '\"') filestr = string.replace(filestr, '$', '\$') # and quote it with double-quotes. @@ -161,7 +174,8 @@ def calculate_diff(filespec, contextlines): else: # quote it with single-quotes. filestr = "'" + file + "'" - if oldrev == 'NONE': + if oldrev is None: + # File is being added. try: if os.path.exists(file): fp = open(file) @@ -183,9 +197,10 @@ def calculate_diff(filespec, contextlines): except IOError, e: lines = ['***** Error reading new file: ', str(e), '\n***** file: ', file, ' cwd: ', os.getcwd()] - elif newrev == 'NONE': + elif newrev is None: lines = ['--- %s DELETED ---\n' % file] else: + # File has been changed. # This /has/ to happen in the background, otherwise we'll run into CVS # lock contention. What a crock. if contextlines > 0: @@ -196,21 +211,8 @@ def calculate_diff(filespec, contextlines): % (difftype, oldrev, newrev, filestr) fp = os.popen(diffcmd) lines = fp.readlines() - sts = fp.close() # ignore the error code, it always seems to be 1 :( -## if sts: -## return 'Error code %d occurred during diff\n' % (sts >> 8) - - try: - # On vire du diff les lignes marquées avec *PASS* - a=0 - while 1: - a=a+1 - if string.find(lines[a],'*PASS*') > -1: - lines[a]= '[ Mot de passe ]\n' - except: - a=0 - + fp.close() if len(lines) > DIFF_TRUNCATE_IF_LARGER: removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES] @@ -220,7 +222,17 @@ def calculate_diff(filespec, contextlines): -def blast_mail(subject, people, filestodiff, contextlines, fromhost): +rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]') + +def quotename(name): + if name and rfc822_specials_re.search(name): + return '"%s"' % string.replace(name, '"', '\\"') + else: + return name + + + +def blast_mail(subject, people, entries, contextlines, fromhost, replyto, charset): # cannot wait for child process or that will cause parent to retain cvs # lock for too long. Urg! if not os.fork(): @@ -231,55 +243,173 @@ def blast_mail(subject, people, filestodiff, contextlines, fromhost): conn = smtplib.SMTP() conn.connect(MAILHOST, MAILPORT) user = pwd.getpwuid(os.getuid())[0] + name = string.split(pwd.getpwuid(os.getuid())[4], ',')[0] + # Modif pour avoir le bon from (=> signature automatique) if user=="root" : - # Obtention du real user => signature automatique des commits for proc in os.popen("ps --no-headers -o user -t $(ps --no-headers -o tty %s)" % os.getpid() ).readlines() : userp=string.strip(proc) if userp=="root" : break else : user=userp - - domain = fromhost or getfqdn() - #Modif cosmétique pour afficher un sujet explicite : - host = string.split(domain,'.') - author = '%s@%s' % (user, domain) + name = string.split(pwd.getpwnam(user)[4], ',')[0] + + domain = fromhost or getfqdn() + host = string.split(domain,'.')[0] + address = '%s@%s' % (user, domain) s = StringIO() sys.stdout = s + datestamp = time.strftime('%a, %d %b %Y %H:%M:%S +0000', + time.gmtime(time.time())) try: + vars = {'address' : address, + 'name' : quotename(name), + 'people' : string.join(people, COMMASPACE), + 'subject' : subject, + 'host' : host, + 'version' : __version__, + 'date' : datestamp, + } print '''\ -From: %(author)s -To: %(people)s -Subject: %(subject)s +From: %(name)s <%(address)s> +To: %(people)s''' % vars + if replyto: + print 'Reply-To: %s' % replyto + if charset: + print 'MIME-Version: 1.0\nContent-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit' % charset + print '''\ +Subject: CVS commit sur %(host)s : %(subject)s X-CVSinfo: CRANS -''' % {'author' : author, - 'people' : string.join(people, COMMASPACE), - 'subject': 'CVS commit sur '+host[0]+' : '+ subject, - } +Date: %(date)s +X-Mailer: Python syncmail %(version)s +''' % vars s.write(sys.stdin.read()) # append the diffs if available print - for file in filestodiff: - print calculate_diff(file, contextlines) + for entry in entries: + print calculate_diff(entry, contextlines) finally: sys.stdout = sys.__stdout__ - resp = conn.sendmail(author, people, s.getvalue()) + resp = conn.sendmail(address, people, s.getvalue()) conn.close() os._exit(0) +class CVSEntry: + def __init__(self, name, revision, timestamp, conflict, options, tagdate): + self.name = name + self.revision = revision + self.timestamp = timestamp + self.conflict = conflict + self.options = options + self.tagdate = tagdate + +def get_entry(prefix, mapping, line, filename): + line = string.strip(line) + parts = string.split(line, "/") + _, name, revision, timestamp, options, tagdate = parts + key = namekey(prefix, name) + try: + entry = mapping[key] + except KeyError: + if revision == "0": + revision = None + if string.find(timestamp, "+") != -1: + timestamp, conflict = tuple(string.split(timestamp, "+")) + else: + conflict = None + entry = CVSEntry(key, revision, timestamp, conflict, + options, tagdate) + mapping[key] = entry + return entry + +def namekey(prefix, name): + if prefix: + return os.path.join(prefix, name) + else: + return name + +def load_change_info(prefix=None): + if prefix is not None: + entries_fn = os.path.join(prefix, "CVS", "Entries") + else: + entries_fn = os.path.join("CVS", "Entries") + entries_log_fn = entries_fn + ".Log" + mapping = {} + f = open(entries_fn) + while 1: + line = f.readline() + if not line: + break +## if string.strip(line) == "D": +## continue + # we could recurse down subdirs, except the Entries.Log files + # we need haven't been written to the subdirs yet, so it + # doesn't do us any good +## if line[0] == "D": +## name = string.split(line, "/")[1] +## dirname = namekey(prefix, name) +## if os.path.isdir(dirname): +## m = load_change_info(dirname) +## mapping.update(m) + if line[0] == "/": + # normal file + get_entry(prefix, mapping, line, entries_fn) + # else: bogus Entries line + f.close() + if os.path.isfile(entries_log_fn): + f = open(entries_log_fn) + while 1: + line = f.readline() + if not line: + break + if line[1:2] != ' ': + # really old version of CVS + break + entry = get_entry(prefix, mapping, line[2:], entries_log_fn) + parts = string.split(line, "/")[1:] + if line[0] == "A": + # adding a file + entry.new_revision = parts[1] + elif line[0] == "R": + # removing a file + entry.new_revision = None + f.close() + for entry in mapping.values(): + if not hasattr(entry, "new_revision"): + print 'confused about file', entry.name, '-- ignoring' + del mapping[entry.name] + return mapping + +def load_branch_name(): + tag_fn = os.path.join("CVS", "Tag") + if os.path.isfile(tag_fn): + f = open(tag_fn) + line = string.strip(f.readline()) + f.close() + if line[:1] == "T": + return line[1:] + return None + # scan args for options def main(): + # XXX Should really move all the options to an object, just to + # avoid threading so many positional args through everything. try: opts, args = getopt.getopt( - sys.argv[1:], 'hC:cuqf:', - ['fromhost=', 'context=', 'cvsroot=', 'help', 'quiet']) + sys.argv[1:], 'hC:cuS:R:qf:m:', + ['fromhost=', 'context=', 'cvsroot=', 'mailhost=', + 'subject-prefix=', 'reply-to=', + 'help', 'quiet', 'charset=']) except getopt.error, msg: usage(1, msg) # parse the options contextlines = 2 verbose = 1 + subject_prefix = "" + replyto = None + charset = None fromhost = None for opt, arg in opts: if opt in ('-h', '--help'): @@ -293,10 +423,19 @@ def main(): contextlines = 2 elif opt == '-u': contextlines = 0 + elif opt in ('-S', '--subject-prefix'): + subject_prefix = arg + elif opt in ('-R', '--reply-to'): + replyto = arg + elif opt == '--charset': + charset = arg elif opt in ('-q', '--quiet'): verbose = 0 elif opt in ('-f', '--fromhost'): fromhost = arg + elif opt in ('-m', '--mailhost'): + global MAILHOST + MAILHOST = arg # What follows is the specification containing the files that were # modified. The argument actually must be split, with the first component @@ -304,17 +443,10 @@ def main(): # $CVSROOT, followed by the list of files that are changing. if not args: usage(1, 'No CVS module specified') - subject = args[0] + subject = subject_prefix + args[0] specs = string.split(args[0]) del args[0] - - #Pour ne pas recevoir tout la pollution des batx.cf - nom_fichier = specs[0] + '/' + string.split(specs[1],',')[0] - Ignore=['CRANS/confs/bat', 'CRANS/confs/personnes.cf??'] - for ign_fich in Ignore: - if string.find(nom_fichier,ign_fich) == 0: - sys.exit(0) - + # The remaining args should be the email addresses if not args: usage(1, 'No recipients specified') @@ -322,26 +454,18 @@ def main(): # Now do the mail command people = args + if specs[-3:] == ['-', 'Imported', 'sources']: + print 'Not sending email for imported sources.' + return + + branch = load_branch_name() + changes = load_change_info() + if verbose: print 'Mailing %s...' % string.join(people, COMMASPACE) - - if specs == ['-', 'Imported', 'sources']: - return - if specs[-3:] == ['-', 'New', 'directory']: - del specs[-3:] - elif len(specs) > 2: - L = specs[:2] - for s in specs[2:]: - prev = L[-1] - if string.count(prev, ',') < 2: - L[-1] = "%s %s" % (prev, s) - else: - L.append(s) - specs = L - - if verbose: print 'Generating notification message...' - blast_mail(subject, people, specs[1:], contextlines, fromhost) + blast_mail(subject, people, changes.values(), + contextlines, fromhost, replyto, charset) if verbose: print 'Generating notification message... done.'