|
Packit |
3a9065 |
# repodiff.py
|
|
Packit |
3a9065 |
# DNF plugin adding a command to show differencies between two sets
|
|
Packit |
3a9065 |
# of repositories.
|
|
Packit |
3a9065 |
#
|
|
Packit |
3a9065 |
# Copyright (C) 2018 Red Hat, Inc.
|
|
Packit |
3a9065 |
#
|
|
Packit |
3a9065 |
# This copyrighted material is made available to anyone wishing to use,
|
|
Packit |
3a9065 |
# modify, copy, or redistribute it subject to the terms and conditions of
|
|
Packit |
3a9065 |
# the GNU General Public License v.2, or (at your option) any later version.
|
|
Packit |
3a9065 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
Packit |
3a9065 |
# ANY WARRANTY expressed or implied, including the implied warranties of
|
|
Packit |
3a9065 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
Packit |
3a9065 |
# Public License for more details. You should have received a copy of the
|
|
Packit |
3a9065 |
# GNU General Public License along with this program; if not, write to the
|
|
Packit |
3a9065 |
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
Packit |
3a9065 |
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
|
Packit |
3a9065 |
# source code or documentation are not subject to the GNU General Public
|
|
Packit |
3a9065 |
# License and may only be used or replicated with the express permission of
|
|
Packit |
3a9065 |
# Red Hat, Inc.
|
|
Packit |
3a9065 |
#
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
from __future__ import absolute_import
|
|
Packit |
3a9065 |
from __future__ import unicode_literals
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
import dnf.cli
|
|
Packit |
3a9065 |
from dnf.cli.option_parser import OptionParser
|
|
Packit |
3a9065 |
import hawkey
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
from dnfpluginscore import _
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
class RepoDiff(dnf.Plugin):
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
name = "repodiff"
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def __init__(self, base, cli):
|
|
Packit |
3a9065 |
super(RepoDiff, self).__init__(base, cli)
|
|
Packit |
3a9065 |
if cli is None:
|
|
Packit |
3a9065 |
return
|
|
Packit |
3a9065 |
cli.register_command(RepoDiffCommand)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
class RepoDiffCommand(dnf.cli.Command):
|
|
Packit |
3a9065 |
aliases = ("repodiff",)
|
|
Packit |
3a9065 |
summary = _("List differences between two sets of repositories")
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
@staticmethod
|
|
Packit |
3a9065 |
def set_argparser(parser):
|
|
Packit |
3a9065 |
# I'd like to use --old and --new options like Yum did.
|
|
Packit |
3a9065 |
# But ability to disable abbreviated long options is added
|
|
Packit |
3a9065 |
# only in Python >= 3.5
|
|
Packit |
3a9065 |
# So in command arguments we are not able to use arguments,
|
|
Packit |
3a9065 |
# which are prefixes of main arguments (i.w. --new would be
|
|
Packit |
3a9065 |
# treated as --newpackage). This is because we run .parse_args
|
|
Packit |
3a9065 |
# two times - for main and then for command arguments.
|
|
Packit |
3a9065 |
# https://stackoverflow.com/questions/33900846
|
|
Packit |
3a9065 |
parser.add_argument("--repo-old", "-o", default=[], action="append", dest="old",
|
|
Packit |
3a9065 |
help=_("Specify old repository, can be used multiple times"))
|
|
Packit |
3a9065 |
parser.add_argument("--repo-new", "-n", default=[], action="append", dest="new",
|
|
Packit |
3a9065 |
help=_("Specify new repository, can be used multiple times"))
|
|
Packit |
3a9065 |
parser.add_argument("--arch", "--archlist", "-a", default=[],
|
|
Packit |
3a9065 |
action=OptionParser._SplitCallback, dest="arches",
|
|
Packit |
3a9065 |
help=_("Specify architectures to compare, can be used "
|
|
Packit |
3a9065 |
"multiple times. By default, only source rpms are "
|
|
Packit |
3a9065 |
"compared."))
|
|
Packit |
3a9065 |
parser.add_argument("--size", "-s", action="store_true",
|
|
Packit |
3a9065 |
help=_("Output additional data about the size of the changes."))
|
|
Packit |
3a9065 |
parser.add_argument("--compare-arch", action="store_true",
|
|
Packit |
3a9065 |
help=_("Compare packages also by arch. By default "
|
|
Packit |
3a9065 |
"packages are compared just by name."))
|
|
Packit |
3a9065 |
parser.add_argument("--simple", action="store_true",
|
|
Packit |
3a9065 |
help=_("Output a simple one line message for modified packages."))
|
|
Packit |
3a9065 |
parser.add_argument("--downgrade", action="store_true",
|
|
Packit |
3a9065 |
help=_("Split the data for modified packages between "
|
|
Packit |
3a9065 |
"upgraded and downgraded packages."))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def configure(self):
|
|
Packit |
3a9065 |
demands = self.cli.demands
|
|
Packit |
3a9065 |
demands.sack_activation = True
|
|
Packit |
3a9065 |
demands.available_repos = True
|
|
Packit |
3a9065 |
demands.changelogs = True
|
|
Packit |
3a9065 |
self.base.conf.disable_excludes = ["all"]
|
|
Packit |
3a9065 |
# TODO yum was able to handle mirrorlist in --new/--old arguments
|
|
Packit |
3a9065 |
# Can be resolved by improving --repofrompath option
|
|
Packit |
3a9065 |
if not self.opts.new or not self.opts.old:
|
|
Packit |
3a9065 |
msg = _("Both old and new repositories must be set.")
|
|
Packit |
3a9065 |
raise dnf.exceptions.Error(msg)
|
|
Packit |
3a9065 |
for repo in self.base.repos.all():
|
|
Packit |
3a9065 |
if repo.id in self.opts.new + self.opts.old:
|
|
Packit |
3a9065 |
repo.enable()
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
repo.disable()
|
|
Packit |
3a9065 |
if not self.opts.arches:
|
|
Packit |
3a9065 |
self.opts.arches = ['src']
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def _pkgkey(self, pkg):
|
|
Packit |
3a9065 |
if self.opts.compare_arch:
|
|
Packit |
3a9065 |
return (pkg.name, pkg.arch)
|
|
Packit |
3a9065 |
return pkg.name
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def _repodiff(self, old, new):
|
|
Packit |
3a9065 |
'''compares packagesets old and new, returns dictionary with packages:
|
|
Packit |
3a9065 |
added: only in new set
|
|
Packit |
3a9065 |
removed: only in old set
|
|
Packit |
3a9065 |
upgraded: in both old and new, new has bigger evr
|
|
Packit |
3a9065 |
downgraded: in both old and new, new has lower evr
|
|
Packit |
3a9065 |
obsoletes: dictionary of which old package is obsoleted by which new
|
|
Packit |
3a9065 |
'''
|
|
Packit |
3a9065 |
old_d = dict([(self._pkgkey(p), p) for p in old])
|
|
Packit |
3a9065 |
old_keys = set(old_d.keys())
|
|
Packit |
3a9065 |
new_d = dict([(self._pkgkey(p), p) for p in new])
|
|
Packit |
3a9065 |
new_keys = set(new_d.keys())
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
# mapping obsoleted_package_from_old: obsoleted_by_package_from_new
|
|
Packit |
3a9065 |
obsoletes = dict()
|
|
Packit |
3a9065 |
for obsoleter in new.filter(obsoletes=old):
|
|
Packit |
3a9065 |
for obsoleted in old.filter(provides=obsoleter.obsoletes):
|
|
Packit |
3a9065 |
obsoletes[self._pkgkey(obsoleted)] = obsoleter
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
evr_cmp = self.base.sack.evr_cmp
|
|
Packit |
3a9065 |
repodiff = dict(
|
|
Packit |
3a9065 |
added=[new_d[k] for k in new_keys - old_keys],
|
|
Packit |
3a9065 |
removed=[old_d[k] for k in old_keys - new_keys],
|
|
Packit |
3a9065 |
obsoletes=obsoletes,
|
|
Packit |
3a9065 |
upgraded=[],
|
|
Packit |
3a9065 |
downgraded=[])
|
|
Packit |
3a9065 |
for k in old_keys.intersection(new_keys):
|
|
Packit |
3a9065 |
pkg_old = old_d[k]
|
|
Packit |
3a9065 |
pkg_new = new_d[k]
|
|
Packit |
3a9065 |
if pkg_old.evr == pkg_new.evr:
|
|
Packit |
3a9065 |
continue
|
|
Packit |
3a9065 |
if evr_cmp(pkg_old.evr, pkg_new.evr) > 0:
|
|
Packit |
3a9065 |
repodiff['downgraded'].append((pkg_old, pkg_new))
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
repodiff['upgraded'].append((pkg_old, pkg_new))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
return repodiff
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def _report(self, repodiff):
|
|
Packit |
3a9065 |
def pkgstr(pkg):
|
|
Packit |
3a9065 |
if self.opts.compare_arch:
|
|
Packit |
3a9065 |
return str(pkg)
|
|
Packit |
3a9065 |
return "%s-%s" % (pkg.name, pkg.evr)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def sizestr(num):
|
|
Packit |
3a9065 |
msg = str(num)
|
|
Packit |
3a9065 |
if num > 0:
|
|
Packit |
3a9065 |
msg += " ({})".format(dnf.cli.format.format_number(num).strip())
|
|
Packit |
3a9065 |
elif num < 0:
|
|
Packit |
3a9065 |
msg += " (-{})".format(dnf.cli.format.format_number(-num).strip())
|
|
Packit |
3a9065 |
return msg
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def report_modified(pkg_old, pkg_new):
|
|
Packit |
3a9065 |
msgs = []
|
|
Packit |
3a9065 |
if self.opts.simple:
|
|
Packit |
3a9065 |
msgs.append("%s -> %s" % (pkgstr(pkg_old), pkgstr(pkg_new)))
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
msgs.append('')
|
|
Packit |
3a9065 |
msgs.append("%s -> %s" % (pkgstr(pkg_old), pkgstr(pkg_new)))
|
|
Packit |
3a9065 |
msgs.append('-' * len(msgs[-1]))
|
|
Packit |
3a9065 |
if pkg_old.changelogs:
|
|
Packit |
3a9065 |
old_chlog = pkg_old.changelogs[0]
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
old_chlog = None
|
|
Packit |
3a9065 |
for chlog in pkg_new.changelogs:
|
|
Packit |
3a9065 |
if old_chlog:
|
|
Packit |
3a9065 |
if chlog['timestamp'] < old_chlog['timestamp']:
|
|
Packit |
3a9065 |
break
|
|
Packit |
3a9065 |
elif (chlog['timestamp'] == old_chlog['timestamp'] and
|
|
Packit |
3a9065 |
chlog['author'] == old_chlog['author'] and
|
|
Packit |
3a9065 |
chlog['text'] == old_chlog['text']):
|
|
Packit |
3a9065 |
break
|
|
Packit |
3a9065 |
msgs.append('* %s %s\n%s' % (
|
|
Packit |
3a9065 |
chlog['timestamp'].strftime("%a %b %d %Y"),
|
|
Packit |
3a9065 |
dnf.i18n.ucd(chlog['author']),
|
|
Packit |
3a9065 |
dnf.i18n.ucd(chlog['text'])))
|
|
Packit |
3a9065 |
if self.opts.size:
|
|
Packit |
3a9065 |
msgs.append(_("Size change: {} bytes").format(
|
|
Packit |
3a9065 |
pkg_new.size - pkg_old.size))
|
|
Packit |
3a9065 |
print('\n'.join(msgs))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
sizes = dict(added=0, removed=0, upgraded=0, downgraded=0)
|
|
Packit |
3a9065 |
for pkg in sorted(repodiff['added']):
|
|
Packit |
3a9065 |
print(_("Added package : {}").format(pkgstr(pkg)))
|
|
Packit |
3a9065 |
sizes['added'] += pkg.size
|
|
Packit |
3a9065 |
for pkg in sorted(repodiff['removed']):
|
|
Packit |
3a9065 |
print(_("Removed package: {}").format(pkgstr(pkg)))
|
|
Packit |
3a9065 |
obsoletedby = repodiff['obsoletes'].get(self._pkgkey(pkg))
|
|
Packit |
3a9065 |
if obsoletedby:
|
|
Packit |
3a9065 |
print(_("Obsoleted by : {}").format(pkgstr(obsoletedby)))
|
|
Packit |
3a9065 |
sizes['removed'] += pkg.size
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
if self.opts.downgrade:
|
|
Packit |
3a9065 |
if repodiff['upgraded']:
|
|
Packit |
3a9065 |
print(_("\nUpgraded packages"))
|
|
Packit |
3a9065 |
for (pkg_old, pkg_new) in sorted(repodiff['upgraded']):
|
|
Packit |
3a9065 |
sizes['upgraded'] += (pkg_new.size - pkg_old.size)
|
|
Packit |
3a9065 |
report_modified(pkg_old, pkg_new)
|
|
Packit |
3a9065 |
if repodiff['downgraded']:
|
|
Packit |
3a9065 |
print(_("\nDowngraded packages"))
|
|
Packit |
3a9065 |
for (pkg_old, pkg_new) in sorted(repodiff['downgraded']):
|
|
Packit |
3a9065 |
sizes['downgraded'] += (pkg_new.size - pkg_old.size)
|
|
Packit |
3a9065 |
report_modified(pkg_old, pkg_new)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
modified = repodiff['upgraded'] + repodiff['downgraded']
|
|
Packit |
3a9065 |
if modified:
|
|
Packit |
3a9065 |
print(_("\nModified packages"))
|
|
Packit |
3a9065 |
for (pkg_old, pkg_new) in sorted(modified):
|
|
Packit |
3a9065 |
sizes['upgraded'] += (pkg_new.size - pkg_old.size)
|
|
Packit |
3a9065 |
report_modified(pkg_old, pkg_new)
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
print(_("\nSummary"))
|
|
Packit |
3a9065 |
print(_("Added packages: {}").format(len(repodiff['added'])))
|
|
Packit |
3a9065 |
print(_("Removed packages: {}").format(len(repodiff['removed'])))
|
|
Packit |
3a9065 |
if self.opts.downgrade:
|
|
Packit |
3a9065 |
print(_("Upgraded packages: {}").format(len(repodiff['upgraded'])))
|
|
Packit |
3a9065 |
print(_("Downgraded packages: {}").format(len(repodiff['downgraded'])))
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
print(_("Modified packages: {}").format(
|
|
Packit |
3a9065 |
len(repodiff['upgraded']) + len(repodiff['downgraded'])))
|
|
Packit |
3a9065 |
if self.opts.size:
|
|
Packit |
3a9065 |
print(_("Size of added packages: {}").format(sizestr(sizes['added'])))
|
|
Packit |
3a9065 |
print(_("Size of removed packages: {}").format(sizestr(sizes['removed'])))
|
|
Packit |
3a9065 |
if not self.opts.downgrade:
|
|
Packit |
3a9065 |
print(_("Size of modified packages: {}").format(
|
|
Packit |
3a9065 |
sizestr(sizes['upgraded'] + sizes['downgraded'])))
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
print(_("Size of upgraded packages: {}").format(
|
|
Packit |
3a9065 |
sizestr(sizes['upgraded'])))
|
|
Packit |
3a9065 |
print(_("Size of downgraded packages: {}").format(
|
|
Packit |
3a9065 |
sizestr(sizes['downgraded'])))
|
|
Packit |
3a9065 |
print(_("Size change: {}").format(
|
|
Packit |
3a9065 |
sizestr(sizes['added'] + sizes['upgraded'] + sizes['downgraded'] -
|
|
Packit |
3a9065 |
sizes['removed'])))
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
def run(self):
|
|
Packit |
3a9065 |
# prepare old and new packagesets based by given arguments
|
|
Packit |
3a9065 |
q_new = self.base.sack.query(hawkey.IGNORE_EXCLUDES).filter(
|
|
Packit |
3a9065 |
reponame=self.opts.new)
|
|
Packit |
3a9065 |
q_old = self.base.sack.query(hawkey.IGNORE_EXCLUDES).filter(
|
|
Packit |
3a9065 |
reponame=self.opts.old)
|
|
Packit |
3a9065 |
if self.opts.arches and '*' not in self.opts.arches:
|
|
Packit |
3a9065 |
q_new.filterm(arch=self.opts.arches)
|
|
Packit |
3a9065 |
q_old.filterm(arch=self.opts.arches)
|
|
Packit |
3a9065 |
if self.opts.compare_arch:
|
|
Packit |
3a9065 |
q_new.filterm(latest_per_arch=1)
|
|
Packit |
3a9065 |
q_old.filterm(latest_per_arch=1)
|
|
Packit |
3a9065 |
else:
|
|
Packit |
3a9065 |
q_new.filterm(latest=1)
|
|
Packit |
3a9065 |
q_old.filterm(latest=1)
|
|
Packit |
3a9065 |
q_new.apply()
|
|
Packit |
3a9065 |
q_old.apply()
|
|
Packit |
3a9065 |
|
|
Packit |
3a9065 |
self._report(self._repodiff(q_old, q_new))
|