Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package opi for openSUSE:Factory checked in 
at 2023-12-12 19:32:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/opi (Old)
 and      /work/SRC/openSUSE:Factory/.opi.new.25432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "opi"

Tue Dec 12 19:32:36 2023 rev:52 rq:1132666 version:4.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/opi/opi.changes  2023-12-07 19:13:01.833069301 
+0100
+++ /work/SRC/openSUSE:Factory/.opi.new.25432/opi.changes       2023-12-12 
19:33:07.242432706 +0100
@@ -1,0 +2,11 @@
+Tue Dec 12 12:40:43 UTC 2023 - Dominik Heidler <dheid...@suse.de>
+
+- Version 4.2.0
+  * Support multiple repos defined in a single .repo file
+  * Automatically import packman key in non-interactive mode
+  * Restructure code: Add classes for Repository, OBSPackage and LocalPackage
+  * Hide package release for pkgs from local repos (same as with OBS pkgs)
+  * Use tumbleweed repo for openh264 on Slowroll
+  * Expand repovar $basearch (to e.g. x86_64 or aarch64)
+
+-------------------------------------------------------------------

Old:
----
  opi-4.1.0.tar.gz

New:
----
  opi-4.2.0.tar.gz

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

Other differences:
------------------
++++++ opi.spec ++++++
--- /var/tmp/diff_new_pack.g40CLP/_old  2023-12-12 19:33:07.674448646 +0100
+++ /var/tmp/diff_new_pack.g40CLP/_new  2023-12-12 19:33:07.674448646 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           opi
-Version:        4.1.0
+Version:        4.2.0
 Release:        0
 Summary:        OBS Package Installer (CLI)
 License:        GPL-3.0-only

++++++ opi-4.1.0.tar.gz -> opi-4.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opi-4.1.0/bin/opi new/opi-4.2.0/bin/opi
--- old/opi-4.1.0/bin/opi       2023-12-07 11:30:33.000000000 +0100
+++ new/opi-4.2.0/bin/opi       2023-12-12 13:40:35.000000000 +0100
@@ -68,46 +68,47 @@
        try:
                print(f'Searching repos for: {(" ".join(query) if 
isinstance(query, list) else query)}')
 
-               binaries = []
-               binaries.extend(opi.search_published_binary('openSUSE', query))
-               binaries.extend(opi.search_published_binary('Packman', query))
-               binaries = opi.sort_uniq_binaries(binaries)
-               if len(binaries) == 0:
+               packages = []
+               packages.extend(opi.search_published_packages('openSUSE', 
query))
+               packages.extend(opi.search_published_packages('Packman', query))
+               packages = opi.sort_uniq_packages(packages)
+               if len(packages) == 0:
                        print('No package found.')
                        return
 
                # Print and select a package name option
-               binary_names = opi.get_binary_names(binaries)
-               selected_name = opi.ask_for_option(binary_names)
-               print('You have selected package name:', selected_name)
-
-               # Inject binaries from local repos
-               binaries = opi.search_local_repos(selected_name) + binaries
-
-               binary_options = opi.get_binaries_by_name(selected_name, 
binaries)
-
-               # Print and select a binary package option
-               selected_binary = opi.ask_for_option(binary_options, 
option_filter=opi.format_binary_option, disable_pager=True)
-               print('You have selected binary package:', 
opi.format_binary_option(selected_binary, table=False))
-               if opi.is_personal_project(selected_binary['project']):
-                       print(colored(
-                               'BE CAREFUL! The package is from a personal 
repository and NOT reviewed by others.\n'
-                               'You can ask the author to submit the package 
to development projects and openSUSE:Factory.\n'
-                               'Learn more at 
https://en.opensuse.org/openSUSE:How_to_contribute_to_Factory',
-                               'red'
-                       ))
-               elif selected_binary['project'] == 'openSUSE:Factory':
-                       print(opi.colored(
-                               'BE CAREFUL! You are about to add the Factory 
Repository.\n'
-                               'This repo contains the unreleased Tumbleweed 
distro before openQA tests have been run.\n'
-                               'Only proceed if you know what you are doing!',
-                               'yellow'
-                       ))
-                       if not opi.ask_yes_or_no('Do you want to continue?', 
default_answer='n'):
-                               return
+               package_names = opi.get_package_names(packages)
+               selected_pkg_name = opi.ask_for_option(package_names)
+               print('You have selected package name:', selected_pkg_name)
+
+               # Inject packages from local repos
+               packages = opi.search_local_repos(selected_pkg_name) + packages
+
+               instable_pkg_options = [pkg for pkg in packages if pkg.name == 
selected_pkg_name]
+
+               # Print and select a package option
+               selected_pkg = opi.ask_for_option(instable_pkg_options, 
option_filter=opi.format_pkg_option, disable_pager=True)
+               print('You have selected package:', 
opi.format_pkg_option(selected_pkg, table=False))
+               if isinstance(selected_pkg, opi.OBSPackage):
+                       if selected_pkg.is_from_personal_project():
+                               print(colored(
+                                       'BE CAREFUL! The package is from a 
personal repository and NOT reviewed by others.\n'
+                                       'You can ask the author to submit the 
package to development projects and openSUSE:Factory.\n'
+                                       'Learn more at 
https://en.opensuse.org/openSUSE:How_to_contribute_to_Factory',
+                                       'red'
+                               ))
+                       elif selected_pkg.project == 'openSUSE:Factory':
+                               print(opi.colored(
+                                       'BE CAREFUL! You are about to add the 
Factory Repository.\n'
+                                       'This repo contains the unreleased 
Tumbleweed distro before openQA tests have been run.\n'
+                                       'Only proceed if you know what you are 
doing!',
+                                       'yellow'
+                               ))
+                               if not opi.ask_yes_or_no('Do you want to 
continue?', default_answer='n'):
+                                       return
 
                # Install selected package
-               opi.install_binary(selected_binary)
+               selected_pkg.install()
        except (opi.NoOptionSelected, opi.HTTPError):
                return
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opi-4.1.0/opi/__init__.py 
new/opi-4.2.0/opi/__init__.py
--- old/opi-4.1.0/opi/__init__.py       2023-12-07 11:30:33.000000000 +0100
+++ new/opi-4.2.0/opi/__init__.py       2023-12-12 13:40:35.000000000 +0100
@@ -94,16 +94,19 @@
                project = 'openSUSE.org:' + project
        return project
 
-def get_version():
+def get_version() -> str:
        os_release = get_os_release()
        version = os_release.get('VERSION') # VERSION is not set for TW
        return version
 
-def expand_releasever(s: str) -> str:
+def expand_vars(s: str) -> str:
        s = s.replace('${releasever}', get_version() or '${releasever}')
        s = s.replace('$releasever', get_version() or '$releasever')
+       s = s.replace('${basearch}', get_cpu_arch() or '${basearch}')
+       s = s.replace('$basearch', get_cpu_arch() or '$basearch')
        return s
 
+
 ###############
 ### PACKMAN ###
 ###############
@@ -118,7 +121,8 @@
                name = 'Packman',
                url = 
f'https://ftp.gwdg.de/pub/linux/misc/packman/suse/{project}/',
                auto_refresh = 
config.get_key_from_config('new_repo_auto_refresh'),
-               priority = 90
+               priority = 90,
+               auto_import_keys = global_state.arg_non_interactive
        )
 
        if dup:
@@ -126,6 +130,7 @@
 
 def add_openh264_repo(dup=False):
        project = get_os_release()['NAME']
+       project = project.replace('-Slowroll', '')
        project = project.replace('openSUSE MicroOS', 'openSUSE Tumbleweed')
        project = project.replace('openSUSE Leap Micro', 'openSUSE Leap')
        project = project.replace(':', '_').replace(' ', '_')
@@ -158,6 +163,23 @@
 ### ZYPP/DNF ###
 ################
 
+class Repository:
+       def __init__(self, alias: str, name: str, url: str, auto_refresh: bool, 
gpgkey: str = None, filename: str = None):
+               self.alias = alias
+               self.name = name
+               self.url = url
+               self.auto_refresh = auto_refresh
+               self.gpgkey = gpgkey
+               self.filename = filename or alias
+
+       def name_expanded(self):
+               """ Return name with all supported vars expanded """
+               return expand_vars(self.name)
+
+       def url_expanded(self):
+               """ Return url with all supported vars expanded """
+               return expand_vars(self.url)
+
 def search_local_repos(package):
        """
                Search local default repos
@@ -167,39 +189,40 @@
                sr = subprocess.check_output(['zypper', '-n', '--no-refresh', 
'se', '-sx', '-tpackage', package], env={'LANG': 'c'}).decode()
                for line in re.split(r'-\+-+\n', sr, 
re.MULTILINE)[1].strip().split('\n'):
                        version, arch, repo_name = [s.strip() for s in 
line.split('|')[3:]]
+                       version, release = version.split('-')
                        if arch not in (get_cpu_arch(), 'noarch'):
                                continue
                        if repo_name == '(System Packages)':
                                continue
-                       search_results[repo_name].append({'version': version, 
'arch': arch})
+                       search_results[repo_name].append({'version': version, 
'release': release, 'arch': arch})
        except subprocess.CalledProcessError as e:
                if e.returncode != 104:
                        # 104 ZYPPER_EXIT_INF_CAP_NOT_FOUND is returned if 
there are no results
                        raise # TODO: don't exit program, use exception that 
will be handled in repo_query except block
 
-       repos_by_name = {expand_releasever(repo['name']): repo for repo in 
get_repos()}
+       repos_by_name = {repo.name_expanded(): repo for repo in get_repos()}
        local_installables = []
        for repo_name, installables in search_results.items():
+               # get the newest package for each repo
                try:
-                       installables.sort(key=lambda p: 
cmp_to_key(rpm.labelCompare)(p['version']))
+                       installables.sort(key=lambda p: 
cmp_to_key(rpm.labelCompare)("%(version)s-%(release)s" % p))
                except TypeError:
-                       # rpm 4.14 needs a tuple of epoch, version, release - 
rpm 4.18 can handle a string
-                       installables.sort(key=lambda p: 
cmp_to_key(rpm.labelCompare)(['1']+p['version'].split('-')))
+                       # rpm 4.14 needs a tuple of (epoch, version, release) - 
rpm 4.18 can handle a string
+                       installables.sort(key=lambda p: 
cmp_to_key(rpm.labelCompare)(['1', p['version'], p['release']]))
                installable = installables[-1]
+
                installable['repository'] = repos_by_name[repo_name]
                installable['name'] = package
-               installable['obs_instance'] = 'LOCAL_REPO'
-               installable['project'] = 
expand_releasever(installable['repository']['name'])
                # filter out OBS/Packman repos as they are already searched via 
OBS/Packman API
-               if 'download.opensuse.org/repositories' in 
installable['repository']['url']:
+               if 'download.opensuse.org/repositories' in 
installable['repository'].url:
                        continue
-               if installable['repository']['filename'] == 'packman':
+               if installable['repository'].filename == 'packman':
                        continue
-               local_installables.append(installable)
+               local_installables.append(LocalPackage(**installable))
        return local_installables
 
 def url_normalize(url):
-       return expand_releasever(re.sub(r'^https?', '', url).rstrip('/'))
+       return expand_vars(re.sub(r'^https?', '', url).rstrip('/'))
 
 def get_repos():
        for repo_file in os.listdir(REPO_DIR):
@@ -208,25 +231,25 @@
                try:
                        cp = configparser.ConfigParser()
                        cp.read(os.path.join(REPO_DIR, repo_file))
-                       mainsec = cp.sections()[0]
-                       if not bool(int(cp.get(mainsec, 'enabled'))):
-                               continue
-                       repo = {
-                               'alias': mainsec,
-                               'filename': re.sub(r'\.repo$', '', repo_file),
-                               'name': cp[mainsec].get('name', mainsec),
-                               'url': cp[mainsec].get('baseurl'),
-                               'auto_refresh': 
bool(int(cp[mainsec].get('autorefresh', '0'))),
-                       }
-                       if cp.has_option(mainsec, 'gpgkey'):
-                               repo['gpgkey'] = cp[mainsec].get('gpgkey')
-                       yield repo
+                       for alias in cp.sections():
+                               if not bool(int(cp.get(alias, 'enabled'))):
+                                       continue
+                               repo = {
+                                       'alias': alias,
+                                       'filename': re.sub(r'\.repo$', '', 
repo_file),
+                                       'name': cp[alias].get('name', alias),
+                                       'url': cp[alias].get('baseurl'),
+                                       'auto_refresh': 
bool(int(cp[alias].get('autorefresh', '0'))),
+                               }
+                               if cp.has_option(alias, 'gpgkey'):
+                                       repo['gpgkey'] = cp[alias].get('gpgkey')
+                               yield Repository(**repo)
                except Exception as e:
                        print(f"Error parsing '{repo_file}': {e}")
 
 def get_enabled_repo_by_url(url):
        for repo in get_repos():
-               if url_normalize(repo['url']) == url_normalize(url):
+               if url_normalize(repo.url) == url_normalize(url):
                        return repo
 
 def add_repo(filename, name, url, enabled=True, gpgcheck=True, gpgkey=None, 
repo_type='rpm-md', auto_import_keys=False, auto_refresh=False, priority=None):
@@ -250,7 +273,7 @@
        tf.file.close()
        refresh_repos(auto_import_keys=auto_import_keys)
 
-def refresh_repos(repo=None, auto_import_keys=False):
+def refresh_repos(repo_alias=None, auto_import_keys=False):
        refresh_cmd = []
        if get_backend() == BackendConstants.zypp:
                refresh_cmd = ['sudo', 'zypper']
@@ -259,8 +282,8 @@
                refresh_cmd.append('ref')
        elif get_backend() == BackendConstants.dnf:
                refresh_cmd = ['sudo', 'dnf', 'ref']
-       if repo:
-               refresh_cmd.append(repo)
+       if repo_alias:
+               refresh_cmd.append(repo_alias)
        subprocess.call(refresh_cmd)
 
 def normalize_key(pem):
@@ -341,7 +364,126 @@
 ### OBS ###
 ###########
 
-def search_published_binary(obs_instance, query):
+class Installable:
+       def __init__(self, name: str, version: str, release: str, arch: str):
+               self.name = name # e.g. MozillaFirefox
+               self.version = version # e.g. 1.2.3
+               self.release = release # e.g. 150500.1.1
+               self.arch = arch # e.g. x86_64, aarch64 or noarch
+
+       def install(self):
+               raise NotImplemented()
+
+       def name_with_arch(self):
+               return f'{self.name}.{self.arch}'
+
+       def _install_from_existing_repo(self, repo: Repository):
+               # Install from existing repos (don't add a repo)
+               print(f"Installing from existing repo '{repo.name_expanded()}'")
+               # ensure that this repo is up to date if no auto_refresh is 
configured
+               if not repo.auto_refresh:
+                       refresh_repos(repo.alias)
+               install_packages([self.name_with_arch()], from_repo=repo.alias)
+
+class OBSPackage(Installable):
+       """ Package returned from OBS API """
+       def __init__(self,
+                    name: str, version: str, release: str, arch: str,
+                    package: str, project: str, repository: str, obs_instance: 
str):
+               super().__init__(name, version, release, arch)
+               self.package = package # same as name unless this is a 
subpackage (then package is name of parent)
+               self.project = project # e.g. devel:languages:perl
+               self.repository = repository # e.g. openSUSE_Tumbleweed
+               self.obs_instance = obs_instance # openSUSE or Packman
+
+       def install(self):
+               if self.obs_instance == 'Packman':
+                       # Install from Packman Repo
+                       add_packman_repo()
+                       install_packman_packages([self.name_with_arch()])
+                       return
+
+               repo_alias = self.project.replace(':', '_')
+               project_path = self.project.replace(':', ':/')
+               repository = self.repository
+               if config.get_key_from_config('use_releasever_var'):
+                       version = get_version()
+                       if version:
+                               # version is None on tw
+                               repository = repository.replace(version, 
'$releasever')
+               url = 
f'https://download.opensuse.org/repositories/{project_path}/{repository}/'
+               gpgkey = url + 'repodata/repomd.xml.key'
+               existing_repo = get_enabled_repo_by_url(url)
+
+               if existing_repo:
+                       self._install_from_existing_repo(existing_repo)
+               else:
+                       print(f"Adding repo '{self.project}'")
+                       add_repo(
+                               filename = repo_alias,
+                               name = self.project,
+                               url = url,
+                               gpgkey = gpgkey,
+                               gpgcheck = True,
+                               auto_refresh = 
config.get_key_from_config('new_repo_auto_refresh')
+                       )
+                       install_packages([self.name_with_arch()], 
from_repo=repo_alias,
+                               allow_downgrade=True,
+                               allow_arch_change=True,
+                               allow_name_change=True,
+                               allow_vendor_change=True
+                       )
+                       ask_keep_repo(repo_alias)
+
+       def weight(self):
+               weight = 0
+
+               dash_count = self.name.count('-')
+               weight += 1e5 * (0.5 ** dash_count)
+
+               weight -= 1e4 * len(self.name)
+
+               if self.is_from_official_project():
+                       weight += 2e3
+               elif not self.is_from_personal_project():
+                       weight += 1e3
+
+               if self.name == self.package:
+                       # this rpm is the main or only subpackage
+                       weight += 1e2
+
+               if not (get_cpu_arch() == 'x86_64' and self.arch == 'i586'):
+                       weight += 1e1
+
+               if self.repository.startswith('openSUSE_Tumbleweed'):
+                       weight += 2
+               elif self.repository.startswith('openSUSE_Factory'):
+                       weight += 1
+               elif self.repository == 'standard':
+                       weight += 0
+
+               return weight
+
+       def __lt__(self, other):
+               """ Note that we sort from high weight to low weight """
+               return self.weight() > other.weight()
+
+       def is_from_official_project(self):
+               return self.project.startswith('openSUSE:')
+
+       def is_from_personal_project(self):
+               return self.project.startswith('home:') or 
self.project.startswith('isv:')
+
+class LocalPackage(Installable):
+       """ Package found in local repo (metadata) cache """
+       def __init__(self, name: str, version: str, release: str, arch: str, 
repository: Repository):
+               super().__init__(name, version, release, arch)
+               self.repository = repository
+
+       def install(self):
+               self._install_from_existing_repo(self.repository)
+
+def search_published_packages(obs_instance, query):
        distribution = get_distribution(prefix=(obs_instance != 'openSUSE'))
        endpoint = '/search/published/binary/id'
        url = OBS_APIROOT[obs_instance] + endpoint
@@ -356,12 +498,16 @@
                r.raise_for_status()
 
                dom = lxml.etree.fromstring(r.text)
-               binaries = []
+               packages = []
                for binary in dom.xpath('/collection/binary'):
                        binary_data = {k: v for k, v in binary.items()}
                        binary_data['obs_instance'] = obs_instance
 
-                       for k in ('name', 'project', 'repository', 'version', 
'release', 'arch', 'filename', 'filepath', 'baseproject', 'type'):
+                       del binary_data['filename']
+                       del binary_data['filepath']
+                       del binary_data['baseproject']
+                       del binary_data['type']
+                       for k in ('name', 'project', 'repository', 'version', 
'release', 'arch'):
                                assert k in binary_data, f"Key '{k}' missing"
 
                        # Filter out ghost binary
@@ -369,43 +515,45 @@
                        if not binary_data.get('package'):
                                continue
 
+                       package = OBSPackage(**binary_data)
+
                        # Filter out branch projects
-                       if ':branches:' in binary_data['project']:
+                       if ':branches:' in package.project:
                                continue
 
                        # Filter out Packman personal projects
-                       if binary_data['obs_instance'] != 'openSUSE' and 
is_personal_project(binary_data['project']):
+                       if package.obs_instance != 'openSUSE' and 
package.is_from_personal_project():
                                continue
 
                        # Filter out debuginfo, debugsource, devel, 
buildsymbols, lang and docs packages
                        regex = 
r'-(debuginfo|debugsource|buildsymbols|devel|lang|l10n|trans|doc|docs)(-.+)?$'
-                       if re.match(regex, binary_data['name']):
+                       if re.match(regex, package.name):
                                continue
 
                        # Filter out source packages
-                       if binary_data['arch'] == 'src':
+                       if package.arch == 'src':
                                continue
 
                        # Filter architecture
                        cpu_arch = get_cpu_arch()
-                       if binary_data['arch'] not in (cpu_arch, 'noarch'):
+                       if package.arch not in (cpu_arch, 'noarch'):
                                continue
 
                        # Filter repo architecture
-                       if binary_data['repository'] == 'openSUSE_Factory' and 
(cpu_arch not in ('x86_64' 'i586')):
+                       if package.repository == 'openSUSE_Factory' and 
(cpu_arch not in ('x86_64' 'i586')):
                                continue
-                       elif binary_data['repository'] == 
'openSUSE_Factory_ARM' and not cpu_arch.startswith('arm') and not cpu_arch == 
'aarch64':
+                       elif package.repository == 'openSUSE_Factory_ARM' and 
not cpu_arch.startswith('arm') and not cpu_arch == 'aarch64':
                                continue
-                       elif binary_data['repository'] == 
'openSUSE_Factory_PowerPC' and not cpu_arch.startswith('ppc'):
+                       elif package.repository == 'openSUSE_Factory_PowerPC' 
and not cpu_arch.startswith('ppc'):
                                continue
-                       elif binary_data['repository'] == 
'openSUSE_Factory_zSystems' and not cpu_arch.startswith('s390'):
+                       elif package.repository == 'openSUSE_Factory_zSystems' 
and not cpu_arch.startswith('s390'):
                                continue
-                       elif binary_data['repository'] == 
'openSUSE_Factory_RISCV' and not cpu_arch.startswith('risc'):
+                       elif package.repository == 'openSUSE_Factory_RISCV' and 
not cpu_arch.startswith('risc'):
                                continue
 
-                       binaries.append(binary_data)
+                       packages.append(package)
 
-               return binaries
+               return packages
        except requests.exceptions.HTTPError as e:
                if e.response.status_code == 413:
                        print('Please use different search keywords. Some short 
keywords cause OBS timeout.')
@@ -413,116 +561,28 @@
                        print('HTTPError:', e)
                raise HTTPError()
 
-def get_binary_names(binaries):
+def get_package_names(packages: list):
+       """ return a list of package names but without duplicates """
        names = []
-       for binary in binaries:
-               name = binary['name']
+       for pkg in packages:
+               name = pkg.name
                if name not in names:
                        names.append(name)
        return names
 
-def sort_uniq_binaries(binaries):
-       """ sort -u for binaries; sort by weight and keep only the one with the 
best repo """
-       binaries = sorted(binaries, key=get_binary_weight, reverse=True)
-       new_binaries = []
-       added_binaries = set()
-       for binary in binaries:
-               # only select the first binary for each name/project combination
+def sort_uniq_packages(packages: list):
+       """ sort -u for packages; sort by weight and keep only the one with the 
best repo """
+       packages = sorted(packages)
+       new_packages = []
+       added_packages = set()
+       for package in packages:
+               # only select the first package for each name/project 
combination
                # which will be the one with the highest repo weight
-               query = (binary['name'], binary['project'])
-               if query not in added_binaries:
-                       new_binaries.append(binary)
-                       added_binaries.add(query)
-       return new_binaries
-
-def get_binary_weight(binary):
-       weight = 0
-
-       dash_count = binary['name'].count('-')
-       weight += 1e5 * (0.5 ** dash_count)
-
-       weight -= 1e4 * len(binary['name'])
-
-       if is_official_project(binary['project']):
-               weight += 2e3
-       elif not is_personal_project(binary['project']):
-               weight += 1e3
-
-       if binary['name'] == binary['package']:
-               weight += 1e2
-
-       if not (get_cpu_arch() == 'x86_64' and binary['arch'] == 'i586'):
-               weight += 1e1
-
-       if binary['repository'].startswith('openSUSE_Tumbleweed'):
-               weight += 2
-       elif binary['repository'].startswith('openSUSE_Factory'):
-               weight += 1
-       elif binary['repository'] == 'standard':
-               weight += 0
-
-       return weight
-
-def is_official_project(project):
-       return project.startswith('openSUSE:')
-
-def is_personal_project(project):
-       return project.startswith('home:') or project.startswith('isv:')
-
-def get_binaries_by_name(name, binaries):
-       return [binary for binary in binaries if binary['name'] == name]
-
-def install_binary(binary):
-       name = binary['name']
-       obs_instance = binary['obs_instance']
-       arch = binary['arch']
-       project = binary['project']
-       repository = binary['repository']
-       name_with_arch = f'{name}.{arch}'
-
-       if obs_instance == 'Packman':
-               # Install from Packman Repo
-               add_packman_repo()
-               install_packman_packages([name_with_arch])
-       else:
-               if binary['obs_instance'] == 'LOCAL_REPO':
-                       existing_repo = repository
-               else:
-                       repo_alias = project.replace(':', '_')
-                       project_path = project.replace(':', ':/')
-                       if config.get_key_from_config('use_releasever_var'):
-                               version = get_version()
-                               if version:
-                                       # version is None on tw
-                                       repository = 
repository.replace(version, '$releasever')
-                       url = 
f'https://download.opensuse.org/repositories/{project_path}/{repository}/'
-                       gpgkey = url + 'repodata/repomd.xml.key'
-                       existing_repo = get_enabled_repo_by_url(url)
-               if existing_repo:
-                       # Install from existing repos (don't add a repo)
-                       print(f"Installing from existing repo 
'{expand_releasever(existing_repo['name'])}'")
-                       # ensure that this repo is up to date if no 
auto_refresh is configured
-                       if not existing_repo['auto_refresh']:
-                               refresh_repos(existing_repo['alias'])
-                       install_packages([name_with_arch], 
from_repo=existing_repo['alias'])
-               else:
-                       print(f"Adding repo '{project}'")
-                       add_repo(
-                               filename = repo_alias,
-                               name = project,
-                               url = url,
-                               gpgkey = gpgkey,
-                               gpgcheck = True,
-                               auto_refresh = 
config.get_key_from_config('new_repo_auto_refresh')
-                       )
-                       install_packages([name_with_arch], from_repo=repo_alias,
-                               allow_downgrade=True,
-                               allow_arch_change=True,
-                               allow_name_change=True,
-                               allow_vendor_change=True
-                       )
-                       ask_keep_repo(repo_alias)
-
+               query = (package.name, package.project)
+               if query not in added_packages:
+                       new_packages.append(package)
+                       added_packages.add(query)
+       return new_packages
 
 ########################
 ### User Interaction ###
@@ -590,7 +650,7 @@
                return options[num - 1]
 
 def ask_import_key(keyurl):
-       keys = requests.get(expand_releasever(keyurl)).text
+       keys = requests.get(expand_vars(keyurl)).text
        db_keys = get_keys_from_rpmdb()
        for key in split_keys(keys):
                for line in subprocess.check_output(['gpg', '--quiet', 
'--show-keys', '--with-colons', '-'], 
input=key.encode()).decode().strip().split('\n'):
@@ -606,28 +666,28 @@
                                subprocess.call(['sudo', 'rpm', '--import', 
tf.name])
                                tf.file.close()
 
-def ask_keep_key(keyurl, repo_name=None):
+def ask_keep_key(keyurl, repo_alias=None):
        """
                Ask to remove the key given by url to key file.
-               Warns about all repos still using the key except the repo given 
by repo_name param.
+               Warns about all repos still using the key except the repo given 
by repo_alias param.
        """
-       urlkeys = split_keys(requests.get(expand_releasever(keyurl)).text)
+       urlkeys = split_keys(requests.get(expand_vars(keyurl)).text)
        urlkeys_normalized = [normalize_key(urlkey) for urlkey in urlkeys]
        db_keys = get_keys_from_rpmdb()
        keys_to_ask_user = [key for key in db_keys if key['pubkey'] in 
urlkeys_normalized]
        for key in keys_to_ask_user:
                repos_using_this_key = []
                for repo in get_repos():
-                       if repo_name and repo['filename'] == repo_name:
+                       if repo_alias and repo.filename == repo_alias:
                                continue
-                       if repo.get('gpgkey'):
-                               repokey = 
normalize_key(requests.get(repo['gpgkey']).text)
+                       if repo.gpgkey:
+                               repokey = 
normalize_key(requests.get(repo.gpgkey).text)
                                if repokey == key['pubkey']:
                                        repos_using_this_key.append(repo)
                if repos_using_this_key:
                        default_answer = 'y'
                        print('This key is still in use by the following 
remaining repos - removal is NOT recommended:')
-                       print(' - ' + '\n - '.join([repo['filename'] for repo 
in repos_using_this_key]))
+                       print(' - ' + '\n - '.join([repo.filename for repo in 
repos_using_this_key]))
                else:
                        default_answer = 'n'
                        print('This key is not in use by any remaining repos.')
@@ -635,37 +695,40 @@
                if not ask_yes_or_no(f"Keep package signing key 
'{key['name']}'?", default_answer):
                        subprocess.call(['sudo', 'rpm', '-e', key['kid']])
 
-def ask_keep_repo(repo):
-       if not ask_yes_or_no(f"Do you want to keep the repo '{repo}'?"):
-               repo_info = next((r for r in get_repos() if r['filename'] == 
repo))
+def ask_keep_repo(repo_alias):
+       if not ask_yes_or_no(f"Do you want to keep the repo '{repo_alias}'?"):
+               repo = next((r for r in get_repos() if r.filename == 
repo_alias))
                if get_backend() == BackendConstants.zypp:
-                       subprocess.call(['sudo', 'zypper', 'rr', repo])
+                       subprocess.call(['sudo', 'zypper', 'rr', repo_alias])
                if get_backend() == BackendConstants.dnf:
-                       subprocess.call(['sudo', 'rm', os.path.join(REPO_DIR, 
f'{repo}.repo')])
-               if repo_info.get('gpgkey'):
-                       ask_keep_key(repo_info['gpgkey'], repo)
+                       subprocess.call(['sudo', 'rm', os.path.join(REPO_DIR, 
f'{repo_alias}.repo')])
+               if repo.gpgkey:
+                       ask_keep_key(repo.gpgkey, repo)
 
-def format_binary_option(binary, table=True):
-       if binary['obs_instance'] == 'LOCAL_REPO':
+def format_pkg_option(package, table=True):
+       if isinstance(package, LocalPackage):
                color = 'green'
                symbol = '+'
-       elif is_official_project(binary['project']):
+       elif package.is_from_official_project():
                color = 'yellow'
                symbol = '-'
-       elif is_personal_project(binary['project']):
+       elif package.is_from_personal_project():
                color = 'red'
                symbol = '!'
        else:
                color = 'cyan'
                symbol = '?'
 
-       project = binary['project']
-       if binary['obs_instance'] not in ('openSUSE', 'LOCAL_REPO'):
-               project = f"{binary['obs_instance']} {project}"
+       if isinstance(package, LocalPackage):
+               project = package.repository.name_expanded()
+       else:
+               project = package.project
+               if package.obs_instance != 'openSUSE':
+                       project = f"{package.obs_instance} {project}"
 
        colored_name = colored(f'{project[:39]} {symbol}', color)
 
        if table:
-               return f"{colored_name:50} | {binary['version'][:25]:25} | 
{binary['arch']}"
+               return f"{colored_name:50} | {package.version[:25]:25} | 
{package.arch}"
        else:
-               return f"{colored_name} | {binary['version']} | 
{binary['arch']}"
+               return f"{colored_name} | {package.version} | {package.arch}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opi-4.1.0/opi/version.py new/opi-4.2.0/opi/version.py
--- old/opi-4.1.0/opi/version.py        2023-12-07 11:30:33.000000000 +0100
+++ new/opi-4.2.0/opi/version.py        2023-12-12 13:40:35.000000000 +0100
@@ -1 +1 @@
-__version__ = '4.1.0'
+__version__ = '4.2.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/opi-4.1.0/opi.changes new/opi-4.2.0/opi.changes
--- old/opi-4.1.0/opi.changes   2023-12-07 11:30:33.000000000 +0100
+++ new/opi-4.2.0/opi.changes   2023-12-12 13:40:35.000000000 +0100
@@ -1,4 +1,15 @@
 -------------------------------------------------------------------
+Tue Dec 12 12:40:20 UTC 2023 - Dominik Heidler <dheid...@suse.de>
+
+- Version 4.2.0
+  * Support multiple repos defined in a single .repo file
+  * Automatically import packman key in non-interactive mode
+  * Restructure code: Add classes for Repository, OBSPackage and LocalPackage
+  * Hide package release for pkgs from local repos (same as with OBS pkgs)
+  * Use tumbleweed repo for openh264 on Slowroll
+  * Expand repovar $basearch (to e.g. x86_64 or aarch64)
+
+-------------------------------------------------------------------
 Thu Dec  7 10:30:24 UTC 2023 - Dominik Heidler <dheid...@suse.de>
 
 - Version 4.1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opi-4.1.0/test/08_install_from_packman_non_interactive.py 
new/opi-4.2.0/test/08_install_from_packman_non_interactive.py
--- old/opi-4.1.0/test/08_install_from_packman_non_interactive.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/opi-4.2.0/test/08_install_from_packman_non_interactive.py       
2023-12-12 13:40:35.000000000 +0100
@@ -0,0 +1,20 @@
+#!/usr/bin/python3
+
+import sys
+import pexpect
+import subprocess
+
+c = pexpect.spawn('./bin/opi -n x265', logfile=sys.stdout.buffer, echo=False)
+
+c.expect('1. x265\r\n')
+c.expect('Pick a number')
+
+c.expect('1. .*Packman Essentials', timeout=10)
+
+c.expect('Overall download size', timeout=60)
+c.interact()
+c.wait()
+c.close()
+assert c.exitstatus == 0, f'Exit code: {c.exitstatus}'
+
+subprocess.check_call(['rpm', '-qi', 'x265'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/opi-4.1.0/test/09_install_with_multi_repos_in_single_file_non_interactive.py
 
new/opi-4.2.0/test/09_install_with_multi_repos_in_single_file_non_interactive.py
--- 
old/opi-4.1.0/test/09_install_with_multi_repos_in_single_file_non_interactive.py
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/opi-4.2.0/test/09_install_with_multi_repos_in_single_file_non_interactive.py
    2023-12-12 13:40:35.000000000 +0100
@@ -0,0 +1,38 @@
+#!/usr/bin/python3
+
+import sys
+import pexpect
+import subprocess
+
+subprocess.check_call("cat /etc/zypp/repos.d/*.repo > /tmp/singlefile.repo", 
shell=True)
+subprocess.check_call("rm -v /etc/zypp/repos.d/*.repo", shell=True)
+subprocess.check_call("mv -v /tmp/singlefile.repo /etc/zypp/repos.d/", 
shell=True)
+
+c = pexpect.spawn('./bin/opi -n html2text', logfile=sys.stdout.buffer, 
echo=False)
+
+c.expect(r'([0-9]+)\. html2text', timeout=10)
+c.expect('Pick a number')
+c.expect(r'([0-9]+)\. [^ ]*(openSUSE-Tumbleweed-Oss|Main Repository)', 
timeout=10)
+c.expect('Installing from existing repo', timeout=10)
+c.expect('Continue?', timeout=60)
+c.interact()
+c.wait()
+c.close()
+print()
+assert c.exitstatus == 0, f'Exit code: {c.exitstatus}'
+subprocess.check_call(['rpm', '-qi', 'html2text'])
+
+
+c = pexpect.spawn('./bin/opi -n zfs', logfile=sys.stdout.buffer, echo=False)
+
+c.expect(r'([0-9]+)\. zfs', timeout=10)
+c.expect('Pick a number')
+c.expect(r'([0-9]+)\. [^ ]*(filesystems)', timeout=10)
+c.expect('Adding repo \'filesystems\'', timeout=10)
+c.expect('Continue?', timeout=60)
+c.interact()
+c.wait()
+c.close()
+print()
+assert c.exitstatus == 0, f'Exit code: {c.exitstatus}'
+subprocess.check_call(['rpm', '-qi', 'zfs'])

Reply via email to