Rev 5491: Merge lp:~vila/bzr/fixed-in fixing NEWS entry in http://bazaar.launchpad.net/~vila/bzr/integration/

Vincent Ladeuil v.ladeuil+lp at free.fr
Wed Oct 13 07:33:56 BST 2010


At http://bazaar.launchpad.net/~vila/bzr/integration/

------------------------------------------------------------
revno: 5491 [merge]
revision-id: v.ladeuil+lp at free.fr-20101013063355-fwb60yy1cmy748h9
parent: pqm at pqm.ubuntu.com-20101013043153-ki2mvt5miuxbun8l
parent: v.ladeuil+lp at free.fr-20100928090258-tf419g5olimxuagl
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: trunk
timestamp: Wed 2010-10-13 08:33:55 +0200
message:
  Merge lp:~vila/bzr/fixed-in fixing NEWS entry
added:
  tools/fixed-in.py              fixedin.py-20100927214922-jxti5i4kt2ij9f6d-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS	2010-10-13 03:47:21 +0000
+++ b/NEWS	2010-10-13 06:33:55 +0000
@@ -59,6 +59,10 @@
 Internals
 *********
 
+* ``tools/fixed-in.py`` find a bug in NEWS from its number or a regexp
+  matching the news entry and display the corresponding release, date, fix
+  authors and the news entry itself. (Vincent Ladeuil)
+
 Testing
 *******
 

=== added file 'tools/fixed-in.py'
--- a/tools/fixed-in.py	1970-01-01 00:00:00 +0000
+++ b/tools/fixed-in.py	2010-09-28 08:57:31 +0000
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+
+# Simple script that will output the release where a given bug was fixed
+# searching the NEWS file
+
+import optparse
+import re
+import sys
+
+
+class NewsParser(object):
+
+    paren_exp_re = re.compile('\(([^)]+)\)')
+    release_re = re.compile("bzr[ -]")
+    release_prefix_length = len('bzr ')
+    bugs_re = re.compile('#([0-9]+)')
+
+    def __init__(self, news):
+        self.news = news
+        # Temporary attributes used by the parser
+        self.release = None
+        self.date = None
+        self.may_be_release = None
+        self.release_markup = None
+        self.entry = ''
+        self.line = None
+        self.lrs = None
+
+    def set_line(self, line):
+        self.line = line
+        self.lrs = line.rstrip()
+
+    def try_release(self):
+        if self.release_re.match(self.lrs) is not None:
+            # May be a new release
+            self.may_be_release = self.lrs
+            # We know the markup will have the same length as the release
+            self.release_markup = '#' * len(self.may_be_release)
+            return True
+        return False
+
+    def confirm_release(self):
+        if self.may_be_release is not None and self.lrs == self.release_markup:
+            # The release is followed by the right markup
+            self.release = self.may_be_release[self.release_prefix_length:]
+            # Wait for the associated date
+            self.date = None
+            return True
+        return False
+
+    def try_date(self):
+        if self.release is None:
+            return False
+        date_re = re.compile(':%s: (NOT RELEASED YET|\d{4}-\d{2}-\d{2})'
+                             % (self.release,))
+        match = date_re.match(self.lrs)
+        if  match is not None:
+            self.date = match.group(1)
+            return True
+        # The old fashion way
+        released_re = re.compile(':Released:\s+(\d{4}-\d{2}-\d{2})')
+        match = released_re.match(self.lrs)
+        if  match is not None:
+            self.date = match.group(1)
+            return True
+        return False
+
+    def add_line_to_entry(self):
+        if self.lrs == '':
+            return False
+        self.entry += self.line
+        return True
+
+    def extract_bugs_from_entry(self):
+        """Possibly extract bugs from a NEWS entry and yield them.
+
+        Not all entries will contain bugs and some entries are even garbage and
+        we don't try to parse them (yet). The trigger is a '#' and what looks
+        like a bug number inside parens to start with. From that we extract
+        authors (when present) and multiple bugs if needed.
+        """
+        # FIXME: Malone entries are different
+        # Join all entry lines to simplify multiple line matching
+        flat_entry = ' '.join(self.entry.splitlines())
+        # Fixed bugs are always inside parens
+        for par in self.paren_exp_re.findall(flat_entry):
+            sharp = par.find('#')
+            if sharp is not None:
+                # We have at least one bug inside parens.
+                bugs = list(self.bugs_re.finditer(par))
+                if bugs:
+                    # See where the first bug is mentioned
+                    start = bugs[0].start()
+                    end = bugs[-1].end()
+                    if start == 0:
+                        # (bugs/authors)
+                        authors = par[end:]
+                    else:
+                        # (authors/bugs)
+                         authors = par[:start]
+                    for bug_match in bugs:
+                        bug_number = bug_match.group(0)
+                        yield (bug_number, authors,
+                               self.release, self.date, self.entry)
+        # We've consumed the entry
+        self.entry = ''
+
+    def parse_bugs(self):
+        for line in self.news:
+            self.set_line(line)
+            if self.try_release():
+                continue # line may a be release
+            try:
+                if self.confirm_release():
+                    continue # previous line was indeed a release
+            finally:
+                self.may_be_release = None
+            if self.try_date():
+                continue # The release date has been seen
+            if self.add_line_to_entry():
+                continue # accumulate in self.enrty
+            for b in self.extract_bugs_from_entry():
+                yield b # all bugs in the news entry
+
+def main():
+    opt_parser = optparse.OptionParser(
+        usage="""Usage: %prog [options] BUG_NUMBER
+    """)
+    opt_parser.add_option(
+        '-f', '--file', type='str', dest='news_file',
+        help='NEWS file (defaults to ./NEWS)')
+    opt_parser.add_option(
+        '-m', '--message', type='str', dest='msg_re',
+        help='A regexp to search for in the news entry '
+        '(BUG_NUMBER should not be specified in this case)')
+    opt_parser.set_defaults(news_file='./NEWS')
+    (opts, args) = opt_parser.parse_args(sys.argv[1:])
+    if opts.msg_re is not None:
+        if len(args) != 0:
+            opt_parser.error('BUG_NUMBER and -m are mutually exclusive')
+        bug = None
+        msg_re = re.compile(opts.msg_re)
+    elif len(args) != 1:
+        opt_parser.error('Expected a single bug number, got %r' % args)
+    else:
+        bug = args[0]
+
+    news = open(opts.news_file)
+    parser = NewsParser(news)
+    try:
+        seen = 0
+        for b in parser.parse_bugs():
+            (number, authors, release, date, entry,) = b
+            # indent entry
+            entry = '\n'.join(['    ' + l for l in entry.splitlines()])
+            found = False
+            if bug is not None:
+                if number[1:] == bug: # Strip the leading '#'
+                    found = True
+            elif msg_re.search(entry) is not None:
+                found = True
+            if found:
+                print 'Bug %s was fixed in bzr-%s/%s by %s:' % (
+                    number, release, date, authors)
+                print entry
+            seen += 1
+    finally:
+        print '%s bugs seen' % (seen,)
+        news.close()
+
+
+main()



More information about the bazaar-commits mailing list