Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package release-compare for openSUSE:Factory 
checked in at 2023-06-13 16:11:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/release-compare (Old)
 and      /work/SRC/openSUSE:Factory/.release-compare.new.15902 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "release-compare"

Tue Jun 13 16:11:08 2023 rev:20 rq:1092853 version:0.9.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/release-compare/release-compare.changes  
2023-05-30 22:03:18.415316002 +0200
+++ 
/work/SRC/openSUSE:Factory/.release-compare.new.15902/release-compare.changes   
    2023-06-13 16:11:09.319467777 +0200
@@ -1,0 +2,9 @@
+Tue Jun 13 10:28:08 UTC 2023 - Joachim Gleissner <[email protected]>
+
+- 0.9.1
+  * Capture package version changes
+  * Fix version match regular epxpression
+  * Convert (back) to single file script
+  * Add runtime dependency on setuptools to spec file
+
+-------------------------------------------------------------------

Old:
----
  release-compare-0.9.0.obscpio

New:
----
  release-compare-0.9.1.obscpio

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

Other differences:
------------------
++++++ release-compare.spec ++++++
--- /var/tmp/diff_new_pack.wbCQVD/_old  2023-06-13 16:11:09.823470750 +0200
+++ /var/tmp/diff_new_pack.wbCQVD/_new  2023-06-13 16:11:09.827470773 +0200
@@ -21,11 +21,12 @@
 License:        GPL-3.0-or-later
 Group:          Development/Tools/Building
 URL:            https://github.com/openSUSE/release-compare
-Version:        0.9.0
+Version:        0.9.1
 Release:        0
 Source:         %name-%version.tar.xz
 BuildArch:      noarch
 Requires:       python3-PyYAML
+Requires:       python3-setuptools
 BuildRequires:  python3-PyYAML
 BuildRequires:  python3-pytest
 BuildRequires:  python3-setuptools

++++++ _service ++++++
--- /var/tmp/diff_new_pack.wbCQVD/_old  2023-06-13 16:11:09.855470939 +0200
+++ /var/tmp/diff_new_pack.wbCQVD/_new  2023-06-13 16:11:09.859470962 +0200
@@ -3,8 +3,8 @@
     <param name="url">https://github.com/openSUSE/release-compare.git</param>
     <param name="scm">git</param>
 
-    <param name="version">0.9.0</param>
-    <param name="revision">0.9.0</param>
+    <param name="version">0.9.1</param>
+    <param name="revision">0.9.1</param>
 
     <param name="extract">release-compare.spec</param>
   </service>

++++++ release-compare-0.9.0.obscpio -> release-compare-0.9.1.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/Makefile 
new/release-compare-0.9.1/Makefile
--- old/release-compare-0.9.0/Makefile  2023-05-26 16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/Makefile  2023-06-13 12:10:15.000000000 +0200
@@ -4,5 +4,4 @@
 
 install:
        mkdir -p $(HOOKDIR)
-       install -m 755 create_changelog $(HOOKDIR)
-       cp -r release_compare $(HOOKDIR)
+       install -m 755 release_compare.py $(HOOKDIR)/release_compare
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/create_changelog 
new/release-compare-0.9.1/create_changelog
--- old/release-compare-0.9.0/create_changelog  2023-05-26 16:35:18.000000000 
+0200
+++ new/release-compare-0.9.1/create_changelog  1970-01-01 01:00:00.000000000 
+0100
@@ -1,39 +0,0 @@
-#!/usr/bin/python3
-
-# Copyright (c) 2023 SUSE Software Solutions
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Library General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library  is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Library General Public License for more details.
-#
-# You should have received a copy of the GNU Library General Public
-# License along with this library; see the file COPYING.LIB. If not,
-# write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307, USA
-
-import argparse
-import os
-import sys
-sys.path.insert(0, os.path.dirname(__file__))
-
-import release_compare
-from release_compare.version import __version__
-
-parser = argparse.ArgumentParser(
-    prog='create_changelog',
-    description='Generate change log data from image build'
-)
-parser.add_argument('--version', action='version', version=__version__)
-parser.add_argument(
-    '--root',
-    default='/.build.packages',
-    help="Root directory of packages build info [default: /.build.packages]"
-)
-args = parser.parse_args()
-release_compare.main(args.root)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/release-compare.spec 
new/release-compare-0.9.1/release-compare.spec
--- old/release-compare-0.9.0/release-compare.spec      2023-05-26 
16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/release-compare.spec      2023-06-13 
12:10:15.000000000 +0200
@@ -25,9 +25,10 @@
 Source:         %name-%version.tar.xz
 BuildArch:      noarch
 Requires:       python3-PyYAML
-BuildRequires:  python3-setuptools
+Requires:       python3-setuptools
 BuildRequires:  python3-pytest
 BuildRequires:  python3-PyYAML
+BuildRequires:  python3-setuptools
 
 %description
 This package contains scripts to create changelog files relative
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/release_compare/__init__.py 
new/release-compare-0.9.1/release_compare/__init__.py
--- old/release-compare-0.9.0/release_compare/__init__.py       2023-05-26 
16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/release_compare/__init__.py       1970-01-01 
01:00:00.000000000 +0100
@@ -1,616 +0,0 @@
-# Copyright (c) 2023 SUSE Software Solutions
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Library General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library  is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Library General Public License for more details.
-#
-# You should have received a copy of the GNU Library General Public
-# License along with this library; see the file COPYING.LIB. If not,
-# write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307, USA
-import difflib
-import glob
-import json
-import logging
-import os
-import pathlib
-import re
-import shutil
-import subprocess
-import tempfile
-import textwrap
-import traceback
-import yaml
-import xml.etree.ElementTree as ET
-from setuptools._vendor.packaging import version as pkg_version
-from urllib.parse import urlparse
-from release_compare.version import __log_version__
-
-LOG = None
-CONFIG = None
-ROOT = None
-
-
-class Config:
-    def __init__(self, config_file):
-        # defaults
-        self.output_text = True
-        self.output_yaml = False
-        self.output_json = True
-        self.package_list = 'new'
-        self.anonymize_changes = True
-        self.debug = False
-
-        if os.path.exists(config_file):
-            tree = ET.parse(config_file)
-            root = tree.getroot()
-            for elem in root.findall('./param'):
-                param = elem.get('name')
-                value = str(elem.text)
-                if param == 'output_text':
-                    self.output_text = value.lower() == 'true'
-                elif param == 'output_json':
-                    self.output_json = value.lower() == 'true'
-                elif param == 'output_yaml':
-                    self.output_yaml = value.lower() == 'true'
-                elif param == 'package_list':
-                    if value not in ['always', 'new', 'never']:
-                        raise Exception(
-                            'Unknown config value "{}" for parameter 
"package_list"'.format(value)
-                        )
-                    self.package_list = value
-                elif param == 'anonymize_changes':
-                    self.anonymize_changes = value.lower() == 'true'
-                elif param == 'debug':
-                    self.debug = value.lower() == 'true'
-                else:
-                    LOG.warning('Unknown config parameter "{}"'.format(param))
-
-
-class PackageInfo:
-    def __init__(self, name, version, release=None, arch=None, source=None, 
repo=None):
-        self.name = name
-        self.version = version
-        self.release = release
-        self.arch = arch
-        self.source = source
-        self.repo = repo
-
-    def __eq__(self, s):
-        return self.name == s
-
-    def __repr__(self):
-        return self.name
-
-    def __str__(self):
-        return self.name
-
-    def get_src_name(self):
-        # source URL format is 
obs://build.suse.de/SUSE:PROJECT:SUB/repo/hash-pkg_name[.maint_prj]
-        # .maint_prj is only there when PROJECT is a maintenance project
-        # since package names may contain dots, simply cutting off trailing 
'\..*' is not an option
-        p = pathlib.Path(urlparse(self.source).path)
-        is_maint = 'Maintenance' in p.parts[1]
-        p_name = p.name[p.name.find('-')+1:]
-        if is_maint:
-            last_dot = p_name.rfind('.')
-            if last_dot == -1:
-                LOG.warn('expected maintenance suffix in "{}", 
continuing'.format(p.name))
-            else:
-                p_name = p_name[:last_dot]
-        return p_name
-
-    def get_path(self, pkg_root):
-        filename_long = '{name}-{version}-{release}.{arch}.rpm'.format(
-            name=self.name,
-            version=self.version,
-            release=self.release,
-            arch=self.arch
-        )
-        filename_short = '{name}.rpm'.format(name=self.name)
-        if not self.repo:
-            # no reliable path info available, as the KIWI cache uses the 
release
-            # project names not the maintenance names, and the release project
-            # names only exist with : transformed to _ in the .packages file;
-            # instead of guessing the real project name, we just scan the whole
-            # tree for the package with the right name. There should be only
-            # one anyway.
-            pkg_path = next(pathlib.Path(pkg_root).rglob(filename_long), None)
-            if not pkg_path:
-                # appliance build in OBS uses short format
-                pkg_path = next(pathlib.Path(pkg_root).rglob(filename_short), 
None)
-            if pkg_path:
-                pkg_path = str(pkg_path)
-        else:
-            pkg_path = os.path.join(pkg_root, self.repo, filename_long)
-            if not os.path.exists(pkg_path):
-                pkg_path = os.path.join(pkg_root, self.repo, filename_short)
-            if not os.path.exists(pkg_path):
-                pkg_path = None
-        return pkg_path
-
-
-def get_packages_from_report_file(report_file):
-    tree = ET.parse(report_file)
-    root = tree.getroot()
-    pkgs = []
-
-    for item in root.findall('./binary'):
-        pkg_name = item.get('name')
-        if pkg_name:
-            pkgs.append(
-                PackageInfo(
-                    name=pkg_name,
-                    version=item.get('version'),
-                    release=item.get('release'),
-                    arch=item.get('binaryarch'),
-                    source=item.get('disturl'),
-                    repo=os.path.join(item.get('project'), 
item.get('repository'))
-                )
-            )
-
-    return pkgs
-
-
-def get_packages_from_packages_file(packages_file):
-    pkgs = []
-    line_no = 0
-
-    with open(packages_file, 'r') as ins:
-        line = ins.readline()
-        while line:
-            records = line.split('|')
-            line_no += 1
-            try:
-                if records[5] == '(none)' or records[5] == '':
-                    LOG.debug('ignoring package "{}", no source 
information'.format(records[0]))
-                else:
-                    pkgs.append(
-                        PackageInfo(
-                            name=records[0],
-                            version=records[2],
-                            release=records[3],
-                            arch=records[4],
-                            source=records[5]
-                        )
-                    )
-            except IndexError:
-                LOG.warn('line no {} in {} does not have expected format, 
skipping'.format(
-                    line_no, packages_file)
-                )
-            line = ins.readline()
-
-    return pkgs
-
-
-def get_packages_from_file(data_file):
-    if data_file.endswith('.report'):
-        return get_packages_from_report_file(data_file)
-    elif data_file.endswith('.packages'):
-        return get_packages_from_packages_file(data_file)
-    else:
-        raise RuntimeError('{}: unkown report file format'.format(data_file))
-
-
-def write_pkg_info(pkg, outdir):
-    rpm = pkg.get_path(os.path.join(ROOT, 'SOURCES', 'repos'))
-    if not rpm or not os.path.exists(rpm):
-        LOG.warning('could not find rpm for package "{}", 
skipping'.format(pkg.name))
-    else:
-        rpm_src_name = pkg.get_src_name()
-        with open(os.path.join(outdir, 'changelogs', rpm_src_name), 'w', 
encoding='UTF-8') as outf:
-            subprocess.Popen(
-                [
-                    'rpm', '-qp', rpm, '--changelog', '--nodigest', 
'--nosignature'
-                ],
-                env={'LC_ALL': 'C.UTF-8'},
-                stdout=outf
-            )
-        with open(os.path.join(outdir, 'rpms', pkg.name), 'w') as outf:
-            outf.write('{}-{}'.format(pkg.version, pkg.release))
-
-
-def get_pkg_changelog(pkg):
-    rpm = pkg.get_path(os.path.join(ROOT, 'SOURCES', 'repos'))
-    if not rpm or not os.path.exists(rpm):
-        LOG.warning('could not find rpm for package "{}", cannot read 
changelog'.format(pkg.name))
-        return None
-    else:
-        proc = subprocess.Popen(
-            [
-                'rpm', '-qp', rpm, '--changelog', '--nodigest', '--nosignature'
-            ],
-            env={'LC_ALL': 'C.UTF-8'},
-            stdout=subprocess.PIPE
-        )
-        return [x.decode('utf-8').rstrip('\n') for x in 
proc.stdout.readlines()]
-
-
-def get_matching_files(search_dir, regex):
-    match_re = re.compile(regex)
-    files = os.listdir(search_dir)
-    matches = []
-    for f in files:
-        if match_re.fullmatch(f):
-            matches.append(f)
-    return matches
-
-
-def get_latest_obsgendiff_version(filenames):
-    if len(filenames) == 1:
-        return filenames[0]
-    version_re = re.compile(r'(-)([0-9]+(\.[0-9]+)+)(-)')
-    build_re = re.compile(r'(Build)([0-9]+(\.[0-9]+)?)')
-    last_version = pkg_version.Version('0.0.0')
-    last_build = pkg_version.Version('0.0')
-    latest = None
-    LOG.debug('finding latest obsgendiff')
-    for f in filenames:
-        LOG.debug('  considering {}'.format(f))
-        cur_version = 0
-        cur_build = 0
-        version_match = version_re.search(f)
-        build_match = build_re.search(f)
-        if version_match:
-            cur_version = pkg_version.parse(version_match.group(2))
-        if build_match:
-            cur_build = pkg_version.parse(build_match.group(2))
-        if (
-                (cur_version > last_version) or
-                (cur_version == last_version and cur_build > last_build)
-        ):
-            latest = f
-            last_version = cur_version
-            last_build = cur_build
-            LOG.debug('  new candidate {}'.format(f))
-    return latest
-
-
-def extract_old_obsgendiff(report_file, outdir):
-    image_name_full = pathlib.Path(report_file).stem
-    build_match = re.search(r'(Build)([0-9]+(\.[0-9]+)?)?', image_name_full)
-    if build_match:
-        obsgendiff_regex = r'{}Build[0-9]+(\.[0-9]+){}.obsgendiff'.format(
-            re.escape(image_name_full[:build_match.start()]),
-            re.escape(image_name_full[build_match.end():])
-        )
-    else:
-        # no build number fallback (e.g. Jump ftp tree)
-        LOG.debug('{} does not contain a build number'.format(report_file))
-        if image_name_full.endswith('-Media1'):
-            obsgendiff_regex = 
r'{}-Build[0-9]+(\.[0-9]+)-Media1.obsgendff'.format(
-                re.escape(image_name_full[:-7])
-            )
-        else:
-            LOG.warning(
-                '{} no build number and not a Media report file, 
skipping'.format(report_file)
-            )
-            return None
-    LOG.debug('using regex "{}" to select old 
obsgendiff'.format(obsgendiff_regex))
-    src_matches = get_matching_files(os.path.join(ROOT, 'SOURCES'), 
obsgendiff_regex)
-    if not src_matches:
-        LOG.debug(
-            'no old obsgendiff found for "{}", trying for older 
versions'.format(image_name_full)
-        )
-        version_match = re.search(r'-[0-9]+(\\.[0-9]+)+', obsgendiff_regex)
-        if version_match:
-            obsgendiff_regex = r'{}-[0-9]+(\.[0-9]+)+{}'.format(
-                obsgendiff_regex[:version_match.start()],
-                obsgendiff_regex[version_match.end():]
-            )
-            LOG.debug('using regex "{}" to select old 
obsgendiff'.format(obsgendiff_regex))
-            src_matches = get_matching_files(os.path.join(ROOT, 'SOURCES'), 
obsgendiff_regex)
-        else:
-            LOG.warning('no version number found in 
"{}"'.format(image_name_full))
-            return None
-    obsgendiff = get_latest_obsgendiff_version(src_matches)
-    if not obsgendiff:
-        LOG.warning('no old obsgendiff found for "{}"'.format(image_name_full))
-        return None
-    extract_dir = os.path.join(outdir, 'obsgendiff.released')
-    os.mkdir(extract_dir)
-    LOG.info('extracting {}'.format(obsgendiff))
-    subprocess.call(['tar', 'xf', os.path.join(ROOT, 'SOURCES', obsgendiff), 
'-C', extract_dir])
-    return os.path.join(outdir, extract_dir)
-
-
-def load_file(input_file, loader):
-    try:
-        with open(input_file, 'r') as inf:
-            return loader(inf)
-    except Exception as error:
-        LOG.warning('error loading {} ({})'.format(input_file, str(error)))
-        if CONFIG.debug:
-            print(traceback.format_exc())
-        return None
-
-
-def parse_old_obsgendiff(report_file, tmpdir):
-    extract_path = extract_old_obsgendiff(report_file, tmpdir)
-    if not extract_path:
-        return [], {}, None
-    pkgs = []
-    changelogs = {}
-    rpms = os.listdir(os.path.join(extract_path, 'rpms'))
-
-    for rpm in rpms:
-        with open(os.path.join(extract_path, 'rpms', rpm), 'r') as in_file:
-            fullver = in_file.read()
-        pkgs.append(PackageInfo(rpm, *fullver.split('-')))
-
-    changes_files = os.listdir(os.path.join(extract_path, 'changelogs'))
-    for changes_file in changes_files:
-        with open(os.path.join(extract_path, 'changelogs', changes_file),
-                  'r', encoding='utf-8') as in_file:
-            changelogs[changes_file] = [x.rstrip('\n') for x in 
in_file.readlines()]
-
-    history = None
-    history_file = os.path.join(extract_path, 'image_changes.json')
-    loader = json.load
-    if not os.path.exists(history_file):
-        history_file = os.path.join(extract_path, 'image_changes.yaml')
-        loader = yaml.safe_load
-    if not os.path.exists(history_file):
-        LOG.warning('No image version history in old obsgendiff')
-    else:
-        history = load_file(history_file, loader)
-    return pkgs, changelogs, history
-
-
-def compare_changelogs(changes_old, changes_current):
-    if not changes_old or not changes_current:
-        # unless there was a problem with package query or with
-        # the old obsgendiff, this should not happen
-        return 'n/a'
-    differ = difflib.Differ()
-    changes = ''
-    delta = differ.compare(changes_old, changes_current)
-
-    if CONFIG.anonymize_changes:
-        email_re = re.compile(r'\+ \* .*@.*')
-    else:
-        email_re = None
-    for line in delta:
-        if line.startswith('+ '):
-            if not email_re or not email_re.match(line):
-                changes += line[2:] + '\n'
-        else:
-            # stop once we've reached the first line that is not an addition
-            # existing change log entries are not supposed to be altered anyway
-            break
-    return changes.rstrip('\n')
-
-
-def get_changelog_data(new_pkgs, new_changelogs, old_pkgs, old_changelogs):
-    cl_dict = {
-        'format-version': __log_version__,
-        'removed': [],
-        'added': [],
-        'source-changes': {},
-        'references': [],
-        'config-changes': {}
-    }
-
-    for pkg in new_pkgs:
-        if pkg not in old_pkgs:
-            cl_dict['added'].append(pkg.name)
-
-    for pkg in old_pkgs:
-        if pkg not in new_pkgs:
-            cl_dict['removed'].append(pkg.name)
-
-    common_logs = []
-    for changelog in new_changelogs:
-        if changelog in old_changelogs:
-            common_logs.append(changelog)
-
-    # diff package changelogs and generate list of CVEs
-    cve_refs = set()
-    cve_re = re.compile(r'CVE-[0-9]{4}-[0-9]+')
-    for clog in common_logs:
-        changes = compare_changelogs(old_changelogs[clog], 
new_changelogs[clog])
-        if changes:
-            cl_dict['source-changes'][clog] = changes
-            cve_matches = cve_re.findall(changes)
-            for cve_match in cve_matches:
-                cve_refs.add(cve_match)
-    cl_dict['references'] = sorted(cve_refs)
-    return cl_dict
-
-
-def write_changelog_text(output_file, changelog):
-    with open(output_file, 'w', encoding='utf-8') as outf:
-        outf.write('Removed rpms\n')
-        outf.write('============\n')
-        if changelog.get('removed'):
-            outf.write('\n - ')
-            print(*changelog['removed'], sep='\n - ', file=outf)
-        outf.write('\nAdded rpms\n')
-        outf.write('==========\n')
-        if changelog.get('added'):
-            outf.write('\n - ')
-            print(*changelog['added'], sep='\n - ', file=outf)
-        outf.write('\nPackage Source Changes\n')
-        outf.write('======================\n')
-        if changelog.get('source-changes'):
-            outf.write('\n')
-            for src_name, changes in changelog['source-changes'].items():
-                print(src_name, file=outf)
-                outf.write(textwrap.indent(changes, '+ ', lambda line: True))
-                outf.write('\n')
-        outf.write('\nReferences\n')
-        outf.write('==========\n')
-        if changelog.get('references'):
-            outf.write('\n - ')
-            print(*changelog['references'], sep='\n - ', file=outf)
-
-
-def write_changelog_yaml(output_file, changelog):
-    with open(output_file, 'w') as outf:
-        yaml.dump(changelog, outf, default_flow_style=False, sort_keys=False)
-
-
-def write_changelog_json(output_file, changelog):
-    with open(output_file, 'w') as outf:
-        json.dump(changelog, outf, indent=2, sort_keys=False)
-
-
-def match_changes_file(image_name, sources_dir):
-    changes_files = glob.glob(os.path.join(sources_dir, '*changes.json'))
-    if not changes_files:
-        changes_files = glob.glob(os.path.join(sources_dir, '*changes.yaml'))
-    if not changes_files:
-        LOG.warning('No version history file in {}'.format(sources_dir))
-        return None
-    if len(changes_files) == 1:
-        return changes_files[0]
-    else:
-        # figure out right changes files
-        for changes_file in changes_files:
-            profile_name = pathlib.Path(changes_file).name.split('.')[0]
-            if '-'+profile_name+'-' in image_name:
-                return changes_file
-        else:
-            LOG.warning('No changes file in {} matches {}'.format(sources_dir, 
image_name))
-            return None
-
-
-def get_config_changes(new_history_file, old_history):
-    if new_history_file.endswith('.json'):
-        loader = json.load
-    elif new_history_file.endswith('.yaml'):
-        loader = yaml.safe_load
-    else:
-        LOG.warning('unknown format "{}", cannot parse image history')
-        return {}
-
-    LOG.debug('using image version history from {}'.format(new_history_file))
-    history = load_file(new_history_file, loader)
-    config_changes = {}
-    for ver in history:
-        if ver not in old_history:
-            config_changes[ver] = history[ver]
-    return config_changes
-
-
-def main(root) -> None:
-    global ROOT
-    global CONFIG
-    global LOG
-    ROOT = root
-    CONFIG = Config(os.path.join(ROOT, 'SOURCES', '_release_compare'))
-    if CONFIG.debug:
-        log_level = logging.DEBUG
-    else:
-        log_level = logging.INFO
-    logging.basicConfig(level=log_level, format='%(name)s:[%(levelname)s] 
%(message)s')
-    LOG = logging.getLogger('create_changelog')
-
-    report_files = glob.glob(os.path.join(ROOT, 'OTHER', '*.report'))
-    report_files += glob.glob(os.path.join(ROOT, 'KIWI', '*.packages'))
-    report_files += glob.glob(os.path.join(ROOT, 'DOCKER', '*.packages'))
-
-    os.makedirs(os.path.join(ROOT, 'OTHER'), exist_ok=True)
-
-    for report in report_files:
-        if '-Media2' in report or '-Media3' in report:
-            # skip source and debug media
-            continue
-
-        LOG.info('parsing {}'.format(report))
-        pkgs = get_packages_from_file(report)
-        pkg_changelogs = {}
-        image_name = pathlib.Path(report).stem
-
-        for pkg in pkgs:
-            # RPM change logs are identical for all sub packages
-            # so we store and diff them based on source packages names
-            src_name = pkg.get_src_name()
-            if not pkg_changelogs.get(src_name):
-                pkg_changelogs[src_name] = get_pkg_changelog(pkg)
-
-        history_file = match_changes_file(image_name, os.path.join(ROOT, 
'SOURCES'))
-        image_net_new = False
-
-        with tempfile.TemporaryDirectory() as tmpdir:
-            LOG.info('writing package version info and change logs')
-            os.mkdir(os.path.join(tmpdir, 'changelogs'))
-            os.mkdir(os.path.join(tmpdir, 'rpms'))
-
-            for pkg in pkgs:
-                write_pkg_info(pkg, tmpdir)
-
-            if history_file:
-                shutil.copyfile(
-                    history_file,
-                    os.path.join(tmpdir, './image_changes' + 
pathlib.Path(history_file).suffix)
-                )
-            else:
-                LOG.warning('image "{}" does not have a changes 
file'.format(image_name))
-
-            obsgendiff = os.path.join(ROOT, 'OTHER', image_name + 
'.obsgendiff')
-            LOG.info('creating obsgendiff {}'.format(obsgendiff))
-            subprocess.call(['tar', 'cfJ', obsgendiff, '-C', tmpdir, '.'])
-
-            (
-                released_pkgs,
-                released_changelogs,
-                released_history
-            ) = parse_old_obsgendiff(report, tmpdir)
-            changelog_name = 'ChangeLog.' + image_name
-            changelog_data = {}
-
-            if released_pkgs:
-                LOG.info('collecting change information')
-                changelog_data = get_changelog_data(
-                    pkgs,
-                    pkg_changelogs,
-                    released_pkgs,
-                    released_changelogs
-                )
-            else:
-                LOG.warning(
-                    'no information about released packages available, 
treating as net new release'
-                )
-                image_net_new = True
-
-            if (CONFIG.package_list == 'yes' or (image_net_new and 
CONFIG.package_list == 'new')):
-                changelog_data['package-list'] = [
-                    {'name': x.name, 'version': x.version} for x in pkgs
-                ]
-
-            if released_history and history_file:
-                config_changes = get_config_changes(
-                    history_file,
-                    released_history
-                )
-                changelog_data['config-changes'] = config_changes
-            else:
-                LOG.warning(
-                    'no information about released image history, not 
generating config changelog'
-                )
-
-            if CONFIG.output_text:
-                LOG.info('writing {}'.format(changelog_name + '.txt'))
-                write_changelog_text(
-                    os.path.join(ROOT, 'OTHER', changelog_name + '.txt'),
-                    changelog_data
-                )
-            if CONFIG.output_yaml:
-                LOG.info('writing {}'.format(changelog_name + '.yaml'))
-                write_changelog_yaml(
-                    os.path.join(ROOT, 'OTHER', changelog_name + '.yaml'),
-                    changelog_data
-                )
-            if CONFIG.output_json:
-                LOG.info('writing {}'.format(changelog_name + '.json'))
-                write_changelog_json(
-                    os.path.join(ROOT, 'OTHER', changelog_name + '.json'),
-                    changelog_data
-                )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/release_compare/version.py 
new/release-compare-0.9.1/release_compare/version.py
--- old/release-compare-0.9.0/release_compare/version.py        2023-05-26 
16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/release_compare/version.py        1970-01-01 
01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-__version__ = '0.9.0'
-__log_version__ = '2'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/release_compare.py 
new/release-compare-0.9.1/release_compare.py
--- old/release-compare-0.9.0/release_compare.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.1/release_compare.py        2023-06-13 
12:10:15.000000000 +0200
@@ -0,0 +1,651 @@
+#!/usr/bin/python3
+
+# Copyright (c) 2023 SUSE Software Solutions
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library  is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; see the file COPYING.LIB. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA  02111-1307, USA
+import argparse
+import difflib
+import glob
+import json
+import logging
+import os
+import pathlib
+import re
+import shutil
+import subprocess
+import tempfile
+import textwrap
+import traceback
+import yaml
+import xml.etree.ElementTree as ET
+from setuptools._vendor.packaging import version as pkg_version
+from urllib.parse import urlparse
+
+__version__ = '0.9.0'
+__log_version__ = '2'
+
+LOG = None
+CONFIG = None
+ROOT = None
+
+
+class Config:
+    def __init__(self, config_file):
+        # defaults
+        self.output_text = True
+        self.output_yaml = False
+        self.output_json = True
+        self.package_list = 'new'
+        self.anonymize_changes = True
+        self.debug = False
+
+        if os.path.exists(config_file):
+            tree = ET.parse(config_file)
+            root = tree.getroot()
+            for elem in root.findall('./param'):
+                param = elem.get('name')
+                value = str(elem.text)
+                if param == 'output_text':
+                    self.output_text = value.lower() == 'true'
+                elif param == 'output_json':
+                    self.output_json = value.lower() == 'true'
+                elif param == 'output_yaml':
+                    self.output_yaml = value.lower() == 'true'
+                elif param == 'package_list':
+                    if value not in ['always', 'new', 'never']:
+                        raise Exception(
+                            'Unknown config value "{}" for parameter 
"package_list"'.format(value)
+                        )
+                    self.package_list = value
+                elif param == 'anonymize_changes':
+                    self.anonymize_changes = value.lower() == 'true'
+                elif param == 'debug':
+                    self.debug = value.lower() == 'true'
+                else:
+                    LOG.warning('Unknown config parameter "{}"'.format(param))
+
+
+class PackageInfo:
+    def __init__(self, name, version, release=None, arch=None, source=None, 
repo=None):
+        self.name = name
+        self.version = version
+        self.release = release
+        self.arch = arch
+        self.source = source
+        self.repo = repo
+
+    def __eq__(self, s):
+        return self.name == s
+
+    def __repr__(self):
+        return self.name
+
+    def __str__(self):
+        return self.name
+
+    def get_src_name(self):
+        # source URL format is 
obs://build.suse.de/SUSE:PROJECT:SUB/repo/hash-pkg_name[.maint_prj]
+        # .maint_prj is only there when PROJECT is a maintenance project
+        # since package names may contain dots, simply cutting off trailing 
'\..*' is not an option
+        p = pathlib.Path(urlparse(self.source).path)
+        is_maint = 'Maintenance' in p.parts[1]
+        p_name = p.name[p.name.find('-')+1:]
+        if is_maint:
+            last_dot = p_name.rfind('.')
+            if last_dot == -1:
+                LOG.warn('expected maintenance suffix in "{}", 
continuing'.format(p.name))
+            else:
+                p_name = p_name[:last_dot]
+        return p_name
+
+    def get_path(self, pkg_root):
+        filename_long = '{name}-{version}-{release}.{arch}.rpm'.format(
+            name=self.name,
+            version=self.version,
+            release=self.release,
+            arch=self.arch
+        )
+        filename_short = '{name}.rpm'.format(name=self.name)
+        if not self.repo:
+            # no reliable path info available, as the KIWI cache uses the 
release
+            # project names not the maintenance names, and the release project
+            # names only exist with : transformed to _ in the .packages file;
+            # instead of guessing the real project name, we just scan the whole
+            # tree for the package with the right name. There should be only
+            # one anyway.
+            pkg_path = next(pathlib.Path(pkg_root).rglob(filename_long), None)
+            if not pkg_path:
+                # appliance build in OBS uses short format
+                pkg_path = next(pathlib.Path(pkg_root).rglob(filename_short), 
None)
+            if pkg_path:
+                pkg_path = str(pkg_path)
+        else:
+            pkg_path = os.path.join(pkg_root, self.repo, filename_long)
+            if not os.path.exists(pkg_path):
+                pkg_path = os.path.join(pkg_root, self.repo, filename_short)
+            if not os.path.exists(pkg_path):
+                pkg_path = None
+        return pkg_path
+
+
+def get_packages_from_report_file(report_file):
+    tree = ET.parse(report_file)
+    root = tree.getroot()
+    pkgs = []
+
+    for item in root.findall('./binary'):
+        pkg_name = item.get('name')
+        if pkg_name:
+            pkgs.append(
+                PackageInfo(
+                    name=pkg_name,
+                    version=item.get('version'),
+                    release=item.get('release'),
+                    arch=item.get('binaryarch'),
+                    source=item.get('disturl'),
+                    repo=os.path.join(item.get('project'), 
item.get('repository'))
+                )
+            )
+
+    return pkgs
+
+
+def get_packages_from_packages_file(packages_file):
+    pkgs = []
+    line_no = 0
+
+    with open(packages_file, 'r') as ins:
+        line = ins.readline()
+        while line:
+            records = line.split('|')
+            line_no += 1
+            try:
+                if records[5] == '(none)' or records[5] == '':
+                    LOG.debug('ignoring package "{}", no source 
information'.format(records[0]))
+                else:
+                    pkgs.append(
+                        PackageInfo(
+                            name=records[0],
+                            version=records[2],
+                            release=records[3],
+                            arch=records[4],
+                            source=records[5]
+                        )
+                    )
+            except IndexError:
+                LOG.warn('line no {} in {} does not have expected format, 
skipping'.format(
+                    line_no, packages_file)
+                )
+            line = ins.readline()
+
+    return pkgs
+
+
+def get_packages_from_file(data_file):
+    if data_file.endswith('.report'):
+        return get_packages_from_report_file(data_file)
+    elif data_file.endswith('.packages'):
+        return get_packages_from_packages_file(data_file)
+    else:
+        raise RuntimeError('{}: unkown report file format'.format(data_file))
+
+
+def write_pkg_info(pkg, outdir):
+    rpm = pkg.get_path(os.path.join(ROOT, 'SOURCES', 'repos'))
+    if not rpm or not os.path.exists(rpm):
+        LOG.warning('could not find rpm for package "{}", 
skipping'.format(pkg.name))
+    else:
+        rpm_src_name = pkg.get_src_name()
+        with open(os.path.join(outdir, 'changelogs', rpm_src_name), 'w', 
encoding='UTF-8') as outf:
+            subprocess.Popen(
+                [
+                    'rpm', '-qp', rpm, '--changelog', '--nodigest', 
'--nosignature'
+                ],
+                env={'LC_ALL': 'C.UTF-8'},
+                stdout=outf
+            )
+        with open(os.path.join(outdir, 'rpms', pkg.name), 'w') as outf:
+            outf.write('{}-{}'.format(pkg.version, pkg.release))
+
+
+def get_pkg_changelog(pkg):
+    rpm = pkg.get_path(os.path.join(ROOT, 'SOURCES', 'repos'))
+    if not rpm or not os.path.exists(rpm):
+        LOG.warning('could not find rpm for package "{}", cannot read 
changelog'.format(pkg.name))
+        return None
+    else:
+        proc = subprocess.Popen(
+            [
+                'rpm', '-qp', rpm, '--changelog', '--nodigest', '--nosignature'
+            ],
+            env={'LC_ALL': 'C.UTF-8'},
+            stdout=subprocess.PIPE
+        )
+        return [x.decode('utf-8').rstrip('\n') for x in 
proc.stdout.readlines()]
+
+
+def get_matching_files(search_dir, regex):
+    match_re = re.compile(regex)
+    files = os.listdir(search_dir)
+    matches = []
+    for f in files:
+        if match_re.fullmatch(f):
+            matches.append(f)
+    return matches
+
+
+def get_latest_obsgendiff_version(filenames):
+    if len(filenames) == 1:
+        return filenames[0]
+    version_re = re.compile(r'(-)([0-9]+(\.[0-9]+)+)(-)')
+    build_re = re.compile(r'(Build)([0-9]+(\.[0-9]+)?)')
+    last_version = pkg_version.Version('0.0.0')
+    last_build = pkg_version.Version('0.0')
+    latest = None
+    LOG.debug('finding latest obsgendiff')
+    for f in filenames:
+        LOG.debug('  considering {}'.format(f))
+        cur_version = 0
+        cur_build = 0
+        version_match = version_re.search(f)
+        build_match = build_re.search(f)
+        if version_match:
+            cur_version = pkg_version.parse(version_match.group(2))
+        if build_match:
+            cur_build = pkg_version.parse(build_match.group(2))
+        if (
+                (cur_version > last_version) or
+                (cur_version == last_version and cur_build > last_build)
+        ):
+            latest = f
+            last_version = cur_version
+            last_build = cur_build
+            LOG.debug('  new candidate {}'.format(f))
+    return latest
+
+
+def extract_old_obsgendiff(report_file, outdir):
+    image_name_full = pathlib.Path(report_file).stem
+    build_match = re.search(r'(Build)([0-9]+(\.[0-9]+)?)?', image_name_full)
+    if build_match:
+        obsgendiff_regex = r'{}Build[0-9]+(\.[0-9]+){}.obsgendiff'.format(
+            re.escape(image_name_full[:build_match.start()]),
+            re.escape(image_name_full[build_match.end():])
+        )
+    else:
+        # no build number fallback (e.g. Jump ftp tree)
+        LOG.debug('{} does not contain a build number'.format(report_file))
+        if image_name_full.endswith('-Media1'):
+            obsgendiff_regex = 
r'{}-Build[0-9]+(\.[0-9]+)-Media1.obsgendff'.format(
+                re.escape(image_name_full[:-7])
+            )
+        else:
+            LOG.warning(
+                '{} no build number and not a Media report file, 
skipping'.format(report_file)
+            )
+            return None
+    LOG.debug('using regex "{}" to select old 
obsgendiff'.format(obsgendiff_regex))
+    src_matches = get_matching_files(os.path.join(ROOT, 'SOURCES'), 
obsgendiff_regex)
+    if not src_matches:
+        LOG.debug(
+            'no old obsgendiff found for "{}", trying for older 
versions'.format(image_name_full)
+        )
+        # matching in a regex string, so we need to match escapes as well, 
hence
+        # all the backslashes
+        version_match = re.search(r'-[0-9]+(\\\.[0-9]+)+\\-', obsgendiff_regex)
+        if version_match:
+            obsgendiff_regex = r'{}-[0-9]+(\.[0-9]+)+-{}'.format(
+                obsgendiff_regex[:version_match.start()],
+                obsgendiff_regex[version_match.end():]
+            )
+            LOG.debug('using regex "{}" to select old 
obsgendiff'.format(obsgendiff_regex))
+            src_matches = get_matching_files(os.path.join(ROOT, 'SOURCES'), 
obsgendiff_regex)
+        else:
+            LOG.warning('no version number found in 
"{}"'.format(image_name_full))
+            return None
+    obsgendiff = get_latest_obsgendiff_version(src_matches)
+    if not obsgendiff:
+        LOG.warning('no old obsgendiff found for "{}"'.format(image_name_full))
+        return None
+    extract_dir = os.path.join(outdir, 'obsgendiff.released')
+    os.mkdir(extract_dir)
+    LOG.info('extracting {}'.format(obsgendiff))
+    subprocess.call(['tar', 'xf', os.path.join(ROOT, 'SOURCES', obsgendiff), 
'-C', extract_dir])
+    return os.path.join(outdir, extract_dir)
+
+
+def load_file(input_file, loader):
+    try:
+        with open(input_file, 'r') as inf:
+            return loader(inf)
+    except Exception as error:
+        LOG.warning('error loading {} ({})'.format(input_file, str(error)))
+        if CONFIG.debug:
+            print(traceback.format_exc())
+        return None
+
+
+def parse_old_obsgendiff(report_file, tmpdir):
+    extract_path = extract_old_obsgendiff(report_file, tmpdir)
+    if not extract_path:
+        return [], {}, None
+    pkgs = []
+    changelogs = {}
+    rpms = os.listdir(os.path.join(extract_path, 'rpms'))
+
+    for rpm in rpms:
+        with open(os.path.join(extract_path, 'rpms', rpm), 'r') as in_file:
+            fullver = in_file.read()
+        pkgs.append(PackageInfo(rpm, *fullver.split('-')))
+
+    changes_files = os.listdir(os.path.join(extract_path, 'changelogs'))
+    for changes_file in changes_files:
+        with open(os.path.join(extract_path, 'changelogs', changes_file),
+                  'r', encoding='utf-8') as in_file:
+            changelogs[changes_file] = [x.rstrip('\n') for x in 
in_file.readlines()]
+
+    history = None
+    history_file = os.path.join(extract_path, 'image_changes.json')
+    loader = json.load
+    if not os.path.exists(history_file):
+        history_file = os.path.join(extract_path, 'image_changes.yaml')
+        loader = yaml.safe_load
+    if not os.path.exists(history_file):
+        LOG.warning('No image version history in old obsgendiff')
+    else:
+        history = load_file(history_file, loader)
+    return pkgs, changelogs, history
+
+
+def compare_changelogs(changes_old, changes_current):
+    if not changes_old or not changes_current:
+        # unless there was a problem with package query or with
+        # the old obsgendiff, this should not happen
+        return 'n/a'
+    differ = difflib.Differ()
+    changes = ''
+    delta = differ.compare(changes_old, changes_current)
+
+    if CONFIG.anonymize_changes:
+        email_re = re.compile(r'\+ \* .*@.*')
+    else:
+        email_re = None
+    for line in delta:
+        if line.startswith('+ '):
+            if not email_re or not email_re.match(line):
+                changes += line[2:] + '\n'
+        else:
+            # stop once we've reached the first line that is not an addition
+            # existing change log entries are not supposed to be altered anyway
+            break
+    return changes.rstrip('\n')
+
+
+def get_changelog_data(new_pkgs, new_changelogs, old_pkgs, old_changelogs):
+    cl_dict = {
+        'format-version': __log_version__,
+        'removed': [],
+        'added': [],
+        'source-changes': {},
+        'version-changes': {},
+        'references': [],
+        'config-changes': {}
+    }
+
+    for pkg in new_pkgs:
+        for old_pkg in old_pkgs:
+            if pkg == old_pkg:
+                if pkg.version != old_pkg.version or pkg.release != 
old_pkg.release:
+                    cl_dict['version-changes'][pkg.name] = {
+                        'version': pkg.version,
+                        'build': pkg.release
+                    }
+                break
+        else:
+            cl_dict['added'].append(pkg.name)
+
+    for pkg in old_pkgs:
+        if pkg not in new_pkgs:
+            cl_dict['removed'].append(pkg.name)
+
+    common_logs = []
+    for changelog in new_changelogs:
+        if changelog in old_changelogs:
+            common_logs.append(changelog)
+
+    # diff package changelogs and generate list of CVEs
+    cve_refs = set()
+    cve_re = re.compile(r'CVE-[0-9]{4}-[0-9]+')
+    for clog in common_logs:
+        changes = compare_changelogs(old_changelogs[clog], 
new_changelogs[clog])
+        if changes:
+            cl_dict['source-changes'][clog] = changes
+            cve_matches = cve_re.findall(changes)
+            for cve_match in cve_matches:
+                cve_refs.add(cve_match)
+    cl_dict['references'] = sorted(cve_refs)
+    return cl_dict
+
+
+def write_changelog_text(output_file, changelog):
+    with open(output_file, 'w', encoding='utf-8') as outf:
+        outf.write('Removed rpms\n')
+        outf.write('============\n')
+        if changelog.get('removed'):
+            outf.write('\n - ')
+            print(*changelog['removed'], sep='\n - ', file=outf)
+        outf.write('\nAdded rpms\n')
+        outf.write('==========\n')
+        if changelog.get('added'):
+            outf.write('\n - ')
+            print(*changelog['added'], sep='\n - ', file=outf)
+        outf.write('\nPackage Source Changes\n')
+        outf.write('======================\n')
+        if changelog.get('source-changes'):
+            outf.write('\n')
+            for src_name, changes in changelog['source-changes'].items():
+                print(src_name, file=outf)
+                outf.write(textwrap.indent(changes, '+ ', lambda line: True))
+                outf.write('\n')
+        outf.write('\nReferences\n')
+        outf.write('==========\n')
+        if changelog.get('references'):
+            outf.write('\n - ')
+            print(*changelog['references'], sep='\n - ', file=outf)
+
+
+def write_changelog_yaml(output_file, changelog):
+    with open(output_file, 'w') as outf:
+        yaml.dump(changelog, outf, default_flow_style=False, sort_keys=False)
+
+
+def write_changelog_json(output_file, changelog):
+    with open(output_file, 'w') as outf:
+        json.dump(changelog, outf, indent=2, sort_keys=False)
+
+
+def match_changes_file(image_name, sources_dir):
+    changes_files = glob.glob(os.path.join(sources_dir, '*changes.json'))
+    if not changes_files:
+        changes_files = glob.glob(os.path.join(sources_dir, '*changes.yaml'))
+    if not changes_files:
+        LOG.warning('No version history file in {}'.format(sources_dir))
+        return None
+    if len(changes_files) == 1:
+        return changes_files[0]
+    else:
+        # figure out right changes files
+        for changes_file in changes_files:
+            profile_name = pathlib.Path(changes_file).name.split('.')[0]
+            if '-'+profile_name+'-' in image_name:
+                return changes_file
+        else:
+            LOG.warning('No changes file in {} matches {}'.format(sources_dir, 
image_name))
+            return None
+
+
+def get_config_changes(new_history_file, old_history):
+    if new_history_file.endswith('.json'):
+        loader = json.load
+    elif new_history_file.endswith('.yaml'):
+        loader = yaml.safe_load
+    else:
+        LOG.warning('unknown format "{}", cannot parse image history')
+        return {}
+
+    LOG.debug('using image version history from {}'.format(new_history_file))
+    history = load_file(new_history_file, loader)
+    config_changes = {}
+    for ver in history:
+        if ver not in old_history:
+            config_changes[ver] = history[ver]
+    return config_changes
+
+
+def create_changelog(root) -> None:
+    global ROOT
+    global CONFIG
+    global LOG
+    ROOT = root
+    CONFIG = Config(os.path.join(ROOT, 'SOURCES', '_release_compare'))
+    if CONFIG.debug:
+        log_level = logging.DEBUG
+    else:
+        log_level = logging.INFO
+    logging.basicConfig(level=log_level, format='%(name)s:[%(levelname)s] 
%(message)s')
+    LOG = logging.getLogger('create_changelog')
+
+    report_files = glob.glob(os.path.join(ROOT, 'OTHER', '*.report'))
+    report_files += glob.glob(os.path.join(ROOT, 'KIWI', '*.packages'))
+    report_files += glob.glob(os.path.join(ROOT, 'DOCKER', '*.packages'))
+
+    os.makedirs(os.path.join(ROOT, 'OTHER'), exist_ok=True)
+
+    for report in report_files:
+        if '-Media2' in report or '-Media3' in report:
+            # skip source and debug media
+            continue
+
+        LOG.info('parsing {}'.format(report))
+        pkgs = get_packages_from_file(report)
+        pkg_changelogs = {}
+        image_name = pathlib.Path(report).stem
+
+        for pkg in pkgs:
+            # RPM change logs are identical for all sub packages
+            # so we store and diff them based on source packages names
+            src_name = pkg.get_src_name()
+            if not pkg_changelogs.get(src_name):
+                pkg_changelogs[src_name] = get_pkg_changelog(pkg)
+
+        history_file = match_changes_file(image_name, os.path.join(ROOT, 
'SOURCES'))
+        image_net_new = False
+
+        with tempfile.TemporaryDirectory() as tmpdir:
+            LOG.info('writing package version info and change logs')
+            os.mkdir(os.path.join(tmpdir, 'changelogs'))
+            os.mkdir(os.path.join(tmpdir, 'rpms'))
+
+            for pkg in pkgs:
+                write_pkg_info(pkg, tmpdir)
+
+            if history_file:
+                shutil.copyfile(
+                    history_file,
+                    os.path.join(tmpdir, './image_changes' + 
pathlib.Path(history_file).suffix)
+                )
+            else:
+                LOG.warning('image "{}" does not have a changes 
file'.format(image_name))
+
+            obsgendiff = os.path.join(ROOT, 'OTHER', image_name + 
'.obsgendiff')
+            LOG.info('creating obsgendiff {}'.format(obsgendiff))
+            subprocess.call(['tar', 'cfJ', obsgendiff, '-C', tmpdir, '.'])
+
+            (
+                released_pkgs,
+                released_changelogs,
+                released_history
+            ) = parse_old_obsgendiff(report, tmpdir)
+            changelog_name = 'ChangeLog.' + image_name
+            changelog_data = {}
+
+            if released_pkgs:
+                LOG.info('collecting change information')
+                changelog_data = get_changelog_data(
+                    pkgs,
+                    pkg_changelogs,
+                    released_pkgs,
+                    released_changelogs
+                )
+            else:
+                LOG.warning(
+                    'no information about released packages available, 
treating as net new release'
+                )
+                image_net_new = True
+
+            if (CONFIG.package_list == 'yes' or (image_net_new and 
CONFIG.package_list == 'new')):
+                changelog_data['package-list'] = [
+                    {'name': x.name, 'version': x.version} for x in pkgs
+                ]
+
+            if released_history and history_file:
+                config_changes = get_config_changes(
+                    history_file,
+                    released_history
+                )
+                changelog_data['config-changes'] = config_changes
+            else:
+                LOG.warning(
+                    'no information about released image history, not 
generating config changelog'
+                )
+
+            if CONFIG.output_text:
+                LOG.info('writing {}'.format(changelog_name + '.txt'))
+                write_changelog_text(
+                    os.path.join(ROOT, 'OTHER', changelog_name + '.txt'),
+                    changelog_data
+                )
+            if CONFIG.output_yaml:
+                LOG.info('writing {}'.format(changelog_name + '.yaml'))
+                write_changelog_yaml(
+                    os.path.join(ROOT, 'OTHER', changelog_name + '.yaml'),
+                    changelog_data
+                )
+            if CONFIG.output_json:
+                LOG.info('writing {}'.format(changelog_name + '.json'))
+                write_changelog_json(
+                    os.path.join(ROOT, 'OTHER', changelog_name + '.json'),
+                    changelog_data
+                )
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        prog='create_changelog',
+        description='Generate change log data from image build'
+    )
+    parser.add_argument('--version', action='version', version=__version__)
+    parser.add_argument(
+        '--root',
+        default='/.build.packages',
+        help="Root directory of packages build info [default: 
/.build.packages]"
+    )
+    args = parser.parse_args()
+    create_changelog(args.root)
+
+
+if __name__ == '__main__':
+    main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.9.0/setup.py 
new/release-compare-0.9.1/setup.py
--- old/release-compare-0.9.0/setup.py  2023-05-26 16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/setup.py  1970-01-01 01:00:00.000000000 +0100
@@ -1,49 +0,0 @@
-#!/usr/bin/env python
-# -*- encoding: utf-8 -*-
-
-from os import path
-from setuptools import setup
-from setuptools.command import sdist as setuptools_sdist
-
-import distutils
-import subprocess
-
-from release_compare.version import __version__
-
-here = path.abspath(path.dirname(__file__))
-with open(path.join(here, 'README.rst'), encoding='utf-8') as readme:
-    long_description = readme.read()
-
-config = {
-    'name': 'release-compare',
-    'long_description': long_description,
-    'long_description_content_type': 'text/x-rst',
-    'description': 'Release Compare - Image Changelog Tool',
-    'author': 'Public Cloud Team',
-    'url': 'https://github.com/openSUSE/release-compare',
-    'download_url':
-        'https://download.opensuse.org',
-    'author_email': '[email protected]',
-    'version': __version__,
-    'license' : 'GPLv3+',
-    'install_requires': [
-        'PyYAML'
-    ],
-    'packages': ['release_compare'],
-    'include_package_data': True,
-    'zip_safe': False,
-    'classifiers': [
-       # classifier: http://pypi.python.org/pypi?%3Aaction=list_classifiers
-       'Development Status :: 2 - Alpha',
-       'Intended Audience :: Developers',
-       'License :: OSI Approved :: '
-       'GNU General Public License v3 or later (GPLv3+)',
-       'Operating System :: POSIX :: Linux',
-       'Programming Language :: Python :: 3.6',
-       'Programming Language :: Python :: 3.8',
-       'Programming Language :: Python :: 3.10',
-       'Topic :: System :: Operating System',
-    ]
-}
-
-setup(**config)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.9.0/test/data/output/ChangeLog.json 
new/release-compare-0.9.1/test/data/output/ChangeLog.json
--- old/release-compare-0.9.0/test/data/output/ChangeLog.json   2023-05-26 
16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/test/data/output/ChangeLog.json   2023-06-13 
12:10:15.000000000 +0200
@@ -19,5 +19,13 @@
         "change": "some new image config change"
       }
     ]
-  }
+  },
+  "version-changes": {
+    "package1": {
+      "build": "1.2",
+      "version": "1.2.3"},
+    "package2": {
+      "build": "3.2",
+      "version": "2.3.1"}
+   }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.9.0/test/data/output/ChangeLog.yaml 
new/release-compare-0.9.1/test/data/output/ChangeLog.yaml
--- old/release-compare-0.9.0/test/data/output/ChangeLog.yaml   2023-05-26 
16:35:18.000000000 +0200
+++ new/release-compare-0.9.1/test/data/output/ChangeLog.yaml   2023-06-13 
12:10:15.000000000 +0200
@@ -14,3 +14,10 @@
   1.0.12:
   - date: '2023-01-02T08:00:00'
     change: 'some new image config change'
+version-changes:
+  package1:
+    build: "1.2"
+    version: "1.2.3"
+  package2:
+    build: "3.2"
+    version: "2.3.1"

++++++ release-compare.obsinfo ++++++
--- /var/tmp/diff_new_pack.wbCQVD/_old  2023-06-13 16:11:09.971471624 +0200
+++ /var/tmp/diff_new_pack.wbCQVD/_new  2023-06-13 16:11:09.975471647 +0200
@@ -1,5 +1,5 @@
 name: release-compare
-version: 0.9.0
-mtime: 1685111718
-commit: b5ee192915699eda95faa39930c384c774bb9cae
+version: 0.9.1
+mtime: 1686651015
+commit: a860541bc2416c01743629d0e37969762ba4a350
 

Reply via email to