Skip to content

Instantly share code, notes, and snippets.

@digitaltrails
Last active September 17, 2024 19:44
Show Gist options
  • Save digitaltrails/094cd5a5d08670560c8bb199041321a6 to your computer and use it in GitHub Desktop.
Save digitaltrails/094cd5a5d08670560c8bb199041321a6 to your computer and use it in GitHub Desktop.
Zypper history summarise rpm changes
#!/usr/bin/env python3
#
# Usage: sudo zypphist.py [options]
#
# Report change log entries for recent zypper installs.
#
# Options:
# -h, --help show this help message and exit
# -i ZYPP_INSTALL_DAYS, --installed-since=ZYPP_INSTALL_DAYS
# Include anything zypper installed up to the specified
# days ago (default 1).
# -c CHANGE_ENTRIES, --change-entries
# For matched zypper installs, report a given number
# of dated RPM change-log entries (default 1).
# -g, --group group packages by change-log text (for a minimum sized report)
# -s, --group-by-source group packages by source-rpm and repository
#
# 2024-09-16 0900 Cleanup and made slightly more pythonic.
# 2024-09-16 1302 Use source-rpm, repo-name as the key for grouping.
#
# Copyright (C) 2011: Michael Hamilton
# The code is GPL 3.0(GNU General Public License) ( http://www.gnu.org/copyleft/gpl.html )
#
from datetime import datetime, timedelta
from enum import Enum
from optparse import OptionParser
import csv
import rpm
from fontTools.misc.bezierTools import namedtuple
DEFAULT_ZYPP_DAYS = 1
DEFAULT_RPM_CHANGE_ENTRIES = 1
ZYPP_HISTORY_FILENAME = '/var/log/zypp/history'
class GroupBy(Enum):
CHANGE_LOG = 1
SRC_AND_REPO = 2
PACKAGE_HEADER_START = '=================================================='
PACKAGE_HEADER_END = '------------------------------'
SrcGroupKey = namedtuple('SrcGroupKey', ['source_rpm', 'repo_name'])
def print_package_line(install_date, package_name, package_version, repo_name):
print(f'+Package: {package_name: <30} {package_version} {install_date} {repo_name}')
def recent_changelog(package_name, package_version_and_release, full_log_text, number_of_entries):
if full_log_text is None:
return f"Removed: {package_name} {package_version_and_release} is no longer installed."
extracted_text = ''
if len(full_log_text):
package_change_entry_count = 0
for line in full_log_text[0].splitlines():
try:
if len(line) > 1 and line[0] == '*' and line[1] == ' ' and len(line) > 17:
_ = datetime.strptime(line[6:17], '%b %d %Y')
package_change_entry_count += 1
if package_change_entry_count > number_of_entries:
break
except ValueError:
pass # not a date - move on
extracted_text += line + '\n'
return extracted_text
if __name__ == '__main__':
optParser = OptionParser(description='Report change log entries for recent zypper installs.')
optParser.add_option('-i', '--installed-since', dest='N', type='int',
default=DEFAULT_ZYPP_DAYS,
help=f'Include anything zypper installed up to the specified days ago'
f'(default {DEFAULT_ZYPP_DAYS}).')
optParser.add_option('-c', '--change-entries', dest='M', type='int',
default=DEFAULT_RPM_CHANGE_ENTRIES,
help=f'For matched zypper installs, report a given number of dated RPM'
f' change-log entries (default {DEFAULT_RPM_CHANGE_ENTRIES}).')
optParser.add_option("-g", "--group",
action="store_true", dest="USE_GROUPS", default=False,
help="group packages with duplicate change-log entries")
optParser.add_option("-s", "--group-by-source",
action="store_true", dest="USE_SOURCE_GROUPS", default=False,
help="group packages from same source rpm")
(options, args) = optParser.parse_args()
installedSince = datetime.now() - timedelta(days=options.N)
changelog_limit = options.M
group_packages = options.USE_GROUPS or options.USE_SOURCE_GROUPS
group_by_source = options.USE_SOURCE_GROUPS
zypHistReader = csv.reader(open(ZYPP_HISTORY_FILENAME, 'r'), delimiter='|')
rpms_by_group = {}
changelogs_by_group = {}
for historyRec in zypHistReader:
if historyRec[0][0] != '#' and historyRec[1] == 'install':
install_date = datetime.strptime(historyRec[0], '%Y-%m-%d %H:%M:%S')
if install_date >= installedSince:
package_name, package_version_and_release, repo_name = historyRec[2], historyRec[3], historyRec[6]
if not group_packages:
print(PACKAGE_HEADER_START)
print_package_line(install_date, package_name, package_version_and_release, repo_name)
print(PACKAGE_HEADER_END)
source_rpm = f'unknown source package for {package_name}'
rpm_changelog = None
rpm_transaction_set = rpm.TransactionSet()
rpm_match_inter = rpm_transaction_set.dbMatch('name', package_name)
for rpm_record in rpm_match_inter:
rpm_version_and_release = f"{rpm_record[rpm.RPMTAG_VERSION]}-{rpm_record[rpm.RPMTAG_RELEASE]}"
if rpm_version_and_release == package_version_and_release:
source_rpm = rpm_record[rpm.RPMTAG_SOURCERPM]
rpm_changelog = rpm_record[rpm.RPMTAG_CHANGELOGTEXT]
break
rpm_transaction_set.clear()
if group_packages:
group_key = SrcGroupKey(source_rpm, repo_name) if group_by_source else recent_changelog(
package_name, package_version_and_release, rpm_changelog, changelog_limit)
if group_key not in rpms_by_group:
rpms_by_group[group_key] = []
log_extract = recent_changelog(package_name, package_version_and_release, rpm_changelog,
changelog_limit) if group_by_source else group_key
changelogs_by_group[group_key] = log_extract
rpms_by_group[group_key] += [(package_name, install_date, package_version_and_release)]
else:
print(recent_changelog(package_name, package_version_and_release,
rpm_changelog, changelog_limit), end='')
if group_packages:
for group_key, packages in rpms_by_group.items():
print(PACKAGE_HEADER_START)
for package_name, install_date, package_version_and_release in packages:
print_package_line(install_date, package_name, package_version_and_release, '')
print(f'+Source: {group_key.source_rpm: <30} {group_key.repo_name}') if group_by_source else None
print(PACKAGE_HEADER_END)
print(changelogs_by_group[group_key])
@digitaltrails
Copy link
Author

Refined the help.

@digitaltrails
Copy link
Author

Now uses the python-rpm bindings

@digitaltrails
Copy link
Author

digitaltrails commented Sep 16, 2024

When grouping (-g option), the script now groups by (source-package, repo-name) and reports these as well, for example:

==================================================
+Package: spectacle                       24.08.1-1.1 2024-09-16 12:09:36
+Package: spectacle-lang                  24.08.1-1.1 2024-09-16 12:09:37
+Source:  spectacle-24.08.1-1.1.src.rpm   download.opensuse.org-oss
------------------------------
- Update to 24.08.1
  * New bugfix release
  * For more details please see:
  * https://kde.org/announcements/gear/24.08.1/
- Changes since 24.08.0:
  * Set fallback icon theme (kde#492824)
  * Fix InlineMessage headers/appearance on "no screenshot" dialog
  * Set suggested filename for copied images
  * Go back to copying images with setImageData (kde#485096)

@digitaltrails
Copy link
Author

Added options for how to group

   -g, --group           group packages by change-log text (for a minimum sized report)
   -s, --group-by-source group packages by source-rpm and repository

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment