123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #!/usr/bin/python3
- # vim:set fileencoding=utf-8 et ts=4 sts=4 sw=4:
- #
- # apt-listchanges - Show changelog entries between the installed versions
- # of a set of packages and the versions contained in
- # corresponding .deb files
- #
- # Copyright (C) 2000-2006 Matt Zimmerman <mdz@debian.org>
- # Copyright (C) 2006 Pierre Habouzit <madcoder@debian.org>
- # Copyright (C) 2016 Robert Luberda <robert@debian.org>
- #
- # 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 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
- #
- import sys, os, os.path
- import apt_pkg
- import signal
- import subprocess
- import traceback
- sys.path += [os.path.dirname(sys.argv[0]) + '/apt-listchanges', '/usr/share/apt-listchanges']
- import ALCLog
- from ALChacks import _
- import apt_listchanges, DebianFiles, ALCApt, ALCConfig, ALCSeenDb
- def main(config):
- config.read('/etc/apt/listchanges.conf')
- debs = config.getopt(sys.argv)
- if config.dump_seen:
- ALCSeenDb.make_seen_db(config, True).dump()
- sys.exit(0);
- apt_pkg.init_system()
- if config.apt_mode:
- debs = ALCApt.AptPipeline(config).read()
- if not debs:
- sys.exit(0)
- if not sys.stdin.isatty():
- try:
- # Give any forked processes (eg. lynx) a normal stdin;
- # See Debian Bug #343423. (Note: with $APT_HOOK_INFO_FD
- # support introduced in version 3.2, stdin should point to
- # a terminal already, so there should be no need to reopen it).
- tty = open('/dev/tty', 'rb+', buffering=0)
- os.close(0)
- os.dup2(tty.fileno(), 0)
- tty.close()
- except Exception as ex:
- ALCLog.warning(_("Cannot reopen /dev/tty for stdin: %s") % str(ex))
- # Force quiet (loggable) mode if not running interactively
- if not sys.stdout.isatty() and not config.quiet:
- config.quiet = 1
- try:
- frontend = apt_listchanges.make_frontend(config, len(debs))
- except apt_listchanges.EUnknownFrontend:
- ALCLog.error(_("Unknown frontend: %s") % config.frontend)
- sys.exit(1)
- if frontend == None:
- sys.exit(0)
- if not config.show_all:
- status = DebianFiles.ControlParser()
- status.readfile('/var/lib/dpkg/status')
- status.makeindex('Package')
- seen_db = ALCSeenDb.make_seen_db(config)
- all_news = {}
- all_changelogs = {}
- all_binnmus = {}
- notes = []
- # Mapping of source->binary packages
- source_packages = {}
- # Flag for each source package, only set if changelogs were actually found
- found = {}
- # Main loop
- for deb in debs:
- pkg = DebianFiles.Package(deb)
- binpackage = pkg.binary
- srcpackage = pkg.source
- srcversion = pkg.Version # XXX take the real version or we'll lose binNMUs
- frontend.update_progress()
- # Show changes later than fromversion
- fromversion = None
- if not config.show_all:
- if srcpackage in seen_db:
- fromversion = seen_db[srcpackage]
- elif config.since:
- fromversion = config.since
- else:
- statusentry = status.find('Package', binpackage)
- if statusentry and statusentry.installed():
- fromversion = statusentry.version()
- else:
- # Package not installed or seen
- notes.append(_("%s: will be newly installed") % binpackage)
- continue
- source_packages.setdefault(srcpackage, []).append(binpackage)
- # For packages with non uniform binary versions wrt the source
- # version, the version reported for the binary package is the source
- # one, which lacks binNMU.
- #
- # This is why even if we've seen a package we may miss bits of
- # changelog in some odd cases
- if srcpackage in found and \
- apt_pkg.version_compare(srcversion, found[srcpackage]) <= 0:
- continue
- if not config.show_all and apt_pkg.version_compare(fromversion, srcversion) >= 0:
- notes.append(_("%(pkg)s: Version %(version)s has already been seen")
- % {'pkg': binpackage, 'version': srcversion})
- continue
- (news, changelog, binnmu) = pkg.extract_changes(config.which, fromversion, config.reverse)
- if news or changelog or binnmu:
- found[srcpackage] = srcversion
- seen_db[srcpackage] = srcversion
- if news:
- all_news[srcpackage] = news
- if changelog:
- all_changelogs[srcpackage] = changelog
- if binnmu:
- all_binnmus[srcpackage] = binnmu
- frontend.progress_done()
- seen_db.close_db()
- # Merge binnmu entries with regular changelog entries.
- # Assumption: the binnmu version is greater than the last non-binnmu version.
- for srcpackage in all_binnmus:
- if srcpackage in all_changelogs:
- all_changelogs[srcpackage].merge_binnmu(all_binnmus[srcpackage], config.reverse)
- else:
- all_changelogs[srcpackage] = all_binnmus[srcpackage]
- all_news = list(all_news.values())
- all_changelogs = list(all_changelogs.values())
- for batch in (all_news, all_changelogs):
- batch.sort(key=lambda x: (x.urgency, x.package))
- if config.headers:
- changes = ''
- news = ''
- for rec in all_news:
- if not rec.changes:
- continue
- package = rec.package
- header = _('News for %s') % package
- if len([x for x in source_packages[package] if x != package]) > 0:
- # Differing source and binary packages
- header += ' (' + ' '.join(source_packages[package]) + ')'
- news += '--- ' + header + ' ---\n' + rec.changes
- for rec in all_changelogs:
- if not rec.changes:
- continue
- package = rec.package
- header = _('Changes for %s') % package
- if len([x for x in source_packages[package] if x != package]) > 0:
- # Differing source and binary packages
- header += ' (' + ' '.join(source_packages[package]) + ')'
- changes += '--- ' + header + ' ---\n' + rec.changes
- else:
- news = ''.join([x.changes for x in all_news if x.changes])
- changes = ''.join([x.changes for x in all_changelogs if x.changes])
- if config.verbose and len(notes) > 0:
- changes += _("Informational notes") + ":\n\n" + '\n'.join(notes)
- if news:
- frontend.set_title( _('apt-listchanges: News') )
- frontend.display_output(news)
- if changes:
- frontend.set_title( _('apt-listchanges: Changelogs') )
- frontend.display_output(changes)
- if news or changes:
- apt_listchanges.confirm_or_exit(config, frontend)
- hostname = subprocess.getoutput('hostname')
- if apt_listchanges.can_send_emails(config):
- if changes:
- subject = _("apt-listchanges: changelogs for %s") % hostname
- apt_listchanges.mail_changes(config, changes, subject)
- if news:
- subject = _("apt-listchanges: news for %s") % hostname
- apt_listchanges.mail_changes(config, news, subject)
- # Write out seen db
- seen_db.apply_changes()
- elif not config.apt_mode and not source_packages.keys():
- ALCLog.error(_("Didn't find any valid .deb archives"))
- sys.exit(1)
- def _setup_signals():
- def signal_handler(signum, frame):
- ALCLog.error(_('Received signal %d, exiting') % signum)
- sys.exit(apt_listchanges.BREAK_APT_EXIT_CODE)
- for s in [ signal.SIGHUP, signal.SIGQUIT, signal.SIGTERM ]:
- signal.signal(s, signal_handler)
- if __name__ == '__main__':
- _setup_signals()
- config = ALCConfig.ALCConfig()
- try:
- main(config)
- except KeyboardInterrupt:
- sys.exit(apt_listchanges.BREAK_APT_EXIT_CODE)
- except ALCApt.AptPipelineError as ex:
- ALCLog.error(str(ex))
- sys.exit(apt_listchanges.BREAK_APT_EXIT_CODE)
- except ALCSeenDb.DbError as ex:
- ALCLog.error(str(ex))
- sys.exit(1)
- except Exception:
- traceback.print_exc()
- apt_listchanges.confirm_or_exit(config, apt_listchanges.ttyconfirm(config))
- sys.exit(1)
|