Hi,

I put together a script called diff-comps.py that may be a good fit for
yum-utils.  It takes in two comps.xml files, creates Comps objects, and
then prints a list of the differences.  I've attached the script as well
as sample output.

You will see that the output is intended to be human-readable.  This
isn't meant to replace the standard diff/patch tools.  Instead, the goal
is to print the differences in such a way that it's easy to sanity-check
and easy to understand the differences.

Please let me know if you have any thoughts or suggestions.

Cheers
-- Dennis


$ ./diff-comps.py ../comps/comps-f12.xml.in ../comps/comps-f13.xml.in 
CATEGORY_ADDED: content
CATEGORY_CHANGED: development
  ADDED(groups): openoffice.org-development
  REMOVED(groups): books
GROUP_ADDED: dogtag
GROUP_ADDED: openoffice.org-development
GROUP_CHANGED: base
  ADDED(default_packages): cifs-utils
  ADDED(default_packages): gnupg2
  ADDED(default_packages): sssd
  ADDED(default_packages): yum-presto
  REMOVED(default_packages): gnupg
  ADDED(mandatory_packages): dbus
  ADDED(optional_packages): gnupg2-smime
  ADDED(optional_packages): yum-langpacks
  REMOVED(optional_packages): gnupg2
GROUP_CHANGED: base-x
  REMOVED(default_packages): bitmap-fonts
  ADDED(optional_packages): bitmap-console-fonts
  ADDED(optional_packages): bitmap-fangsongti-fonts
  ADDED(optional_packages): bitmap-fixed-fonts
  ADDED(optional_packages): bitmap-lucida-typewriter-fonts
  ADDED(optional_packages): ucs-miscfixed-fonts
GROUP_CHANGED: chinese-support
  ADDED(conditional_packages): ibus-pinyin-open-phrase
  REMOVED(conditional_packages): poppler-data
  ADDED(default_packages): cjkuni-ukai-fonts
  ADDED(default_packages): cjkuni-uming-fonts
  ADDED(default_packages): wqy-zenhei-fonts
  REMOVED(mandatory_packages): cjkuni-ukai-fonts
  REMOVED(mandatory_packages): cjkuni-uming-fonts
  ADDED(optional_packages): wqy-microhei-fonts
  REMOVED(optional_packages): wqy-zenhei-fonts
GROUP_CHANGED: clustering
  REMOVED(default_packages): system-config-cluster
GROUP_CHANGED: development-libs
  ADDED(optional_packages): poco-devel
  ADDED(optional_packages): poco-doc
GROUP_CHANGED: development-tools
  REMOVED(optional_packages): bazaar
GROUP_CHANGED: eclipse
  ADDED(default_packages): eclipse-collabnet-merge
  ADDED(optional_packages): eclipse-dltk-ruby
  ADDED(optional_packages): eclipse-dltk-tcl
GROUP_CHANGED: editors
  REMOVED(optional_packages): virtaal
GROUP_CHANGED: electronic-lab
  ADDED(default_packages): covered
  ADDED(default_packages): dia-CMOS
  ADDED(default_packages): dia-Digital
  ADDED(default_packages): dia-electric2
  ADDED(default_packages): dia-electronic
  ADDED(default_packages): geda-gaf
  REMOVED(default_packages): geda-docs
  REMOVED(default_packages): geda-examples
  REMOVED(default_packages): geda-gattrib
  REMOVED(default_packages): geda-gnetlist
  REMOVED(default_packages): geda-gschem
  REMOVED(default_packages): geda-gsymcheck
  REMOVED(default_packages): geda-symbols
  REMOVED(default_packages): geda-utils
GROUP_CHANGED: ethiopic-support
  ADDED(default_packages): sil-abyssinica-fonts
  REMOVED(default_packages): abyssinica-fonts
GROUP_CHANGED: font-design
  ADDED(default_packages): fontaine
  ADDED(optional_packages): woff
  ADDED(optional_packages): woffTools
GROUP_CHANGED: fonts
  ADDED(default_packages): lohit-devanagari-fonts
  ADDED(default_packages): paratype-pt-sans-fonts
  ADDED(default_packages): sil-abyssinica-fonts
  ADDED(default_packages): wqy-zenhei-fonts
  REMOVED(default_packages): abyssinica-fonts
  REMOVED(default_packages): cjkuni-uming-fonts
  REMOVED(default_packages): lohit-hindi-fonts
  REMOVED(default_packages): lohit-maithili-fonts
  REMOVED(default_packages): lohit-marathi-fonts
  ADDED(optional_packages): cjkuni-uming-fonts
  ADDED(optional_packages): gfs-goschen-fonts
  ADDED(optional_packages): saab-fonts
  ADDED(optional_packages): sil-padauk-fonts
  ADDED(optional_packages): vemana2000-fonts
  ADDED(optional_packages): wqy-microhei-fonts
  REMOVED(optional_packages): lohit-kashmiri-fonts
  REMOVED(optional_packages): lohit-konkani-fonts
  REMOVED(optional_packages): lohit-nepali-fonts
  REMOVED(optional_packages): lohit-sindhi-fonts
  REMOVED(optional_packages): paratype-pt-sans-fonts
  REMOVED(optional_packages): wqy-zenhei-fonts
GROUP_CHANGED: games
  ADDED(optional_packages): mine_detector
GROUP_CHANGED: gnome-desktop
  ADDED(default_packages): caribou
  ADDED(default_packages): deja-dup
  ADDED(default_packages): gnome-applets
  ADDED(default_packages): gnome-color-manager
  ADDED(default_packages): gnome-games
  ADDED(default_packages): preupgrade
  ADDED(default_packages): shotwell
  ADDED(default_packages): simple-scan
  REMOVED(default_packages): dasher
  REMOVED(default_packages): evince-djvu
  REMOVED(default_packages): gthumb
  REMOVED(default_packages): yum-presto
  ADDED(mandatory_packages): polkit-gnome
  REMOVED(mandatory_packages): gnome-applets
  ADDED(optional_packages): control-center-extra
  ADDED(optional_packages): dasher
  ADDED(optional_packages): evince-djvu
  ADDED(optional_packages): gthumb
  REMOVED(optional_packages): shotwell
GROUP_CHANGED: gnome-software-development
  ADDED(optional_packages): cairomm-doc
  REMOVED(optional_packages): eel2-devel
GROUP_CHANGED: graphical-internet
  ADDED(default_packages): transmission-gtk
  REMOVED(default_packages): ekiga
  REMOVED(default_packages): transmission
  ADDED(optional_packages): ekiga
  ADDED(optional_packages): transmission-qt
  REMOVED(optional_packages): gift-gnutella
  REMOVED(optional_packages): gift-openft
GROUP_CHANGED: graphics
  ADDED(default_packages): shotwell
  REMOVED(default_packages): f-spot
  ADDED(optional_packages): f-spot
  REMOVED(optional_packages): digikam-doc
GROUP_CHANGED: greek-support
  ADDED(optional_packages): gfs-goschen-fonts
GROUP_CHANGED: hardware-support
  ADDED(default_packages): ar9170-firmware
  ADDED(default_packages): iwl5150-firmware
  ADDED(default_packages): usb_modeswitch
GROUP_CHANGED: haskell
  REMOVED(default_packages): ghc-time-devel
  REMOVED(default_packages): ghc-utf8-string-devel
GROUP_CHANGED: hindi-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-hindi-fonts
GROUP_CHANGED: input-methods
  ADDED(default_packages): ibus-pinyin-open-phrase
  ADDED(optional_packages): ibus-xkbc
GROUP_CHANGED: japanese-support
  REMOVED(conditional_packages): poppler-data
GROUP_CHANGED: java-development
  REMOVED(optional_packages): jlint
GROUP_CHANGED: kashmiri-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-kashmiri-fonts
GROUP_CHANGED: kde-desktop
  REMOVED(conditional_packages): xine-lib-pulseaudio
  ADDED(default_packages): kbluetooth
  ADDED(default_packages): kcm_touchpad
  ADDED(default_packages): knetworkmanager
  ADDED(default_packages): pinentry-qt4
  ADDED(default_packages): qtcurve-gtk2
  ADDED(default_packages): qtcurve-kde4
  REMOVED(default_packages): NetworkManager-gnome
  REMOVED(default_packages): kdebluetooth
  REMOVED(default_packages): pinentry-qt
  ADDED(optional_packages): kde-plasma-smooth-tasks
  ADDED(optional_packages): transmission-qt
  REMOVED(optional_packages): apollon
  REMOVED(optional_packages): kcm_touchpad
  REMOVED(optional_packages): kde-plasma-stasks
  REMOVED(optional_packages): knetworkmanager
  REMOVED(optional_packages): qtcurve-kde4
  REMOVED(optional_packages): scim-qtimm
GROUP_CHANGED: konkani-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-konkani-fonts
GROUP_CHANGED: korean-support
  REMOVED(conditional_packages): poppler-data
GROUP_CHANGED: legacy-fonts
  REMOVED(default_packages): bitmap-fonts
  ADDED(optional_packages): bitmap-console-fonts
  ADDED(optional_packages): bitmap-fangsongti-fonts
  ADDED(optional_packages): bitmap-fixed-fonts
  ADDED(optional_packages): bitmap-lucida-typewriter-fonts
  ADDED(optional_packages): ucs-miscfixed-fonts
  REMOVED(optional_packages): bitmap-cjk-fonts
GROUP_CHANGED: lxde-desktop
  REMOVED(mandatory_packages): lxde-settings-daemon
GROUP_CHANGED: maithili-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-maithili-fonts
GROUP_CHANGED: marathi-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-marathi-fonts
GROUP_CHANGED: nepali-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-nepali-fonts
GROUP_CHANGED: office
  ADDED(optional_packages): gfa
  ADDED(optional_packages): openoffice.org-ogltrans
  ADDED(optional_packages): openoffice.org-presentation-minimizer
  ADDED(optional_packages): openoffice.org-report-builder
  ADDED(optional_packages): openoffice.org-wiki-publisher
  REMOVED(optional_packages): openoffice.org-pyuno
  REMOVED(optional_packages): openoffice.org-testtools
GROUP_CHANGED: printing
  ADDED(default_packages): paps
  ADDED(mandatory_packages): ghostscript-cups
GROUP_CHANGED: romanian-support
  ADDED(mandatory_packages): terminus-console-fonts
  ADDED(optional_packages): terminus-fonts
GROUP_CHANGED: russian-support
  REMOVED(conditional_packages): poppler-data
GROUP_CHANGED: sindhi-support
  ADDED(mandatory_packages): lohit-devanagari-fonts
  REMOVED(mandatory_packages): lohit-sindhi-fonts
GROUP_CHANGED: smb-server
  ADDED(default_packages): cifs-utils
GROUP_CHANGED: sound-and-video
  REMOVED(conditional_packages): xine-lib-pulseaudio
  ADDED(optional_packages): gtk-v4l
  REMOVED(optional_packages): bmpx
GROUP_CHANGED: sql-server
  ADDED(default_packages): PyGreSQL
  REMOVED(default_packages): postgresql-python
  ADDED(optional_packages): tcl-pgtcl
  REMOVED(optional_packages): postgresql-tcl
GROUP_CHANGED: system-tools
  ADDED(default_packages): cifs-utils
  ADDED(default_packages): openswan
  REMOVED(default_packages): ipsec-tools
  ADDED(optional_packages): ipsec-tools
GROUP_CHANGED: thai-support
  REMOVED(conditional_packages): poppler-data
GROUP_CHANGED: ukrainian-support
  REMOVED(conditional_packages): poppler-data
GROUP_CHANGED: xfce-desktop
  ADDED(default_packages): polkit-gnome
  REMOVED(default_packages): PolicyKit-gnome
#!/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 2008 red hat, inc

import sys
import yum.comps
from optparse import OptionParser

class Diff(object):
    '''Base class for Diff objects'''

    def added(self):
        '''return the list of items added'''
        raise NotImplemented

    def removed(self):
        '''return the list of items removed'''
        raise NotImplemented

    def changed(self):
        '''return the list of items changed'''
        raise NotImplemented

    def unchanged(self):
        '''return the list of items unchanged'''
        raise NotImplemented

class StrDiff(Diff):
    '''Class to represent the difference between two strings'''

    def __init__(self, string_a, string_b):
        '''initialize class'''

        super(StrDiff, self).__init__()
        self.string_a = str(string_a)
        self.string_b = str(string_b)

    def changed(self):
        '''return the two strings if they differ'''
        if self.string_a != self.string_b:
            return (self.string_a, self.string_b)
        return ()

    def unchanged(self):
        '''return the string if they're identical'''
        if self.string_a == self.string_b:
            return self.string_a
        return None

    def isdiff(self):
        '''return True if the strings are different'''
        return self.changed()

class ListDiff(Diff):
    '''Class to represent the difference between two lists'''
    def __init__(self, list_a, list_b):
        '''initialize class'''
        super(ListDiff, self).__init__()
        assert list_a != None
        assert list_b != None
        self.list_a = list(list_a)
        self.list_a.sort()
        self.list_b = list(list_b)
        self.list_b.sort()

    @staticmethod
    def _added(list_a, list_b):
        '''private method to get list of changed items'''
        keys = {}
        added = []
        for item in list_a:
            keys.setdefault(item, 0)
            keys[item] += 1
        for item in list_b:
            if keys.get(item, 0) > 0:
                keys[item] -= 1
            else:
                added.append(item)
        return sorted(added)

    def added(self):
        '''return list of added items'''
        return ListDiff._added(self.list_a, self.list_b)

    def removed(self):
        '''return list of removed items'''
        return ListDiff._added(self.list_b, self.list_a)

    def isdiff(self):
        '''return true if there is any difference between the lists'''
        return self.added() or self.removed()

class DictDiff(Diff):
    '''Class to represent the difference between two dicts'''
    def __init__(self, dict_a, dict_b):
        '''initialize class'''
        super(DictDiff, self).__init__()
        assert dict_a != None
        assert dict_b != None
        self.dict_a = dict_a
        self.dict_b = dict_b

    def added(self):
        '''return list of keys that have been added'''
        return ListDiff(self.dict_a.keys(), self.dict_b.keys()).added()

    def removed(self):
        '''return list of keys that have been removed'''
        return ListDiff(self.dict_a.keys(), self.dict_b.keys()).removed()

    def changed(self):
        '''return list of keys where the value has changed'''
        _changed = []
        for key in sorted(self.dict_a.keys()):
            if self.dict_b.has_key(key):
                if isinstance(self.dict_a[key], yum.comps.Category) and \
                        isinstance(self.dict_b[key], yum.comps.Category):
                    if CategoryDiff(self.dict_a[key], self.dict_b[key]).diffs():
                        _changed.append(key)
                elif isinstance(self.dict_a[key], yum.comps.Group) and \
                        isinstance(self.dict_b[key], yum.comps.Group):
                    if GroupDiff(self.dict_a[key], self.dict_b[key]).diffs():
                        _changed.append(key)
                elif self.dict_a[key] != self.dict_b[key]:
                    _changed.append(key)
        return sorted(_changed)

    def isdiff(self):
        '''return true if any keys have been added or removed or if
        any values have changed'''
        return self.added() or self.removed() or self.changed()

class ObjectDiff(object):
    '''Class to represent the difference between two objects'''
    def __init__(self, object_a, object_b, attrs={}):
        '''initialize class'''
        self.object_a = object_a
        self.object_b = object_b
        self.attrs = attrs

    def diffs(self):
        '''return a dict of changes between the two objects'''
        changes = {}
        for attr, attrclass in self.attrs.items():
            diff = attrclass(getattr(self.object_a, attr),
                             getattr(self.object_b, attr))
            if diff.isdiff():
                changes[attr] = diff
        return changes

class CategoryDiff(ObjectDiff):
    '''Class to represent the difference between two categories'''

    def __init__(self, category_a, category_b, include_translations=False):
        '''initialize class'''
        super(CategoryDiff, self).__init__(category_a, category_b, attrs = {
                'name': StrDiff,
                'categoryid': StrDiff,
                'description': StrDiff,
                'display_order': StrDiff,
                'groups': ListDiff
                }
                                           )

class GroupDiff(ObjectDiff):
    '''Class to represent the difference between two groups'''

    def __init__(self, group_a, group_b, include_translations=False):
        '''initialize class'''
        super(GroupDiff, self).__init__(group_a, group_b, attrs = {
                "user_visible": StrDiff,
                "default": StrDiff,
                "selected": StrDiff,
                "name": StrDiff,
                "description": StrDiff,
                "mandatory_packages": DictDiff,
                "optional_packages": DictDiff,
                "default_packages": DictDiff,
                "conditional_packages": DictDiff,
                "langonly": StrDiff,
                "groupid": StrDiff,
                "display_order": StrDiff,
                }
                                        )

def myprint(key, value, indent="  "):
    '''helper method for printing diffs'''
    if isinstance(value, StrDiff):
        print "%sCHANGED(%s): %s -> %s" % (indent,
                                           key,
                                           value.changed()[0],
                                           value.changed()[1])
    elif isinstance(value, DictDiff):
        for identifier in value.added():
            print "%sADDED(%s): %s" % (indent, key, identifier)
        for identifier in value.removed():
            print "%sREMOVED(%s): %s" % (indent, key, identifier)
        for identifier in value.changed():
            print "%sCHANGED(%s): %s: %s -> %s" % (indent,
                                                   key,
                                                   identifier,
                                                   value.dict_a[identifier],
                                                   value.dict_b[identifier])
    elif isinstance(value, ListDiff):
        for identifier in value.added():
            print "%sADDED(%s): %s" % (indent, key, identifier)
        for identifier in value.removed():
            print "%sREMOVED(%s): %s" % (indent, key, identifier)
    else:
        print "unknown", value

def _parse_args(args):
    parser = OptionParser("usage: %prog [options] <comps file> <comps file>")
    args = parser.parse_args(args)[1]

    if len(args) != 3:
        parser.error("Incorrect number of arguments")

    comps_a = yum.comps.Comps()
    comps_a.add(args[1])

    comps_b = yum.comps.Comps()
    comps_b.add(args[2])

    return comps_a, comps_b

def main(args):
    comps_a, comps_b = _parse_args(args)
    comps_a_categories = dict([ (category.categoryid, category) \
                                   for category in comps_a.categories ])
    comps_b_categories = dict([ (category.categoryid, category) \
                                   for category in comps_b.categories ])
    catdiff = DictDiff(comps_a_categories, comps_b_categories)
    for identifier in catdiff.added():
        print "CATEGORY_ADDED: %s" % identifier
    for identifier in catdiff.removed():
        print "CATEGORY_REMOVED: %s" % identifier
    for identifier in catdiff.changed():
        diffs = CategoryDiff(comps_a_categories[identifier],
                             comps_b_categories[identifier]).diffs()
        if diffs:
            print "CATEGORY_CHANGED: %s" % identifier
            for key, value in diffs.items():
                myprint(key, value)

    comps_a_groups = dict([ (group.groupid, group) \
                                for group in comps_a.groups ])
    comps_b_groups = dict([ (group.groupid, group) \
                                for group in comps_b.groups ])
    groupdiff = DictDiff(comps_a_groups, comps_b_groups)
    for identifier in groupdiff.added():
        print "GROUP_ADDED: %s" % identifier
    for identifier in groupdiff.removed():
        print "GROUP_REMOVED: %s" % identifier
    for identifier in groupdiff.changed():
        diffs = GroupDiff(comps_a_groups[identifier],
                          comps_b_groups[identifier]).diffs()
        if diffs:
            print "GROUP_CHANGED: %s" % identifier
            for key, value in sorted(diffs.items()):
                myprint(key, value)

if __name__ == '__main__':
    main(sys.argv)
_______________________________________________
Yum-devel mailing list
Yum-devel@lists.baseurl.org
http://lists.baseurl.org/mailman/listinfo/yum-devel

Reply via email to