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