1#!/neo/opt/bin/python 2 3import sys, os, string, re, getopt, pwd, socket, time 4 5def warn(*args): 6 t = time.time() 7 log_line = "[" + time.strftime("%m/%d %T", time.localtime(t)) + "] " 8 l = [] 9 for arg in args: 10 l.append(str(arg)) 11 log_line = log_line + string.join(l, " ") + "\n" 12 sys.stderr.write(log_line) 13 14class ChangeLog: 15 def __init__ (self, module, release_from, release_to, copydir = None, cvsroot=None): 16 self._module = module 17 self._releaseFrom = release_from 18 self._releaseTo = release_to 19 self._cvsroot = cvsroot 20 if cvsroot is None: 21 self._cvsroot = os.environ.get("CVSROOT", None) 22 23 self._copydir = copydir 24 if copydir is None: 25 self._copydir = os.getcwd() 26 self._names = {} 27 28 def changeInfo (self): 29 cmd = self.cvsCmd ("-q", "rdiff", "-s -r%s -r%s %s" % (self._releaseFrom, self._releaseTo, self._module)) 30 warn (cmd) 31 fpi = os.popen (cmd) 32 data = fpi.readlines() 33 r = fpi.close() 34 if r is None: r = 0 35 if r != 0: 36 warn ("Return code from command is %d\n" % r) 37 return 38 39 self.oldfiles = {} 40 self.newfiles = [] 41 self.delfiles = [] 42 old_re = re.compile ("File (.*) changed from revision (.*) to (.*)") 43 new_re = re.compile ("File (.*) is new; current revision (.*)") 44 del_re = re.compile ("File (.*) is removed;") 45 for line in data: 46 m = old_re.match (line) 47 if m: 48 file = m.group(1) 49 if file[:len(self._module)+1] == "%s/" % self._module: 50 file = file[len(self._module)+1:] 51 self.oldfiles[file] = (m.group(2), m.group(3)) 52 continue 53 m = new_re.match (line) 54 if m: 55 file = m.group(1) 56 if file[:len(self._module)+1] == "%s/" % self._module: 57 file = file[len(self._module)+1:] 58 self.newfiles.append(file) 59 continue 60 m = del_re.match (line) 61 if m: 62 file = m.group(1) 63 if file[:len(self._module)+1] == "%s/" % self._module: 64 file = file[len(self._module)+1:] 65 self.delfiles.append(file) 66 continue 67 warn ("Unknown response from changeInfo request:\n %s" % line) 68 69 def parselog (self, log): 70 lines = string.split (log, '\n') 71 in_header = 1 72 x = 0 73 num = len(lines) 74 revisions = {} 75 revision = None 76 comment = [] 77 info_re = re.compile ("date: ([^; ]*) ([^;]*); author: ([^;]*);") 78 while (x < num): 79 line = string.strip(lines[x]) 80 if line: 81 if (x + 1 < num): 82 nline = string.strip(lines[x+1]) 83 else: 84 nline = None 85 if in_header: 86 (key, value) = string.split (line, ':', 1) 87 if key == "Working file": 88 filename = string.strip (value) 89 elif key == "description": 90 in_header = 0 91 else: 92 if (line == "----------------------------") and (nline[:9] == "revision "): 93 if revision is not None: 94 key = (date, author, string.join (comment, '\n')) 95 try: 96 revisions[key].append((filename, revision)) 97 except KeyError: 98 revisions[key] = [(filename, revision)] 99 comment = [] 100 elif line == "=" * 77: 101 key = (date, author, string.join (comment, '\n')) 102 try: 103 revisions[key].append((filename, revision)) 104 except KeyError: 105 revisions[key] = [(filename, revision)] 106 in_header = 1 107 revision = None 108 comment = [] 109 elif line[:9] == "revision ": 110 (rev, revision) = string.split (lines[x]) 111 else: 112 m = info_re.match (lines[x]) 113 if m: 114 date = m.group(1) 115 author = m.group(3) 116 else: 117 comment.append (lines[x]) 118 x = x + 1 119 return revisions 120 121 def rcs2log (self): 122 cwd = os.getcwd() 123 os.chdir(self._copydir) 124 files = string.join (self.oldfiles.keys(), ' ') 125 cmd = 'rcs2log -v -r "-r%s:%s" %s' % (self._releaseFrom, self._releaseTo, files) 126 fpi = os.popen (cmd) 127 data = fpi.read() 128 r = fpi.close() 129 os.chdir(cwd) 130 if r is None: r = 0 131 if r != 0: 132 warn (cmd) 133 warn ("Return code from command is %d\n" % r) 134 return 135 136 fpo = open ("ChangeLog.%s" % self._releaseTo, 'w') 137 fpo.write(data) 138 fpo.close() 139 140 def runCmd (self, cmd): 141 cwd = os.getcwd() 142 os.chdir(self._copydir) 143 warn (cmd) 144 fpi = os.popen (cmd) 145 data = fpi.read() 146 r = fpi.close() 147 os.chdir(cwd) 148 if r is None: r = 0 149 if r != 0: 150 warn ("Return code from command is %d\n" % r) 151 return None 152 return data 153 154 def rcslog (self): 155 inverted_log = {} 156 if len(self.newfiles): 157 cmd = self.cvsCmd ("", "log", "-N %s" % string.join(self.newfiles,' ')) 158 data = self.runCmd (cmd) 159 if data is None: return 160 revisions = self.parselog (data) 161 for (key, value) in revisions.items(): 162 try: 163 inverted_log[key] = inverted_log[key] + value 164 except KeyError: 165 inverted_log[key] = value 166 167 filenames = string.join (self.oldfiles.keys(), ' ') 168 if filenames: 169 cmd = self.cvsCmd ("", "log", "-N -r%s:%s %s" % (self._releaseFrom, self._releaseTo, filenames)) 170 data = self.runCmd (cmd) 171 if data is not None: 172 revisions = self.parselog (data) 173 for (key, value) in revisions.items(): 174 for (filename, revision) in value: 175 (rev1, rev2) = self.oldfiles[filename] 176 if revision != rev1: 177 try: 178 inverted_log[key].append((filename, revision)) 179 except KeyError: 180 inverted_log[key] = [(filename, revision)] 181 182 fpo = open ("ChangeLog.%s" % self._releaseTo, 'w') 183 fpo.write ("ChangeLog from %s to %s\n" % (self._releaseFrom, self._releaseTo)) 184 fpo.write ("=" * 72 + "\n") 185 changes = inverted_log.items() 186 changes.sort() 187 changes.reverse() 188 last_stamp = "" 189 for (key, value) in changes: 190 (date, author, comment) = key 191 new_stamp = "%s %s" % (date, self.fullname(author)) 192 if new_stamp != last_stamp: 193 fpo.write ("%s\n\n" % new_stamp) 194 last_stamp = new_stamp 195 for (filename, revision) in value: 196 fpo.write (" * %s:%s\n" % (filename, revision)) 197 fpo.write (" %s\n\n" % comment) 198 199 fpo.close() 200 201 def cvsCmd (self, cvsargs, cmd, cmdargs): 202 root = "" 203 if self._cvsroot is not None: 204 root = "-d %s" % self._cvsroot 205 206 cmd = "cvs -z3 %s %s %s %s" % (root, cvsargs, cmd, cmdargs) 207 return cmd 208 209 def fullname (self, author): 210 try: 211 return self._names[author] 212 except KeyError: 213 try: 214 (name, passwd, uid, gid, gecos, dir, shell) = pwd.getpwnam(author) 215 fullname = "%s <%s@%s>" % (gecos, name, socket.gethostname()) 216 except KeyError: 217 fullname = author 218 219 self._names[author] = fullname 220 return fullname 221 222 223def usage (argv0): 224 print "usage: %s [--help] module release1 release2" % argv0 225 print __doc__ 226 227def main (argv, stdout, environ): 228 list, args = getopt.getopt(argv[1:], "", ["help"]) 229 230 for (field, val) in list: 231 if field == "--help": 232 usage (argv[0]) 233 return 234 235 if len (args) < 3: 236 usage (argv[0]) 237 return 238 239 cl = ChangeLog (args[0], args[1], args[2]) 240 cl.changeInfo() 241 cl.rcslog() 242 243 244if __name__ == "__main__": 245 main (sys.argv, sys.stdout, os.environ) 246