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-05-30 22:03:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/release-compare (Old)
 and      /work/SRC/openSUSE:Factory/.release-compare.new.1533 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "release-compare"

Tue May 30 22:03:02 2023 rev:19 rq:1089801 version:0.9.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/release-compare/release-compare.changes  
2022-05-23 15:52:26.990686205 +0200
+++ 
/work/SRC/openSUSE:Factory/.release-compare.new.1533/release-compare.changes    
    2023-05-30 22:03:18.415316002 +0200
@@ -1,0 +2,13 @@
+Fri May 26 15:05:28 UTC 2023 - Joachim Gleissner <[email protected]>
+
+- 0.9.0
+  * Re-write of create_changelog in python
+  * Support for JSON output
+  * Support for handling image config changelog (image history) as
+    produced by keg OBS source service (JSON and YAML formats supported)
+  * Support for including full package list in change log
+  * Parse version number for selecting most recent old obsgendiff
+    (avoids mismatches)
+  * Add configuration options via _release_compare in package source
+
+-------------------------------------------------------------------

Old:
----
  release-compare-0.5.6.obscpio

New:
----
  release-compare-0.9.0.obscpio

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

Other differences:
------------------
++++++ release-compare.spec ++++++
--- /var/tmp/diff_new_pack.uuzXf8/_old  2023-05-30 22:03:18.911318925 +0200
+++ /var/tmp/diff_new_pack.uuzXf8/_new  2023-05-30 22:03:18.915318949 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package release-compare
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -21,10 +21,14 @@
 License:        GPL-3.0-or-later
 Group:          Development/Tools/Building
 URL:            https://github.com/openSUSE/release-compare
-Version:        0.5.6
+Version:        0.9.0
 Release:        0
 Source:         %name-%version.tar.xz
 BuildArch:      noarch
+Requires:       python3-PyYAML
+BuildRequires:  python3-PyYAML
+BuildRequires:  python3-pytest
+BuildRequires:  python3-setuptools
 
 %description
 This package contains scripts to create changelog files relative
@@ -39,15 +43,14 @@
 %build
 
 %install
-mkdir -p $RPM_BUILD_ROOT/usr/lib/build/obsgendiff.d 
$RPM_BUILD_ROOT/%_defaultdocdir/%name
-install -m 0755 create_changelog $RPM_BUILD_ROOT/usr/lib/build/obsgendiff.d/
+make DESTDIR=%{buildroot} PREFIX=%{_prefix}
 
 %check
-# basic syntax check
-bash -n $RPM_BUILD_ROOT/usr/lib/build/obsgendiff.d/create_changelog || exit 1
+pytest
 
 %files
 %license LICENSE
+%doc README.rst
 /usr/lib/build
 
 %changelog

++++++ _service ++++++
--- /var/tmp/diff_new_pack.uuzXf8/_old  2023-05-30 22:03:18.963319231 +0200
+++ /var/tmp/diff_new_pack.uuzXf8/_new  2023-05-30 22:03:18.967319256 +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.5.6</param>
-    <param name="revision">0.5.6</param>
+    <param name="version">0.9.0</param>
+    <param name="revision">0.9.0</param>
 
     <param name="extract">release-compare.spec</param>
   </service>

++++++ release-compare-0.5.6.obscpio -> release-compare-0.9.0.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/Makefile 
new/release-compare-0.9.0/Makefile
--- old/release-compare-0.5.6/Makefile  1970-01-01 01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/Makefile  2023-05-26 16:35:18.000000000 +0200
@@ -0,0 +1,8 @@
+PREFIX?=/usr
+DESTDIR=
+HOOKDIR=$(DESTDIR)$(PREFIX)/lib/build/obsgendiff.d
+
+install:
+       mkdir -p $(HOOKDIR)
+       install -m 755 create_changelog $(HOOKDIR)
+       cp -r release_compare $(HOOKDIR)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/README.rst 
new/release-compare-0.9.0/README.rst
--- old/release-compare-0.5.6/README.rst        1970-01-01 01:00:00.000000000 
+0100
+++ new/release-compare-0.9.0/README.rst        2023-05-26 16:35:18.000000000 
+0200
@@ -0,0 +1,113 @@
+Release Compare
+===============
+
+This project contains a script **create_changelog** that analyzes an Open
+Build Service image build and generated change log information. It is intended
+to be run automatically in Open Build Service via its `obsgendiff` build hook.
+
+When run in an OBS image build environemnt, after the image build is
+completed, **create_changelog** scans the build environment and produces an
+archive containing a list of the packages that were used to create the image,
+plus version information and change log of said packages. The archive has the
+extensions ``.obsgendiff`` and is stored in the directory
+``/.build.packages/OTHER`` in the build environment.
+
+Additionally, in case an **obsgendiff** archive of a previous build is
+available, **create_changelog** produces one or more change log files listing
+the differences in between the two builds. OBS can inject **obsgendiff**
+archives from previous builds into the build environment, if the build target
+repository has a ``releasetarget`` defined with the trigger ``obsgendiff`` in
+its project meta configuration. **create_changelog** expects old
+**obsgendiff** archives in ``/.build.packages/SOURCES``.
+
+Change Log output
+-----------------
+
+**create_changelog** supports three different types of change log format, plain
+text, YAML, and JSON. The produced change log contains the following sections:
+
+1. removed (YAML/JSON) / Removed rpms (text)
+
+   List of packages that are in the old build but are absent in the current 
one.
+
+2. added / Added rpms
+
+   List of packages that are in the current build but are absent in the old 
one.
+
+3. source-changes / Package Source Changes
+
+   This section the added change log entries for every package that is
+   included in both images. Since sub-packages of the same source package have
+   identical change log, the respective source packages of the binary packages
+   are used for this, to avoid duplication. The text format output is the
+   source package name on its own line, followed by a unidiff of the changes
+   (additions only). The YAML and JSON formats use the source package name as
+   keys with the changes assigned to them as a multiline string.
+
+4. references / References
+
+   List of CVE references, obtained by scanning the change logs for CVE tags.
+
+5. config-changes (YAML/JSON only)
+
+   In case both the previous build and the current build contain a file with
+   the image version history, **create_changelog** produces a section 
containing
+   the image configuration changes. The expected input format of the version
+   history file is a YAML or a JSON file with one or more entries like this:
+
+   .. code:: yaml
+
+     version_tag:
+       - date: ISO date string
+         change: One-line change description
+         details: Optional multi-line string description
+       ...
+     ...
+
+   The file name of the image version history file is expected to be of the
+   following scheme: `[<PROFILE>.]changes.{json,yaml}`
+
+   `<PROFILE>` corresponds to build profile in case the image description is
+   multi-build.
+
+6. package-list (YAML/JSON only, optional)
+
+   List of all packages installed in image. Not actually a change log, but
+   potentially useful information, especially for net-new images which
+   naturally do not have a change log.
+
+Configuration
+-------------
+
+The **create_changelog** script accepts an optional configuration file, which
+needs to be named ``_release_compare`` and be added to the image source
+package. The format is as follows:
+
+::
+
+  <config>
+      <param name="output_text">true/false</param>
+      <param name="output_yaml">true/false</param>
+      <param name="output_json">true/false</param>
+      <param name="package_list">always/never/new</param>
+      <param name="anonymize_changes">true/false</param>
+      <param name="debug">true/false</param>
+  </config>
+
+Most should be self-explanatory. Default output modes are ``text`` and
+``json``.  Parameter `package_list` controls whether the full package list is
+included in the change log. The default setting of ``new`` only adds the full
+package list for net new images, i.e. no matching previous **obsgendiff** was
+found for the image in question. `anonymize_changes` if true (the default) will
+cause **create_changelog** to strip packager names and email addresses from the
+generated change log.
+
+Command line usage
+------------------
+
+**create_changelog** is intended to be run as an **obsgendiff** hook in the
+Open Build Service, but it can be used manually. For this purpose, it accepts
+a command line parameter ``--root``, which can be used to change the default
+directory where **create_changelog** expects the package and source
+information, what would be ``/.build.packages`` in a KIWI image build
+environment.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/create_changelog 
new/release-compare-0.9.0/create_changelog
--- old/release-compare-0.5.6/create_changelog  2022-05-02 08:22:20.000000000 
+0200
+++ new/release-compare-0.9.0/create_changelog  2023-05-26 16:35:18.000000000 
+0200
@@ -1,6 +1,6 @@
-#!/bin/bash
+#!/usr/bin/python3
 
-# Copyright (c) 2020 Adrian Schröter <[email protected]>
+# 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
@@ -17,213 +17,23 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place,
 # Suite 330, Boston, MA  02111-1307, USA
 
-#
-# This just basic demo code for now, to be rewritten/completed later
-#
-
-
-# our outut directory
-out=/.build.packages/obsgendiff
-# the former output of last released build
-released=/.build.packages/obsgendiff.released
-
-eol=$'\n'
-shopt -s extglob
-
-diff_to_yaml()
-{
-  while read line ; do
-    if [[ ${line/#-/} = $line && ${line/#+/} = $line ]]; then
-      echo -e -n "$closer"
-      echo -n "  ${line}: \""
-      closer="\"\n"
-      opener=""
-    else
-      # supress deletions (should not really exist)
-      if [[ ${line:0:1} != "-" ]] ; then
-        line="${line:1}"
-        line="${line//\\/\\\\/}"
-        line="${line//\"/\\\"/}"
-        if [ -n "$line" ]; then
-          echo -e -n "${opener}${line}"
-          opener='\\n'
-        fi
-      fi
-    fi
-  done
-  echo -n -e "$closer"
-}
-
-echo "Running obsgendiff data differ..."
-
-# create changelogs based on the packaged rpms
-mkdir -p $out/{rpms,changelogs}
-for report in /.build.packages/OTHER/*.report \
-  /.build.packages/KIWI/*.packages \
-  /.build.packages/DOCKER/*.packages; do
-
-  [ -e "$report" ] || continue
-
-  # skip source and debug media
-  [ "$report" = "${report/-Media2/}" ] || continue
-  [ "$report" = "${report/-Media3/}" ] || continue
-
-  # we need to be able to handle .packages from kiwi appliances
-  # and .report files from product builds. Check which case we have.
-  unset PACKAGES_MODE
-  [ "$report" == "${report%.packages}" ] || PACKAGES_MODE=1
-
-  # find and extract right obsgendiff archive
-  oldobsgendiff=${report%.packages}
-  oldobsgendiff=${oldobsgendiff%.report}.obsgendiff
-  oldobsgendiff=/.build.packages/SOURCES/${oldobsgendiff##*/}
-  # find old obsgendiff with different build number.
-  # try to find a matching name and version first
-  if [ "${oldobsgendiff/-Build*-/}" != "$oldobsgendiff" ]; then
-    oldobsgendiff=`echo ${oldobsgendiff/-Build*-/-Build*-}`
-  elif [ "${oldobsgendiff/-Build*./}" != "$oldobsgendiff" ]; then
-    # kiwi appliance
-    oldobsgendiff=`echo ${oldobsgendiff/-Build*./-Build*.}`
-  elif [ "${oldobsgendiff/-Snapshot*-/}" != "$oldobsgendiff" ]; then
-    # Factory fallback, it gets named to custom -Snapshot file name
-    oldobsgendiff=`echo ${oldobsgendiff/-Build*-/-Snapshot*-}`
-  else
-    # Dropped build number fallback (eg Jump ftp tree)
-    oldobsgendiff=`echo ${oldobsgendiff/-Media1./-Build*-Media1.}`
-  fi
-  # take last one if multiple
-  oldobsgendiff="${oldobsgendiff##* }"
-  if [ ! -e "$oldobsgendiff" ]; then
-    # try to guess where the version is in the string, no guarantee
-    oldobsgendiff=`echo $oldobsgendiff | sed -r -e 
's,-[0123456789]+(\.[0123456789]+)+-,-*([0123456789.])-,'`
-    oldobsgendiff=`echo $oldobsgendiff`
-    oldobsgendiff="${oldobsgendiff##* }"
-  fi
-  if [ -e "$oldobsgendiff" ]; then
-    echo "Extracting $oldobsgendiff"
-    mkdir -p "${released}"
-    tar xf "$oldobsgendiff" -C "${released}"
-  else
-    echo "WARNING no old obsgendiff found: $oldobsgendiff"
-  fi
-
-  if [ -n "$PACKAGES_MODE" ]; then
-    sed -n -e 
"s,\([^|]*\)|\([^|]*\)|\([^|]*\)|\([^|]*\)|\([^|]*\)|\(obs://[^-]*-[^|]*\)|\?.*,\6::::\1-\3-\4.\5.rpm,"
 -e "s,^obs://.*/[^-]*-,,p" "$report"
-  else
-   # product-builder uses single quote, but bs_worker writes it with double 
quote...
-   sed -n -e "s,.*<binary .*disturl=.\(obs://[^-]*-[^ ]*\). 
.*>.*/\(.*\)</binary>$,\1::::\2," -e 's,.*/[^-]*-\(.*::::.*\),\1,p' "$report"
-  fi | while read line; do
-
-     # rpm file name
-     rpm="${line##*::::}"
-     rpm_name=${rpm%-[^-]*-[^-]*.rpm}
-     rpm_version=${rpm#${rpm_name}-}
-     rpm_version=${rpm_version%.*.rpm}
-     # source package name
-     srcname="${line%::::*}"
-     # strip dot suffix from maintenance incidents
-     srcname="${srcname%%.*}"
-
-     # only the worker knows where it was downloaded from....
-     # the disturl may contained a different build repo
-     file=`echo /.build.packages/SOURCES/repos/*/*/*/$rpm`
-     file="${file//${eol}*/}" # bash internal "head -n 1" to be faster
-     if [ ! -e "$file" ]; then
-       # appliance builds server side have the shorter structure
-       file=`echo /.build.packages/SOURCES/repos/*/*/${rpm_name}.rpm`
-       file="${file//${eol}*/}" # bash internal "head -n 1" to be faster
-     fi
-
-     # dump changelog for into source package name to avoid duplicates
-     # hide "first" lines to hide email adresses
-     LC_ALL=C.UTF-8 rpm -qp "$file" --changelog --nodigest --nosignature 
2>/dev/null | sed '/^\* .*@.*/d' > $out/changelogs/${srcname}
-     echo -n "${rpm_version}" > $out/rpms/${rpm_name}
-  done
-
-  # create archive
-  pushd $out
-  gendiff=${report%.report}
-  gendiff=${gendiff%.packages}.obsgendiff
-  tar cfJ /.build.packages/OTHER/${gendiff##*/} .
-  popd
-
-  #
-  # All data is collected at this point
-  # Just generating the changelog files below.
-  #
-
-  # create diff to released archive
-  # NOTE: it had to be published or it won't exist
-  if [ -d "${released}" ]; then
-    # The OBS publisher is publishing all ChangeLog.*.txt files by default.
-    changelog=/.build.packages/OTHER/ChangeLog.${report##*/}
-    changelog=${changelog%.report}
-    changelog_yaml=${changelog%.packages}.yaml
-    changelog=${changelog%.packages}.txt
-    echo ""> $changelog
-    rm -f $changelog_yaml
-
-    # removed packages
-    echo "Removed rpms">> $changelog
-    echo "============">> $changelog
-    echo "">> $changelog
-    echo "removed:" >> $changelog_yaml
-  
-    find "$released/rpms/" -type f | sort | sed "s,^$released/rpms/,," | while 
read file; do
-      [ -e "${out}/rpms/$file" ] || echo " - ${file##*::}" | tee -a $changelog 
| \
-             sed -e 's/^/ /' >> $changelog_yaml
-    done
-    echo "">> $changelog
-  
-    # new packages
-    echo "Added rpms">> $changelog
-    echo "==========">> $changelog
-    echo "">> $changelog
-    echo "added:" >> $changelog_yaml
-    find "$out/rpms/" -type f | sort | sed "s,^$out/rpms/,," | while read 
file; do
-      [ -e "${released}/rpms/$file" ] || echo " - ${file##*::}" | tee -a 
$changelog | \
-             sed -e 's/^/ /' >> $changelog_yaml
-    done
-    echo "">> $changelog
-  
-    # changed packages based on used src rpm name only
-    echo "Package Source Changes">> $changelog
-    echo "======================">> $changelog
-    echo "">> $changelog
-    echo "source-changes:" >> $changelog_yaml
-    # poor mans changelog generation
-    diff -ur "${released}/changelogs/" "$out/changelogs/" \
-    | grep -v '^Only in ' \
-    | grep '^[+-]' \
-    | grep -v '^--- ' \
-    | sed -e's,^+++ .*/\([^\t]*\).*$,\1,' -e 's,^::import::.*::,,' | \
-    tee -a $changelog | diff_to_yaml >> $changelog_yaml
-
-    # version changes of binary packages
-    echo "version-changes:"  >> $changelog_yaml
-    find "$out/rpms/" -type f | sort | sed "s,^$out/rpms/,," | while read 
file; do
-      if [ -e "${released}/rpms/$file" ]; then
-        current_version=`cat ${out}/rpms/${file}`
-        released_version=`cat ${released}/rpms/${file}`
-        if [ "$current_version" != "$released_version" ] ; then
-          echo "  ${file##*::}:"
-          LC_ALL=C.UTF-8 rpm -q "${file##*::}"  --dbpath 
/.build.packages/KIWIROOT*/var/lib/rpm \
-                         --queryformat "    version: %{VERSION}\n    build: 
%{RELEASE}\n"
-        fi
-      fi
-      done >> $changelog_yaml
-
-    echo "" >> $changelog
-    echo "References" >> $changelog
-    echo "==========" >> $changelog
-    echo "" >> $changelog
-    echo "references:" >> $changelog_yaml
-    grep -E -v '^-' $changelog | sed -n -r -e 
's/(.*)(CVE-[[:digit:]]{4}-[[:digit:]]{4}[[:digit:]]*)(.*)/\2/pg' | \
-    sort -u | sed -e 's/^/ - /' | tee -a $changelog | sed -e 's/^/ /' >> 
$changelog_yaml
-  fi
-
-done
-
-
-exit 0
-
+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.5.6/release-compare.spec 
new/release-compare-0.9.0/release-compare.spec
--- old/release-compare-0.5.6/release-compare.spec      2022-05-02 
08:22:20.000000000 +0200
+++ new/release-compare-0.9.0/release-compare.spec      2023-05-26 
16:35:18.000000000 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package release-compare
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -15,16 +15,19 @@
 # Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
-
 Name:           release-compare
 Summary:        Release Compare Script
 License:        GPL-3.0-or-later
 Group:          Development/Tools/Building
 URL:            https://github.com/openSUSE/release-compare
-Version:        0.2
+Version:        0.9.0
 Release:        0
 Source:         %name-%version.tar.xz
 BuildArch:      noarch
+Requires:       python3-PyYAML
+BuildRequires:  python3-setuptools
+BuildRequires:  python3-pytest
+BuildRequires:  python3-PyYAML
 
 %description
 This package contains scripts to create changelog files relative
@@ -33,22 +36,20 @@
 Note: you need to use a releasetarget definition in your OBS repository
       to get this working. And the release target needs to have published 
binaries.
 
-
 %prep
 %setup -q
 
 %build
 
 %install
-mkdir -p $RPM_BUILD_ROOT/usr/lib/build/obsgendiff.d 
$RPM_BUILD_ROOT/%_defaultdocdir/%name
-install -m 0755 create_changelog $RPM_BUILD_ROOT/usr/lib/build/obsgendiff.d/
+make DESTDIR=%{buildroot} PREFIX=%{_prefix}
 
 %check
-# basic syntax check
-bash -n $RPM_BUILD_ROOT/usr/lib/build/obsgendiff.d/create_changelog || exit 1
+pytest
 
 %files
 %license LICENSE
+%doc README.rst
 /usr/lib/build
 
 %changelog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/release_compare/__init__.py 
new/release-compare-0.9.0/release_compare/__init__.py
--- old/release-compare-0.5.6/release_compare/__init__.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/release_compare/__init__.py       2023-05-26 
16:35:18.000000000 +0200
@@ -0,0 +1,616 @@
+# 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.5.6/release_compare/version.py 
new/release-compare-0.9.0/release_compare/version.py
--- old/release-compare-0.5.6/release_compare/version.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/release_compare/version.py        2023-05-26 
16:35:18.000000000 +0200
@@ -0,0 +1,2 @@
+__version__ = '0.9.0'
+__log_version__ = '2'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/setup.cfg 
new/release-compare-0.9.0/setup.cfg
--- old/release-compare-0.5.6/setup.cfg 1970-01-01 01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/setup.cfg 2023-05-26 16:35:18.000000000 +0200
@@ -0,0 +1,5 @@
+[flake8]
+max-line-length = 99
+
+[tool:pytest]
+testpaths = test/unit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/setup.py 
new/release-compare-0.9.0/setup.py
--- old/release-compare-0.5.6/setup.py  1970-01-01 01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/setup.py  2023-05-26 16:35:18.000000000 +0200
@@ -0,0 +1,49 @@
+#!/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.5.6/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.packages
 
new/release-compare-0.9.0/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.packages
--- 
old/release-compare-0.5.6/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.packages
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/release-compare-0.9.0/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.packages
 2023-05-26 16:35:18.000000000 +0200
@@ -0,0 +1,3 @@
+package1|(none)|1.2.3|1.2|x86_64|obs://build.host/standard/12345678-package1|license
+package2|(none)|2.3.1|3.2|x86_64|obs://build.host/Maintenance/12345678-package2.standard|license
+package3|(none)|3.2.3|1.11|x86_64|obs://build.host/standard/12345678-package3|license
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.5.6/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.report
 
new/release-compare-0.9.0/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.report
--- 
old/release-compare-0.5.6/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.report
   1970-01-01 01:00:00.000000000 +0100
+++ 
new/release-compare-0.9.0/test/data/input/KIWI/foo-os.x86_64-1.0.12-profile1-Build.report
   2023-05-26 16:35:18.000000000 +0200
@@ -0,0 +1,5 @@
+<report version="1.0.12" release="1.1" buildtime="1640974042" 
disturl="obs://build.host/project/foo-os:profile1"> 
+  <binary name="package1" version="1.2.3" release="1.2" binaryarch="x86_64" 
disturl="obs://build.host/standard/12345678-package1" license="license" 
project="standard" repository="standard" package="package1" arch="x86_64"/>
+  <binary name="package2" version="2.3.1" release="3.2" binaryarch="x86_64" 
disturl="obs://build.host/standard/12345678-package2.standard" 
license="license" project="Maintenance" repository="standard" 
package="package2" arch="x86_64"/>
+  <binary name="package3" version="3.2.3" release="1.11" binaryarch="x86_64" 
disturl="obs://build.host/standard/12345678-package3" license="license" 
project="standard" repository="standard" package="package3" arch="x86_64"/>
+</report>
Binary files 
old/release-compare-0.5.6/test/data/input/SOURCES/foo-os.x86_64-1.0.12-profile1-Build1.23.obsgendiff
 and 
new/release-compare-0.9.0/test/data/input/SOURCES/foo-os.x86_64-1.0.12-profile1-Build1.23.obsgendiff
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.5.6/test/data/input/SOURCES/profile1.changes.yaml 
new/release-compare-0.9.0/test/data/input/SOURCES/profile1.changes.yaml
--- old/release-compare-0.5.6/test/data/input/SOURCES/profile1.changes.yaml     
1970-01-01 01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/test/data/input/SOURCES/profile1.changes.yaml     
2023-05-26 16:35:18.000000000 +0200
@@ -0,0 +1,14 @@
+{
+  "1.0.12": [
+    {
+      "date": "2023-01-02T08:00:00",
+      "change": "some new image config change"
+    }
+  ],
+  "1.0.11": [
+    {
+      "date": "2023-01-01T08:00:00",
+      "change": "some image config change"
+    }
+  ]
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.5.6/test/data/output/ChangeLog.json 
new/release-compare-0.9.0/test/data/output/ChangeLog.json
--- old/release-compare-0.5.6/test/data/output/ChangeLog.json   1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/test/data/output/ChangeLog.json   2023-05-26 
16:35:18.000000000 +0200
@@ -0,0 +1,23 @@
+{
+  "format-version": "2",
+  "removed": [
+    "package0"
+  ],
+  "added": [
+    "package3"
+  ],
+  "source-changes": {
+    "package1": "* Wed Mar 1 2023 [email protected]\n- some other changes 
CVE-2022-1234"
+  },
+  "references": [
+    "CVE-2022-1234"
+  ],
+  "config-changes": {
+    "1.0.12": [
+      {
+        "date": "2023-01-02T08:00:00",
+        "change": "some new image config change"
+      }
+    ]
+  }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/release-compare-0.5.6/test/data/output/ChangeLog.txt 
new/release-compare-0.9.0/test/data/output/ChangeLog.txt
--- old/release-compare-0.5.6/test/data/output/ChangeLog.txt    1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/test/data/output/ChangeLog.txt    2023-05-26 
16:35:18.000000000 +0200
@@ -0,0 +1,21 @@
+Removed rpms
+============
+
+ - package0
+
+Added rpms
+==========
+
+ - package3
+
+Package Source Changes
+======================
+
+package1
++ * Wed Mar 1 2023 [email protected]
++ - some other changes CVE-2022-1234
+
+References
+==========
+
+ - CVE-2022-1234
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.5.6/test/data/output/ChangeLog.yaml 
new/release-compare-0.9.0/test/data/output/ChangeLog.yaml
--- old/release-compare-0.5.6/test/data/output/ChangeLog.yaml   1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/test/data/output/ChangeLog.yaml   2023-05-26 
16:35:18.000000000 +0200
@@ -0,0 +1,16 @@
+format-version: "2"
+removed:
+- package0
+added:
+- package3
+source-changes:
+  package1: '* Wed Mar 1 2023 [email protected]
+
+    - some other changes CVE-2022-1234'
+
+references:
+- CVE-2022-1234
+config-changes:
+  1.0.12:
+  - date: '2023-01-02T08:00:00'
+    change: 'some new image config change'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/release-compare-0.5.6/test/unit/test_release_compare.py 
new/release-compare-0.9.0/test/unit/test_release_compare.py
--- old/release-compare-0.5.6/test/unit/test_release_compare.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/release-compare-0.9.0/test/unit/test_release_compare.py 2023-05-26 
16:35:18.000000000 +0200
@@ -0,0 +1,178 @@
+from unittest.mock import patch, Mock
+import filecmp
+import os
+import pathlib
+import tempfile
+import yaml
+import json
+import release_compare
+
+new_changelog1 = """\
+* Wed Mar 1 2023 [email protected]
+- some other changes CVE-2022-1234
+
+* Tue Feb 28 2023 [email protected]
+- some more changes
+
+* Mon Feb 27 2023 [email protected]
+- some changes
+"""
+
+new_changelog2 = """\
+* Wed Mar 1 2023 [email protected]
+- some other changes
+
+* Tue Feb 28 2023 [email protected]
+- some more changes
+
+* Mon Feb 27 2023 [email protected]
+- some changes
+"""
+
+new_changelog3 = """\
+* Wed Mar 1 2023 [email protected]
+- some other changes
+
+* Tue Feb 28 2023 [email protected]
+- some more changes
+
+* Mon Feb 27 2023 [email protected]
+- some changes
+"""
+
+new_anonym_changelog1 = """\
+- some other changes CVE-2022-1234
+
+- some more changes
+
+- some changes
+"""
+
+old_changelog1 = """\
+* Tue Feb 28 2023 [email protected]
+- some more changes
+
+* Mon Feb 27 2023 [email protected]
+- some changes
+"""
+
+old_changelog2 = """\
+* Mon Feb 27 2023 [email protected]
+- some changes
+"""
+
+img_history = {
+  "1.0.11": [
+    {
+      "date": "2023-01-01T08:00:00",
+      "change": "some image config change"
+    }
+  ]
+}
+
+new_changelog_dict = {
+  'package1': new_changelog1.splitlines(),
+  'package2': new_changelog2.splitlines(),
+  'package3': new_changelog3.splitlines()
+}
+
+data_dir = os.path.join(str(pathlib.Path(__file__).parent), '../data')
+
+
+def test_get_packages_from_file():
+    pkgs = release_compare.get_packages_from_file(
+        os.path.join(data_dir, 'input', 'KIWI', 
'foo-os.x86_64-1.0.12-profile1-Build.report')
+    )
+    assert pkgs == ['package1', 'package2', 'package3']
+
+    pkgs = release_compare.get_packages_from_file(
+        os.path.join(data_dir, 'input', 'KIWI', 
'foo-os.x86_64-1.0.12-profile1-Build.packages')
+    )
+    assert pkgs == ['package1', 'package2', 'package3']
+
+
+@patch('release_compare.subprocess.Popen')
+def test_write_pkg_info(mock_subprocess):
+    release_compare.CONFIG = release_compare.Config('')
+    with tempfile.TemporaryDirectory() as tmpdir:
+        release_compare.ROOT = os.path.join(data_dir, 'input')
+        os.mkdir(os.path.join(tmpdir, 'changelogs'))
+        os.mkdir(os.path.join(tmpdir, 'rpms'))
+        pkgs = release_compare.get_packages_from_file(
+            os.path.join(data_dir, 'input', 'KIWI', 
'foo-os.x86_64-1.0.12-profile1-Build.report')
+        )
+        release_compare.write_pkg_info(pkgs[0], tmpdir)
+        with open(os.path.join(tmpdir, 'rpms', 'package1')) as inf:
+            assert inf.read() == '1.2.3-1.2'
+
+
+def test_parse_old_obsgendiff():
+    release_compare.LOG = Mock()
+    with tempfile.TemporaryDirectory() as tmpdir:
+        pkgs, changelogs, history = release_compare.parse_old_obsgendiff(
+            os.path.join(
+                data_dir, 'input', 'KIWI',
+                'foo-os.x86_64-1.0.12-profile1-Build.packages'
+            ),
+            tmpdir
+        )
+    assert 'package0' in pkgs
+    assert 'package1' in pkgs
+    assert 'package2' in pkgs
+    assert changelogs['package1'] == old_changelog1.splitlines()
+    assert history == img_history
+
+
+def test_write_changelog():
+    new_pkgs = release_compare.get_packages_from_file(
+        os.path.join(data_dir, 'input', 'KIWI', 
'foo-os.x86_64-1.0.12-profile1-Build.packages')
+    )
+    release_compare.CONFIG.anonymize_changes = False
+    with tempfile.TemporaryDirectory() as tmpdir:
+        old_pkgs, old_logs, old_history = release_compare.parse_old_obsgendiff(
+            os.path.join(
+                data_dir, 'input', 'KIWI',
+                'foo-os.x86_64-1.0.12-profile1-Build.packages'
+            ),
+            tmpdir
+        )
+
+        changelog_data = release_compare.get_changelog_data(
+            new_pkgs, new_changelog_dict, old_pkgs, old_logs
+        )
+        new_history_file = release_compare.match_changes_file(
+            'foo-os.x86_64-1.0.12-profile1-Build',
+            os.path.join(data_dir, 'input', 'SOURCES')
+        )
+        changelog_data['config-changes'] = release_compare.get_config_changes(
+            new_history_file, old_history
+        )
+
+        release_compare.write_changelog_text(
+            os.path.join(tmpdir, 'ChangeLog.txt'), changelog_data
+        )
+        release_compare.write_changelog_json(
+            os.path.join(tmpdir, 'ChangeLog.json'), changelog_data
+        )
+        release_compare.write_changelog_yaml(
+            os.path.join(tmpdir, 'ChangeLog.yaml'), changelog_data
+        )
+
+        assert open(os.path.join(tmpdir, 'ChangeLog.txt'), 'r').read() == 
open(os.path.join(data_dir, 'output', 'ChangeLog.txt'), 'r').read()
+        assert filecmp.cmp(
+            os.path.join(tmpdir, 'ChangeLog.txt'),
+            os.path.join(data_dir, 'output', 'ChangeLog.txt')
+        )
+        # compare yaml and json content rather than verbatim
+        # change in order is ok
+        with open(os.path.join(tmpdir, 'ChangeLog.json'), 'r') as inf:
+            generated_data = json.load(inf)
+        with open(os.path.join(data_dir, 'output', 'ChangeLog.json'), 'r') as 
inf:
+            expected_data = json.load(inf)
+        assert generated_data == expected_data
+
+        with open(os.path.join(tmpdir, 'ChangeLog.yaml'), 'r') as inf:
+            generated_data = yaml.safe_load(inf)
+        with open(os.path.join(data_dir, 'output', 'ChangeLog.yaml'), 'r') as 
inf:
+            expected_data = yaml.safe_load(inf)
+        assert generated_data == expected_data

++++++ release-compare.obsinfo ++++++
--- /var/tmp/diff_new_pack.uuzXf8/_old  2023-05-30 22:03:19.087319963 +0200
+++ /var/tmp/diff_new_pack.uuzXf8/_new  2023-05-30 22:03:19.091319986 +0200
@@ -1,5 +1,5 @@
 name: release-compare
-version: 0.5.6
-mtime: 1651472540
-commit: ff3235f233bfc4274e1109b85cfc591c8de716ef
+version: 0.9.0
+mtime: 1685111718
+commit: b5ee192915699eda95faa39930c384c774bb9cae
 

Reply via email to