[blivet/master/rhel7 2/3] Add makebumpver script

Brian C. Lane bcl at redhat.com
Fri Oct 25 23:54:14 UTC 2013


From: "Brian C. Lane" <bcl at redhat.com>

Add a modified version of the makebumpver script from anaconda, it
handles bumping the version number, checking bug states, and adds
Related and Resolves to the spec changelog.
---
 Makefile            |  21 +--
 scripts/makebumpver | 478 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 489 insertions(+), 10 deletions(-)
 create mode 100755 scripts/makebumpver

diff --git a/Makefile b/Makefile
index eb1ae87..4f7d527 100644
--- a/Makefile
+++ b/Makefile
@@ -70,16 +70,17 @@ rpmlog:
 	@echo
 
 bumpver: po-pull
-	@NEWSUBVER=$$((`echo $(VERSION) |cut -d . -f 3` + 1)) ; \
-	NEWVERSION=`echo $(VERSION).$$NEWSUBVER |cut -d . -f 1,2,4` ; \
-	DATELINE="* `date "+%a %b %d %Y"` `git config user.name` <`git config user.email`> - $$NEWVERSION-1"  ; \
-	cl=`grep -n %changelog $(SPECFILE) |cut -d : -f 1` ; \
-	tail --lines=+$$(($$cl + 1)) $(SPECFILE) > speclog ; \
-	(head -n $$cl $(SPECFILE) ; echo "$$DATELINE" ; make --quiet rpmlog 2>/dev/null ; echo ""; cat speclog) > $(SPECFILE).new ; \
-	mv $(SPECFILE).new $(SPECFILE) ; rm -f speclog ; \
-	sed -i "s/Version: $(VERSION)/Version: $$NEWVERSION/" $(SPECFILE) ; \
-	sed -i "s/version='$(VERSION)'/version='$$NEWVERSION'/" setup.py ; \
-	sed -i "s/__version__ = '$(VERSION)'/__version__ = '$$NEWVERSION'/" blivet/__init__.py ; \
+	@opts="-n $(PKGNAME) -v $(VERSION) -r $(RELEASE)" ; \
+	if [ ! -z "$(IGNORE)" ]; then \
+		opts="$${opts} -i $(IGNORE)" ; \
+	fi ; \
+	if [ ! -z "$(MAP)" ]; then \
+		opts="$${opts} -m $(MAP)" ; \
+	fi ; \
+	if [ ! -z "$(BZDEBUG)" ]; then \
+		opts="$${opts} -d" ; \
+	fi ; \
+	( scripts/makebumpver $${opts} ) || exit 1 ; \
 	make -C po $(PKGNAME).pot ; \
 	tx push $(TX_PUSH_ARGS)
 
diff --git a/scripts/makebumpver b/scripts/makebumpver
new file mode 100755
index 0000000..f815143
--- /dev/null
+++ b/scripts/makebumpver
@@ -0,0 +1,478 @@
+#!/usr/bin/python
+#
+# makebumpver - Increment version number and add in RPM spec file changelog
+#               block.  Ensures rhel*-branch commits reference RHEL bugs.
+#
+# Copyright (C) 2009-2013  Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Cantrell <dcantrell at redhat.com>
+
+import logging
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger("bugzilla")
+log.setLevel(logging.INFO)
+
+import bugzilla
+import datetime
+import getopt
+import getpass
+import os
+import re
+import subprocess
+import sys
+import textwrap
+
+class MakeBumpVer:
+    def __init__(self, *args, **kwargs):
+        log.debug("%s", kwargs)
+        self.bzserver = 'bugzilla.redhat.com'
+        self.bzurl = "https://%s/xmlrpc.cgi" % self.bzserver
+        self.username = None
+        self.password = None
+        self.bz = None
+
+        authfile = os.path.realpath(os.getenv('HOME') + '/.rhbzauth')
+        if os.path.isfile(authfile):
+            f = open(authfile, 'r')
+            lines = map(lambda x: x.strip(), f.readlines())
+            f.close()
+
+            for line in lines:
+                if line.startswith('RHBZ_USER='):
+                    self.username = line[10:].strip('"\'')
+                elif line.startswith('RHBZ_PASSWORD='):
+                    self.password = line[14:].strip('"\'')
+
+        self.gituser = self._gitConfig('user.name')
+        self.gitemail = self._gitConfig('user.email')
+
+        self.name = kwargs.get('name')
+        self.fixedin_name = kwargs.get('fixedin', self.name)
+        self.version = kwargs.get('version')
+        self.release = kwargs.get('release')
+        self.ignore = kwargs.get('ignore')
+
+        self.bugmap = {}
+        bugmap = kwargs.get('bugmap')
+        if bugmap and bugmap != '':
+            maps = bugmap.split(',')
+            for mapping in maps:
+                bugs = mapping.split('=')
+                if len(bugs) == 2:
+                    self.bugmap[bugs[0]] = bugs[1]
+
+        self.configure = kwargs.get('configure')
+        self.spec = kwargs.get('spec')
+        self.version_files = kwargs.get('version_files')
+
+    def _gitConfig(self, field):
+        proc = subprocess.Popen(['git', 'config', field],
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE).communicate()
+        return proc[0].strip('\n')
+
+    def _incrementVersion(self):
+        fields = self.version.split('.')
+        fields[-1] = str(int(fields[-1]) + 1)
+        new = ".".join(fields)
+        return new
+
+    def _isRHEL(self):
+        proc = subprocess.Popen(['git', 'branch'],
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE).communicate()
+        lines = filter(lambda x: x.startswith('*'),
+                       proc[0].strip('\n').split('\n'))
+
+        if lines == [] or len(lines) > 1:
+            return False
+
+        fields = lines[0].split(' ')
+
+        if len(fields) == 2 and fields[1].startswith('rhel'):
+            return True
+        else:
+            return False
+
+    def _getCommitDetail(self, commit, field):
+        proc = subprocess.Popen(['git', 'log', '-1',
+                                 "--pretty=format:%s" % field, commit],
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE).communicate()
+
+        ret = proc[0].strip('\n').split('\n')
+
+        if len(ret) == 1 and ret[0].find('@') != -1:
+            ret = ret[0].split('@')[0]
+        elif len(ret) == 1:
+            ret = ret[0]
+        else:
+            ret = filter(lambda x: x != '', ret)
+
+        return ret
+
+    def _queryBug(self, bug):
+        if not self.bz:
+            sys.stdout.write("Connecting to %s...\n" % self.bzserver)
+
+            if not self.username:
+                sys.stdout.write('Username: ')
+                self.username = sys.stdin.readline()
+                self.username = self.username.strip()
+
+            if not self.password:
+                self.password = getpass.getpass()
+
+            bzclass = bugzilla.getBugzillaClassForURL(self.bzurl)
+            self.bz = bzclass(url=self.bzurl)
+            print
+
+        if not self.bz.logged_in:
+            rc = self.bz.login(self.username, self.password)
+            log.debug("login rc = %s", rc)
+
+        bugs = self.bz.query({'bug_id': bug})
+        log.debug("bugs = %s", bugs)
+
+        if len(bugs) != 1:
+            return None
+        else:
+            return bugs[0]
+
+    def _isRHELBug(self, bug, commit, summary):
+        bzentry = self._queryBug(bug)
+
+        if not bzentry:
+            print "*** Bugzilla query for %s failed.\n" % bug
+            return False
+
+        if bzentry.product.startswith('Red Hat Enterprise Linux'):
+            return True
+        else:
+            print "*** Bug %s is not a RHEL bug." % bug
+            print "***     Commit: %s" % commit
+            print "***     %s\n" % summary
+            return False
+
+    def _isRHELBugInCorrectState(self, bug, commit, summary):
+        bzentry = self._queryBug(bug)
+
+        if not bzentry:
+            print "*** Bugzilla query for %s failed.\n" % bug
+            return False
+
+        if bzentry.bug_status in ['MODIFIED', 'ON_QA']:
+            return True
+        else:
+            print "*** Bug %s is not in MODIFIED or ON_QA." % bug
+            print "***     Commit: %s" % commit
+            print "***     %s\n" % summary
+            return False
+
+    def _isRHELBugFixedInVersion(self, bug, commit, summary, fixedIn):
+        bzentry = self._queryBug(bug)
+
+        if not bzentry:
+            print "*** Bugzilla query for %s failed.\n" % bug
+            return False
+
+        if bzentry.fixed_in == fixedIn:
+            return True
+        else:
+            print "*** Bug %s does not have correct Fixed In Version." % bug
+            print "***     Found:     %s" % bzentry.fixed_in
+            print "***     Expected:  %s" % fixedIn
+            print "***     Commit:    %s" % commit
+            print "***     %s\n" % summary
+            return False
+
+    def _rpmLog(self, fixedIn):
+        git_range = "%s-%s-%s.." % (self.name, self.version, self.release)
+        proc = subprocess.Popen(['git', 'log', '--pretty=oneline', git_range],
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE).communicate()
+        lines = filter(lambda x: x.find('l10n: ') != 41 and \
+                                 x.find('Merge commit') != 41 and \
+                                 x.find('Merge branch') != 41,
+                       proc[0].strip('\n').split('\n'))
+
+        if self.ignore and self.ignore != '':
+            for commit in self.ignore.split(','):
+                lines = filter(lambda x: not x.startswith(commit), lines)
+
+        rpm_log = []
+        bad = False
+        rhel = self._isRHEL()
+
+        for line in lines:
+            if not line:
+                continue
+            fields = line.split(' ')
+            commit = fields[0]
+
+            summary = self._getCommitDetail(commit, "%s")
+            body = self._getCommitDetail(commit, "%b")
+            if type(body) is not list:
+                body = [body]
+            author = self._getCommitDetail(commit, "%aE")
+
+            if rhel:
+                rhbz = set()
+
+                # look for a bug in the summary line, validate if found
+                m = re.search(r"\(#\d+(\,.*)*\)", summary)
+                if m:
+                    fullbug = summary[m.start():m.end()]
+                    bugstr = summary[m.start()+2:m.end()-1]
+
+                    bug = ''
+                    for c in bugstr:
+                        if c.isdigit():
+                            bug += c
+                        else:
+                            break
+
+                    if len(bugstr) > len(bug):
+                        tmp = bugstr[len(bug):]
+
+                        for c in tmp:
+                            if not c.isalpha():
+                                tmp = tmp[1:]
+                            else:
+                                break
+
+                        if len(tmp) > 0:
+                            author = tmp
+
+                    ckbug = self.bugmap.get(bug, bug)
+                    valid = self._isRHELBug(ckbug, commit, summary)
+
+                    if valid:
+                        summary = summary.replace(fullbug, "(%s)" % author)
+                        rhbz.add("Resolves: rhbz#%s" % ckbug)
+
+                        if not self._isRHELBugInCorrectState(ckbug, commit,
+                                                             summary):
+                            bad = True
+
+                        if not self._isRHELBugFixedInVersion(ckbug, commit,
+                                                             summary, fixedIn):
+                            bad = True
+                    else:
+                        bad = True
+                else:
+                    summary = summary.strip()
+                    summary += " (%s)" % author
+
+                for bodyline in body:
+                    m = re.match(r"^(Resolves|Related|Conflicts):\ +rhbz#\d+.*$",
+                                 bodyline)
+                    if not m:
+                        continue
+
+                    actionre = re.search("(Resolves|Related|Conflicts)",
+                                         bodyline)
+                    bugre = re.search(r"\d+", bodyline)
+                    if actionre and bugre:
+                        action = actionre.group()
+                        bug = bugre.group()
+                        ckbug = self.bugmap.get(bug, bug)
+                        valid = self._isRHELBug(ckbug, commit, summary)
+
+                        if valid:
+                            rhbz.add("%s: rhbz#%s" % (action, ckbug))
+                        else:
+                            bad = True
+
+                        if valid and action == 'Resolves' and \
+                           (not self._isRHELBugInCorrectState(ckbug, commit,
+                                                              summary) or \
+                            not self._isRHELBugFixedInVersion(ckbug, commit,
+                                                              summary,
+                                                              fixedIn)):
+                            bad = True
+
+                if len(rhbz) == 0:
+                    print "*** No bugs referenced in commit %s\n" % commit
+                    bad = True
+
+                rpm_log.append((summary.strip(), list(rhbz)))
+            else:
+                rpm_log.append(("%s (%s)" % (summary.strip(), author), None))
+
+        if bad:
+            sys.exit(1)
+
+        return rpm_log
+
+    def _replaceString(self, lines, search, replace):
+        """ find first occurrence of search and replace it with replace
+        """
+        for i, l in enumerate(lines):
+            if l.find(search) > -1:
+                break
+        else:
+            print "!!! Failed to replace '%s' with '%s'" % (search, replace)
+            sys.exit(1)
+
+        lines[i] = re.sub(search, replace, lines[i])
+
+    def _writeNewSpec(self, newVersion, rpmlog):
+        f = open(self.spec, 'r')
+        l = f.readlines()
+        f.close()
+
+        self._replaceString(l, "Version: %s\n" % (self.version),
+                               "Version: %s\n" % (newVersion))
+
+        i = l.index('%changelog\n')
+        top = l[:i]
+        bottom = l[i+1:]
+
+        f = open(self.spec, 'w')
+        f.writelines(top)
+
+        f.write("%changelog\n")
+        today = datetime.date.today()
+        stamp = today.strftime("%a %b %d %Y")
+        f.write("* %s %s <%s> - %s-%s\n" % (stamp, self.gituser, self.gitemail,
+                                            newVersion, self.release))
+
+        for msg, rhbz in rpmlog:
+            msg = re.sub('(?<!%)%%(?!%)|(?<!%%)%(?!%%)', '%%', msg)
+            sublines = textwrap.wrap(msg, 77)
+            f.write("- %s\n" % sublines[0])
+
+            if len(sublines) > 1:
+                for subline in sublines[1:]:
+                    f.write("  %s\n" % subline)
+
+            if rhbz:
+                for entry in rhbz:
+                    f.write("  %s\n" % entry)
+
+        f.write("\n")
+        f.writelines(bottom)
+        f.close()
+
+    def _writeNewVersionFile(self, filename, template, newVersion):
+        """ Replace a version string in a file, using template to match
+            string to replace and to create new string.
+        """
+        f = open(filename, 'r')
+        l = f.readlines()
+        f.close()
+
+        self._replaceString(l, template % self.version,
+                               template % newVersion)
+
+        f = open(filename, 'w')
+        f.writelines(l)
+        f.close()
+
+    def run(self):
+        newVersion = self._incrementVersion()
+        fixedIn = "%s-%s-%s" % (self.fixedin_name, newVersion, self.release)
+        rpmlog = self._rpmLog(fixedIn)
+
+        self._writeNewSpec(newVersion, rpmlog)
+        for filename, template in self.version_files:
+            self._writeNewVersionFile(filename, template, newVersion)
+
+def usage(cmd):
+    sys.stdout.write("Usage: %s [OPTION]...\n" % (cmd,))
+    sys.stdout.write("Options:\n")
+    sys.stdout.write("    -n, --name       Package name.\n")
+    sys.stdout.write("    -v, --version    Current package version number.\n")
+    sys.stdout.write("    -r, --release    Package release number.\n")
+    sys.stdout.write("    -i, --ignore     Comma separated list of git commits to ignore.\n")
+    sys.stdout.write("    -m, --map        Comma separated list of FEDORA_BZ=RHEL_BZ mappings.\n")
+    sys.stdout.write("    -d, --debug      Turn on debug logging to stdout\n")
+    sys.stdout.write("\nThe -i switch is intended for use with utility commits that we do not need to\n")
+    sys.stdout.write("reference in the spec file changelog.  The -m switch is used to map a Fedora\n")
+    sys.stdout.write("BZ number to a RHEL BZ number for the spec file changelog.  Use -m if you have\n")
+    sys.stdout.write("a commit that needs to reference a RHEL bug and have cloned the bug, but the\n")
+    sys.stdout.write("original commit was already pushed to the central repo.\n")
+
+def main(argv):
+    prog = os.path.basename(sys.argv[0])
+    cwd = os.getcwd()
+    spec = os.path.realpath(cwd + '/python-blivet.spec')
+    name, version, release = None, None, None
+    ignore, bugmap = None, None
+    show_help, unknown = False, False
+    opts = []
+
+    try:
+        opts, _args = getopt.getopt(sys.argv[1:], 'n:v:r:i:m:d?',
+                                   ['name=', 'version=', 'release=',
+                                    'ignore=', 'map=', 'debug', 'help'])
+    except getopt.GetoptError:
+        show_help = True
+
+    for o, a in opts:
+        if o in ('-n', '--name'):
+            name = a
+        elif o in ('-v', '--version'):
+            version = a
+        elif o in ('-r', '--release'):
+            release = a
+        elif o in ('-i', '--ignore'):
+            ignore = a
+        elif o in ('-m', '--map'):
+            bugmap = a
+        elif o in ('-d', '--debug'):
+            log.setLevel(logging.DEBUG)
+        elif o in ('-?', '--help'):
+            show_help = True
+        else:
+            unknown = True
+
+    if show_help:
+        usage(prog)
+        sys.exit(0)
+    elif unknown:
+        sys.stderr.write("%s: extra operand `%s'" % (prog, sys.argv[1],))
+        sys.stderr.write("Try `%s --help' for more information." % (prog,))
+        sys.exit(1)
+
+    if not name:
+        sys.stderr.write("Missing required -n/--name option\n")
+        sys.exit(1)
+
+    if not version:
+        sys.stderr.write("Missing required -v/--version option\n")
+        sys.exit(1)
+
+    if not release:
+        sys.stderr.write("Missing required -r/--release option\n")
+        sys.exit(1)
+
+    if not os.path.isfile(spec):
+        sys.stderr.write("You must be at the top level of the blivet source tree.\n")
+        sys.exit(1)
+
+    # Files to replace version strings in
+    version_files = [(os.path.realpath(cwd+"/setup.py"), "version='%s'"),
+                     (os.path.realpath(cwd+"/blivet/__init__.py"), "__version__ = '%s'")]
+
+    mbv = MakeBumpVer(name=name, version=version, release=release,
+                      ignore=ignore, bugmap=bugmap, spec=spec,
+                      version_files=version_files, fixedin="python-"+name)
+    mbv.run()
+
+if __name__ == "__main__":
+    main(sys.argv)
-- 
1.8.3.1



More information about the anaconda-patches mailing list