Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package osc for openSUSE:Factory checked in 
at 2022-04-27 21:41:20
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/osc (Old)
 and      /work/SRC/openSUSE:Factory/.osc.new.1538 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "osc"

Wed Apr 27 21:41:20 2022 rev:160 rq:973067 version:0.177.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/osc/osc.changes  2022-03-02 18:20:48.356655317 
+0100
+++ /work/SRC/openSUSE:Factory/.osc.new.1538/osc.changes        2022-04-27 
21:41:29.989025615 +0200
@@ -1,0 +2,20 @@
+Tue Apr 26 07:53:48 UTC 2022 - Marco Strigl <[email protected]>
+
+- 0.177.0
+  * switch to python3 in osc-wrapper and make python3 explicit
+  * allow formatting of the sccache uri
+  * show repository state and details
+  * a few minor fixes and improvements in credentials handling
+  * order credential managers by priority
+  * kernel keyring is now supported as credential manager
+  * support regex based name filtering in core.get_prj_results()
+  * revision parsing parseRevisionOption(): cleanup and make logic consistent
+  * use sr_ids[0] for superseding (fixes issues with superseding requests
+    containing many packages
+  * download logs and metadata in subdirs named by packages when osc 
getbinaries
+    is issued on project level or in multibuild case
+
+- spec file:
+  * recommed python-keyring-keyutils for new kernel keyring backend 
+
+-------------------------------------------------------------------

Old:
----
  osc-0.176.0.tar.gz

New:
----
  osc-0.177.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ osc.spec ++++++
--- /var/tmp/diff_new_pack.9wgOgC/_old  2022-04-27 21:41:30.845026624 +0200
+++ /var/tmp/diff_new_pack.9wgOgC/_new  2022-04-27 21:41:30.853026633 +0200
@@ -27,7 +27,7 @@
 %define use_python python
 %endif
 
-%define version_unconverted 0.176.0
+%define version_unconverted 0.177.0
 %define osc_plugin_dir %{_prefix}/lib/osc-plugins
 %define macros_file macros.osc
 %if ! %{defined _rpmmacrodir}
@@ -35,7 +35,7 @@
 %endif
 
 Name:           osc
-Version:        0.176.0
+Version:        0.177.0
 Release:        0
 Summary:        Open Build Service Commander
 License:        GPL-2.0-or-later
@@ -98,8 +98,10 @@
 %if 0%{?suse_version} > 1000 || 0%{?mandriva_version} || 0%{?mdkversion} || 
0%{?fedora} >= 29 || 0%{?rhel} >= 8
 %if %{with python3}
 Recommends:     python3-keyring
+Recommends:     python3-keyring-keyutils
 %else
 Recommends:     python-keyring
+Recommends:     python-keyring-keyutils
 %endif
 %endif
 %if 0%{?rhel} && 0%{?rhel} < 6

++++++ PKGBUILD ++++++
--- /var/tmp/diff_new_pack.9wgOgC/_old  2022-04-27 21:41:30.881026666 +0200
+++ /var/tmp/diff_new_pack.9wgOgC/_new  2022-04-27 21:41:30.885026671 +0200
@@ -1,5 +1,5 @@
 pkgname=osc
-pkgver=0.176.0
+pkgver=0.177.0
 pkgrel=0
 pkgdesc="Open Build Service client"
 arch=('x86_64')

++++++ _service ++++++
--- /var/tmp/diff_new_pack.9wgOgC/_old  2022-04-27 21:41:30.901026689 +0200
+++ /var/tmp/diff_new_pack.9wgOgC/_new  2022-04-27 21:41:30.905026694 +0200
@@ -1,7 +1,7 @@
 <services>
   <service name="tar_scm" mode="disabled">
-    <param name="version">0.176.0</param>
-    <param name="revision">0.176.0</param>
+    <param name="version">0.177.0</param>
+    <param name="revision">0.177.0</param>
     <param name="url">https://github.com/openSUSE/osc.git</param>
     <param name="scm">git</param>
   </service>

++++++ debian.changelog ++++++
--- /var/tmp/diff_new_pack.9wgOgC/_old  2022-04-27 21:41:30.965026765 +0200
+++ /var/tmp/diff_new_pack.9wgOgC/_new  2022-04-27 21:41:30.973026774 +0200
@@ -1,4 +1,4 @@
-osc (0.176.0-0) unstable; urgency=low
+osc (0.177.0-0) unstable; urgency=low
   - Update to 0.174.0:
     - fix password deletion via "osc config -d <apiurl> pass"
     - support changing the password store via "osc config <apiurl>

++++++ osc-0.176.0.tar.gz -> osc-0.177.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/NEWS new/osc-0.177.0/NEWS
--- old/osc-0.176.0/NEWS        2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/NEWS        2022-04-26 09:48:53.000000000 +0200
@@ -1,3 +1,17 @@
+0.177.0
+  - switch to python3 in osc-wrapper and make python3 explicit
+  - allow formatting of the sccache uri
+  - show repository state and details
+  - a few minor fixes and improvements in credentials handling
+  - order credential managers by priority
+  - kernel keyring is now supported as credential manager
+  - support regex based name filtering in core.get_prj_results()
+  - revision parsing parseRevisionOption(): cleanup and make logic consistent
+  - use sr_ids[0] for superseding (fixes issues with superseding requests
+    containing many packages
+  - download logs and metadata in subdirs named by packages when osc 
getbinaries
+    is issued on project level or in multibuild case
+
 0.176.0
   - add -F option to osc submitreq
   - add --verbose option to build command
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/babysitter.py 
new/osc-0.177.0/osc/babysitter.py
--- old/osc-0.176.0/osc/babysitter.py   2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/babysitter.py   2022-04-26 09:48:53.000000000 +0200
@@ -151,7 +151,7 @@
             raise
         print(e, file=sys.stderr)
     except (oscerr.ConfigError, oscerr.NoConfigfile) as e:
-        print(e.msg, file=sys.stderr)
+        print(e, file=sys.stderr)
     except configparser.Error as e:
         print(e.message, file=sys.stderr)
     except oscerr.OscIOError as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/build.py new/osc-0.177.0/osc/build.py
--- old/osc-0.176.0/osc/build.py        2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/build.py        2022-04-26 09:48:53.000000000 +0200
@@ -144,7 +144,15 @@
             self.release = root.find('release').text
         else:
             self.release = None
-        self.downloadurl = root.get('downloadurl')
+        if config['api_host_options'][apiurl]['downloadurl']:
+            self.enable_cpio = False
+            self.downloadurl = 
config['api_host_options'][apiurl]['downloadurl'] + "/repositories"
+            if config['http_debug']:
+                print("??????   setting dl_url to %s" % 
config['api_host_options'][apiurl]['downloadurl'])
+        else:
+            self.enable_cpio = True
+            self.downloadurl = root.get('downloadurl')
+
         self.debuginfo = 0
         if root.find('debuginfo') != None:
             try:
@@ -181,7 +189,12 @@
             # a hash providing the matching URL for specific repos for newer 
OBS instances
             if node.get('url'):
                 url = node.get('url').replace('%', '%%')
-                self.urls[node.get('project')+"/"+node.get('repository')] = 
url + '/%(arch)s/%(filename)s'
+                if config['api_host_options'][apiurl]['downloadurl']:
+                    # Add the path element to the download url override.
+                    baseurl = 
config['api_host_options'][apiurl]['downloadurl'] + urlsplit(node.get('url'))[2]
+                else:
+                    baseurl = node.get('url')
+                self.urls[node.get('project')+"/"+node.get('repository')] = 
baseurl + '/%(arch)s/%(filename)s'
 
         self.vminstall_list = [ dep.name for dep in self.deps if dep.vminstall 
]
         self.preinstall_list = [ dep.name for dep in self.deps if 
dep.preinstall ]
@@ -682,16 +695,6 @@
     if opts.pkg_ccache:
         buildargs.append('--pkg-ccache=%s' % opts.pkg_ccache)
         xp.append('ccache')
-    if opts.sccache_uri or config['sccache_uri'] or opts.sccache or 
config['sccache']:
-        if opts.pkg_ccache or opts.ccache or config['ccache']:
-            raise oscerr.WrongArgs('Error: sccache and ccache can not be 
enabled at the same time')
-        if opts.sccache_uri:
-            buildargs.append('--sccache-uri=%s' % opts.sccache_uri)
-        elif config['sccache_uri']:
-            buildargs.append('--sccache-uri=%s' % config['sccache_uri'])
-        else:
-            buildargs.append('--sccache')
-        xp.append('sccache')
     if opts.linksources:
         buildargs.append('--linksources')
     if opts.baselibs:
@@ -779,6 +782,21 @@
         except:
             pass
 
+    # We configure sccache after pacname, so that in default cases we can have 
an sccache for each
+    # package to prevent cross-cache polutions. It helps to make the local-use 
case a bit nicer.
+    if opts.sccache_uri or config['sccache_uri'] or opts.sccache or 
config['sccache']:
+        if opts.pkg_ccache or opts.ccache or config['ccache']:
+            raise oscerr.WrongArgs('Error: sccache and ccache can not be 
enabled at the same time')
+        sccache_arg = "--sccache-uri=/var/tmp/osbuild-sccache-{pkgname}.tar"
+        if opts.sccache_uri:
+            sccache_arg = '--sccache-uri=%s' % opts.sccache_uri
+        elif config['sccache_uri']:
+            sccache_arg = '--sccache-uri=%s' % config['sccache_uri']
+        # Format the package name.
+        sccache_arg = sccache_arg.format(pkgname=pacname)
+        buildargs.append(sccache_arg)
+        xp.append('sccache')
+
     # define buildinfo & config local cache
     bi_file = None
     bc_file = None
@@ -1057,7 +1075,7 @@
                       offline = opts.noinit or opts.offline,
                       http_debug = config['http_debug'],
                       modules = bi.modules,
-                      enable_cpio = not opts.disable_cpio_bulk_download,
+                      enable_cpio=not opts.disable_cpio_bulk_download and 
bi.enable_cpio,
                       cookiejar=cookiejar,
                       download_api_only=opts.download_api_only)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/cmdln.py new/osc-0.177.0/osc/cmdln.py
--- old/osc-0.176.0/osc/cmdln.py        2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/cmdln.py        2022-04-26 09:48:53.000000000 +0200
@@ -791,10 +791,10 @@
             # practice dictates that command help strings begin with this, but
             # it isn't at all wanted for the command list.
             to_strip = "${cmd_name}:"
-            if doc and doc.startswith(to_strip):
+            if doc and doc.lstrip().startswith(to_strip):
                 #log.debug("stripping %r from start of %s's help string",
                 #          to_strip, cmdname)
-                doc = doc[len(to_strip):].lstrip()
+                doc = (doc.lstrip())[len(to_strip):].lstrip()
             if not getattr(self._get_cmd_handler(cmdname), "hidden", None):
                 linedata.append( (cmdstr, doc) )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/commandline.py 
new/osc-0.177.0/osc/commandline.py
--- old/osc-0.176.0/osc/commandline.py  2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/commandline.py  2022-04-26 09:48:53.000000000 +0200
@@ -1379,7 +1379,7 @@
             if len(myreqs) > 0:
                 for req in myreqs:
                     change_request_state(apiurl, str(req), 'superseded',
-                                             'superseded by %s' % result, 
result)
+                                             'superseded by %s' % sr_ids[0], 
sr_ids[0])
 
             sys.exit('Successfully finished')
 
@@ -2150,6 +2150,8 @@
 
     @cmdln.option('-d', '--diff', action='store_true',
                   help='generate a diff')
+    @cmdln.option('-S', '--superseded-request', metavar='SUPERSEDED_REQUEST',
+                        help='Create the diff relative to a given former 
request')
     @cmdln.option('-u', '--unified', action='store_true',
                   help='output the diff in the unified diff format')
     @cmdln.option('--no-devel', action='store_true',
@@ -2575,8 +2577,15 @@
                 diff = b''
                 try:
                     # works since OBS 2.1
-                    diff = request_diff(apiurl, reqid)
+                    diff = request_diff(apiurl, reqid, opts.superseded_request)
                 except HTTPError as e:
+                    if e.code == 404:
+                        # Any referenced object does not exist, eg. the 
superseded request
+                        root = ET.fromstring(e.read())
+                        summary = root.find('summary')
+                        print(summary.text, file=sys.stderr)
+                        raise oscerr.WrongOptions("Object does not exist")
+
                     # for OBS 2.0 and before
                     sr_actions = r.get_actions('submit')
                     if not r.get_actions('submit') and not 
r.get_actions('maintenance_incident') and not 
r.get_actions('maintenance_release'):
@@ -3110,8 +3119,7 @@
         """${cmd_name}: Release sources and binaries
 
         This command is used to transfer sources and binaries without 
rebuilding them.
-        It requires defined release targets set to trigger="manual". Please 
refer the
-        release management chapter in the OBS book for details.
+        It requires defined release targets set to trigger="manual".
 
         usage:
             osc release [ SOURCEPROJECT [ SOURCEPACKAGE ] ]
@@ -7040,7 +7048,7 @@
             project = args[0]
             package = args[1]
 
-        rev, rev_upper = parseRevisionOption(opts.revision)
+        rev, rev_upper = parseRevisionOption(opts.revision, allow_md5=False)
         if rev and not checkRevision(project, package, rev, apiurl, opts.meta):
             print('Revision \'%s\' does not exist' % rev, file=sys.stderr)
             sys.exit(1)
@@ -7510,8 +7518,10 @@
 
 
         if package is None:
+            package_specified = False
             package = meta_get_packagelist(apiurl, project, deleted=0)
         else:
+            package_specified = True
             if opts.multibuild_package:
                 packages = []
                 for subpackage in opts.multibuild_package:
@@ -7538,12 +7548,6 @@
                 for i in binaries:
                     if binary != None and binary != i.name:
                         continue
-                    # skip metadata (unless explicitly specified as the `FILE` 
(== `binary`) argument)
-                    if not binary and i.name.startswith("_"):
-                        continue
-                    # skip logs (unless explicitly specified as the `FILE` (== 
`binary`) argument)
-                    if not binary and i.name.endswith(".log"):
-                        continue
                     # skip source rpms
                     if not opts.sources and (i.name.endswith('src.rpm') or 
i.name.endswith('sdeb')):
                         continue
@@ -7552,7 +7556,18 @@
                             continue
                         if i.name.find('-debugsource-') >= 0:
                             continue
-                    fname = '%s/%s' % (target_dir, i.name)
+
+                    if package_specified:
+                        # if package is specified, download everything into 
the target dir
+                        fname = '%s/%s' % (target_dir, i.name)
+                    elif i.name.startswith("_") or i.name.endswith(".log"):
+                        # download logs and metadata into subdirs
+                        # to avoid overwriting them with files with indentical 
names
+                        fname = '%s/%s/%s' % (target_dir, pac, i.name)
+                    else:
+                        # always download packages into the target dir
+                        fname = '%s/%s' % (target_dir, i.name)
+
                     if os.path.exists(fname):
                         st = os.stat(fname)
                         if st.st_mtime == i.mtime and st.st_size == i.size:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/conf.py new/osc-0.177.0/osc/conf.py
--- old/osc-0.176.0/osc/conf.py 2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/conf.py 2022-04-26 09:48:53.000000000 +0200
@@ -221,7 +221,8 @@
     'status_mtime_heuristic', 'print_web_links', 'ccache', 'sccache', 
'build-shell-after-fail']
 integer_opts = ['build-jobs']
 
-api_host_options = ['user', 'pass', 'passx', 'aliases', 'http_headers', 
'realname', 'email', 'sslcertck', 'cafile', 'capath', 'trusted_prj']
+api_host_options = ['user', 'pass', 'passx', 'aliases', 'http_headers', 
'realname', 'email', 'sslcertck', 'cafile', 'capath', 'trusted_prj',
+    'downloadurl']
 
 new_conf_template = """
 [general]
@@ -304,9 +305,14 @@
 # ccache = 1
 
 # Enable sccache in build roots. Conflicts with ccache.
+# Equivalent to sccache_uri = file:///var/tmp/osbuild-sccache-{pkgname}.tar
 # sccache = 1
 
-# Optional remote URI for sccache storage.
+# Optional URI for sccache storage. Maybe a file://, redis:// or other URI 
supported
+# by the configured sccache install. This uri MAY take {pkgname} as a special 
parameter
+# which will be replaced with the name of the package to be built.
+# sccache_uri = file:///var/tmp/osbuild-sccache-{pkgname}.tar.lzop
+# sccache_uri = file:///var/tmp/osbuild-sccache-{pkgname}.tar
 # sccache_uri = redis://127.0.0.1:6379
 
 # extra packages to install when building packages locally (osc build)
@@ -759,7 +765,7 @@
         # change password store
         creds_mgr = _get_credentials_manager(section, cp)
         user = _extract_user_compat(cp, section, creds_mgr)
-        val = creds_mgr.get_password(section, user)
+        val = creds_mgr.get_password(section, user, defer=False)
 
     run = False
     if val:
@@ -879,6 +885,8 @@
     def __getitem__(self, key, *args, **kwargs):
         value = super(self.__class__, self).__getitem__(key, *args, **kwargs)
         if key == 'pass' and callable(value):
+            print('Warning: use of a deprecated credentials manager API.',
+                  file=sys.stderr)
             value = value()
         return value
 
@@ -974,7 +982,7 @@
         user = _extract_user_compat(cp, url, creds_mgr)
         if user is None:
             raise oscerr.ConfigMissingCredentialsError('No user found in 
section %s' % url, conffile, url)
-        password = creds_mgr.get_password(url, user)
+        password = creds_mgr.get_password(url, user, defer=True)
         if password is None:
             raise oscerr.ConfigMissingCredentialsError('No password found in 
section %s' % url, conffile, url)
 
@@ -1019,6 +1027,16 @@
         else:
             api_host_options[apiurl]['trusted_prj'] = []
 
+        # ??????  This option is experimental and may be removed at any time 
in the future!
+        # This allows overriding the download url for an OBS instance to 
specify a closer mirror
+        # or proxy system, which can greatly improve download performance, 
latency and more.
+        # For example, this can use 
https://github.com/Firstyear/opensuse-proxy-cache in a local
+        # geo to improve performance.
+        if cp.has_option(url, 'downloadurl'):
+            api_host_options[apiurl]['downloadurl'] = cp.get(url, 
'downloadurl')
+        else:
+            api_host_options[apiurl]['downloadurl'] = None
+
     # add the auth data we collected to the config dict
     config['api_host_options'] = api_host_options
     config['apiurl_aliases'] = aliases
@@ -1104,9 +1122,21 @@
     if not credentials.has_keyring_support():
         print('To use keyrings please install python%d-keyring.' % 
sys.version_info.major)
     creds_mgr_descriptors = credentials.get_credentials_manager_descriptors()
+
+    rows = []
     for i, creds_mgr_descr in enumerate(creds_mgr_descriptors, 1):
-        print('%d) %s (%s)' % (i, creds_mgr_descr.name(), 
creds_mgr_descr.description()))#
-    i = raw_input('Select credentials manager: ')
+        rows += [str(i), creds_mgr_descr.name(), creds_mgr_descr.description()]
+
+    from .core import build_table
+    headline = ('NUM', 'NAME', 'DESCRIPTION')
+    table = build_table(len(headline), rows, headline)
+    print()
+    for row in table:
+        print(row)
+
+    i = raw_input('Select credentials manager [default=1]: ')
+    if not i:
+        i = "1"
     if not i.isdigit():
         sys.exit('Invalid selection')
     i = int(i) - 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/core.py new/osc-0.177.0/osc/core.py
--- old/osc-0.176.0/osc/core.py 2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/core.py 2022-04-26 09:48:53.000000000 +0200
@@ -5,7 +5,7 @@
 
 from __future__ import print_function
 
-__version__ = '0.176.0'
+__version__ = '0.177.0'
 
 # __store_version__ is to be incremented when the format of the working copy
 # "store" changes in an incompatible way. Please add any needed migration
@@ -4302,9 +4302,9 @@
         r = root.get('id')
     except HTTPError as e:
         if e.hdrs.get('X-Opensuse-Errorcode') == "submit_request_rejected":
-            print("WARNING:")
-            print("WARNING: Project does not accept submit request, a NEW 
maintenance incident request will be created instead")
-            print("WARNING:")
+            print('WARNING: As the project is in maintenance, a maintenance 
incident request is')
+            print('WARNING: being created (instead of a regular submit 
request). If this is not your')
+            print('WARNING: intention please revoke it to avoid unnecessary 
work for all involved parties.')
             xpath = 'maintenance/maintains/@project = \'%s\' and 
attribute/@name = \'%s\'' % (dst_project, conf.config['maintenance_attribute'])
             res = search(apiurl, project_id=xpath)
             root = res['project_id']
@@ -4747,6 +4747,15 @@
 
     target_filename = target_filename or filename
 
+    # create target directory if it doesn't exist
+    target_dir = os.path.dirname(target_filename)
+    if target_dir:
+        try:
+            os.makedirs(target_dir, 0o755)
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
+
     where = package or '_repository'
     u = makeurl(apiurl, ['build', prj, repo, arch, where, filename])
     download(u, target_filename, progress_obj, target_mtime)
@@ -4952,8 +4961,11 @@
             return rdiff
 
 
-def request_diff(apiurl, reqid):
-    u = makeurl(apiurl, ['request', reqid], query={'cmd': 'diff'} )
+def request_diff(apiurl, reqid, superseded_reqid=None):
+    query = {'cmd': 'diff'}
+    if superseded_reqid:
+        query['diff_to_superseded'] = superseded_reqid
+    u = makeurl(apiurl, ['request', reqid], query)
 
     f = http_POST(u)
     return f.read()
@@ -5634,24 +5646,37 @@
         r.insert(1, '------------              -------')
 
     else:
-        result_line_templ = '%(name)-25s %(project)-25s %(repository)-25s 
%(reponame)s'
         f = http_GET(makeurl(apiurl, ['distributions']))
         root = ET.fromstring(b''.join(f))
 
+        distlist = []
         for node in root.findall('distribution'):
-            rmap = {}
-            for node2 in node.findall('name'):
-                rmap['name'] = node2.text
-            for node3 in node.findall('project'):
-                rmap['project'] = node3.text
-            for node4 in node.findall('repository'):
-                rmap['repository'] = node4.text
-            for node5 in node.findall('reponame'):
-                rmap['reponame'] = node5.text
-            r.append(result_line_templ % rmap)
-
-        r.insert(0, 'distribution              project                   
repository                reponame')
-        r.insert(1, '------------              -------                   
----------                --------')
+            dmap = {}
+            for child in node:
+                if child.tag in ('name', 'project', 'repository', 'reponame'):
+                   dmap[child.tag] = child.text
+            dmap['distribution'] = dmap.pop('name')
+            distlist.append(dmap)
+
+        # pretty printing table
+        headers = ('distribution', 'project', 'repository', 'reponame')
+        maxlen = [len(h) for h in headers]
+        for d in distlist:
+            for i,field in enumerate(headers):
+                maxlen[i] = max(maxlen[i], len(d[field]))
+
+        def format_row(dist, proj, repotype, reponame):
+            result_line_templ = '%-*s  %-*s  %-*s  %-s'
+            return result_line_templ % (
+                maxlen[0], dist,
+                maxlen[1], proj,
+                maxlen[2], repotype,
+                reponame
+                )
+        r.append(format_row('distribution', 'project', 'repository', 
'reponame'))
+        r.append(format_row('-'*maxlen[0], '-'*maxlen[1], '-'*maxlen[2], 
'-'*maxlen[2]))
+        for d in distlist:
+            r.append(format_row(d['distribution'], d['project'], 
d['repository'], d['reponame']))
 
     return r
 
@@ -5785,7 +5810,7 @@
         rmap['repostate'] = node.get('code')
         rmap['pkg'] = rmap['package'] = rmap['pac'] = ''
         rmap['code'] = node.get('code')
-        rmap['details'] = ''
+        rmap['details'] = node.get('details')
         # the way we currently use this function, there should be
         # always a status element
         snodes = node.findall('status')
@@ -5806,6 +5831,10 @@
             details = statusnode.find('details')
             if details is not None:
                 smap['details'] = details.text
+            if rmap['code'] == 'broken':
+                # real error just becomes visible in details/verbose
+                smap['code'] = rmap['code']
+                smap['details'] = "repository: " + rmap['details']
             yield smap, is_multi
 
 
@@ -5927,6 +5956,9 @@
     f = show_prj_results_meta(apiurl, prj)
     root = ET.fromstring(b''.join(f))
 
+    if name_filter is not None:
+        name_filter = re.compile(name_filter)
+
     pacs = []
     # sequence of (repo,arch) tuples
     targets = []
@@ -5948,6 +5980,8 @@
             state = "outdated"
         else:
             state = node.get('state')
+        if node.get('details'):
+            state += ' details: ' + node.get('details')
         tg = (node.get('repository'), node.get('arch'), state)
         targets.append(tg)
         for pacnode in node.findall('status'):
@@ -5981,12 +6015,12 @@
                             if not name_filter:
                                 pacs_to_show.append(pkg)
                                 targets_to_show.append(repo)
-                            elif name_filter in pkg:
+                            elif name_filter.search(pkg) is not None:
                                 pacs_to_show.append(pkg)
         #filtering for Package Name
         elif name_filter:
             for pkg in pacs:
-                if name_filter in pkg:
+                if name_filter.search(pkg) is not None:
                     pacs_to_show.append(pkg)
 
         #filter non building states
@@ -6687,32 +6721,22 @@
     return root.get('code')
 
 
-def parseRevisionOption(string):
+def parseRevisionOption(string, allow_md5=True):
     """
     returns a tuple which contains the revisions
     """
 
+    revisions = [None, None]
     if string:
-        if ':' in string:
-            splitted_rev = string.split(':')
-            try:
-                for i in splitted_rev:
-                    int(i)
-                return splitted_rev
-            except ValueError:
+        parts = string.split(':')
+        for i, revision in enumerate(parts[0:2], 0):
+            if revision.isdigit() or (allow_md5 and revision.isalnum() and 
len(revision) == 32):
+                revisions[i] = revision
+            elif revision != '' and revision != 'latest':
                 print('your revision \'%s\' will be ignored' % string, 
file=sys.stderr)
                 return None, None
-        else:
-            if string.isdigit():
-                return string, None
-            elif string.isalnum() and len(string) == 32:
-                # could be an md5sum
-                return string, None
-            else:
-                print('your revision \'%s\' will be ignored' % string, 
file=sys.stderr)
-                return None, None
-    else:
-        return None, None
+
+    return tuple(revisions)
 
 def checkRevision(prj, pac, revision, apiurl=None, meta=False):
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/credentials.py 
new/osc-0.177.0/osc/credentials.py
--- old/osc-0.176.0/osc/credentials.py  2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/credentials.py  2022-04-26 09:48:53.000000000 +0200
@@ -2,18 +2,65 @@
 import bz2
 import base64
 import getpass
+import sys
+
 try:
     from urllib.parse import urlsplit
 except ImportError:
     from urlparse import urlsplit
+
 try:
     import keyring
 except ImportError:
     keyring = None
+except BaseException as e:
+    # catch and report any exceptions raised in the 'keyring' module
+    msg = "Warning: Unable to load the 'keyring' module due to an internal 
error:"
+    print(msg, e, file=sys.stderr)
+    keyring = None
+
 try:
     import gnomekeyring
 except ImportError:
     gnomekeyring = None
+except BaseException as e:
+    # catch and report any exceptions raised in the 'gnomekeyring' module
+    msg = "Warning: Unable to load the 'gnomekeyring' module due to an 
internal error:"
+    print(msg, e, file=sys.stderr)
+    gnomekeyring = None
+
+from . import conf
+from . import oscerr
+
+
+class _LazyPassword(object):
+    def __init__(self, pwfunc):
+        self._pwfunc = pwfunc
+        self._password = None
+
+    def __str__(self):
+        if self._password is None:
+            password = self._pwfunc()
+            if callable(password):
+                print('Warning: use of a deprecated credentials manager API.',
+                      file=sys.stderr)
+                password = password()
+            if password is None:
+                raise oscerr.OscIOError(None, 'Unable to retrieve password')
+            self._password = password
+        return self._password
+
+    def __len__(self):
+        return len(str(self))
+
+    def __add__(self, other):
+        return str(self) + other
+
+    def __radd__(self, other):
+        return other + str(self)
+
+    def __getattr__(self, name):
+        return getattr(str(self), name)
 
 
 class AbstractCredentialsManagerDescriptor(object):
@@ -23,11 +70,16 @@
     def description(self):
         raise NotImplementedError()
 
+    def priority(self):
+        # priority determines order in the credentials managers list
+        # higher number means higher priority
+        raise NotImplementedError()
+
     def create(self, cp):
         raise NotImplementedError()
 
     def __lt__(self, other):
-        return self.name() < other.name()
+        return (-self.priority(), self.name()) < (-other.priority(), 
other.name())
 
 
 class AbstractCredentialsManager(object):
@@ -42,14 +94,15 @@
     def create(cls, cp, options):
         return cls(cp, options)
 
-    def get_password(self, url, user, defer=True):
-        # If defer is True a callable can be returned
-        # and the password is retrieved if the callable
-        # is called. Implementations are free to ignore
-        # defer parameter and can directly return the password.
-        # If defer is False the password is directly returned.
+    def _get_password(self, url, user):
         raise NotImplementedError()
 
+    def get_password(self, url, user, defer=True):
+        if defer:
+            return _LazyPassword(lambda: self._get_password(url, user))
+        else:
+            return self._get_password(url, user)
+
     def set_password(self, url, user, password):
         raise NotImplementedError()
 
@@ -81,10 +134,13 @@
 
 class PlaintextConfigFileDescriptor(AbstractCredentialsManagerDescriptor):
     def name(self):
-        return 'Config file credentials manager'
+        return 'Config'
 
     def description(self):
-        return 'Store the credentials in the config file (plain text)'
+        return 'Store the password in plain text in the osc config file 
[insecure, persistent]'
+
+    def priority(self):
+        return 1
 
     def create(self, cp):
         return PlaintextConfigFileCredentialsManager(cp, None)
@@ -116,10 +172,13 @@
 
 class ObfuscatedConfigFileDescriptor(AbstractCredentialsManagerDescriptor):
     def name(self):
-        return 'Obfuscated Config file credentials manager'
+        return 'Obfuscated config'
 
     def description(self):
-        return 'Store the credentials in the config file (obfuscated)'
+        return 'Store the password in obfuscated form in the osc config file 
[insecure, persistent]'
+
+    def priority(self):
+        return 2
 
     def create(self, cp):
         return ObfuscatedConfigFileCredentialsManager(cp, None)
@@ -134,10 +193,10 @@
         if options is not None:
             raise RuntimeError('options must be None')
 
-    def get_password(self, url, user, defer=True):
-        if defer:
-            return self
-        return self()
+    def _get_password(self, url, user):
+        if self._password is None:
+            self._password = getpass.getpass('Password: ')
+        return self._password
 
     def set_password(self, url, user, password):
         self._password = password
@@ -146,18 +205,16 @@
     def delete_password(self, url, user):
         self._password = None
 
-    def __call__(self):
-        if self._password is None:
-            self._password = getpass.getpass('Password: ')
-        return self._password
-
 
 class TransientDescriptor(AbstractCredentialsManagerDescriptor):
     def name(self):
-        return 'Transient password store'
+        return 'Transient'
 
     def description(self):
-        return 'Do not store the password and always ask for the password'
+        return 'Do not store the password and always ask for it [secure, 
in-memory]'
+
+    def priority(self):
+        return 3
 
     def create(self, cp):
         return TransientCredentialsManager(cp, None)
@@ -170,7 +227,11 @@
         self._backend_cls_name = options
 
     def _load_backend(self):
-        keyring_backend = keyring.core.load_keyring(self._backend_cls_name)
+        try:
+            keyring_backend = keyring.core.load_keyring(self._backend_cls_name)
+        except ModuleNotFoundError:
+            msg = "Invalid credentials_mgr_class: 
{}".format(self._backend_cls_name)
+            raise oscerr.ConfigError(msg, conf.config['conffile'])
         keyring.set_keyring(keyring_backend)
 
     @classmethod
@@ -179,7 +240,7 @@
             return None
         return super(cls, cls).create(cp, options)
 
-    def get_password(self, url, user, defer=True):
+    def _get_password(self, url, user):
         self._load_backend()
         return keyring.get_password(urlsplit(url)[1], user)
 
@@ -195,18 +256,29 @@
 
 
 class KeyringCredentialsDescriptor(AbstractCredentialsManagerDescriptor):
-    def __init__(self, keyring_backend):
+    def __init__(self, keyring_backend, name=None, description=None, 
priority=None):
         self._keyring_backend = keyring_backend
+        self._name = name
+        self._description = description
+        self._priority = priority
 
     def name(self):
+        if self._name:
+            return self._name
         if hasattr(self._keyring_backend, 'name'):
             return self._keyring_backend.name
-        else:
-            return self._keyring_backend.__class__.__name__
+        return self._keyring_backend.__class__.__name__
 
     def description(self):
+        if self._description:
+            return self._description
         return 'Backend provided by python-keyring'
 
+    def priority(self):
+        if self._priority is not None:
+            return self._priority
+        return 0
+
     def create(self, cp):
         qualified_backend_name = qualified_name(self._keyring_backend)
         return KeyringCredentialsManager(cp, qualified_backend_name)
@@ -219,7 +291,7 @@
             return None
         return super(cls, cls).create(cp, options)
 
-    def get_password(self, url, user, defer=True):
+    def _get_password(self, url, user):
         gk_data = self._keyring_data(url, user)
         if gk_data is None:
             return None
@@ -276,24 +348,55 @@
         return 'Deprecated GNOME Keyring Manager. If you use \
                 this we will send you a Dial-In modem'
 
+    def priority(self):
+        return 0
+
     def create(self, cp):
         return GnomeKeyringCredentialsManager(cp, None)
 
 
+# we're supporting only selected python-keyring backends in osc
+SUPPORTED_KEYRING_BACKENDS = {
+    "keyutils.osc.OscKernelKeyringBackend": {
+        "name": "Kernel keyring",
+        "description": "Store password in user session keyring in kernel 
keyring [secure, in-memory, per-session]",
+        "priority": 10,
+    },
+    "keyring.backends.SecretService.Keyring": {
+        "name": "Secret Service",
+        "description": "Store password in Secret Service (GNOME Keyring 
backend) [secure, persistent]",
+        "priority": 9,
+    },
+    "keyring.backends.kwallet.DBusKeyring": {
+        "name": "KWallet",
+        "description": "Store password in KWallet [secure, persistent]",
+        "priority": 8,
+    },
+}
+
+
 def get_credentials_manager_descriptors():
-    if has_keyring_support():
-        backend_list = keyring.backend.get_all_keyring()
-    else:
-        backend_list = []
     descriptors = []
-    for backend in backend_list:
-        descriptors.append(KeyringCredentialsDescriptor(backend))
-    descriptors.sort()
+
+    if has_keyring_support():
+        for backend in keyring.backend.get_all_keyring():
+            qualified_backend_name = qualified_name(backend)
+            data = SUPPORTED_KEYRING_BACKENDS.get(qualified_backend_name, None)
+            if not data:
+                continue
+            descriptor = KeyringCredentialsDescriptor(
+                backend,
+                data["name"],
+                data["description"],
+                data["priority"]
+            )
+            descriptors.append(descriptor)
     if gnomekeyring:
         descriptors.append(GnomeKeyringCredentialsDescriptor())
     descriptors.append(PlaintextConfigFileDescriptor())
     descriptors.append(ObfuscatedConfigFileDescriptor())
     descriptors.append(TransientDescriptor())
+    descriptors.sort()
     return descriptors
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc/oscerr.py 
new/osc-0.177.0/osc/oscerr.py
--- old/osc-0.176.0/osc/oscerr.py       2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc/oscerr.py       2022-04-26 09:48:53.000000000 +0200
@@ -22,6 +22,9 @@
         self.msg = msg
         self.file = fname
 
+    def __str__(self):
+        return "Error in config file {}\n   {}".format(self.file, self.msg)
+
 class ConfigMissingApiurl(ConfigError):
     """Exception raised when a apiurl does not exist in the config file"""
     def __init__(self, msg, fname, url):
@@ -46,6 +49,9 @@
         self.file = fname
         self.msg = msg
 
+    def __str__(self):
+        return "Config file cannot be found: {}\n   {}".format(self.file, 
self.msg)
+
 class ExtRuntimeError(OscBaseError):
     """Exception raised when there is a runtime error of an external tool"""
     def __init__(self, msg, fname):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/osc-wrapper.py 
new/osc-0.177.0/osc-wrapper.py
--- old/osc-0.176.0/osc-wrapper.py      2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/osc-wrapper.py      2022-04-26 09:48:53.000000000 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # this wrapper exists so it can be put into /usr/bin, but still allows the
 # python module to be called within the source directory during development
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/osc-0.176.0/setup.py new/osc-0.177.0/setup.py
--- old/osc-0.176.0/setup.py    2022-02-28 16:44:11.000000000 +0100
+++ new/osc-0.177.0/setup.py    2022-04-26 09:48:53.000000000 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 from distutils.core import setup
 import distutils.core

++++++ osc.dsc ++++++
--- /var/tmp/diff_new_pack.9wgOgC/_old  2022-04-27 21:41:31.285027142 +0200
+++ /var/tmp/diff_new_pack.9wgOgC/_new  2022-04-27 21:41:31.289027147 +0200
@@ -1,6 +1,6 @@
 Format: 1.0
 Source: osc
-Version: 0.176.0-0
+Version: 0.177.0-0
 Binary: osc
 Maintainer: Adrian Schroeter <[email protected]>
 Architecture: any

Reply via email to