Home | History | Annotate | Download | only in scripts
      1 #!/neo/opt/bin/python
      2 
      3 import sys, os, string, re, getopt, pwd, socket, time
      4 
      5 def 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 
     14 class 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 
    223 def usage (argv0):
    224   print "usage: %s [--help] module release1 release2" % argv0
    225   print __doc__
    226 
    227 def 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 
    244 if __name__ == "__main__":
    245   main (sys.argv, sys.stdout, os.environ)
    246