• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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