#!/usr/bin/python -tt # 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. # Copyright 2009 Red Hat, Inc # written by Seth Vidal import yum import rpm import sys import rpmUtils.transaction import difflib def munge_item(item, pkgobj): new = item new = new.replace(pkgobj.epoch+':'+pkgobj.ver+'-'+pkgobj.rel, 'E:V-R') new = new.replace(pkgobj.ver+'-'+pkgobj.rel, 'V-R') new = new.replace(pkgobj.ver, 'VERSION') return new def munge_items(itemlist, pkgobj): """munges the list of items to replace problematic strings from a package with vars returns a dictionary of 'munged item': 'original item'""" returndict = {} for item in itemlist: new = munge_item(item, pkgobj) returndict[new] = item return returndict def cmp_list(o, n): removed = [] added = [] for old in o: if not old in n: removed.append(old) for new in n: if not new in o: added.append(new) return removed, added def simple_diff(old, new): o = n = '' if old: o = old.split('\n') if new: n = new.split('\n') if o == n: return None return '\n'.join(difflib.ndiff(o, n)) def _generate_file_data_dict(pkg, do_fi=False): # dict of dicts: # munged name = {realname, size, user, group, mode} file_info = {} if not do_fi: files = pkg.filelist + pkg.dirlist + pkg.ghostlist munged_files = munge_items(files, pkg) for (mn,real) in munged_files.items(): file_info[mn] = {} file_info[mn]['name'] = real else: for filetup in pkg.hdr.fiFromHeader(): (fn, size, mode, mtime, flags, dev, inode, link, state, vflags, user, group, csum) = filetup mn = munge_item(fn, pkg) file_info[mn] = {} file_info[mn]['name'] = fn file_info[mn]['size'] = size file_info[mn]['mode'] = mode file_info[mn]['user'] = user file_info[mn]['group'] = group return file_info def files_diff(old, new): # diff filelists (including perms, owners, etc if we have an rpmhdr) if isinstance(old, yum.packages.YumHeaderPackage) and \ isinstance(new, yum.packages.YumHeaderPackage): do_fi = True else: do_fi = False new_file_info = _generate_file_data_dict(new, do_fi) old_file_info = _generate_file_data_dict(old, do_fi) rem, add = cmp_list(old_file_info.keys(), new_file_info.keys()) removed = [ old_file_info[r]['name'] for r in rem ] added = [ new_file_info[a]['name'] for a in add ] changed = [] if do_fi: for fn in old_file_info: if fn in new_file_info: for item in ['mode', 'user', 'group']: ## add size? It's REALLY noisy if old_file_info[fn][item] != new_file_info[fn][item]: msg = '%s changed %s: \n from %s\n to %s' % ( old_file_info[fn]['name'], item, old_file_info[fn][item], new_file_info[fn][item]) changed.append(msg) # we should probably go through the rem/added files and check to see # if any of them are get_close_matchable. If they are, then toss them back out: # for example: /usr/lib/python2.5/site-packages/yum/__init__.py vs # /usr/lib/python2.6/site-packages/yum/__init__.py return (removed, added, changed) def pkg_diff(old, new): differences = {} # diff common items - summary/description, url, vendor, packager, group differences['size'] = new.size - old.size differences['summary'] = simple_diff(old.summary, new.summary) differences['description'] = simple_diff(old.description, new.description) differences['license'] = simple_diff(old.license, new.license) differences['vendor'] = simple_diff(old.vendor, new.vendor) differences['packager'] = simple_diff(old.packager, new.packager) differences['group'] = simple_diff(old.group, new.group) differences['url'] = simple_diff(old.url, new.url) # diff prcos for (att, name) in (('provides_print', 'provides'), ('requires_print', 'requires'), ('conflicts_print', 'conflicts'), ('obsoletes_print', 'obsoletes')): munged_new = munge_items(getattr(new, att), new) munged_old = munge_items(getattr(old, att), old) rem, add = cmp_list(munged_old.keys(), munged_new.keys()) if rem or add: removed = [ munged_old[r] for r in rem ] added = [ munged_new[a] for a in add ] differences[name]= (removed, added) # diff filelists (including perms, owners, etc if we have an rpmhdr) differences['files'] = files_diff(old, new) # scriptlets diffing if isinstance(old, yum.packages.YumHeaderPackage) and \ isinstance(new, yum.packages.YumHeaderPackage): for att in ('postin', 'prein', 'preun', 'postun'): differences[att] = simple_diff(getattr(old, att), getattr(new, att)) # and return return differences old_file = sys.argv[1] new_file = sys.argv[2] ts = rpmUtils.transaction.initReadOnlyTransaction() old_po = yum.packages.YumLocalPackage(ts, old_file) new_po = yum.packages.YumLocalPackage(ts, new_file) differences = pkg_diff(old_po, new_po) for k in differences: if k == 'files': rem, add, changed = differences[k] if rem or add or changed: print '%s changes:' % k for i in rem: print ' - %s' % i for i in add: print ' + %s' % i for i in changed: print '%s' % i continue if differences[k]: if type(differences[k]) == type(()): rem, add = differences[k] if rem or add: print '%s changes:' % k for i in rem: print ' - %s' % i for i in add: print ' + %s' % i else: print '%s change: \n%s' % (k, differences[k])