#!/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)