File: //usr/lib/yum-plugins/changelog.py
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# by Panu Matilainen <pmatilai@laiskiainen.org>
# James Antill <james@and.org>
#
# TODO:
# - In 'pre' mode we could get the changelogs from rpmdb thus avoiding
# the costly 'otherdata' import.
import time
from rpmUtils.miscutils import splitFilename
from yum.plugins import TYPE_INTERACTIVE
from yum.plugins import PluginYumExit
from yum import logginglevels
import logging
from yum.i18n import to_unicode, to_str
from yum.update_md import UpdateMetadata
try:
import dateutil.parser as dateutil_parser
except ImportError:
dateutil_parser = None
requires_api_version = '2.5'
plugin_type = (TYPE_INTERACTIVE,)
origpkgs = {}
changelog = False
orignots = set()
also_updateinfo = False
updateinfo = False
def changelog_delta(pkg, olddate):
out = []
for date, author, message in pkg.returnChangelog():
if int(date) > olddate:
out.append("* %s %s\n%s" % (time.ctime(int(date)), author, message))
return out
def srpmname(pkg):
n,v,r,e,a = splitFilename(pkg.returnSimple('sourcerpm'))
return n
def _show_changes_changelog(conduit, srpms):
for name in sorted(srpms.keys()):
rpms = []
if name in origpkgs:
for rpm in srpms[name]:
rpms.append("%s" % rpm)
done = False
kvf = conduit._base.fmtKeyValFill
rpm_names = ", ".join(sorted(rpms))
for line in changelog_delta(srpms[name][0], origpkgs[name]):
if not done:
conduit.info(2,kvf("ChangeLog for: ", rpm_names))
done = True
conduit.info(2, "%s\n" % to_unicode(line))
if not done:
conduit.info(2, "%s\n" % kvf("** No ChangeLog for: ",rpm_names))
def _show_changes_updateinfo(conduit):
for note in sorted(orignots):
conduit.info(2, note)
def show_changes(conduit, msg):
# Group by src.rpm name, not binary to avoid showing duplicate changelogs
# for subpackages
srpms = {}
ts = conduit.getTsInfo()
for tsmem in ts.getMembers():
if not tsmem.updates:
continue
name = srpmname(tsmem.po)
if name in srpms:
srpms[name].append(tsmem.po)
else:
srpms[name] = [tsmem.po]
conduit.info(2, "\n%s\n" % msg)
if changelog:
_show_changes_changelog(conduit, srpms)
if updateinfo:
_show_changes_updateinfo(conduit)
class ChangeLogCommand:
def getNames(self):
return ['changelog', 'ChangeLog']
def getUsage(self):
return "[<date>|<number>|all] [PACKAGE|all|installed|updates|extras|obsoletes|recent]"
def getSummary(self):
return """\
Display changelog data, since a specified time, on a group of packages"""
def doCheck(self, base, basecmd, extcmds):
pass
def changelog(self, pkg):
if self._since_all:
for date, author, message in pkg.returnChangelog():
yield "* %s %s\n%s" % (time.ctime(int(date)), author, message)
return
if self._since_num is not None:
num = self._since_num
for date, author, message in pkg.returnChangelog():
if num <= 0:
return
num -= 1
yield "* %s %s\n%s" % (time.ctime(int(date)), author, message)
return
if True:
for date, author, message in pkg.returnChangelog():
if int(date) < self._since_tt:
return
yield "* %s %s\n%s" % (time.ctime(int(date)), author, message)
def show_data(self, msg, pkgs, name):
done = False
for pkg in pkgs:
self._pkgs += 1
if pkg.sourcerpm in self._done_spkgs:
continue
self._spkgs += 1
for line in self.changelog(pkg):
if pkg.sourcerpm not in self._done_spkgs:
if not self._done_spkgs:
msg('')
if self._since_all:
msg('Listing all changelogs')
elif self._since_num is not None:
sn = "s"
if self._since_num == 1:
sn = ""
msg('Listing %d changelog%s' % (self._since_num,sn))
else:
msg('Listing changelogs since ' +
str(self._since_dto.date()))
msg('')
if not done:
msg("%s %s %s" % ('=' * 20, name, '=' * 20))
done = True
self._done_spkgs[pkg.sourcerpm] = True
msg('%-40.40s %s' % (pkg, pkg.repoid))
self._changelogs += 1
msg(to_unicode(line))
msg('')
def doCommand(self, base, basecmd, extcmds):
logger = logging.getLogger("yum.verbose.main")
def msg(x):
logger.log(logginglevels.INFO_2, x)
def msg_warn(x):
logger.warn(x)
self._done_spkgs = {}
self._pkgs = 0
self._spkgs = 0
self._changelogs = 0
self._since_all = False
self._since_dto = None
self._since_tt = None
self._since_num = None
if not len(extcmds):
return 1, [basecmd + " " + self.getUsage()]
since = extcmds[0]
extcmds = extcmds[1:]
if since == 'all':
self._since_all = True
else:
try:
num = int(since)
if num <= 0:
raise ValueError
self._since_num = num
except:
if dateutil_parser is None:
msg = "Dateutil module not available, so can't parse dates"
raise PluginYumExit(msg)
try:
self._since_dto = dateutil_parser.parse(since, fuzzy=True)
except ValueError:
msg = "Argument -- %s -- is not \"all\", a positive number or a date" % since
raise PluginYumExit(msg)
try:
tt = self._since_dto.timetuple()
self._since_tt = time.mktime(tt)
except ValueError, e:
msg = "Argument -- %s -- is not valid: %s" % (since, to_str(e))
raise PluginYumExit(msg)
if self._since_dto == dateutil_parser.parse('garbage', fuzzy=True):
# 'since' was a package name, not a date
extcmds.insert(0, since)
# assume since="all"
self._since_all = True
self._since_dto = None
self._since_tt = None
ypl = base.returnPkgLists(extcmds)
self.show_data(msg, ypl.installed, 'Installed Packages')
self.show_data(msg, ypl.available, 'Available Packages')
self.show_data(msg, ypl.extras, 'Extra Packages')
self.show_data(msg, ypl.updates, 'Updated Packages')
self.show_data(msg, ypl.obsoletes, 'Obsoleting Packages')
self.show_data(msg, ypl.recent, 'Recent Packages')
ps = sps = cs = ""
if self._pkgs != 1: ps = "s"
if self._spkgs != 1: sps = "s"
if self._changelogs != 1: cs = "s"
return 0, [basecmd +
' stats. %d pkg%s, %d source pkg%s, %d changelog%s' %
(self._pkgs, ps, self._spkgs, sps, self._changelogs, cs)]
def needTs(self, base, basecmd, extcmds):
if len(extcmds) and extcmds[0] == 'installed':
return False
return True
def config_hook(conduit):
conduit.registerCommand(ChangeLogCommand())
parser = conduit.getOptParser()
if parser:
if conduit.confBool('main', 'always', default=False):
global changelog
changelog = True
if conduit.confBool('main', 'updateinfo', default=True):
global also_updateinfo
also_updateinfo = True
if (also_updateinfo and
conduit.confBool('main', 'updateinfo_always', default=changelog)):
global updateinfo
updateinfo = True
if changelog and (not also_updateinfo or updateinfo):
return
if hasattr(parser, 'plugin_option_group'):
parser = parser.plugin_option_group
parser.add_option('--changelog', action='store_true',
help='Show changelog delta of updated packages')
def _setup_changelog_from_cmdline(conduit):
global changelog
global updateinfo
opts, args = conduit.getCmdLine()
if opts:
if not changelog:
changelog = opts.changelog
if not updateinfo:
updateinfo = opts.changelog
def postresolve_hook(conduit):
_setup_changelog_from_cmdline(conduit)
if not (changelog or updateinfo) or conduit.resultcode == 1:
return
# Find currently installed versions of packages we're about to update
ts = conduit.getTsInfo()
rpmdb = conduit.getRpmDB()
if updateinfo:
repos = set()
for tsmem in ts.getMembers():
if tsmem.po.repoid == 'installed':
continue
repos.add(tsmem.po.repo)
mdi = UpdateMetadata(repos=list(repos))
for tsmem in ts.getMembers():
for po in rpmdb.searchNevra(name=tsmem.po.name, arch=tsmem.po.arch):
times = po['changelogtime']
try:
n,v,r,e,a = splitFilename(po.sourcerpm)
except TypeError:
n = po.name
if len(times) == 0:
# deal with packages without changelog
origpkgs[n] = 0
else:
origpkgs[n] = times[0]
if updateinfo:
for (pkgtup, notice) in mdi.get_applicable_notices(po.pkgtup):
orignots.add(notice)
if conduit.confString('main', 'when', default='post') == 'pre':
show_changes(conduit, 'Changes in packages about to be updated:')
def posttrans_hook(conduit):
if not (changelog or updateinfo):
return
if conduit.confString('main', 'when', default='post') == "post":
show_changes(conduit, 'Changes in updated packages:')