You can subscribe to this list here.
2002 |
Jan
|
Feb
|
Mar
(5) |
Apr
(5) |
May
(23) |
Jun
|
Jul
(11) |
Aug
(3) |
Sep
(1) |
Oct
(8) |
Nov
(24) |
Dec
(3) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2003 |
Jan
(17) |
Feb
(5) |
Mar
(18) |
Apr
(10) |
May
(4) |
Jun
(5) |
Jul
(67) |
Aug
(7) |
Sep
(4) |
Oct
(2) |
Nov
(4) |
Dec
(9) |
2004 |
Jan
(16) |
Feb
(4) |
Mar
(7) |
Apr
(5) |
May
(4) |
Jun
(5) |
Jul
(3) |
Aug
(3) |
Sep
(3) |
Oct
(8) |
Nov
|
Dec
|
2005 |
Jan
(5) |
Feb
(6) |
Mar
(4) |
Apr
(1) |
May
(2) |
Jun
(2) |
Jul
(1) |
Aug
|
Sep
(5) |
Oct
(1) |
Nov
|
Dec
(7) |
2006 |
Jan
(10) |
Feb
(4) |
Mar
(10) |
Apr
(8) |
May
(8) |
Jun
(14) |
Jul
(7) |
Aug
(4) |
Sep
(4) |
Oct
(24) |
Nov
(29) |
Dec
(10) |
2007 |
Jan
(5) |
Feb
(12) |
Mar
(11) |
Apr
(10) |
May
(3) |
Jun
(3) |
Jul
(15) |
Aug
(28) |
Sep
(8) |
Oct
(5) |
Nov
(8) |
Dec
(13) |
2008 |
Jan
(7) |
Feb
(11) |
Mar
(29) |
Apr
(28) |
May
(17) |
Jun
(9) |
Jul
(18) |
Aug
(7) |
Sep
(8) |
Oct
(9) |
Nov
(11) |
Dec
(53) |
2009 |
Jan
(112) |
Feb
(19) |
Mar
(46) |
Apr
(32) |
May
(90) |
Jun
(91) |
Jul
(33) |
Aug
(11) |
Sep
(16) |
Oct
(23) |
Nov
(15) |
Dec
(3) |
2010 |
Jan
(1) |
Feb
|
Mar
(37) |
Apr
(47) |
May
(66) |
Jun
(69) |
Jul
(29) |
Aug
(45) |
Sep
(23) |
Oct
(3) |
Nov
(1) |
Dec
|
2011 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(1) |
Dec
|
2012 |
Jan
|
Feb
|
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
2013 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(2) |
Jun
(1) |
Jul
(3) |
Aug
(6) |
Sep
(1) |
Oct
(7) |
Nov
(1) |
Dec
(1) |
2014 |
Jan
(1) |
Feb
|
Mar
|
Apr
|
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(1) |
2015 |
Jan
(2) |
Feb
|
Mar
|
Apr
(1) |
May
|
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
(3) |
2016 |
Jan
(4) |
Feb
(5) |
Mar
(2) |
Apr
|
May
|
Jun
(2) |
Jul
(1) |
Aug
|
Sep
|
Oct
|
Nov
(2) |
Dec
(1) |
2017 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
(2) |
Sep
|
Oct
|
Nov
|
Dec
|
2018 |
Jan
|
Feb
(1) |
Mar
(25) |
Apr
(3) |
May
(1) |
Jun
(2) |
Jul
(2) |
Aug
|
Sep
|
Oct
|
Nov
(5) |
Dec
(1) |
2019 |
Jan
|
Feb
|
Mar
(1) |
Apr
(1) |
May
(1) |
Jun
(4) |
Jul
(3) |
Aug
|
Sep
(3) |
Oct
(6) |
Nov
(1) |
Dec
|
2020 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
(1) |
Jul
(1) |
Aug
(2) |
Sep
|
Oct
|
Nov
|
Dec
|
2022 |
Jan
|
Feb
(1) |
Mar
(1) |
Apr
(1) |
May
|
Jun
(1) |
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(1) |
Dec
(1) |
2023 |
Jan
|
Feb
(1) |
Mar
(1) |
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
|
2024 |
Jan
(2) |
Feb
(2) |
Mar
(5) |
Apr
(2) |
May
(1) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(1) |
2025 |
Jan
(1) |
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: Fred L. D. <fd...@us...> - 2003-07-10 22:15:03
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv29435 Modified Files: Tag: new-config-branch syncmail Log Message: Use rcsdiff instead of cvs diff to generate diffs. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.10 retrieving revision 1.36.2.11 diff -u -d -r1.36.2.10 -r1.36.2.11 --- syncmail 10 Jul 2003 22:03:05 -0000 1.36.2.10 +++ syncmail 10 Jul 2003 22:15:00 -0000 1.36.2.11 @@ -173,25 +173,14 @@ if oldrev is None and newrev is None: return NOVERSION % file - if "'" in file: - # 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(filestr, '`', '\`') - filestr = string.replace(filestr, '"', '\"') - filestr = string.replace(filestr, '$', '\$') - # and quote it with double-quotes. - filestr = '"' + filestr + '"' - else: - # quote it with single-quotes. - filestr = "'" + file + "'" if oldrev is None: # File is being added. try: if os.path.exists(file): fp = open(file) else: - update_cmd = "cvs -fn update -r %s -p %s" % (newrev, filestr) + update_cmd = ("cvs -fn update -r %s -p %s" + % (newrev, filestr(file))) fp = os.popen(update_cmd) lines = fp.readlines() fp.close() @@ -218,8 +207,9 @@ difftype = "-C %d" % config.contextlines else: difftype = "-u" - diffcmd = "/usr/bin/cvs -f diff -kk %s --minimal -r %s -r %s %s" \ - % (difftype, oldrev, newrev, filestr) + rcsfile = get_rcs_file(config, file) + diffcmd = "/usr/bin/rcsdiff -kk %s --minimal -r %s -r %s %s" \ + % (difftype, oldrev, newrev, filestr(rcsfile)) fp = os.popen(diffcmd) lines = fp.readlines() # ignore the error code, it always seems to be 1 :( @@ -231,6 +221,26 @@ '[...%d lines suppressed...]\n' % removedlines) return string.join(lines, '') +def filestr(file): + if "'" in file: + # 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(filestr, '`', '\`') + filestr = string.replace(filestr, '"', '\"') + filestr = string.replace(filestr, '$', '\$') + # and quote it with double-quotes. + return '"%s"' % filestr + else: + # quote it with single-quotes. + return "'%s'" % file + +def get_rcs_file(config, file): + fn = os.path.join(config.repodir, file) + if not os.path.isfile(fn): + fn = os.path.join(config.repodir, "Attic", file) + return fn + rfc822_specials_re = re.compile(r'[\(\)\<\>\@\,\;\:\\\"\.\[\]]') @@ -592,6 +602,7 @@ self.people = string.join(args[1:], COMMASPACE) else: self.people = config.get("to") + self.repodir = os.curdir def load_configuration(args, branch=None): cmdline, args = load_cmdline(args) @@ -611,6 +622,8 @@ def main(): # load the options config = load_configuration(sys.argv[1:], load_branch_name()) + s = open(os.path.join("CVS", "Repository")).readline() + config.repodir = string.strip(s) # args[0] is the specification containing the files that were # modified. The argument actually must be split, with the first component |
From: Fred L. D. <fd...@us...> - 2003-07-10 22:03:16
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv27288 Modified Files: Tag: new-config-branch syncmail Log Message: Slightly better type checking. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.9 retrieving revision 1.36.2.10 diff -u -d -r1.36.2.9 -r1.36.2.10 --- syncmail 10 Jul 2003 21:44:56 -0000 1.36.2.9 +++ syncmail 10 Jul 2003 22:03:05 -0000 1.36.2.10 @@ -215,7 +215,7 @@ # This /has/ to happen in the background, otherwise we'll run into CVS # lock contention. What a crock. if config.diff_type == "context": - difftype = "-C " + str(config.contextlines) + difftype = "-C %d" % config.contextlines else: difftype = "-u" diffcmd = "/usr/bin/cvs -f diff -kk %s --minimal -r %s -r %s %s" \ |
From: Fred L. D. <fd...@us...> - 2003-07-10 22:02:25
|
Update of /cvsroot/cvs-syncmail/CVSROOT In directory sc8-pr-cvs1:/tmp/cvs-serv27147 Modified Files: syncmail Log Message: More diagnostics. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/CVSROOT/syncmail,v retrieving revision 1.20 retrieving revision 1.21 diff -u -d -r1.20 -r1.21 --- syncmail 10 Jul 2003 19:07:51 -0000 1.20 +++ syncmail 10 Jul 2003 22:02:22 -0000 1.21 @@ -443,7 +443,8 @@ if verbose: print 'Python version', sys.version os.system("type python1.6; type python2;" - " type python2.0; type python2.1; type python2.2") + " type python2.0; type python2.1; type python2.2;" + " type rcsdiff; type rlog") print 'Mailing %s...' % string.join(people, COMMASPACE) print 'Generating notification message...' blast_mail(subject, people, changes.values(), |
From: Fred L. D. <fd...@us...> - 2003-07-10 21:44:59
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv24163 Modified Files: Tag: new-config-branch syncmail Log Message: Update explanations of the positional arguments on the command line. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.8 retrieving revision 1.36.2.9 diff -u -d -r1.36.2.8 -r1.36.2.9 --- syncmail 10 Jul 2003 21:40:20 -0000 1.36.2.8 +++ syncmail 10 Jul 2003 21:44:56 -0000 1.36.2.9 @@ -33,7 +33,7 @@ Usage: - %(PROGRAM)s [options] <%%S> email-addr [email-addr ...] + %(PROGRAM)s [options] [<%%S> [email-addr ...]] Where options are: @@ -91,14 +91,16 @@ The rest of the command line arguments are: <%%S> - CVS %%s loginfo expansion. When invoked by CVS, this will be a single - string containing the directory the checkin is being made in, relative - to $CVSROOT, followed by the list of files that are changing. If the - %%s in the loginfo file is %%{sVv}, context diffs for each of the - modified files are included in any email messages that are generated. + CVS %%s loginfo expansion. When invoked by CVS, this will be + a single string containing the directory the checkin is being + made in, relative to $CVSROOT, followed by the list of files + that are changing. Context diffs for each of the modified + files are included in any email messages that are generated. email-addrs - At least one email address. + Email addresses to whcih notifcations are sent. If specified, + the addresses given on the command line completely replace + those given in the configuration file. """ __version__ = '1.2' |
From: Fred L. D. <fd...@us...> - 2003-07-10 21:40:26
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv23503 Modified Files: Tag: new-config-branch syncmail Log Message: Add documentation for --config and --no-config in the help text. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.7 retrieving revision 1.36.2.8 diff -u -d -r1.36.2.7 -r1.36.2.8 --- syncmail 10 Jul 2003 18:48:05 -0000 1.36.2.7 +++ syncmail 10 Jul 2003 21:40:20 -0000 1.36.2.8 @@ -37,6 +37,18 @@ Where options are: + --config=file + Use file as the configuration file. By default, a file named + syncmail.conf is used if it exists. If the name of the + configuration file is relative, it is interpreted as relative + to the CVSROOT administrative directory of the repository. + --config is incompatible with --no-config. + + --no-config + Do not use a configuration file. This can be used to disable + the use of the default configuration file. --no-config is + incompatible with --config. + --cvsroot=<path> Use <path> as the environment variable CVSROOT. Otherwise this variable must exist in the environment. |
From: Fred L. D. <fd...@us...> - 2003-07-10 19:12:14
|
Update of /cvsroot/cvs-syncmail/CVSROOT In directory sc8-pr-cvs1:/tmp/cvs-serv29979 Modified Files: syncmail Log Message: More monitoring of what SourceForge has available, just in case. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/CVSROOT/syncmail,v retrieving revision 1.19 retrieving revision 1.20 diff -u -d -r1.19 -r1.20 --- syncmail 9 Jul 2003 21:25:23 -0000 1.19 +++ syncmail 10 Jul 2003 19:07:51 -0000 1.20 @@ -442,7 +442,8 @@ if verbose: print 'Python version', sys.version - os.system("type python2.0; type python2.1; type python2.2") + os.system("type python1.6; type python2;" + " type python2.0; type python2.1; type python2.2") print 'Mailing %s...' % string.join(people, COMMASPACE) print 'Generating notification message...' blast_mail(subject, people, changes.values(), |
From: Fred L. D. <fd...@us...> - 2003-07-10 18:49:27
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv26744 Modified Files: Tag: new-config-branch syncmail tests.py Log Message: Various small corrections and simplifications. Index: tests.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/Attic/tests.py,v retrieving revision 1.1.2.3 retrieving revision 1.1.2.4 diff -u -d -r1.1.2.3 -r1.1.2.4 --- tests.py 10 Jul 2003 16:52:29 -0000 1.1.2.3 +++ tests.py 10 Jul 2003 18:48:06 -0000 1.1.2.4 @@ -84,8 +84,8 @@ eq(options.get('third'), 'three') eq(options.get('fourth'), 'four') eq(options.get('missing'), None) -eq(options.get('common', 'splat'), '1') -eq(options.get('third', 'foo'), 'three') +eq(options.get('common'), '1') +eq(options.get('third'), 'three') options = OptionLookup([{"foo": "bar", "branch": "$BRANCH", Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.6 retrieving revision 1.36.2.7 diff -u -d -r1.36.2.6 -r1.36.2.7 --- syncmail 10 Jul 2003 16:56:41 -0000 1.36.2.6 +++ syncmail 10 Jul 2003 18:48:05 -0000 1.36.2.7 @@ -386,31 +386,30 @@ "HOSTNAME": os.environ.get("HOSTNAME") or getfqdn(), }) - def get(self, option, default=None): + def get(self, option): for dict in self._dicts: v = dict.get(option) if v is not None: return self._replace(v) - return default + return None - def getbool(self, option, default=None): + def getbool(self, option): v = self.get(option) - if v is None: - return default - v = string.lower(v) - if v in TRUE_VALUES: - return 1 - elif v in FALSE_VALUES: - return 0 - else: - raise ValueError("illegal boolean value: %s" % `v`) + if v is not None: + v = string.lower(v) + if v in TRUE_VALUES: + v = 1 + elif v in FALSE_VALUES: + v = 0 + else: + raise ValueError("illegal boolean value: %s" % `v`) + return v - def getint(self, option, default=None): + def getint(self, option): v = self.get(option) - if v is None: - return default - else: - return int(v) + if v is not None: + v = int(v) + return v def getaddress(self, option): """Return (host, port) for a host:port or host string. @@ -576,9 +575,9 @@ self.subject = self.subject_prefix + " changes" # The remaining args should be the email addresses if len(args) >= 2: - people = string.join(args[1:], COMMASPACE) + self.people = string.join(args[1:], COMMASPACE) else: - people = config.get("to") + self.people = config.get("to") def load_configuration(args, branch=None): cmdline, args = load_cmdline(args) @@ -596,9 +595,6 @@ def main(): - # XXX Should really move all the options to an object, just to - # avoid threading so many positional args through everything. - # load the options config = load_configuration(sys.argv[1:], load_branch_name()) @@ -620,7 +616,7 @@ changes.append(entry) if config.verbose: - print 'Mailing %s...' % people + print 'Mailing %s...' % config.people print 'Generating notification message...' blast_mail(config, changes) if config.verbose: |
From: Fred L. D. <fd...@us...> - 2003-07-10 17:06:25
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv14295 Modified Files: Tag: new-config-branch syncmail Log Message: Check that diff-type in the config file has a valid value. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.5 retrieving revision 1.36.2.6 diff -u -d -r1.36.2.5 -r1.36.2.6 --- syncmail 10 Jul 2003 16:52:29 -0000 1.36.2.5 +++ syncmail 10 Jul 2003 16:56:41 -0000 1.36.2.6 @@ -557,6 +557,9 @@ class Options: def __init__(self, config, args): self.contextlines = config.getint('context-lines') + self.diff_type = config.get('diff-type') + if self.diff_type not in ('context', 'unified'): + usage(1, "invalid diff-type specified in configuration file") self.verbose = config.getbool('verbose') self.subject_prefix = config.get('subject-prefix') self.replyto = config.get('reply-to') |
From: Fred L. D. <fd...@us...> - 2003-07-10 16:57:09
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv13697 Modified Files: Tag: new-config-branch syncmail tests.py Log Message: Be more careful not to set an option more than once from the command line. Index: tests.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/Attic/tests.py,v retrieving revision 1.1.2.2 retrieving revision 1.1.2.3 diff -u -d -r1.1.2.2 -r1.1.2.3 --- tests.py 10 Jul 2003 12:56:11 -0000 1.1.2.2 +++ tests.py 10 Jul 2003 16:52:29 -0000 1.1.2.3 @@ -5,23 +5,52 @@ import os +from cStringIO import StringIO + # Since there's no .py extension, we need to load syncmail magically # so we don't trigger the main() function: __name__ = "notmain" execfile("syncmail") -VARS = {"FOO": "<whack!>"} - def eq(a, b, msg=None): if msg is None: msg = "%s != %s" % (`a`, `b`) - assert a == b, msg + if not a == b: + raise AssertionError(msg) + +def raises(exctype, f, *args, **kw): + try: + apply(f, args, kw) + except exctype: + pass + else: + raise AssertionError("expected exception") + +VARS = {"FOO": "<whack!>"} +# "variable" replacement replace = Replacer(VARS) eq(replace("abc$FOO-def${SPLAT}"), "abc<whack!>-def") eq(replace("$FOO"), "<whack!>") +eq(replace("$ FOO"), "$ FOO") +eq(replace("${FOO}"), "<whack!>") +eq(replace(" ${FOO} "), " <whack!> ") + +# load_cmdline() +eq(load_cmdline([]), ({}, [])) + +sys.stderr = StringIO() +sys.stdout = sys.stderr +try: + raises(SystemExit, load_cmdline, ['-qq']) + raises(SystemExit, load_cmdline, ['--config=filename', '--no-config']) + raises(SystemExit, load_cmdline, ['--no-config', '--no-config']) +finally: + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ +# load_configuration() config = load_configuration([]) eq(config.contextlines, 2) eq(config.verbose, 1) @@ -41,6 +70,7 @@ eq(config.smtp_server, MAILHOST) eq(config.smtp_port, 8025) +# OptionLookup dicts = [ {'common': '1', 'first': 'one'}, {'common': '2', 'second': 'two'}, Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.4 retrieving revision 1.36.2.5 diff -u -d -r1.36.2.4 -r1.36.2.5 --- syncmail 10 Jul 2003 14:26:35 -0000 1.36.2.4 +++ syncmail 10 Jul 2003 16:52:29 -0000 1.36.2.5 @@ -522,33 +522,36 @@ 'reply-to=', 'help', 'quiet']) except getopt.error, msg: usage(2, msg) - cmdline = {"config-file": "syncmail.conf"} + cmdline = {} + def set(option, value, cmdline=cmdline): + if cmdline.has_key(option): + usage(2, "can't set option more than once") + cmdline[option] = value for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt == '--cvsroot': - cmdline['cvsroot'] = arg + set('cvsroot', arg) elif opt == "--config": - cmdline['config-file'] = arg + set('config-file', arg) elif opt == '--no-config': - if cmdline.has_key('config-file'): - del cmdline['config-file'] + set('config-file', '') elif opt in ('-C', '--context'): - cmdline['context-lines'] = arg + set('context-lines', arg) elif opt == '-c': - cmdline['diff-type'] = 'context' + set('diff-type', 'context') elif opt == '-u': - cmdline['diff-type'] = 'unified' + set('diff-type', 'unified') elif opt in ('-S', '--subject-prefix'): - cmdline['subject-prefix'] = string.strip(arg) + set('subject-prefix', string.strip(arg)) elif opt in ('-R', '--reply-to'): - cmdline['reply-to'] = arg + set('reply-to', arg) elif opt in ('-q', '--quiet'): - cmdline['verbose'] = 'false' + set('verbose', 'false') elif opt in ('-f', '--fromhost'): - cmdline['from-host'] = arg + set('from-host', arg) elif opt in ('-m', '--mailhost'): - cmdline['smtp-server'] = arg + set('smtp-server', arg) return cmdline, args class Options: @@ -576,7 +579,11 @@ def load_configuration(args, branch=None): cmdline, args = load_cmdline(args) - cfgfile = cmdline.get('config-file') + if cmdline.has_key('config-file'): + cfgfile = cmdline['config-file'] + del cmdline['config-file'] + else: + cfgfile = "syncmail.conf" # cfgfile is specified relative to the CVSROOT directory; we need # to transform the path appropriately, since that won't be the # current directory when we try to read it. In fact, a wrong |
From: Fred L. D. <fd...@us...> - 2003-07-10 14:26:38
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv23543 Modified Files: Tag: new-config-branch syncmail Log Message: Before generated the diffs, sort the change records by name so they are presented in a predictable order. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.3 retrieving revision 1.36.2.4 diff -u -d -r1.36.2.3 -r1.36.2.4 --- syncmail 10 Jul 2003 12:56:11 -0000 1.36.2.3 +++ syncmail 10 Jul 2003 14:26:35 -0000 1.36.2.4 @@ -603,12 +603,16 @@ print 'Not sending email for imported sources.' return - changes = load_change_info() + items = load_change_info().items() + items.sort() + changes = [] + for name, entry in items: + changes.append(entry) if config.verbose: print 'Mailing %s...' % people print 'Generating notification message...' - blast_mail(config, changes.values()) + blast_mail(config, changes) if config.verbose: print 'Generating notification message... done.' |
From: Fred L. D. <fd...@us...> - 2003-07-10 14:08:27
|
Update of /cvsroot/cvs-syncmail/syncmail/doc In directory sc8-pr-cvs1:/tmp/cvs-serv20031 Modified Files: Tag: new-config-branch syncmail.sgml Log Message: Added documentation for the new command line options used to select the configuration file. The file format remains undocumented. Index: syncmail.sgml =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/doc/syncmail.sgml,v retrieving revision 1.3 retrieving revision 1.3.2.1 diff -u -d -r1.3 -r1.3.2.1 --- syncmail.sgml 8 Jul 2003 21:20:23 -0000 1.3 +++ syncmail.sgml 10 Jul 2003 14:08:24 -0000 1.3.2.1 @@ -57,6 +57,8 @@ <replaceable>lines</replaceable></arg> <arg>-c</arg> <arg>-u</arg> + <arg>--config <replaceable>file</replaceable></arg> + <arg>--no-config</arg> <arg><option>--quiet</option> | <option>-q</option></arg> <arg>--fromhost <replaceable>hostname</replaceable></arg> <arg>-f <replaceable>hostname</replaceable></arg> @@ -277,6 +279,34 @@ </para> <variablelist> + <varlistentry> + <term><option>--config <replaceable>file</replaceable></option></term> + <listitem> + <para> + Use <replaceable>file</replaceable> as the configuration + file for &dhpackage;. By default, a file named + <filename>syncmail.conf</filename> is used if it exists. + If the name of the configuration file is relative, it is + interpreted as relative to the <filename + class="directory">CVSROOT</filename> administrative + directory of the repository. <option>--config</option> is + incompatible with <option>--no-config</option>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--no-config</option></term> + <listitem> + <para> + Do not use a configuration file for &dhpackage;. This can + be used to disable the use of the default configuration + file. <option>--no-config</option> is incompatible with + <option>--config</option>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--cvsroot <replaceable>path</replaceable></option></term> <listitem> |
From: Fred L. D. <fd...@us...> - 2003-07-10 12:56:14
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv7143 Modified Files: Tag: new-config-branch syncmail tests.py Log Message: Move the setting of options from main() to a helper object. All options are attributes of the object; no more global variables! Index: tests.py =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/Attic/tests.py,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.2 diff -u -d -r1.1.2.1 -r1.1.2.2 --- tests.py 10 Jul 2003 05:40:10 -0000 1.1.2.1 +++ tests.py 10 Jul 2003 12:56:11 -0000 1.1.2.2 @@ -22,21 +22,24 @@ eq(replace("abc$FOO-def${SPLAT}"), "abc<whack!>-def") eq(replace("$FOO"), "<whack!>") -config, args = load_configuration([]) -eq(config.getint("context-lines"), 2) -eq(config.getbool("verbose"), 1) -eq(config.getaddress("smtp-server"), (MAILHOST, MAILPORT)) +config = load_configuration([]) +eq(config.contextlines, 2) +eq(config.verbose, 1) +eq(config.smtp_server, MAILHOST) +eq(config.smtp_port, MAILPORT) -config, args = load_configuration(['-q', '--mailhost=smtp.example.com']) -eq(config.getint("context-lines"), 2) -eq(config.getbool("verbose"), 0) -eq(config.getaddress("smtp-server"), ("smtp.example.com", MAILPORT)) +config = load_configuration(['-q', '--mailhost=smtp.example.com']) +eq(config.verbose, 0) +eq(config.smtp_server, "smtp.example.com") +eq(config.smtp_port, MAILPORT) -config, args = load_configuration(['--mailhost=smtp.example.com:8025']) -eq(config.getaddress("smtp-server"), ("smtp.example.com", 8025)) +config = load_configuration(['--mailhost=smtp.example.com:8025']) +eq(config.smtp_server, "smtp.example.com") +eq(config.smtp_port, 8025) -config, args = load_configuration(['--mailhost=:8025']) -eq(config.getaddress("smtp-server"), (MAILHOST, 8025)) +config = load_configuration(['--mailhost=:8025']) +eq(config.smtp_server, MAILHOST) +eq(config.smtp_port, 8025) dicts = [ {'common': '1', 'first': 'one'}, Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.2 retrieving revision 1.36.2.3 diff -u -d -r1.36.2.2 -r1.36.2.3 --- syncmail 10 Jul 2003 06:08:47 -0000 1.36.2.2 +++ syncmail 10 Jul 2003 12:56:11 -0000 1.36.2.3 @@ -150,7 +150,7 @@ -def calculate_diff(entry, contextlines): +def calculate_diff(config, entry): file = entry.name oldrev = entry.revision newrev = entry.new_revision @@ -200,8 +200,8 @@ # 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: - difftype = "-C " + str(contextlines) + if config.diff_type == "context": + difftype = "-C " + str(config.contextlines) else: difftype = "-u" diffcmd = "/usr/bin/cvs -f diff -kk %s --minimal -r %s -r %s %s" \ @@ -229,7 +229,7 @@ -def blast_mail(subject, people, entries, contextlines, fromhost, replyto): +def blast_mail(config, entries): # cannot wait for child process or that will cause parent to retain cvs # lock for too long. Urg! if not os.fork(): @@ -238,11 +238,10 @@ time.sleep(2) # Create the smtp connection to the localhost conn = smtplib.SMTP() - conn.connect(MAILHOST, MAILPORT) + conn.connect(config.smtp_server, config.smtp_port) user = pwd.getpwuid(os.getuid())[0] name = string.split(pwd.getpwuid(os.getuid())[4], ',')[0] - domain = fromhost or getfqdn() - address = '%s@%s' % (user, domain) + address = '%s@%s' % (user, config.fromhost) s = StringIO() sys.stdout = s datestamp = time.strftime('%a, %d %b %Y %H:%M:%S +0000', @@ -250,16 +249,16 @@ try: vars = {'address' : address, 'name' : quotename(name), - 'people' : people, - 'subject' : subject, + 'people' : config.people, + 'subject' : config.subject, 'version' : __version__, 'date' : datestamp, } print '''\ From: %(name)s <%(address)s> To: %(people)s''' % vars - if replyto: - print 'Reply-To: %s' % replyto + if config.replyto: + print 'Reply-To: %s' % config.replyto print '''\ Subject: %(subject)s Date: %(date)s @@ -269,10 +268,10 @@ # append the diffs if available print for entry in entries: - print calculate_diff(entry, contextlines) + print calculate_diff(config, entry) finally: sys.stdout = sys.__stdout__ - resp = conn.sendmail(address, people, s.getvalue()) + resp = conn.sendmail(address, config.people, s.getvalue()) conn.close() os._exit(0) @@ -533,7 +532,7 @@ cmdline['config-file'] = arg elif opt == '--no-config': if cmdline.has_key('config-file'): - def cmdline['config-file'] + del cmdline['config-file'] elif opt in ('-C', '--context'): cmdline['context-lines'] = arg elif opt == '-c': @@ -541,7 +540,7 @@ elif opt == '-u': cmdline['diff-type'] = 'unified' elif opt in ('-S', '--subject-prefix'): - cmdline['subject-prefix'] = arg + cmdline['subject-prefix'] = string.strip(arg) elif opt in ('-R', '--reply-to'): cmdline['reply-to'] = arg elif opt in ('-q', '--quiet'): @@ -552,6 +551,29 @@ cmdline['smtp-server'] = arg return cmdline, args +class Options: + def __init__(self, config, args): + self.contextlines = config.getint('context-lines') + self.verbose = config.getbool('verbose') + self.subject_prefix = config.get('subject-prefix') + self.replyto = config.get('reply-to') + self.fromhost = config.get('from-host') + self.email = config.getbool('email') + address = config.getaddress('smtp-server') + self.smtp_server, self.smtp_port = address + self.args = args + if args: + self.cvsinfo = args[0] + self.subject = self.subject_prefix + " " + self.cvsinfo + else: + self.cvsinfo = "" + self.subject = self.subject_prefix + " changes" + # The remaining args should be the email addresses + if len(args) >= 2: + people = string.join(args[1:], COMMASPACE) + else: + people = config.get("to") + def load_configuration(args, branch=None): cmdline, args = load_cmdline(args) cfgfile = cmdline.get('config-file') @@ -559,8 +581,8 @@ # to transform the path appropriately, since that won't be the # current directory when we try to read it. In fact, a wrong # config file may exist at the alternate location. - return load_configfile(cfgfile, cmdline, branch), args - + config = load_configfile(cfgfile, cmdline, branch) + return Options(config, args) def main(): @@ -568,31 +590,13 @@ # avoid threading so many positional args through everything. # load the options - config, args = load_configuration(sys.argv[1:], load_branch_name()) - contextlines = config.getint('context-lines') - verbose = config.getbool('verbose') - subject_prefix = config.get('subject-prefix') - replyto = config.get('reply-to') - fromhost = config.get('from-host') - email = config.getbool('email') - global MAILHOST, MAILPORT - MAILHOST, MAILPORT = config.getaddress('smtp-server') + config = load_configuration(sys.argv[1:], load_branch_name()) # args[0] is the specification containing the files that were # modified. The argument actually must be split, with the first component # containing the directory the checkin is being made in, relative to # $CVSROOT, followed by the list of files that are changing. - if not args: - usage(1, 'No CVS module specified') - subject = subject_prefix + args[0] - specs = string.split(args[0]) - del args[0] - - # The remaining args should be the email addresses - if args: - people = string.join(args, COMMASPACE) - else: - people = config.get("to") + specs = string.split(config.cvsinfo) if specs[-3:] == ['-', 'Imported', 'sources']: # What to do here should be configurable. @@ -601,12 +605,11 @@ changes = load_change_info() - if verbose: + if config.verbose: print 'Mailing %s...' % people print 'Generating notification message...' - blast_mail(subject, people, changes.values(), - contextlines, fromhost, replyto) - if verbose: + blast_mail(config, changes.values()) + if config.verbose: print 'Generating notification message... done.' |
From: Fred L. D. <fd...@us...> - 2003-07-10 06:08:50
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv19465 Modified Files: Tag: new-config-branch syncmail Log Message: load_cmdline(): Update the options passed to getopt(), and handle --config and --no-config as described in the strawman. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36.2.1 retrieving revision 1.36.2.2 diff -u -d -r1.36.2.1 -r1.36.2.2 --- syncmail 10 Jul 2003 05:40:10 -0000 1.36.2.1 +++ syncmail 10 Jul 2003 06:08:47 -0000 1.36.2.2 @@ -519,6 +519,7 @@ 'hC:cuS:R:qf:m:', ['fromhost=', 'context=', 'cvsroot=', 'mailhost=', 'subject-prefix=', + 'config=', 'no-config', 'reply-to=', 'help', 'quiet']) except getopt.error, msg: usage(2, msg) @@ -529,10 +530,10 @@ elif opt == '--cvsroot': cmdline['cvsroot'] = arg elif opt == "--config": - if arg == '-' and cmdline.has_key('config-file'): - del cmdline['config-file'] - else: - cmdline['config-file'] = arg + cmdline['config-file'] = arg + elif opt == '--no-config': + if cmdline.has_key('config-file'): + def cmdline['config-file'] elif opt in ('-C', '--context'): cmdline['context-lines'] = arg elif opt == '-c': |
From: Fred L. D. <fd...@us...> - 2003-07-10 05:40:14
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv16195 Modified Files: Tag: new-config-branch syncmail Added Files: Tag: new-config-branch syncmail.conf tests.py Log Message: Checkpoint work on the new configuration support. Not currently usable, but I want to make sure I do not lose the changes. --- NEW FILE: tests.py --- #! /usr/bin/env python """Tests for the helper functions in syncmail.""" # These tests assume that the syncmail script is in the current directory. import os # Since there's no .py extension, we need to load syncmail magically # so we don't trigger the main() function: __name__ = "notmain" execfile("syncmail") VARS = {"FOO": "<whack!>"} def eq(a, b, msg=None): if msg is None: msg = "%s != %s" % (`a`, `b`) assert a == b, msg replace = Replacer(VARS) eq(replace("abc$FOO-def${SPLAT}"), "abc<whack!>-def") eq(replace("$FOO"), "<whack!>") config, args = load_configuration([]) eq(config.getint("context-lines"), 2) eq(config.getbool("verbose"), 1) eq(config.getaddress("smtp-server"), (MAILHOST, MAILPORT)) config, args = load_configuration(['-q', '--mailhost=smtp.example.com']) eq(config.getint("context-lines"), 2) eq(config.getbool("verbose"), 0) eq(config.getaddress("smtp-server"), ("smtp.example.com", MAILPORT)) config, args = load_configuration(['--mailhost=smtp.example.com:8025']) eq(config.getaddress("smtp-server"), ("smtp.example.com", 8025)) config, args = load_configuration(['--mailhost=:8025']) eq(config.getaddress("smtp-server"), (MAILHOST, 8025)) dicts = [ {'common': '1', 'first': 'one'}, {'common': '2', 'second': 'two'}, {'common': '3', 'third': 'three'}, {'common': '4', 'fourth': 'four'}, ] options = OptionLookup(dicts) eq(options.get('common'), '1') eq(options.get('first'), 'one') eq(options.get('second'), 'two') eq(options.get('third'), 'three') eq(options.get('fourth'), 'four') eq(options.get('missing'), None) eq(options.get('common', 'splat'), '1') eq(options.get('third', 'foo'), 'three') options = OptionLookup([{"foo": "bar", "branch": "$BRANCH", "hostname": "$HOSTNAME"}], "my-branch") eq(options.get("branch"), "my-branch") eq(options.get("hostname"), os.environ.get("HOSTNAME", getfqdn())) --- NEW FILE: syncmail.conf --- ; In values, substitutions for $BRANCH, $CVSROOT, and $HOSTNAME are ; available. These may also be spelled with curlies (for example: ; ${BRANCH}). These are replaced by the branch name (or the empty ; string for the trunk), the CVSROOT environment variable (not the ; root specified using --cvsroot), and the hostname (from the HOSTNAME ; environment variable or, if not set, the fully-qualified hostname). [general] ; miscellaneous cvsroot = $CVSROOT verbose = true email = true ; the kind of diff we generate context-lines = 2 diff-type = unified ; how email is generated from-host = $HOSTNAME reply-to = smtp-server = localhost subject-prefix = to = cvs...@li... [branch] ; the * branch applies to all branches subject-prefix = $BRANCH: [branch my-project-branch] email = false [branch another-branch] subject-prefix = [Special] to = my...@ex... Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.36 retrieving revision 1.36.2.1 diff -u -d -r1.36 -r1.36.2.1 --- syncmail 9 Jul 2003 23:13:37 -0000 1.36 +++ syncmail 10 Jul 2003 05:40:10 -0000 1.36.2.1 @@ -159,7 +159,7 @@ if oldrev is None and newrev is None: return NOVERSION % file - if string.find(file, "'") <> -1: + if "'" in file: # 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, '\\', '\\\\') @@ -250,7 +250,7 @@ try: vars = {'address' : address, 'name' : quotename(name), - 'people' : string.join(people, COMMASPACE), + 'people' : people, 'subject' : subject, 'version' : __version__, 'date' : datestamp, @@ -297,7 +297,7 @@ except KeyError: if revision == "0": revision = None - if string.find(timestamp, "+") != -1: + if "+" in timestamp: timestamp, conflict = tuple(string.split(timestamp, "+")) else: conflict = None @@ -374,50 +374,210 @@ 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. + +TRUE_VALUES = ('true', 'on', 'enabled') +FALSE_VALUES = ('false', 'off', 'disabled') + +class OptionLookup: + def __init__(self, dicts, branch=None): + self._dicts = dicts + self._replace = Replacer({ + "BRANCH": branch or "", + "CVSROOT": os.environ.get("CVSROOT", ""), + "HOSTNAME": os.environ.get("HOSTNAME") or getfqdn(), + }) + + def get(self, option, default=None): + for dict in self._dicts: + v = dict.get(option) + if v is not None: + return self._replace(v) + return default + + def getbool(self, option, default=None): + v = self.get(option) + if v is None: + return default + v = string.lower(v) + if v in TRUE_VALUES: + return 1 + elif v in FALSE_VALUES: + return 0 + else: + raise ValueError("illegal boolean value: %s" % `v`) + + def getint(self, option, default=None): + v = self.get(option) + if v is None: + return default + else: + return int(v) + + def getaddress(self, option): + """Return (host, port) for a host:port or host string. + + The port, if ommitted, will be None. + """ + v = self.get(option) + if v is None: + return MAILHOST, MAILPORT + elif ":" in v: + h, p = tuple(string.split(v, ":")) + p = int(p) + return h or MAILHOST, p + else: + return v, MAILPORT + +# Support for $VARIABLE replacement. + +class Replacer: + def __init__(self, vars): + self._vars = vars + rx = re.compile(r"\$([a-zA-Z][a-zA-Z_]*\b|\{[a-zA-Z][a-zA-Z_]*\})") + self._search = rx.search + + def __call__(self, v): + v, name, suffix = self._split(v) + while name: + v = v + self._vars.get(name, "") + prefix, name, suffix = self._split(suffix) + v = v + prefix + return v + + def _split(self, s): + m = self._search(s) + if m is not None: + name = m.group(1) + if name[0] == "{": + name = name[1:-1] + return s[:m.start()], name, s[m.end():] + else: + return s, None, '' + +def get_section_as_dict(config, section): + d = {} + if config.has_section(section): + for opt in config.options(section): + d[opt] = config.get(section, opt, raw=1) + return d + +def load_configfile(filename, cmdline, branch): + dicts = [] + if filename: + from ConfigParser import ConfigParser + class ConfigParser(ConfigParser): + # Regular expressions for parsing section headers and options, + # from the Python 2.3 version of ConfigParser. + SECTCRE = re.compile( + r'\[' # [ + r'(?P<header>[^]]+)' # very permissive! + r'\]' # ] + ) + OPTCRE = re.compile( + r'(?P<option>[^:=\s][^:=]*)' # very permissive! + r'\s*(?P<vi>[:=])\s*' # any number of space/tab, + # followed by separator + # (either : or =), followed + # by any # space/tab + r'(?P<value>.*)$' # everything up to eol + ) + # For compatibility with older versions: + __SECTCRE = SECTCRE + __OPTCRE = OPTCRE + + cp = ConfigParser() + # We have to use this old method for compatibility with + # ancient versions of Python. + cp.read([filename]) + if branch: + dicts.append(get_section_as_dict(cp, "branch " + branch)) + dicts.append(get_section_as_dict(cp, "branch")) + dicts.append(cmdline) + dicts.append(get_section_as_dict(cp, "general")) + else: + dicts.append(cmdline) + dicts = filter(None, dicts) + # The defaults set covers what we need but might not get from the + # command line or configuration file. + defaults = { + "context-lines": "2", + "cvsroot": "$CVSROOT", + "diff-type": "unified", + "email": "true", + "from-host": "$HOSTNAME", + "smtp-server": "localhost", + "smtp-server": "localhost", + "subject-prefix": "", + "verbose": "true", + } + dicts.append(defaults) + return OptionLookup(dicts, branch) + +def load_cmdline(args): try: - opts, args = getopt.getopt( - sys.argv[1:], 'hC:cuS:R:qf:m:', - ['fromhost=', 'context=', 'cvsroot=', 'mailhost=', - 'subject-prefix=', 'reply-to=', - 'help', 'quiet']) + opts, args = getopt.getopt(args, + 'hC:cuS:R:qf:m:', + ['fromhost=', 'context=', 'cvsroot=', + 'mailhost=', 'subject-prefix=', + 'reply-to=', 'help', 'quiet']) except getopt.error, msg: - usage(1, msg) - - # parse the options - contextlines = 2 - verbose = 1 - subject_prefix = "" - replyto = None - fromhost = None + usage(2, msg) + cmdline = {"config-file": "syncmail.conf"} for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt == '--cvsroot': - os.environ['CVSROOT'] = arg + cmdline['cvsroot'] = arg + elif opt == "--config": + if arg == '-' and cmdline.has_key('config-file'): + del cmdline['config-file'] + else: + cmdline['config-file'] = arg elif opt in ('-C', '--context'): - contextlines = int(arg) + cmdline['context-lines'] = arg elif opt == '-c': - if contextlines <= 0: - contextlines = 2 + cmdline['diff-type'] = 'context' elif opt == '-u': - contextlines = 0 + cmdline['diff-type'] = 'unified' elif opt in ('-S', '--subject-prefix'): - subject_prefix = arg + cmdline['subject-prefix'] = arg elif opt in ('-R', '--reply-to'): - replyto = arg + cmdline['reply-to'] = arg elif opt in ('-q', '--quiet'): - verbose = 0 + cmdline['verbose'] = 'false' elif opt in ('-f', '--fromhost'): - fromhost = arg + cmdline['from-host'] = arg elif opt in ('-m', '--mailhost'): - global MAILHOST - MAILHOST = arg + cmdline['smtp-server'] = arg + return cmdline, args - # What follows is the specification containing the files that were +def load_configuration(args, branch=None): + cmdline, args = load_cmdline(args) + cfgfile = cmdline.get('config-file') + # cfgfile is specified relative to the CVSROOT directory; we need + # to transform the path appropriately, since that won't be the + # current directory when we try to read it. In fact, a wrong + # config file may exist at the alternate location. + return load_configfile(cfgfile, cmdline, branch), args + + + +def main(): + # XXX Should really move all the options to an object, just to + # avoid threading so many positional args through everything. + + # load the options + config, args = load_configuration(sys.argv[1:], load_branch_name()) + contextlines = config.getint('context-lines') + verbose = config.getbool('verbose') + subject_prefix = config.get('subject-prefix') + replyto = config.get('reply-to') + fromhost = config.get('from-host') + email = config.getbool('email') + global MAILHOST, MAILPORT + MAILHOST, MAILPORT = config.getaddress('smtp-server') + + # args[0] is the specification containing the files that were # modified. The argument actually must be split, with the first component # containing the directory the checkin is being made in, relative to # $CVSROOT, followed by the list of files that are changing. @@ -428,21 +588,20 @@ del args[0] # The remaining args should be the email addresses - if not args: - usage(1, 'No recipients specified') - - # Now do the mail command - people = args + if args: + people = string.join(args, COMMASPACE) + else: + people = config.get("to") if specs[-3:] == ['-', 'Imported', 'sources']: + # What to do here should be configurable. 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) + print 'Mailing %s...' % people print 'Generating notification message...' blast_mail(subject, people, changes.values(), contextlines, fromhost, replyto) @@ -453,4 +612,3 @@ if __name__ == '__main__': main() - sys.exit(0) |
From: Fred L. D. <fd...@us...> - 2003-07-09 23:13:40
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv30132 Modified Files: syncmail Log Message: Delay loading information from the CVS work directory until we actually expect to send an email. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.35 retrieving revision 1.36 diff -u -d -r1.35 -r1.36 --- syncmail 9 Jul 2003 21:26:57 -0000 1.35 +++ syncmail 9 Jul 2003 23:13:37 -0000 1.36 @@ -423,8 +423,6 @@ # $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] @@ -439,6 +437,9 @@ 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) |
From: Fred L. D. Jr. <fd...@ac...> - 2003-07-09 22:12:40
|
I've attached a sample config file for syncmail; it's really a strawman, though I don't expect it to be terribly controversial. This relates to my feature request issue #768243: http://sourceforge.net/tracker/?func=detail&aid=768243&group_id=47611&atid=450019 The file is a simple .ini file. There are two kinds of sections: a global section, and branch sections. Command line options override the global section. For changes to the trunk, only the global section is relevant. For changes on a branch, as many as three sections may be relevant: 1. a section for the specific branch, with a section header of the form [branch branch-name] 2. a section for any branch, with a section header of the form [branch] 3. the global section By default, a config file named CVSROOT/syncmail.conf will be used, if present. A --config/-C command line option can be used to specify a different file, or --no-config can be used to disable use of the config file. This will allow different CVS modules to be configured in appropriate ways. I don't know just when I'll implement this, but it probably won't be too far off. I welcome suggestions. -Fred -- Fred L. Drake, Jr. <fdrake at acm.org> PythonLabs at Zope Corporation |
From: Fred L. D. <fd...@us...> - 2003-07-09 21:27:00
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv13251 Modified Files: syncmail Log Message: - bug in yesterday's Date: header patch: time.gmtime() requires arg in older versions of Python Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.34 retrieving revision 1.35 diff -u -d -r1.34 -r1.35 --- syncmail 9 Jul 2003 20:48:39 -0000 1.34 +++ syncmail 9 Jul 2003 21:26:57 -0000 1.35 @@ -246,7 +246,7 @@ s = StringIO() sys.stdout = s datestamp = time.strftime('%a, %d %b %Y %H:%M:%S +0000', - time.gmtime()) + time.gmtime(time.time())) try: vars = {'address' : address, 'name' : quotename(name), |
From: Fred L. D. <fd...@us...> - 2003-07-09 21:25:34
|
Update of /cvsroot/cvs-syncmail/CVSROOT In directory sc8-pr-cvs1:/tmp/cvs-serv13022 Modified Files: loginfo Log Message: forced commit to cause syncmail to run Index: loginfo =================================================================== RCS file: /cvsroot/cvs-syncmail/CVSROOT/loginfo,v retrieving revision 1.9 retrieving revision 1.10 diff -u -d -r1.9 -r1.10 |
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.' |
From: Fred L. D. <fd...@us...> - 2003-07-09 20:48:42
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv6348 Modified Files: syncmail Log Message: update copyright dates Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.33 retrieving revision 1.34 diff -u -d -r1.33 -r1.34 --- syncmail 9 Jul 2003 20:38:34 -0000 1.33 +++ syncmail 9 Jul 2003 20:48:39 -0000 1.34 @@ -1,6 +1,6 @@ #! /usr/bin/python -# Copyright (c) 2002, Barry Warsaw, Fred Drake, and contributors +# Copyright (c) 2002, 2003, Barry Warsaw, Fred Drake, and contributors # All rights reserved. # See the accompanying LICENSE file for details. |
From: Fred L. D. <fd...@us...> - 2003-07-09 20:38:37
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv4304 Modified Files: syncmail Log Message: Fix stupid bug in revision 1.32: only refer to CVSEntry attributes that actually exist. Lots of changes working toword support for multi-directory commit emails, but we hit a roadblock: CVS writes the needed meta-data to only one directory at a time, not all of them. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.32 retrieving revision 1.33 diff -u -d -r1.32 -r1.33 --- syncmail 9 Jul 2003 17:37:58 -0000 1.32 +++ syncmail 9 Jul 2003 20:38:34 -0000 1.33 @@ -287,35 +287,57 @@ self.options = options self.tagdate = tagdate -class CVSDirectory(CVSEntry): - def __init__(self, name): - CVSEntry.__init__(self, name, None, None, None, None, None) +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 load_change_info(): - entries_fn = os.path.join("CVS", "Entries") +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" - d = {} + mapping = {} f = open(entries_fn) while 1: line = f.readline() if not line: break - if string.strip(line) == "D": - continue - if line[0] == "D": - name = string.split(line, "/")[1] - d[name] = CVSDirectory(name) - elif line[0] == "/": +## 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 - parts = string.split(line, "/") - _, name, revision, timestamp, options, tagdate = parts - if revision == "0": - revision = None - conflict = None - if string.find(timestamp, "+") != -1: - timestamp, conflict = tuple(string.split(timestamp, "+")) - d[name] = CVSEntry(name, revision, timestamp, conflict, - options, tagdate) + get_entry(prefix, mapping, line, entries_fn) # else: bogus Entries line f.close() if os.path.isfile(entries_log_fn): @@ -327,25 +349,20 @@ 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:] - name = parts[0] if line[0] == "A": # adding a file - try: - entry = d[name] - except KeyError: - entry = CVSEntry(name, None, parts[2], parts[3], parts[4]) - d[name] = entry - d[name].new_revision = parts[1] + entry.new_revision = parts[1] elif line[0] == "R": # removing a file - d[name].new_revision = None + entry.new_revision = None f.close() - for entry in d.values(): + for entry in mapping.values(): if not hasattr(entry, "new_revision"): - print 'confused about file', entry.file, '-- ignoring' - del d[entry.name] - return d + print 'confused about file', entry.name, '-- ignoring' + del mapping[entry.name] + return mapping def load_branch_name(): tag_fn = os.path.join("CVS", "Tag") |
From: Fred L. D. <fd...@us...> - 2003-07-09 17:38:01
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv29258 Modified Files: syncmail Log Message: When Entries.Log doesn't tell us anything about a file, remove it from the entries we care about and print a message; don't bomb. Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.31 retrieving revision 1.32 diff -u -d -r1.31 -r1.32 --- syncmail 9 Jul 2003 04:54:35 -0000 1.31 +++ syncmail 9 Jul 2003 17:37:58 -0000 1.32 @@ -342,7 +342,9 @@ d[name].new_revision = None f.close() for entry in d.values(): - assert hasattr(entry, "new_revision") + if not hasattr(entry, "new_revision"): + print 'confused about file', entry.file, '-- ignoring' + del d[entry.name] return d def load_branch_name(): |
From: SourceForge.net <no...@so...> - 2003-07-09 05:16:32
|
Bugs item #768243, was opened at 2003-07-09 01:15 Message generated for change (Tracker Item Submitted) made by Item Submitter You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=450019&aid=768243&group_id=47611 Category: None Group: Feature Request Status: Open Resolution: None Priority: 5 Submitted By: Fred L. Drake, Jr. (fdrake) Assigned to: Fred L. Drake, Jr. (fdrake) Summary: configuration file use Initial Comment: syncmail has grown many command line options, and is likely to grow more as functionality improves. Some potential functionality (such as per-branch configuration) is expected to be fairly difficult via the command line. Using a configuration file would make it much easier to configure syncmail. It is most likely that an .ini style configuration file will be used since a (somewhat) suitable parser already exists in the Python standard library. ---------------------------------------------------------------------- You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=450019&aid=768243&group_id=47611 |
From: Fred L. D. Jr. <fd...@ac...> - 2003-07-09 05:02:53
|
In a CVS checkin message, I wrote: > Modified Files: > syncmail > Log Message: > Massive changes to the way we determine what diffs to generate: BELIEVE THIS! Before using this new code on any valuable production repository, please test it carefully. I've done several tests and am reasonably confident this is right, but care should be taken if your users are likely to scream and holler if they don't get exactly the mail they expect. > - read the CVS/Entries and CVS/Entries.Log files from the temporary > workspace on the server; these provide all the information we need > to determine what the filenames are and which revisions should be > used to generate diffs > - as a side effect, we can deal with any character in a filename > except '/' and '\n'; closes SF issue #608641 > - add a helper function that can determine the branch name (not yet > used to any effect) -Fred -- Fred L. Drake, Jr. <fdrake at acm.org> PythonLabs at Zope Corporation |
From: SourceForge.net <no...@so...> - 2003-07-09 04:57:29
|
Bugs item #608641, was opened at 2002-09-12 18:25 Message generated for change (Comment added) made by fdrake You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=450019&aid=608641&group_id=47611 Category: None Group: None >Status: Closed >Resolution: Fixed Priority: 5 Submitted By: James Snyder (jsnyder) >Assigned to: Fred L. Drake, Jr. (fdrake) Summary: no diffs on files w/ spaces and parens Initial Comment: using %{sVv} for passing file version and location info with a file contained in a dir w/ a space and parens in the name there is no diff listed in the email dispatched. i cant get %s alone to work either as is suggested in the source ---------------------------------------------------------------------- >Comment By: Fred L. Drake, Jr. (fdrake) Date: 2003-07-09 00:56 Message: Logged In: YES user_id=3066 I just made massive changes to the way filenames to diff are recovered from CVS; the command line issues no longer apply as of revision 1.31. Closing this as fixed; I've tested the new code with parentheses, spaces, and backslashes. ---------------------------------------------------------------------- Comment By: Craig Hughes (hughescr) Date: 2002-11-26 17:42 Message: Logged In: YES user_id=9266 I have a similar problem -- my CVS module has a space in the name, so whenever I do a commit, the %{sVv} comes through as: "some directory filename,1.1,1.2 file2,1.1,1.2" The line: specs = string.split(args[0]) is the problem -- needs to be smarter at figuring out how to break the string up. I don't know python at all, but is there some way to break the args[0] up using a regex like: (.*)(?: (.*,[.0-9]*,[.0-9]*))+ That's perl regex syntax, but basically it'll make it work unless the first file which is modified contains a space in the filename. I don't think it's going to be possible to deal with the case where both the directory name and the first filename on the line contain spaces in them, because of the fact that CVS does not quote the individual items that it passes when it substitutes %{sVv}. ---------------------------------------------------------------------- You can respond by visiting: https://sourceforge.net/tracker/?func=detail&atid=450019&aid=608641&group_id=47611 |