+##############################
+class Package:
+
+ def __init__(self, package, module, svnpath, spec):
+ self.package=package
+ self.module=module
+ self.svnpath=svnpath
+ self.spec=spec
+ self.specpath="%s/%s"%(svnpath,spec)
+ self.basename=os.path.basename(svnpath)
+
+ # returns a http URL to the trac path where full diff can be viewed (between self and pkg)
+ # typically http://svn.planet-lab.org/changeset?old_path=Monitor%2Ftags%2FMonitor-1.0-7&new_path=Monitor%2Ftags%2FMonitor-1.0-13
+ # xxx quick & dirty: rough url parsing
+ def trac_full_diff (self, pkg):
+ matcher=re.compile("\A(?P<method>.*)://(?P<hostname>[^/]+)/(svn/)?(?P<path>.*)\Z")
+ self_match=matcher.match(self.svnpath)
+ pkg_match=matcher.match(pkg.svnpath)
+ if self_match and pkg_match:
+ (method,hostname,svn,path)=self_match.groups()
+ self_path=path.replace("/","%2F")
+ pkg_path=pkg_match.group('path').replace("/","%2F")
+ return "%s://%s/changeset?old_path=%s&new_path=%s"%(method,hostname,self_path,pkg_path)
+ else:
+ return None
+
+ def details (self):
+ return "[%s %s] [%s (spec)]"%(self.svnpath,self.basename,self.specpath)
+
+class Build (Module):
+
+ # we cannot get build's svnpath as for other packages as we'd get something in svn+ssh
+ # xxx quick & dirty
+ def __init__ (self, buildtag,options):
+ self.buildtag=buildtag
+ # if the buildtag start with a : (to use a branch rather than a tag)
+ if buildtag.find(':') == 0 :
+ module_name="build%(buildtag)s"%locals()
+ self.display=buildtag[1:]
+ self.svnpath="http://svn.planet-lab.org/svn/build/branches/%s"%self.display
+ else :
+ module_name="build@%(buildtag)s"%locals()
+ self.display=buildtag
+ self.svnpath="http://svn.planet-lab.org/svn/build/tags/%s"%self.buildtag
+ Module.__init__(self,module_name,options)
+
+ @staticmethod
+ def get_distro_from_distrotag (distrotag):
+ # mhh: remove -tag* from distrotags to get distro
+ n=distrotag.find('-tag')
+ if n>0:
+ return distrotag[:n]
+ else:
+ return None
+
+ def get_packages (self,distrotag):
+ result={}
+ distro=Build.get_distro_from_distrotag(distrotag)
+ if not distro:
+ return result
+ make_options="--no-print-directory -C %s stage1=true PLDISTRO=%s PLDISTROTAGS=%s 2> /dev/null"%(self.edge_dir(),distro,distrotag)
+ command="make %s packages"%make_options
+ make_packages=Command(command,self.options).output_of()
+ pkg_line=re.compile("\Apackage=(?P<package>[^\s]+)\s+ref_module=(?P<module>[^\s]+)\s.*\Z")
+ for line in make_packages.split("\n"):
+ if not line:
+ continue
+ attempt=pkg_line.match(line)
+ if line and not attempt:
+ print "====="
+ print "WARNING: line not understood from make packages"
+ print "in dir %s"%self.edge_dir
+ print "with options",make_options
+ print 'line=',line
+ print "====="
+ else:
+ (package,module) = (attempt.group('package'),attempt.group('module'))
+ command="make %s +%s-SVNPATH"%(make_options,module)
+ svnpath=Command(command,self.options).output_of().strip()
+ command="make %s +%s-SPEC"%(make_options,package)
+ spec=Command(command,self.options).output_of().strip()
+ result[package]=Package(package,module,svnpath,spec)
+ return result
+
+ def get_distrotags (self):
+ return [os.path.basename(p) for p in glob("%s/*tags*mk"%self.edge_dir())]
+
+class DiffCache:
+
+ def __init__ (self):
+ self._cache={}
+
+ def key(self, frompath,topath):
+ return frompath+'-to-'+topath
+
+ def fetch (self, frompath, topath):
+ key=self.key(frompath,topath)
+ if not self._cache.has_key(key):
+ return None
+ return self._cache[key]
+
+ def store (self, frompath, topath, diff):
+ key=self.key(frompath,topath)
+ self._cache[key]=diff
+
+class Release:
+
+ # header in diff output
+ discard_matcher=re.compile("\A(\+\+\+|---).*")
+
+ @staticmethod
+ def do_changelog (buildtag_new,buildtag_old,options):
+ print "----"
+ print "----"
+ print "----"
+ (build_new,build_old) = (Build (buildtag_new,options), Build (buildtag_old,options))
+ print "= build tag %s to %s = #build-%s"%(build_old.display,build_new.display,build_new.display)
+ for b in (build_new,build_old):
+ b.init_module_dir()
+ b.init_edge_dir()
+ b.update_edge_dir()
+ # find out the tags files that are common, unless option was specified
+ if options.distrotags:
+ distrotags=options.distrotags
+ else:
+ distrotags_new=build_new.get_distrotags()
+ distrotags_old=build_old.get_distrotags()
+ distrotags = list(set(distrotags_new).intersection(set(distrotags_old)))
+ distrotags.sort()
+ if options.verbose: print "Found distrotags",distrotags
+ first_distrotag=True
+ diffcache = DiffCache()
+ for distrotag in distrotags:
+ distro=Build.get_distro_from_distrotag(distrotag)
+ if not distro:
+ continue
+ if first_distrotag:
+ first_distrotag=False
+ else:
+ print '----'
+ print '== distro %s (%s to %s) == #distro-%s-%s'%(distrotag,build_old.display,build_new.display,distro,build_new.display)
+ print ' * from %s/%s'%(build_old.svnpath,distrotag)
+ print ' * to %s/%s'%(build_new.svnpath,distrotag)
+
+ # parse make packages
+ packages_new=build_new.get_packages(distrotag)
+ pnames_new=set(packages_new.keys())
+ if options.verbose: print 'got packages for ',build_new.display
+ packages_old=build_old.get_packages(distrotag)
+ pnames_old=set(packages_old.keys())
+ if options.verbose: print 'got packages for ',build_old.display
+
+ # get created, deprecated, and preserved package names
+ pnames_created = list(pnames_new-pnames_old)
+ pnames_created.sort()
+ pnames_deprecated = list(pnames_old-pnames_new)
+ pnames_deprecated.sort()
+ pnames = list(pnames_new.intersection(pnames_old))
+ pnames.sort()
+
+ if options.verbose: print "Found new/deprecated/preserved pnames",pnames_new,pnames_deprecated,pnames
+
+ # display created and deprecated
+ for name in pnames_created:
+ print '=== %s : new package %s -- appeared in %s === #package-%s-%s-%s'%(
+ distrotag,name,build_new.display,name,distro,build_new.display)
+ pobj=packages_new[name]
+ print ' * %s'%pobj.details()
+ for name in pnames_deprecated:
+ print '=== %s : package %s -- deprecated, last occurrence in %s === #package-%s-%s-%s'%(
+ distrotag,name,build_old.display,name,distro,build_new.display)
+ pobj=packages_old[name]
+ if not pobj.svnpath:
+ print ' * codebase stored in CVS, specfile is %s'%pobj.spec
+ else:
+ print ' * %s'%pobj.details()
+
+ # display other packages
+ for name in pnames:
+ (pobj_new,pobj_old)=(packages_new[name],packages_old[name])
+ if options.verbose: print "Dealing with package",name
+ if pobj_old.specpath == pobj_new.specpath:
+ continue
+ specdiff = diffcache.fetch(pobj_old.specpath,pobj_new.specpath)
+ if specdiff is None:
+ command="svn diff %s %s"%(pobj_old.specpath,pobj_new.specpath)
+ specdiff=Command(command,options).output_of()
+ diffcache.store(pobj_old.specpath,pobj_new.specpath,specdiff)
+ else:
+ if options.verbose: print 'got diff from cache'
+ if not specdiff:
+ continue
+ print '=== %s - %s to %s : package %s === #package-%s-%s-%s'%(
+ distrotag,build_old.display,build_new.display,name,name,distro,build_new.display)
+ print ' * from %s to %s'%(pobj_old.details(),pobj_new.details())
+ trac_diff_url=pobj_old.trac_full_diff(pobj_new)
+ if trac_diff_url:
+ print ' * [%s View full diff]'%trac_diff_url
+ print '{{{'
+ for line in specdiff.split('\n'):
+ if not line:
+ continue
+ if Release.discard_matcher.match(line):
+ continue
+ if line[0] in ['@']:
+ print '----------'
+ elif line[0] in ['+','-']:
+ print_fold(line)
+ print '}}}'
+