#!/neo/opt/bin/python import sys, os, string, re, getopt, pwd, socket, time def warn(*args): t = time.time() log_line = "[" + time.strftime("%m/%d %T", time.localtime(t)) + "] " l = [] for arg in args: l.append(str(arg)) log_line = log_line + string.join(l, " ") + "\n" sys.stderr.write(log_line) class ChangeLog: def __init__ (self, module, release_from, release_to, copydir = None, cvsroot=None): self._module = module self._releaseFrom = release_from self._releaseTo = release_to self._cvsroot = cvsroot if cvsroot is None: self._cvsroot = os.environ.get("CVSROOT", None) self._copydir = copydir if copydir is None: self._copydir = os.getcwd() self._names = {} def changeInfo (self): cmd = self.cvsCmd ("-q", "rdiff", "-s -r%s -r%s %s" % (self._releaseFrom, self._releaseTo, self._module)) warn (cmd) fpi = os.popen (cmd) data = fpi.readlines() r = fpi.close() if r is None: r = 0 if r != 0: warn ("Return code from command is %d\n" % r) return self.oldfiles = {} self.newfiles = [] self.delfiles = [] old_re = re.compile ("File (.*) changed from revision (.*) to (.*)") new_re = re.compile ("File (.*) is new; current revision (.*)") del_re = re.compile ("File (.*) is removed;") for line in data: m = old_re.match (line) if m: file = m.group(1) if file[:len(self._module)+1] == "%s/" % self._module: file = file[len(self._module)+1:] self.oldfiles[file] = (m.group(2), m.group(3)) continue m = new_re.match (line) if m: file = m.group(1) if file[:len(self._module)+1] == "%s/" % self._module: file = file[len(self._module)+1:] self.newfiles.append(file) continue m = del_re.match (line) if m: file = m.group(1) if file[:len(self._module)+1] == "%s/" % self._module: file = file[len(self._module)+1:] self.delfiles.append(file) continue warn ("Unknown response from changeInfo request:\n %s" % line) def parselog (self, log): lines = string.split (log, '\n') in_header = 1 x = 0 num = len(lines) revisions = {} revision = None comment = [] info_re = re.compile ("date: ([^; ]*) ([^;]*); author: ([^;]*);") while (x < num): line = string.strip(lines[x]) if line: if (x + 1 < num): nline = string.strip(lines[x+1]) else: nline = None if in_header: (key, value) = string.split (line, ':', 1) if key == "Working file": filename = string.strip (value) elif key == "description": in_header = 0 else: if (line == "----------------------------") and (nline[:9] == "revision "): if revision is not None: key = (date, author, string.join (comment, '\n')) try: revisions[key].append((filename, revision)) except KeyError: revisions[key] = [(filename, revision)] comment = [] elif line == "=" * 77: key = (date, author, string.join (comment, '\n')) try: revisions[key].append((filename, revision)) except KeyError: revisions[key] = [(filename, revision)] in_header = 1 revision = None comment = [] elif line[:9] == "revision ": (rev, revision) = string.split (lines[x]) else: m = info_re.match (lines[x]) if m: date = m.group(1) author = m.group(3) else: comment.append (lines[x]) x = x + 1 return revisions def rcs2log (self): cwd = os.getcwd() os.chdir(self._copydir) files = string.join (self.oldfiles.keys(), ' ') cmd = 'rcs2log -v -r "-r%s:%s" %s' % (self._releaseFrom, self._releaseTo, files) fpi = os.popen (cmd) data = fpi.read() r = fpi.close() os.chdir(cwd) if r is None: r = 0 if r != 0: warn (cmd) warn ("Return code from command is %d\n" % r) return fpo = open ("ChangeLog.%s" % self._releaseTo, 'w') fpo.write(data) fpo.close() def runCmd (self, cmd): cwd = os.getcwd() os.chdir(self._copydir) warn (cmd) fpi = os.popen (cmd) data = fpi.read() r = fpi.close() os.chdir(cwd) if r is None: r = 0 if r != 0: warn ("Return code from command is %d\n" % r) return None return data def rcslog (self): inverted_log = {} if len(self.newfiles): cmd = self.cvsCmd ("", "log", "-N %s" % string.join(self.newfiles,' ')) data = self.runCmd (cmd) if data is None: return revisions = self.parselog (data) for (key, value) in revisions.items(): try: inverted_log[key] = inverted_log[key] + value except KeyError: inverted_log[key] = value filenames = string.join (self.oldfiles.keys(), ' ') if filenames: cmd = self.cvsCmd ("", "log", "-N -r%s:%s %s" % (self._releaseFrom, self._releaseTo, filenames)) data = self.runCmd (cmd) if data is not None: revisions = self.parselog (data) for (key, value) in revisions.items(): for (filename, revision) in value: (rev1, rev2) = self.oldfiles[filename] if revision != rev1: try: inverted_log[key].append((filename, revision)) except KeyError: inverted_log[key] = [(filename, revision)] fpo = open ("ChangeLog.%s" % self._releaseTo, 'w') fpo.write ("ChangeLog from %s to %s\n" % (self._releaseFrom, self._releaseTo)) fpo.write ("=" * 72 + "\n") changes = inverted_log.items() changes.sort() changes.reverse() last_stamp = "" for (key, value) in changes: (date, author, comment) = key new_stamp = "%s %s" % (date, self.fullname(author)) if new_stamp != last_stamp: fpo.write ("%s\n\n" % new_stamp) last_stamp = new_stamp for (filename, revision) in value: fpo.write (" * %s:%s\n" % (filename, revision)) fpo.write (" %s\n\n" % comment) fpo.close() def cvsCmd (self, cvsargs, cmd, cmdargs): root = "" if self._cvsroot is not None: root = "-d %s" % self._cvsroot cmd = "cvs -z3 %s %s %s %s" % (root, cvsargs, cmd, cmdargs) return cmd def fullname (self, author): try: return self._names[author] except KeyError: try: (name, passwd, uid, gid, gecos, dir, shell) = pwd.getpwnam(author) fullname = "%s <%s@%s>" % (gecos, name, socket.gethostname()) except KeyError: fullname = author self._names[author] = fullname return fullname def usage (argv0): print "usage: %s [--help] module release1 release2" % argv0 print __doc__ def main (argv, stdout, environ): list, args = getopt.getopt(argv[1:], "", ["help"]) for (field, val) in list: if field == "--help": usage (argv[0]) return if len (args) < 3: usage (argv[0]) return cl = ChangeLog (args[0], args[1], args[2]) cl.changeInfo() cl.rcslog() if __name__ == "__main__": main (sys.argv, sys.stdout, os.environ)