|
From: Fred L. D. <fd...@us...> - 2003-07-09 20:49:50
|
Update of /cvsroot/cvs-syncmail/CVSROOT
In directory sc8-pr-cvs1:/tmp/cvs-serv6511
Modified Files:
syncmail
Log Message:
eat our own dogfood: update to revision 1.34
Index: syncmail
===================================================================
RCS file: /cvsroot/cvs-syncmail/CVSROOT/syncmail,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- syncmail 31 Jul 2002 11:51:46 -0000 1.16
+++ syncmail 9 Jul 2003 20:49:42 -0000 1.17
@@ -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 @@
Where options are:
--cvsroot=<path>
- Use <path> as the environment variable CVSROOT. Otherwise this
- variable must exist in the environment.
+ Use <path> as the environment variable CVSROOT. Otherwise this
+ variable must exist in the environment.
--context=#
-C #
@@ -44,12 +48,21 @@
-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
- Preprend TEXT to the email subject line.
+ Prepend TEXT to the email subject line.
+
+ -R ADDR
+ --reply-to=ADDR
+ Add a "Reply-To: ADDR" header to the email message.
--quiet / -q
Don't print as much status to stdout.
@@ -57,7 +70,7 @@
--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
@@ -103,12 +116,12 @@
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
@@ -116,9 +129,6 @@
DIFF_TAIL_LINES = 20
DIFF_TRUNCATE_IF_LARGER = 1000
-EMPTYSTRING = ''
-SPACE = ' '
-DOT = '.'
COMMASPACE = ', '
PROGRAM = sys.argv[0]
@@ -127,8 +137,7 @@
"(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"
@@ -141,25 +150,20 @@
-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.
@@ -167,7 +171,8 @@
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)
@@ -189,9 +194,10 @@
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:
@@ -202,10 +208,8 @@
% (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)
+ 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]
@@ -215,7 +219,17 @@
-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):
# cannot wait for child process or that will cause parent to retain cvs
# lock for too long. Urg!
if not os.fork():
@@ -231,23 +245,31 @@
address = '%s@%s' % (user, domain)
s = StringIO()
sys.stdout = s
+ datestamp = time.strftime('%a, %d %b %Y %H:%M:%S +0000',
+ time.gmtime())
try:
+ vars = {'address' : address,
+ 'name' : quotename(name),
+ 'people' : string.join(people, COMMASPACE),
+ 'subject' : subject,
+ 'version' : __version__,
+ 'date' : datestamp,
+ }
+ print '''\
+From: %(name)s <%(address)s>
+To: %(people)s''' % vars
+ if replyto:
+ print 'Reply-To: %s' % replyto
print '''\
-From: "%(name)s" <%(address)s>
-To: %(people)s
Subject: %(subject)s
+Date: %(date)s
X-Mailer: Python syncmail %(version)s <http://sf.net/projects/cvs-syncmail>
-''' % {'address' : address,
- 'name' : name,
- 'people' : string.join(people, COMMASPACE),
- 'subject' : subject,
- 'version' : __version__,
- }
+''' % 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(address, people, s.getvalue())
@@ -256,12 +278,111 @@
+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:cuS:qf:',
- ['fromhost=', 'context=', 'cvsroot=', 'subject-prefix=',
+ sys.argv[1:], 'hC:cuS:R:qf:m:',
+ ['fromhost=', 'context=', 'cvsroot=', 'mailhost=',
+ 'subject-prefix=', 'reply-to=',
'help', 'quiet'])
except getopt.error, msg:
usage(1, msg)
@@ -270,6 +391,7 @@
contextlines = 2
verbose = 1
subject_prefix = ""
+ replyto = None
fromhost = None
for opt, arg in opts:
if opt in ('-h', '--help'):
@@ -285,10 +407,15 @@
contextlines = 0
elif opt in ('-S', '--subject-prefix'):
subject_prefix = arg
+ elif opt in ('-R', '--reply-to'):
+ replyto = 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
@@ -296,6 +423,8 @@
# $CVSROOT, followed by the list of files that are changing.
if not args:
usage(1, 'No CVS module specified')
+ changes = load_change_info()
+ branch = load_branch_name()
subject = subject_prefix + args[0]
specs = string.split(args[0])
del args[0]
@@ -307,26 +436,15 @@
# Now do the mail command
people = args
- if verbose:
- print 'Mailing %s...' % string.join(people, COMMASPACE)
-
if specs[-3:] == ['-', 'Imported', 'sources']:
+ print 'Not sending email for 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 'Mailing %s...' % string.join(people, COMMASPACE)
print 'Generating notification message...'
- blast_mail(subject, people, specs[1:], contextlines, fromhost)
+ blast_mail(subject, people, changes.values(),
+ contextlines, fromhost, replyto)
if verbose:
print 'Generating notification message... done.'
|