Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package zypper-changelog-plugin for openSUSE:Factory checked in at 2024-05-27 11:54:22 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/zypper-changelog-plugin (Old) and /work/SRC/openSUSE:Factory/.zypper-changelog-plugin.new.24587 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "zypper-changelog-plugin" Mon May 27 11:54:22 2024 rev:6 rq:1176775 version:0.4 Changes: -------- --- /work/SRC/openSUSE:Factory/zypper-changelog-plugin/zypper-changelog-plugin.changes 2022-12-08 16:52:09.559782276 +0100 +++ /work/SRC/openSUSE:Factory/.zypper-changelog-plugin.new.24587/zypper-changelog-plugin.changes 2024-05-27 12:02:29.316512509 +0200 @@ -1,0 +2,8 @@ +Fri May 24 12:26:41 UTC 2024 - Zoltan Balogh <zbal...@suse.com> + +- Fixing bsc#1223985 - zypper-changelog-plugin exhausts all memory +- Fixing bsc#1217299 - zypp:plugins/zypper-changelog-plugin: Bug repodata is now using zstd compression +- Optimized memory usage, more error handling, better structure and + improved comments + +------------------------------------------------------------------- Old: ---- zypper-changelog-plugin-0.3.tar.gz New: ---- zypper-changelog-plugin-0.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ zypper-changelog-plugin.spec ++++++ --- /var/tmp/diff_new_pack.bmsHR6/_old 2024-05-27 12:02:29.772529250 +0200 +++ /var/tmp/diff_new_pack.bmsHR6/_new 2024-05-27 12:02:29.772529250 +0200 @@ -1,7 +1,7 @@ # # spec file for package zypper-changelog-plugin # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: zypper-changelog-plugin -Version: 0.3 +Version: 0.4 Release: 0 Summary: Changelog listing tool License: GPL-2.0-only Group: System/Packages URL: https://github.com/bzoltan1/zypper-changelog-plugin.git -Source: zypper-changelog-plugin-0.3.tar.gz +Source: zypper-changelog-plugin-0.4.tar.gz Requires: /usr/bin/python3 Requires: python3-requests BuildArch: noarch ++++++ zypper-changelog-plugin-0.3.tar.gz -> zypper-changelog-plugin-0.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zypper-changelog-plugin-0.3/zypper-changelog new/zypper-changelog-plugin-0.4/zypper-changelog --- old/zypper-changelog-plugin-0.3/zypper-changelog 2021-01-17 17:12:02.848290488 +0100 +++ new/zypper-changelog-plugin-0.4/zypper-changelog 2024-05-24 19:46:00.014751434 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/python3 # -*- coding: utf-8 -*- # Copyright © 2018 SUSE LLC # @@ -31,100 +31,147 @@ import xml.etree.ElementTree as ET import tempfile import difflib +from time import sleep def log_text(text): - print(text) + """Print debug information if debug mode is enabled.""" + if args.debug: + print(text) def parse_args(): - p = ArgumentParser(prog='zypper-changelog', - description='Shows the changelog' + - 'of one ore more packages.', - formatter_class=argparse.RawDescriptionHelpFormatter) - p.add_argument('-d', '--debug', - default=False, action='store_true', dest='debug', - help='debug mode') - p.add_argument('-c', '--commits', - default=False, action='store_true', dest='commits', - help='Lists only the headline of the change commits') - p.add_argument('-e', '--expression', - default=False, action='store_true', dest='expression', - help='Enable regular expression in package name') - p.add_argument("-p", "--packages", dest="packages", default='', - help="Package name \ - or regular expression to match packages.") - p.add_argument("-r", "--repositories", - dest="repos", default="oss", - help="Comma separated list of repositores \ - to search for changelogs.") - p.add_argument("-a", "--all", - dest="all", default=False, - action='store_true', - help="Lists changelogs for all packages") - p.add_argument("-u", "--update", - dest="update", default=False, - action='store_true', - help="Lists changelogs for all packages to be updated") + """Parse command line arguments.""" + p = ArgumentParser( + prog='zypper changelog', + description='Shows the changelog of one or more packages.', + formatter_class=argparse.RawDescriptionHelpFormatter + ) + p.add_argument( + '-d', '--debug', + default=False, action='store_true', dest='debug', + help='Enable debug mode for detailed output' + ) + p.add_argument( + '-c', '--commits', + default=False, action='store_true', dest='commits', + help='List only the headline of the change commits' + ) + p.add_argument( + '-e', '--expression', + default=False, action='store_true', dest='expression', + help='Enable regular expression in package name' + ) + p.add_argument( + "-p", "--packages", dest="packages", default='', + help="Package name or regular expression to match packages." + ) + p.add_argument( + "-r", "--repositories", + dest="repos", default="ALL", + help="Comma separated list of repositories to search for changelogs." + ) + p.add_argument( + "-a", "--all", + dest="all", default=False, + action='store_true', + help="List changelogs for all packages" + ) + p.add_argument( + "-u", "--update", + dest="update", default=True, + action='store_true', + help="List changelogs for all packages to be updated" + ) + p.add_argument( + "--arch", dest="arch", default="all", + help=( + "Comma separated list of architectures to include " + "(default is all)." + ) + ) if len(sys.argv[1:]) == 0: p.print_help() p.exit() return p -def readRpmHeader(ts, filename): - # Read an rpm header +def read_rpm_header(ts, filename): + """Read the RPM header from a file.""" fd = os.open(filename, os.O_RDONLY) h = None try: h = ts.hdrFromFdno(fd) except rpm.error as e: - print(e) - h = None - os.close(fd) + log_text(f"Error reading RPM header: {e}") + finally: + os.close(fd) return h def get_updates(): - update_list, repo_list = (set([]), set([])) + """Fetch the list of updates from zypper.""" + update_list, repo_list = set(), set() arch = '' - zypp_process = subprocess.Popen(["zypper", - "-x", - "list-updates"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout_value, stderr_value = zypp_process.communicate() - updates_tree = ET.ElementTree(ET.fromstring(stdout_value)) - updates_root = updates_tree.getroot() - for update in updates_root.iter('update'): - update_list.add(update.get('name')) - repo_list.add(update.find('source').get('alias')) - if update.get('arch') not in ('src', 'noarch'): - arch = update.get('arch') + try: + zypp_process = subprocess.Popen( + ["zypper", "-x", "--non-interactive", "list-updates"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout_value, stderr_value = zypp_process.communicate() + updates_tree = ET.ElementTree(ET.fromstring(stdout_value)) + updates_root = updates_tree.getroot() + for update in updates_root.iter('update'): + update_list.add(update.get('name')) + repo_list.add(update.find('source').get('alias')) + if update.get('arch') not in ('src', 'noarch'): + arch = update.get('arch') + except Exception as e: + log_text(f"Error fetching updates: {e}") return update_list, repo_list, arch def local_changelog(package): - zypp_process = subprocess.Popen(["rpm", - "-q", - "%s" % package], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout_value, stderr_value = zypp_process.communicate() - newest = stdout_value.decode("utf-8").strip().split('\n')[-1] - search_result = re.search("^%s-(.*)-.*" % re.escape(package), newest) - if search_result: - # Multiple version of the same package may be installed - # and we want to see the changelog of the latest. - last_version = search_result.group(1) - zypp_process = subprocess.Popen(["rpm", - "-q", - "--changelog", - "%s-%s" % (package, last_version)], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout_value, stderr_value = zypp_process.communicate() - return stdout_value.decode("utf-8") + """Retrieve the changelog for a locally installed package.""" + try: + zypp_process = subprocess.Popen( + ["rpm", "-q", package], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout_value, stderr_value = zypp_process.communicate() + newest = stdout_value.decode("utf-8").strip().split('\n')[-1] + search_result = re.search(f"^{re.escape(package)}-(.*)-.*", newest) + if search_result: + last_version = search_result.group(1) + zypp_process = subprocess.Popen( + ["rpm", "-q", "--changelog", f"{package}-{last_version}"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout_value, stderr_value = zypp_process.communicate() + return stdout_value.decode("utf-8") + except Exception as e: + log_text(f"Error fetching local changelog: {e}") + return "" + + +def fetch_rpm_header(url, end, retries=3, backoff_factor=1): + """Fetch the RPM header from a remote repository.""" + for attempt in range(retries): + try: + response = requests.get( + url, headers={'Range': f'bytes=0-{end}'}, timeout=5 + ) + response.raise_for_status() + return response.content + except requests.exceptions.RequestException as e: + log_text(f"Error fetching RPM header (attempt {attempt + 1}): {e}") + sleep(backoff_factor * (2 ** attempt)) + return None + + +def decode_if_bytes(value): + """Decode a value if it is in bytes.""" + return value.decode("utf-8") if isinstance(value, bytes) else value parser = parse_args() @@ -139,113 +186,127 @@ if args.packages: package_list = args.packages.split(",") -list_of_xml_files = [] +arch_list = args.arch.split(",") +list_of_xml_files = [] # Find the cache file of the repositories for root, dirs, files in os.walk("/var/cache/zypp/raw/"): for file in files: - if file.endswith("primary.xml.gz"): - for repository in repository_list: - if repository in root: - list_of_xml_files.append(os.path.join(root, file)) - -ts = rpm.TransactionSet("", (rpm._RPMVSF_NOSIGNATURES or - rpm.RPMVSF_NOHDRCHK or - rpm._RPMVSF_NODIGESTS or - rpm.RPMVSF_NEEDPAYLOAD)) + if file.endswith("primary.xml.zst") or file.endswith("primary.xml.gz"): + if args.repos == "ALL": + list_of_xml_files.append(os.path.join(root, file)) + else: + for repository in repository_list: + log_text(f"Enabled repository: {repository}") + if repository in root: + list_of_xml_files.append(os.path.join(root, file)) + +# Initialize the RPM transaction set +ts = rpm.TransactionSet("", ( + rpm._RPMVSF_NOSIGNATURES or rpm.RPMVSF_NOHDRCHK or + rpm._RPMVSF_NODIGESTS or rpm.RPMVSF_NEEDPAYLOAD +)) -# Get the available respostories from the xml outout of zypper +# Get the available repositories from the XML output of zypper # and find the URLs of the repositories -zypp_process = subprocess.Popen(["zypper", - "-x", - "lr"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) +zypp_process = subprocess.Popen( + ["zypper", "-x", "lr"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE +) stdout_value, stderr_value = zypp_process.communicate() repo_tree = ET.ElementTree(ET.fromstring(stdout_value)) repo_root = repo_tree.getroot() + for files in list_of_xml_files: + print(files) for repo in repo_root.iter('repo'): if repo.get('alias') in files: mirror_url = repo.find('url').text + log_text(f"Mirror URL: {mirror_url}") + + try: + zstdcat_process = subprocess.Popen( + ["zstdcat", files], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout_value, stderr_value = zstdcat_process.communicate() + + except Exception as e: + log_text(f"Error decompressing XML file: {e}") + continue - zcat_process = subprocess.Popen(["zcat", - "%s" % files], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout_value, stderr_value = zcat_process.communicate() + try: + tree = ET.ElementTree(ET.fromstring(stdout_value)) + except ET.ParseError as e: + log_text(f"Error parsing XML file: {e}") + continue - tree = ET.ElementTree(ET.fromstring(stdout_value)) root = tree.getroot() xml_ns = root.tag.split('}')[0].strip('{') - # Loop thru the packages listed in the repository cache files + + + # Loop through the packages listed in the repository cache files for package in root.findall('doc:package', namespaces={'doc': xml_ns}): +# print(package[0].text) + if not args.all: - # skip foreign arch and source packages - if args.update and package[1].text not in ('%s' % arch, 'noarch'): + # Skip foreign arch and source packages + if args.update and package[1].text not in (arch, 'noarch'): continue if args.expression: - match = False - for package_item in package_list: - pattern = re.compile(package_item) - if pattern.match("%s" % package[0].text): - match = True - if not match: + if not any( + re.compile(p).match(package[0].text) for p in package_list + ): continue else: if package[0].text not in package_list: continue - # Find the segment/offset of the header part of the rpm package - for field in package[11].findall('rpm:header-range', - namespaces={'rpm': - 'http://linux.duke.edu' + - '/metadata/rpm'}): + for field in package[11].findall( + 'rpm:header-range', + namespaces={'rpm': 'http://linux.duke.edu/metadata/rpm'} + ): start = field.get('start') end = field.get('end') - url = '%s/%s' % (mirror_url, package[10].get('href')) - log_text(url) - try: - # Fetch the rpm header as it contains the changelogs - rpm_header = requests.get(url, - headers={'Range': - 'bytes=0-%s' % (end)}) - rpm_header.raise_for_status() - except requests.exceptions.HTTPError as e: - log_text(e) + url = f'{mirror_url}/{package[10].get("href")}' + log_text(f"URL to fetch the rpm header from: {url}") + + rpm_header_content = fetch_rpm_header(url, end) + if rpm_header_content is None: continue - # Dump the header to a temporary file as the ts.hdrFromFdno - # needs a real file to process - header_file, header_filename = tempfile.mkstemp() + + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write(rpm_header_content) + f.flush() + header_filename = f.name try: - with os.fdopen(header_file, 'w+b') as f: - f.write(rpm_header.content) - f.flush() - f.close() - h = readRpmHeader(ts, '%s' % header_filename) + h = read_rpm_header(ts, header_filename) finally: os.remove(header_filename) if h is None: continue - # Parse the changelog, time and contributor's name changelog_name = h[rpm.RPMTAG_CHANGELOGNAME] changelog_time = h[rpm.RPMTAG_CHANGELOGTIME] changelog_text = h[rpm.RPMTAG_CHANGELOGTEXT] changelog = '' - for (name, time, text) in zip(changelog_name, - changelog_time, - changelog_text): - dt = datetime.datetime.fromtimestamp(time).strftime("%a %b " + - "%d %Y") + for name, time, text in zip( + changelog_name, changelog_time, changelog_text + ): + name = decode_if_bytes(name) + text = decode_if_bytes(text) + dt = datetime.datetime.fromtimestamp(time).strftime( + "%a %b %d %Y" + ) if args.commits: - changelog += "* %s %s\n" % (dt, name) + changelog += f"* {dt} {name}\n" else: - changelog += "* %s %s\n%s\n\n" % (dt, name, text) + changelog += f"* {dt} {name}\n{text}\n\n" if args.update: local = str(local_changelog(package[0].text)) diff = difflib.ndiff(local.split('\n'), changelog.split('\n')) - for l in diff: - if l.startswith('+ '): - print(l.replace('+ ', '')) + for line in diff: + if line.startswith('+ '): + print(line.replace('+ ', '')) else: print(changelog) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zypper-changelog-plugin-0.3/zypper-changelog-plugin.changes new/zypper-changelog-plugin-0.4/zypper-changelog-plugin.changes --- old/zypper-changelog-plugin-0.3/zypper-changelog-plugin.changes 2022-12-08 07:20:56.746357798 +0100 +++ new/zypper-changelog-plugin-0.4/zypper-changelog-plugin.changes 2024-05-24 15:40:43.714097916 +0200 @@ -1,7 +1,15 @@ ------------------------------------------------------------------- +Fri May 24 12:26:41 UTC 2024 - Zoltan Balogh <zbal...@suse.com> + +- Fixing bsc#1223985 - zypper-changelog-plugin exhausts all memory +- Fixing bsc#1217299 - zypp:plugins/zypper-changelog-plugin: Bug repodata is now using zstd compression +- Optimized memory usage, more error handling, better structure and + improved comments + +------------------------------------------------------------------- Thu Dec 8 06:20:17 UTC 2022 - Zoltan Balogh <zbal...@suse.com> -- Fixing #1206081 - The zypper changelog plugin fails for packages not installed +- Fixing bsc#1206081 - The zypper changelog plugin fails for packages not installed ------------------------------------------------------------------- Wed Nov 16 06:35:11 UTC 2022 - Zoltan Balogh <zbal...@suse.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zypper-changelog-plugin-0.3/zypper-changelog-plugin.spec new/zypper-changelog-plugin-0.4/zypper-changelog-plugin.spec --- old/zypper-changelog-plugin-0.3/zypper-changelog-plugin.spec 1970-01-01 01:00:00.000000000 +0100 +++ new/zypper-changelog-plugin-0.4/zypper-changelog-plugin.spec 2024-05-24 14:31:00.614706491 +0200 @@ -0,0 +1,51 @@ +# +# spec file for package zypper-changelog-plugin +# +# Copyright (c) 2022 SUSE LLC +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via https://bugs.opensuse.org/ +# + + +Name: zypper-changelog-plugin +Version: 0.4 +Release: 0 +Summary: Changelog listing tool +License: GPL-2.0-only +Group: System/Packages +URL: https://github.com/bzoltan1/zypper-changelog-plugin.git +Source: zypper-changelog-plugin-0.4.tar.gz +Requires: /usr/bin/python3 +Requires: python3-requests +BuildArch: noarch + +%description +This tool is to show the changelog of packages in the repository + +%prep +%setup -q + +%build + +%install +mkdir -p %{buildroot}%{_bindir}/ +install -m 755 zypper-changelog %{buildroot}%{_bindir}/zypper-changelog +mkdir -p %{buildroot}/usr/lib/zypper/commands %{buildroot}/%{_mandir}/man8 +install -m 644 zypper-changelog.8 %{buildroot}/%{_mandir}/man8/ + +%files +%license LICENSE +%doc README.md +%{_bindir}/zypper-changelog +%{_mandir}/man8/* + +%changelog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zypper-changelog-plugin-0.3/zypper-changelog.spec new/zypper-changelog-plugin-0.4/zypper-changelog.spec --- old/zypper-changelog-plugin-0.3/zypper-changelog.spec 2020-05-06 14:30:43.897154403 +0200 +++ new/zypper-changelog-plugin-0.4/zypper-changelog.spec 1970-01-01 01:00:00.000000000 +0100 @@ -1,43 +0,0 @@ -# -# spec file for package zypper-changelog -# -# Copyright (c) 2020 SUSE LLC -# -# All modifications and additions to the file contributed by third parties -# remain the property of their copyright owners, unless otherwise agreed -# upon. The license for this file, and modifications and additions to the -# file, is the same license as for the pristine package itself (unless the -# license for the pristine package is not an Open Source License, in which -# case the license is the MIT License). An "Open Source License" is a -# license that conforms to the Open Source Definition (Version 1.9) -# published by the Open Source Initiative. - -# Please submit bugfixes or comments via https://bugs.opensuse.org/ -# - - -Name: zypper-changelog -Version: 0.1 -Release: 1%{?dist} -Summary: Changelog listing tool -License: GPL-2.0-or-later -URL: https://github.com/bzoltan1/zypper-changelog.git -Source: zypper-changelog-0.1.tar.gz -Requires: python3 -BuildArch: noarch - -%description -This tool is to show the changelog of packages in the repository -%prep -%setup -q - -%install -mkdir -p %{buildroot}%{_bindir}/ -install -m 755 zypper-changelog %{buildroot}%{_bindir}/zypper-changelog - -%files -%doc README.md -%license LICENSE -%{_bindir}/zypper-changelog - -%changelog