protect against circular patch chains

This commit is contained in:
David Rose 2009-10-07 00:17:17 +00:00
parent 3786a86d0b
commit 6b0f19f13e
3 changed files with 82 additions and 40 deletions

View File

@ -23,9 +23,10 @@ class PatchMaker:
self.printName = None self.printName = None
# The Package object that produces this version, if this # The Package object that produces this version, if this
# is the current form or the base form, respectively. # is the current, base, or top form, respectively.
self.packageCurrent = None self.packageCurrent = None
self.packageBase = None self.packageBase = None
self.packageTop = None
# A list of patchfiles that can produce this version. # A list of patchfiles that can produce this version.
self.fromPatches = [] self.fromPatches = []
@ -41,7 +42,7 @@ class PatchMaker:
if self.tempFile: if self.tempFile:
self.tempFile.unlink() self.tempFile.unlink()
def getPatchChain(self, startPv): def getPatchChain(self, startPv, alreadyVisited = []):
""" Returns a list of patches that, when applied in """ Returns a list of patches that, when applied in
sequence to the indicated PackageVersion object, will sequence to the indicated PackageVersion object, will
produce this PackageVersion object. Returns None if no produce this PackageVersion object. Returns None if no
@ -51,11 +52,18 @@ class PatchMaker:
# We're already here. A zero-length patch chain is # We're already here. A zero-length patch chain is
# therefore the answer. # therefore the answer.
return [] return []
if self in alreadyVisited:
# We've already been here; this is a loop. Avoid
# infinite recursion.
return None
alreadyVisited = alreadyVisited[:]
alreadyVisited.append(self)
bestPatchChain = None bestPatchChain = None
for patchfile in self.fromPatches: for patchfile in self.fromPatches:
fromPv = patchfile.fromPv fromPv = patchfile.fromPv
patchChain = fromPv.getPatchChain(startPv) patchChain = fromPv.getPatchChain(startPv, alreadyVisited)
if patchChain is not None: if patchChain is not None:
# There's a path through this patchfile. # There's a path through this patchfile.
patchChain = patchChain + [patchfile] patchChain = patchChain + [patchfile]
@ -66,19 +74,27 @@ class PatchMaker:
# paths found. # paths found.
return bestPatchChain return bestPatchChain
def getRecreateFilePlan(self): def getRecreateFilePlan(self, alreadyVisited = []):
""" Returns the tuple (startFile, startPv, plan), """ Returns the tuple (startFile, startPv, plan),
describing how to recreate the archive file for this describing how to recreate the archive file for this
version. startFile and startPv is the Filename and version. startFile and startPv is the Filename and
packageVersion of the file to start with, and plan is a packageVersion of the file to start with, and plan is a
list of tuples (patchfile, pv), listing the patches to list of tuples (patchfile, pv), listing the patches to
apply in sequence, and the packageVersion object apply in sequence, and the packageVersion object
associated with each patch. Returns (None, None) if there associated with each patch. Returns (None, None, None) if
is no way to recreate this archive file. """ there is no way to recreate this archive file. """
if self.tempFile: if self.tempFile:
return (self.tempFile, self, []) return (self.tempFile, self, [])
if self in alreadyVisited:
# We've already been here; this is a loop. Avoid
# infinite recursion.
return (None, None, None)
alreadyVisited = alreadyVisited[:]
alreadyVisited.append(self)
if self.packageCurrent: if self.packageCurrent:
# This PackageVersion instance represents the current # This PackageVersion instance represents the current
# version of some package. # version of some package.
@ -97,7 +113,7 @@ class PatchMaker:
bestStartPv = None bestStartPv = None
for patchfile in self.fromPatches: for patchfile in self.fromPatches:
fromPv = patchfile.fromPv fromPv = patchfile.fromPv
startFile, startPv, plan = fromPv.getRecreateFilePlan() startFile, startPv, plan = fromPv.getRecreateFilePlan(alreadyVisited)
if plan is not None: if plan is not None:
# There's a path through this patchfile. # There's a path through this patchfile.
plan = plan + [(patchfile, self)] plan = plan + [(patchfile, self)]
@ -279,6 +295,7 @@ class PatchMaker:
self.patchVersion = 1 self.patchVersion = 1
self.currentPv = None self.currentPv = None
self.basePv = None self.basePv = None
self.topPv = None
self.packageName = None self.packageName = None
self.platform = None self.platform = None
@ -303,14 +320,22 @@ class PatchMaker:
return (self.packageName, self.platform, self.version, self.hostUrl, self.baseFile) return (self.packageName, self.platform, self.version, self.hostUrl, self.baseFile)
def getTopKey(self):
""" Returns the key to locate the "top" or newest version
of this package. """
return (self.packageName, self.platform, self.version, self.hostUrl, self.topFile)
def getGenericKey(self, fileSpec): def getGenericKey(self, fileSpec):
""" Returns the key that has the indicated hash. """ """ Returns the key that has the indicated hash. """
return (self.packageName, self.platform, self.version, self.hostUrl, fileSpec) return (self.packageName, self.platform, self.version, self.hostUrl, fileSpec)
def readDescFile(self): def readDescFile(self, doProcessing = False):
""" Reads the existing package.xml file and stores it in """ Reads the existing package.xml file and stores it in
this class for later rewriting. Returns true on success, this class for later rewriting. if doProcessing is true,
false on failure. """ it may massage the file and the directory contents in
preparation for building patches. Returns true on
success, false on failure. """
self.anyChanges = False self.anyChanges = False
@ -361,7 +386,6 @@ class PatchMaker:
isNewVersion = False isNewVersion = False
else: else:
# There's a new version this pass. Update it. # There's a new version this pass. Update it.
self.topFile = copy.copy(self.currentFile)
self.anyChanges = True self.anyChanges = True
else: else:
@ -399,6 +423,9 @@ class PatchMaker:
compressedFile.loadXml(xcompressed) compressedFile.loadXml(xcompressed)
oldCompressedFilename = compressedFile.filename oldCompressedFilename = compressedFile.filename
self.compressedFilename = oldCompressedFilename
if doProcessing:
newCompressedFilename = '%s.%s.pz' % (self.currentFile.filename, self.patchVersion) newCompressedFilename = '%s.%s.pz' % (self.currentFile.filename, self.patchVersion)
oldCompressedPathname = Filename(self.packageDir, oldCompressedFilename) oldCompressedPathname = Filename(self.packageDir, oldCompressedFilename)
newCompressedPathname = Filename(self.packageDir, newCompressedFilename) newCompressedPathname = Filename(self.packageDir, newCompressedFilename)
@ -429,7 +456,7 @@ class PatchMaker:
self.baseFile.filename += '.base' self.baseFile.filename += '.base'
# Also duplicate the (compressed) file itself. # Also duplicate the (compressed) file itself.
if self.compressedFilename: if doProcessing and self.compressedFilename:
fromPathname = Filename(self.packageDir, self.compressedFilename) fromPathname = Filename(self.packageDir, self.compressedFilename)
toPathname = Filename(self.packageDir, self.baseFile.filename + '.pz') toPathname = Filename(self.packageDir, self.baseFile.filename + '.pz')
fromPathname.copyTo(toPathname) fromPathname.copyTo(toPathname)
@ -460,7 +487,7 @@ class PatchMaker:
# Remove all of the old patch entries from the desc file # Remove all of the old patch entries from the desc file
# we read earlier. # we read earlier.
xremove = [] xremove = []
for value in ['base_version', 'patch']: for value in ['base_version', 'top_version', 'patch']:
xpatch = xpackage.FirstChildElement(value) xpatch = xpackage.FirstChildElement(value)
while xpatch: while xpatch:
xremove.append(xpatch) xremove.append(xpatch)
@ -478,8 +505,9 @@ class PatchMaker:
self.baseFile.storeXml(xarchive) self.baseFile.storeXml(xarchive)
xpackage.InsertEndChild(xarchive) xpackage.InsertEndChild(xarchive)
# The current version is now the top version.
xarchive = TiXmlElement('top_version') xarchive = TiXmlElement('top_version')
self.topFile.storeXml(xarchive) self.currentFile.storeXml(xarchive)
xpackage.InsertEndChild(xarchive) xpackage.InsertEndChild(xarchive)
for patchfile in self.patches: for patchfile in self.patches:
@ -554,7 +582,7 @@ class PatchMaker:
object, or None on failure. """ object, or None on failure. """
package = self.Package(Filename(descFilename), self) package = self.Package(Filename(descFilename), self)
if not package.readDescFile(): if not package.readDescFile(doProcessing = False):
return None return None
self.packages.append(package) self.packages.append(package)
@ -581,7 +609,7 @@ class PatchMaker:
if filename and not solo: if filename and not solo:
filename = Filename(filename) filename = Filename(filename)
package = self.Package(filename, self, xpackage) package = self.Package(filename, self, xpackage)
package.readDescFile() package.readDescFile(doProcessing = True)
self.packages.append(package) self.packages.append(package)
xpackage = xpackage.NextSiblingElement('package') xpackage = xpackage.NextSiblingElement('package')
@ -639,6 +667,10 @@ class PatchMaker:
basePv.packageBase = package basePv.packageBase = package
basePv.printName = package.baseFile.filename basePv.printName = package.baseFile.filename
topPv = self.getPackageVersion(package.getTopKey())
package.topPv = topPv
topPv.packageTop = package
for patchfile in package.patches: for patchfile in package.patches:
self.recordPatchfile(patchfile) self.recordPatchfile(patchfile)
@ -682,22 +714,15 @@ class PatchMaker:
# No versions. # No versions.
return return
# Starting with the package base, how far forward can we go? # What's the current version on the top of the tree?
topPv = package.topPv
currentPv = package.currentPv currentPv = package.currentPv
basePv = package.basePv
pv = basePv if topPv != currentPv:
nextPv = pv.getNext(package) # They're different, so build a new patch.
while nextPv:
pv = nextPv
nextPv = pv.getNext(package)
if pv.packageCurrent != package:
# It doesn't reach all the way to the latest version, so
# build a new patch.
filename = Filename(package.currentFile.filename + '.%s.patch' % (package.patchVersion)) filename = Filename(package.currentFile.filename + '.%s.patch' % (package.patchVersion))
assert filename not in self.patchFilenames assert filename not in self.patchFilenames
if not self.buildPatch(pv, currentPv, package, filename): if not self.buildPatch(topPv, currentPv, package, filename):
raise StandardError, "Couldn't build patch." raise StandardError, "Couldn't build patch."
def buildPatch(self, v1, v2, package, patchFilename): def buildPatch(self, v1, v2, package, patchFilename):

View File

@ -40,13 +40,27 @@ PackageVersion(const PackageVersionKey &key) :
// false if no chain can be found. // false if no chain can be found.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
bool P3DPatchFinder::PackageVersion:: bool P3DPatchFinder::PackageVersion::
get_patch_chain(Patchfiles &chain, PackageVersion *start_pv) { get_patch_chain(Patchfiles &chain, PackageVersion *start_pv,
const PackageVersionsList &already_visited_in) {
chain.clear(); chain.clear();
if (this == start_pv) { if (this == start_pv) {
// We're already here. A zero-length patch chain is therefore the // We're already here. A zero-length patch chain is therefore the
// answer. // answer.
return true; return true;
} }
if (::find(already_visited_in.begin(), already_visited_in.end(), this) != already_visited_in.end()) {
// We've already been here; this is a loop. Avoid infinite
// recursion.
return false;
}
// Yeah, we make a new copy of this vector at each stage of the
// recursion. This could be made much faster with a linked list
// instead, but I'm working on the assumption that there will be no
// more than a few dozen patchfiles, in which case this naive
// approach should be fast enough.
PackageVersionsList already_visited = already_visited_in;
already_visited.push_back(this);
bool found_any = false; bool found_any = false;
Patchfiles::iterator pi; Patchfiles::iterator pi;
@ -55,7 +69,7 @@ get_patch_chain(Patchfiles &chain, PackageVersion *start_pv) {
PackageVersion *from_pv = patchfile->_from_pv; PackageVersion *from_pv = patchfile->_from_pv;
assert(from_pv != NULL); assert(from_pv != NULL);
Patchfiles this_chain; Patchfiles this_chain;
if (from_pv->get_patch_chain(this_chain, start_pv)) { if (from_pv->get_patch_chain(this_chain, start_pv, already_visited)) {
// There's a path through this patchfile. // There's a path through this patchfile.
this_chain.push_back(patchfile); this_chain.push_back(patchfile);
if (!found_any || this_chain.size() < chain.size()) { if (!found_any || this_chain.size() < chain.size()) {
@ -341,7 +355,7 @@ get_patch_chain_to_current(Patchfiles &chain, TiXmlDocument *doc,
PackageVersion *to_pv = package->_current_pv; PackageVersion *to_pv = package->_current_pv;
if (to_pv != NULL && from_pv != NULL) { if (to_pv != NULL && from_pv != NULL) {
return to_pv->get_patch_chain(chain, from_pv); return to_pv->get_patch_chain(chain, from_pv, PackageVersionsList());
} }
return false; return false;

View File

@ -35,8 +35,10 @@ class P3DPatchFinder {
public: public:
class Package; class Package;
class Patchfile; class Patchfile;
class PackageVersion;
typedef vector<Patchfile *> Patchfiles; typedef vector<Patchfile *> Patchfiles;
typedef vector<PackageVersion *> PackageVersionsList;
// This class is used to index into a map to locate PackageVersion // This class is used to index into a map to locate PackageVersion
// objects, below. // objects, below.
@ -65,7 +67,8 @@ public:
public: public:
PackageVersion(const PackageVersionKey &key); PackageVersion(const PackageVersionKey &key);
bool get_patch_chain(Patchfiles &chain, PackageVersion *start_pv); bool get_patch_chain(Patchfiles &chain, PackageVersion *start_pv,
const PackageVersionsList &already_visited_in);
public: public:
string _package_name; string _package_name;