diff --git a/bin/is b/bin/is index 3ff65366d5f43b34912c7f72c4dc8da5f52b91d4..4b7932a7485de39655678dcea7b128b56cf8e78e 100755 --- a/bin/is +++ b/bin/is @@ -149,6 +149,18 @@ def c_del(parser, args): raise Exception("Aborted!") repo.delete(img.name, img.version) +def c_diff(parser, args): + ''' + Show diff between two repositories or images + ''' + repoman = load_repositories(args) + if args.object[0] in repoman.onlines and args.object[1] in repoman.onlines: + Repository.diff(repoman[args.object[0]], repoman[args.object[1]]) + else: + img1, repo1 = select_image(args.object[0], repoman, args.best) + img2, repo2 = select_image(args.object[1], repoman, args.best) + PackageImage.diff(img1, img2) + def c_extract(parser, args): ''' Extract an image package inside a directory @@ -357,6 +369,14 @@ p_del.add_argument("-f", "--force", action="store_true", default=False, help="delete image without confirmation") p_del.set_defaults(func=c_del) +# diff command parser +p_diff = subparsers.add_parser("diff", help=c_diff.__doc__.lower()) +p_diff.add_argument("object", nargs=2, + help="object syntax is <path|repository|[repository/]image[:version]>") +p_diff.add_argument("-b", "--best", action="store_true", default=False, + help="in best mode, image is the most recent in all repositories") +p_diff.set_defaults(func=c_diff) + # extract command parser p_extract = subparsers.add_parser("extract", help=c_extract.__doc__.lower()) p_extract.add_argument("-p", action="store_true", dest="payload", default=False, diff --git a/installsystems/image.py b/installsystems/image.py index d261c87ac6ae6917ac9a89b76e2b75e71da741e6..db07b5a2df2a72cefe033475a2b897922abda90f 100644 --- a/installsystems/image.py +++ b/installsystems/image.py @@ -10,6 +10,7 @@ import os import stat import time import json +import difflib import ConfigParser import subprocess import tarfile @@ -351,6 +352,46 @@ class PackageImage(Image): Packaged image manipulation class ''' + @classmethod + def diff(cls, pkg1, pkg2): + ''' + Diff two packaged images + ''' + arrow("Differnce from images #y#%s v%s#R# to #r#%s v%s#R#:" % (pkg1.name, + pkg1.version, + pkg2.name, + pkg2.version)) + # Extract images for diff scripts files + fromfiles = set(pkg1._tarball.getnames(re_pattern="(parser|setup)/.*")) + tofiles = set(pkg2._tarball.getnames(re_pattern="(parser|setup)/.*")) + for f in fromfiles | tofiles: + # preparing from info + if f in fromfiles: + fromfile = os.path.join(pkg1.id, f) + fromdata = pkg1._tarball.extractfile(f).readlines() + else: + fromfile = "/dev/null" + fromdata = "" + # preparing to info + if f in tofiles: + tofile = os.path.join(pkg2.id, f) + todata = pkg2._tarball.extractfile(f).readlines() + else: + tofile = "/dev/null" + todata = "" + # generate diff + for line in difflib.unified_diff(fromdata, todata, + fromfile=fromfile, tofile=tofile): + # coloring diff + if line.startswith("+"): + out("#g#%s#R#" % line, endl="") + elif line.startswith("-"): + out("#r#%s#R#" % line, endl="") + elif line.startswith("@@"): + out("#c#%s#R#" % line, endl="") + else: + out(line, endl="") + def __init__(self, path, fileobj=None, md5name=False): ''' Initialize a package image @@ -569,7 +610,6 @@ class PackageImage(Image): arrowlevel(level=old_level) arrowlevel(-1) - class Payload(object): ''' Payload class represents a payload object diff --git a/installsystems/repository.py b/installsystems/repository.py index 858b528ec7b590c3772e186ec3787ce2fc56a430..418d251484224b389fe7ce7c692b4c74f7e09f35 100644 --- a/installsystems/repository.py +++ b/installsystems/repository.py @@ -26,6 +26,42 @@ class Repository(object): Repository class ''' + @classmethod + def diff(cls, repo1, repo2): + ''' + Comptue a diff between two repositories + ''' + arrow("Diff between repositories #y#%s#R# and #g#%s#R#" % (repo1.config.name, + repo2.config.name)) + # Get info from databases + i_dict1 = dict((b[0], b[1:]) for b in repo1.db.ask( + "SELECT md5, name, version FROM image").fetchall()) + i_set1 = set(i_dict1.keys()) + i_dict2 = dict((b[0], b[1:]) for b in repo2.db.ask( + "SELECT md5, name, version FROM image").fetchall()) + i_set2 = set(i_dict2.keys()) + p_dict1 = dict((b[0], b[1:]) for b in repo1.db.ask( + "SELECT md5, name FROM payload").fetchall()) + p_set1 = set(p_dict1.keys()) + p_dict2 = dict((b[0], b[1:]) for b in repo2.db.ask( + "SELECT md5, name FROM payload").fetchall()) + p_set2 = set(p_dict2.keys()) + # computing diff + i_only1 = i_set1 - i_set2 + i_only2 = i_set2 - i_set1 + p_only1 = p_set1 - p_set2 + p_only2 = p_set2 - p_set1 + # printing functions + pimg = lambda r,c,m,d,: out("#%s#Image only in repository %s: %s v%s (%s)#R#" % + (c, r.config.name, d[m][0], d[m][1], m)) + ppay = lambda r,c,m,d,: out("#%s#Payload only in repository %s: %s (%s)#R#" % + (c, r.config.name, d[m][0], m)) + # printing image diff + for md5 in i_only1: pimg(repo1, "y", md5, i_dict1) + for md5 in p_only1: ppay(repo1, "y", md5, p_dict1) + for md5 in i_only2: pimg(repo2, "g", md5, i_dict2) + for md5 in p_only2: ppay(repo2, "g", md5, p_dict2) + def __init__(self, config): self.config = config if not config.offline: