From: Fred L. D. <fd...@us...> - 2003-07-09 04:55:11
|
Update of /cvsroot/cvs-syncmail/syncmail In directory sc8-pr-cvs1:/tmp/cvs-serv26960 Modified Files: syncmail Log Message: Massive changes to the way we determine what diffs to generate: - 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) Index: syncmail =================================================================== RCS file: /cvsroot/cvs-syncmail/syncmail/syncmail,v retrieving revision 1.30 retrieving revision 1.31 diff -u -d -r1.30 -r1.31 --- syncmail 8 Jul 2003 17:57:26 -0000 1.30 +++ syncmail 9 Jul 2003 04:54:35 -0000 1.31 @@ -129,9 +129,6 @@ DIFF_TAIL_LINES = 20 DIFF_TRUNCATE_IF_LARGER = 1000 -EMPTYSTRING = '' -SPACE = ' ' -DOT = '.' COMMASPACE = ', ' PROGRAM = sys.argv[0] @@ -140,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" @@ -154,35 +150,20 @@ -def calculate_diff(filespec, contextlines): - spec = string.split(filespec, ',') - if len(spec) < 3: - # Too few parts; command line probable used a replacement - # other than "%{sVv}"; don't fail, but don't produce a diff - # since we can't be sure what diff to generate. - return '' - # This allows filenames that contain commas: - file = string.join(spec[:-2], ",") - oldrev = spec[-2] - newrev = spec[-1] +def calculate_diff(entry, contextlines): + file = entry.name + oldrev = entry.revision + newrev = entry.new_revision # 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 - - 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 + 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. @@ -190,7 +171,7 @@ 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): @@ -213,7 +194,7 @@ 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. @@ -227,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] @@ -250,7 +229,7 @@ -def blast_mail(subject, people, filestodiff, contextlines, fromhost, replyto): +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(): @@ -289,8 +268,8 @@ 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()) @@ -299,6 +278,83 @@ +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 + +class CVSDirectory(CVSEntry): + def __init__(self, name): + CVSEntry.__init__(self, name, None, None, None, None, None) + +def load_change_info(): + entries_fn = os.path.join("CVS", "Entries") + entries_log_fn = entries_fn + ".Log" + d = {} + 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] == "/": + # 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) + # 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 + 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] + elif line[0] == "R": + # removing a file + d[name].new_revision = None + f.close() + for entry in d.values(): + assert hasattr(entry, "new_revision") + return d + +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 @@ -348,6 +404,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] @@ -359,26 +417,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, replyto) + blast_mail(subject, people, changes.values(), + contextlines, fromhost, replyto) if verbose: print 'Generating notification message... done.' |
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 |