diff --git a/0001-Working-on-both-2.7-and-3.4.patch b/0001-Working-on-both-2.7-and-3.4.patch new file mode 100644 index 0000000..422fb7f --- /dev/null +++ b/0001-Working-on-both-2.7-and-3.4.patch @@ -0,0 +1,408 @@ +From ae019ce73766ed38f74567459ae5cd74beb1c56f Mon Sep 17 00:00:00 2001 +From: Alex Lord +Date: Thu, 23 Jul 2015 21:20:05 -0700 +Subject: [PATCH] Working on both 2.7 and 3.4 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Matěj Cepl +--- + bzlib/bug.py | 7 ++-- + bzlib/bugzilla.py | 16 +++++--- + bzlib/command.py | 90 +++++++++++++++++++++--------------------- + bzlib/config.py | 5 ++- + bzlib/test_bugzilla.py | 10 ++--- + bzlib/ui.py | 2 +- + 6 files changed, 69 insertions(+), 61 deletions(-) + +diff --git a/bzlib/bug.py b/bzlib/bug.py +index 6586c16..ebcdba0 100644 +--- a/bzlib/bug.py ++++ b/bzlib/bug.py +@@ -101,7 +101,7 @@ class Bug(object): + kwargs[_in] = list(all_values - frozenset(kwargs[_not_in])) + del kwargs[_not_in] # delete the _not_in + +- unknowns = kwargs.viewkeys() - fields ++ unknowns = set(kwargs.keys()) - fields + if unknowns: + # unknown arguments + raise TypeError( +@@ -252,14 +252,13 @@ class Bug(object): + 'comment', + 'version', 'priority', + ]) +- unknowns = kwargs.viewkeys() - fields ++ unknowns = kwargs.keys() - fields + if unknowns: + # unknown arguments + raise TypeError('Invalid keyword arguments: {}.'.format(unknowns)) + + # filter out ``None``s +- kwargs = {k: v for k, v in kwargs.viewitems() if v is not None} +- ++ kwargs = {k: v for k, v in kwargs.keys() if v is not None} + # format deadline (YYYY-MM-DD) + if 'deadline' in kwargs: + date = kwargs['deadline'] +diff --git a/bzlib/bugzilla.py b/bzlib/bugzilla.py +index 14439bf..63822c2 100644 +--- a/bzlib/bugzilla.py ++++ b/bzlib/bugzilla.py +@@ -15,8 +15,14 @@ + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + +-import urlparse +-import xmlrpclib ++try: ++ import urllib.parse as urlparse ++except ImportError: ++ import urlparse ++try: ++ import xmlrpclib ++except ImportError: ++ import xmlrpc.client as xmlrpclib + + from . import bug + from . import config +@@ -58,7 +64,7 @@ class Bugzilla(object): + required, but may be ``None``. + """ + mandatory_args = set(['server', 'url', 'user', 'password']) +- mandatory_args -= kwargs.viewkeys() ++ mandatory_args -= set(kwargs.keys()) + if mandatory_args: + raise TypeError('Mandatory args ({}) not supplied'.format( + ', '.join("'{}'".format(arg) for arg in mandatory_args))) +@@ -81,8 +87,8 @@ class Bugzilla(object): + _server[k] = kwargs[k] + + mandatory_kwargs = {'url'} +- if mandatory_kwargs - _server.viewkeys(): +- missing_args = ', '.join(mandatory_kwargs - _server.viewkeys()) ++ if mandatory_kwargs - set(_server.keys()): ++ missing_args = ', '.join(mandatory_kwargs - _server.items()) + raise UserWarning("missing args: {}".format(missing_args)) + + return cls(**_server) +diff --git a/bzlib/command.py b/bzlib/command.py +index 59d591d..6c42e8a 100644 +--- a/bzlib/command.py ++++ b/bzlib/command.py +@@ -184,7 +184,7 @@ class Config(Command): + if args.list: + for section in conf.sections(): + for option, value in conf.items(section): +- print '{}={}'.format('.'.join((section, option)), value) ++ print('{}={}'.format('.'.join((section, option)), value)) + elif not args.name: + raise UserWarning('No configuration option given.') + else: +@@ -209,10 +209,10 @@ class Config(Command): + if conf.has_option(section, option) else None + conf.set(section, option, args.value) + conf.write() +- print '{}: {} => {}'.format(args.name, oldvalue, args.value) ++ print('{}: {} => {}'.format(args.name, oldvalue, args.value)) + else: + curvalue = conf.get(section, option) +- print '{}: {}'.format(args.name, curvalue) ++ print('{}: {}'.format(args.name, curvalue)) + + + class Help(Command): +@@ -227,12 +227,12 @@ class Help(Command): + self._parser.parse_args(['--help']) + else: + if self._args.subcommand in self._aliases: +- print "'{}': alias for {}".format( ++ print("'{}': alias for {}".format( + self._args.subcommand, + self._aliases[self._args.subcommand] +- ) ++ )) + elif self._args.subcommand not in self._commands: +- print "unknown subcommand: '{}'".format(self._args.subcommand) ++ print("unknown subcommand: '{}'".format(self._args.subcommand)) + else: + self._parser.parse_args([self._args.subcommand, '--help']) + +@@ -288,12 +288,12 @@ class Block(BugzillaCommand): + else: + # show blocked bugs + for bug in bugs: +- print 'Bug {}:'.format(bug.bugno) ++ print('Bug {}:'.format(bug.bugno)) + if bug.data['blocks']: +- print ' Blocked bugs: {}'.format( +- ', '.join(map(str, bug.data['blocks']))) ++ print(' Blocked bugs: {}'.format( ++ ', '.join(map(str, bug.data['blocks'])))) + else: +- print ' No blocked bugs' ++ print(' No blocked bugs') + + + @with_add_remove('given users', 'CC List', metavar='USER') +@@ -326,12 +326,12 @@ class CC(BugzillaCommand): + else: + # show CC List + for bug in bugs: +- print 'Bug {}:'.format(bug.bugno) ++ print('Bug {}:'.format(bug.bugno)) + if bug.data['cc']: +- print ' CC List: {}'.format( +- ', '.join(map(str, bug.data['cc']))) ++ print(' CC List: {}'.format( ++ ', '.join(map(str, bug.data['cc'])))) + else: +- print ' 0 users' ++ print(' 0 users') + + + @with_bugs +@@ -390,7 +390,7 @@ class Comment(BugzillaCommand): + and not (args.which and n not in args.which) + ) + ) +- print '\n'.join(map(cmtfmt, args.bugs)) ++ print('\n'.join(map(cmtfmt, args.bugs))) + + + @with_set('given bugs', 'depdendencies', metavar='BUG', type=int) +@@ -418,12 +418,12 @@ class Depend(BugzillaCommand): + else: + # show dependencies + for bug in bugs: +- print 'Bug {}:'.format(bug.bugno) ++ print('Bug {}:'.format(bug.bugno)) + if bug.data['depends_on']: +- print ' Dependencies: {}'.format( +- ', '.join(map(str, bug.data['depends_on']))) ++ print(' Dependencies: {}'.format( ++ ', '.join(map(str, bug.data['depends_on'])))) + else: +- print ' No dependencies' ++ print(' No dependencies') + + + @with_bugs +@@ -438,7 +438,7 @@ class Desc(BugzillaCommand): + bug, + self.formatstring.format(**desc) + ) +- print '\n'.join(_descfmt(bug) for bug in self._args.bugs) ++ print('\n'.join(_descfmt(bug) for bug in self._args.bugs)) + + + @with_bugs +@@ -446,7 +446,7 @@ class Dump(BugzillaCommand): + """Print internal representation of bug data.""" + def __call__(self): + bugs = (self.bz.bug(x) for x in self._args.bugs) +- print '\n'.join(str((x.data, x.comments)) for x in bugs) ++ print('\n'.join(str((x.data, x.comments)) for x in bugs)) + + + @with_bugs +@@ -480,18 +480,18 @@ class Fields(BugzillaCommand): + sorted(field['values'], None, keyfn), + keyfn + ) +- print field['name'], ':' ++ print("{} :".format(field['name'])) + for key, group in groups: + keyfn = lambda x: int(x.get('sortkey', -1)) + values = sorted(group, None, keyfn) + if key: +- print ' {}: {}'.format( ++ print(' {}: {}'.format( + ','.join(key), + ','.join(map(lambda x: x['name'], values)) +- ) ++ )) + else: + value_names = (v.get('name') for v in values) +- print ' ', ','.join(s for s in value_names if s) ++ print(' ', ','.join(s for s in value_names if s)) + + + def _format_history(history): +@@ -518,10 +518,10 @@ class History(BugzillaCommand): + _history[0][0] = h['who'] + _history[0][1] = h['when'] + history.extend(_history) +- print 'History of Bug {}:'.format(bug.bugno) ++ print('History of Bug {}:'.format(bug.bugno)) + for line in _format_history(history): +- print ' ' + line +- print ++ print(' ' + line) ++ print() + + + @with_bugs +@@ -531,12 +531,12 @@ class Info(BugzillaCommand): + args = self._args + fields = config.show_fields + for bug in map(self.bz.bug, args.bugs): +- print 'Bug {}:'.format(bug.bugno) ++ print('Bug {}:'.format(bug.bugno)) + fields = config.show_fields & bug.data.viewkeys() + width = max(map(len, fields)) - min(map(len, fields)) + 2 + for field in fields: +- print ' {:{}} {}'.format(field + ':', width, bug.data[field]) +- print ++ print(' {:{}} {}'.format(field + ':', width, bug.data[field])) ++ print() + + + @with_bugs +@@ -547,9 +547,9 @@ class List(BugzillaCommand): + lens = [len(str(x)) for x in args.bugs] + width = max(lens) - min(lens) + 2 + for bug in map(self.bz.bug, args.bugs): +- print 'Bug {:{}} {}'.format( ++ print('Bug {:{}} {}'.format( + str(bug.bugno) + ':', width, bug.data['summary'] +- ) ++ )) + + + class New(BugzillaCommand): +@@ -672,9 +672,9 @@ class Products(BugzillaCommand): + products = self.bz.get_products() + width = max(map(lambda x: len(x['name']), products)) + 1 + for product in products: +- print '{:{}} {}'.format( ++ print('{:{}} {}'.format( + product['name'] + ':', width, product['description'] +- ) ++ )) + + + @with_bugs +@@ -815,12 +815,12 @@ class Search(BugzillaCommand): + lens = [len(str(b.bugno)) for b in bugs] + + for _bug in bugs: +- print 'Bug {:{}} {}'.format( ++ print('Bug {:{}} {}'.format( + str(_bug.bugno) + ':', max(lens) - min(lens) + 2, + _bug.data['summary'] +- ) ++ )) + n = len(bugs) +- print '=> {} bug{} matched criteria'.format(n, 's' if n else '') ++ print('=> {} bug{} matched criteria'.format(n, 's' if n else '')) + + + @with_bugs +@@ -860,13 +860,13 @@ class Time(BugzillaCommand): + # be absent from bug data. first check that they're there. + time_fields = ('deadline', 'estimated_time', 'remaining_time') + if not all(x in bug.data for x in time_fields): +- print 'User is not in the time-tracking group.' ++ print('User is not in the time-tracking group.') + return +- print 'Bug {}:'.format(bug.bugno) +- print ' Estimated time: {}'.format(bug.data['estimated_time']) +- print ' Remaining time: {}'.format(bug.data['remaining_time']) +- print ' Deadline: {}'.format(bug.data['deadline']) +- print ' Time worked: {}'.format(bug.actual_time()) ++ print('Bug {}:'.format(bug.bugno)) ++ print(' Estimated time: {}'.format(bug.data['estimated_time'])) ++ print(' Remaining time: {}'.format(bug.data['remaining_time'])) ++ print(' Deadline: {}'.format(bug.data['deadline'])) ++ print(' Time worked: {}'.format(bug.actual_time())) + + + # the list got too long; metaprogram it ^_^ +@@ -874,5 +874,5 @@ commands = filter( + lambda x: type(x) == type # is a class \ + and issubclass(x, Command) # is a Command \ + and x not in [Command, BugzillaCommand], # not abstract +- locals().viewvalues() ++ locals().items() + ) +diff --git a/bzlib/config.py b/bzlib/config.py +index f363eb7..6548075 100644 +--- a/bzlib/config.py ++++ b/bzlib/config.py +@@ -14,7 +14,10 @@ + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + +-import ConfigParser ++try: ++ import ConfigParser ++except ImportError: ++ import configparser as ConfigParser + import os.path + import re + +diff --git a/bzlib/test_bugzilla.py b/bzlib/test_bugzilla.py +index d7238b5..7f6054e 100644 +--- a/bzlib/test_bugzilla.py ++++ b/bzlib/test_bugzilla.py +@@ -41,7 +41,7 @@ class URLTestCase(unittest.TestCase): + with self.assertRaises(bugzilla.URLError) as cm: + bugzilla.Bugzilla('bogus://bugzilla.example.com/', 'u', 'p') + self.assertEqual( +- cm.exception.message, ++ str(cm.exception), + "URL scheme 'bogus' not supported." + ) + +@@ -54,7 +54,7 @@ class URLTestCase(unittest.TestCase): + with self.assertRaises(bugzilla.URLError) as cm: + bugzilla.Bugzilla(url, 'u', 'p') + self.assertEqual( +- cm.exception.message, ++ str(cm.exception), + 'URL {!r} is not valid.'.format(url) + ) + +@@ -63,21 +63,21 @@ class URLTestCase(unittest.TestCase): + with self.assertRaises(bugzilla.URLError) as cm: + bugzilla.Bugzilla('http://bugzilla.example.com/;p', 'u', 'p') + self.assertEqual( +- cm.exception.message, ++ str(cm.exception), + 'URL params, queries and fragments not supported.' + ) + # query + with self.assertRaises(bugzilla.URLError) as cm: + bugzilla.Bugzilla('http://bugzilla.example.com/?q', 'u', 'p') + self.assertEqual( +- cm.exception.message, ++ str(cm.exception), + 'URL params, queries and fragments not supported.' + ) + # fragment + with self.assertRaises(bugzilla.URLError) as cm: + bugzilla.Bugzilla('http://bugzilla.example.com/#f', 'u', 'p') + self.assertEqual( +- cm.exception.message, ++ str(cm.exception), + 'URL params, queries and fragments not supported.' + ) + +diff --git a/bzlib/ui.py b/bzlib/ui.py +index 87895f6..0ec0360 100644 +--- a/bzlib/ui.py ++++ b/bzlib/ui.py +@@ -168,7 +168,7 @@ def filter_user(string, bugzilla=None, default=None): + + class UI(object): + def show(self, msg): +- print msg ++ print(msg) + + def bail(self, msg=None): + """Exit uncleanly with an optional message""" +-- +2.17.0 + diff --git a/no-bzrlib-py3k.patch b/no-bzrlib-py3k.patch new file mode 100644 index 0000000..27694fe --- /dev/null +++ b/no-bzrlib-py3k.patch @@ -0,0 +1,100 @@ +--- a/plugin-bzr/__init__.py ++++ b/plugin-bzr/__init__.py +@@ -91,34 +91,36 @@ a bug URL and status in the revision met + + bzr commit -m 'fix bug 123' --fixes example:123 + """ ++import sys + +-import bzrlib.api +-import bzrlib.commands +-import bzrlib.trace +- +-import bzlib +- +-from . import hooks +- +-# plugin setup +-version_info = bzlib.version_info +- +-COMPATIBLE_BZR_VERSIONS = [ +- (2, 0, 0), +- (2, 1, 0), +- (2, 2, 0), +- (2, 3, 0), +-] +- +-bzrlib.api.require_any_api(bzrlib, COMPATIBLE_BZR_VERSIONS) +- +-if __name__ != 'bzrlib.plugins.bugzillatools': +- bzrlib.trace.warning( +- 'Not running as bzrlib.plugins.bugzillatools; things may break.') +- +-# install the get_command hook +-bzrlib.commands.Command.hooks.install_named_hook( +- 'get_command', +- hooks.get_command_hook, +- 'bugzilla plugin - extend cmd_commit' +-) ++if sys.version_info[0] == 2: ++ import bzrlib.api ++ import bzrlib.commands ++ import bzrlib.trace ++ ++ import bzlib ++ ++ from . import hooks ++ ++ # plugin setup ++ version_info = bzlib.version_info ++ ++ COMPATIBLE_BZR_VERSIONS = [ ++ (2, 0, 0), ++ (2, 1, 0), ++ (2, 2, 0), ++ (2, 3, 0), ++ ] ++ ++ bzrlib.api.require_any_api(bzrlib, COMPATIBLE_BZR_VERSIONS) ++ ++ if __name__ != 'bzrlib.plugins.bugzillatools': ++ bzrlib.trace.warning( ++ 'Not running as bzrlib.plugins.bugzillatools; things may break.') ++ ++ # install the get_command hook ++ bzrlib.commands.Command.hooks.install_named_hook( ++ 'get_command', ++ hooks.get_command_hook, ++ 'bugzilla plugin - extend cmd_commit' ++ ) +--- a/bzlib/config.py ++++ b/bzlib/config.py +@@ -40,7 +40,7 @@ def check_section(section): + raise ConfigError('invalid section: {}'.format(section)) + + +-class Config(ConfigParser.SafeConfigParser): ++class Config(ConfigParser.ConfigParser): + _instances = {} + + @classmethod +@@ -52,16 +52,16 @@ class Config(ConfigParser.SafeConfigPars + + def __init__(self, path): + path = os.path.expanduser(path) +- ConfigParser.SafeConfigParser.__init__(self) ++ ConfigParser.ConfigParser.__init__(self) + self._path = path + self.read(self._path) + + def write(self): + with open(self._path, 'w') as fp: +- ConfigParser.SafeConfigParser.write(self, fp) ++ ConfigParser.ConfigParser.write(self, fp) + + def add_section(self, section): +- ConfigParser.SafeConfigParser.add_section(self, check_section(section)) ++ ConfigParser.ConfigParser.add_section(self, check_section(section)) + + + NoSectionError = ConfigParser.NoSectionError diff --git a/python-bugzillatools.changes b/python-bugzillatools.changes index f92e095..74a729b 100644 --- a/python-bugzillatools.changes +++ b/python-bugzillatools.changes @@ -1,3 +1,20 @@ +------------------------------------------------------------------- +Fri May 25 14:09:50 UTC 2018 - mcepl@suse.com + +- Add upstream patch for porting to Python 3 (and fix some remaining + bugs) +- Adjust SPEC file for adding testing + +------------------------------------------------------------------- +Wed May 23 13:23:07 UTC 2018 - mcepl@suse.com + +- Fix SPEC file with spec-cleaner + +------------------------------------------------------------------- +Thu Aug 24 13:33:33 UTC 2017 - jmatejek@suse.com + +- singlespec auto-conversion + ------------------------------------------------------------------- Tue Apr 28 20:56:37 UTC 2015 - benoit.monin@gmx.fr @@ -39,3 +56,4 @@ Tue Oct 8 14:04:03 UTC 2013 - speilicke@suse.com - Initial version + diff --git a/python-bugzillatools.spec b/python-bugzillatools.spec index 2c4cd60..0ae5570 100644 --- a/python-bugzillatools.spec +++ b/python-bugzillatools.spec @@ -1,7 +1,7 @@ # # spec file for package python-bugzillatools # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,24 +16,28 @@ # +%{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-bugzillatools Version: 0.5.5 Release: 0 Summary: Bugzilla CLI client, XML-RPC binding and VCS plugins -License: GPL-3.0+ +License: GPL-3.0-or-later Group: Development/Languages/Python -Url: https://github.com/frasertweedale/bugzillatools -Source: https://pypi.python.org/packages/source/b/bugzillatools/bugzillatools-%{version}.tar.gz -BuildRequires: python-devel +URL: https://github.com/frasertweedale/bugzillatools +Source: https://files.pythonhosted.org/packages/source/b/bugzillatools/bugzillatools-%{version}.tar.gz +# Port to Python3 from yet unreleased commits in the upstream repo +Patch0: 0001-Working-on-both-2.7-and-3.4.patch +# Some more py3k fixes +# https://github.com/rawrgulmuffins/bugzillatools/pull/26 +Patch1: no-bzrlib-py3k.patch +BuildRequires: %{python_module devel} +BuildRequires: fdupes +BuildRequires: python-rpm-macros #Recommends: bzr # File conflict for /usr/bin/bugzilla: Conflicts: python-bugzilla -BuildRoot: %{_tmppath}/%{name}-%{version}-build -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 -%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} -%else BuildArch: noarch -%endif +%python_subpackages %description Provides a CLI program and Python library for interacting with the @@ -41,21 +45,24 @@ Bugzilla_ bug tracking system, and plugins for version control systems that enable interaction with Bugzilla installations. %prep -%setup -q -n bugzillatools-%{version} +%autosetup -p1 -n bugzillatools-%{version} sed -i "/.bugzillarc.sample/d" setup.py %build -python setup.py build +%python_build %install -python setup.py install --prefix=%{_prefix} --root=%{buildroot} --install-data=%{_docdir}/%{name} +%python_install +%python_expand %fdupes %{buildroot}%{$python_sitelib} -%files -%defattr(-,root,root,-) +%check +%python_exec -munittest discover -v + +%files %{python_files} %doc CHANGES README.rst gpl-3.0.txt -%{_bindir}/bugzilla +%python3_only %{_bindir}/bugzilla %{python_sitelib}/bzlib %{python_sitelib}/bzrlib -%{python_sitelib}/bugzillatools-%{version}-py%{py_ver}.egg-info +%{python_sitelib}/bugzillatools-%{version}-py%{python_version}.egg-info %changelog