1
0
Fork 0

Maintain catalog script starting to work.

This commit is contained in:
James Turner 2015-07-11 21:47:13 +01:00
parent 287380d98d
commit fbda7475f0
5 changed files with 220 additions and 159 deletions

View file

@ -7,7 +7,7 @@ class GITCatalogRepository:
def __init__(self, path, usesSubmodules = False, singleAircraft = False): def __init__(self, path, usesSubmodules = False, singleAircraft = False):
self._path = path self._path = path
if !os.path.exists(os.path.join(path, ".git")): if not os.path.exists(os.path.join(path, ".git")):
raise RuntimeError("not a Git directory:" + path) raise RuntimeError("not a Git directory:" + path)
self._usesSubmodules = usesSubmodules self._usesSubmodules = usesSubmodules

View file

@ -4,7 +4,7 @@ import subprocess
import os import os
import sgprops import sgprops
import GITCatalogRepository import git_catalog_repository
class GitDiscreteSCM: class GitDiscreteSCM:
def __init__(self, node): def __init__(self, node):

View file

@ -12,120 +12,131 @@ import git_catalog_repository
import git_discrete_repository import git_discrete_repository
# TODO # TODO
# uploading / rsyncing # uploading / rsyncing
class VariantData: class VariantData:
def __init__(self, primary, path, node): def __init__(self, primary, path, node):
self._primary = primary self._primary = primary
self._path = path self._path = path
self._name = node.getValue("sim/description") self._name = node.getValue("sim/description")
# ratings # ratings
# seperate thumbnails # seperate thumbnails
@property @property
def catalogNode(self): def catalogNode(self):
n = Node("variant") n = Node("variant")
n.addChild("id") = path n.addChild("id").value = path
m.addChild("name") = self._name n.addChild("name").value = self._name
class PackageData: class PackageData:
def __init__(path): def __init__(self, path):
self._path = path self._path = path
self._previousSCMRevision = None self._previousSCMRevision = None
self._previousRevision = 0 self._previousRevision = 0
self._thumbnails = [] self._thumbnails = []
self._variants = {} self._variants = {}
self._revision = 0
self._node = sgprops.Node() self._md5 = None
self._fileSize = 0
self._node = sgprops.Node("package")
self._node.addChild("id").value = self.id self._node.addChild("id").value = self.id
def setPreviousData(node): def setPreviousData(self, node):
self._previousRevision = node.getValue("revision") self._previousRevision = node.getValue("revision")
self._previousMD5 = node.getValue("md5") self._previousMD5 = node.getValue("md5")
self._previousSCMRevision = node.getValue("scm-revision") self._previousSCMRevision = node.getValue("scm-revision")
self._fileSize = int(node.getValue("file-size-bytes"))
@property @property
def id(self): def id(self):
return os.path.basename(self._path) return os.path.basename(self._path)
@property @property
def thumbnails(self): def thumbnails(self):
return self._thumbnails return self._thumbnails
def isSourceModified(self, scmRepo): @property
if (self._previousSCMRevision == None): def path(self):
return True return self._path
@property
def scmRevision(self):
currentRev = scmRepo.scmRevisionForPath(self._path) currentRev = scmRepo.scmRevisionForPath(self._path)
if (currentRev is None): if (currentRev is None):
raise RuntimeError("Unable to query SCM revision of files") raise RuntimeError("Unable to query SCM revision of files")
if (self._previousSCMRevision == currentRev): return currentRev
self._scm = self._previousSCMRevision
def isSourceModified(self, scmRepo):
if (self._previousSCMRevision == None):
return True
if (self._previousSCMRevision == self.scmRevision):
return False return False
self._scm = currentRev
return True return True
def scanSetXmlFiles(self): def scanSetXmlFiles(self):
foundPrimary = False foundPrimary = False
for f in os.listdir(self._path): for f in os.listdir(self._path):
if !f.endswith("-set.xml"): if not f.endswith("-set.xml"):
continue continue
p = os.path.join(self._path, f) p = os.path.join(self._path, f)
node = readProps(p) node = readProps(p)
simNode = node.getChild("sim") simNode = node.getChild("sim")
if (simNode.getValue("exclude")): if (simNode.getValue("exclude")):
continue continue
if primary = simNode.getValue("variant-of", None): primary = simNode.getValue("variant-of", None)
if primary:
if not primary in variants: if not primary in variants:
self._variants[primary] = [] self._variants[primary] = []
self._variants[primary].append(VariantData(self, node)) self._variants[primary].append(VariantData(self, node))
continue continue
if foundPrimary: if foundPrimary:
print "Multiple primary -set.xml files at:" + self._path print "Multiple primary -set.xml files at:" + self._path
continue continue
else: else:
foundPrimary = True; foundPrimary = True;
parsePrimarySetNode(simNode) parsePrimarySetNode(simNode)
if os.path.exists(os.path.join(self._path, "thumbnail.png")): if os.path.exists(os.path.join(self._path, "thumbnail.png")):
self._thumbnails.append("thumbnail.png") self._thumbnails.append("thumbnail.png")
if not foundPrimary: if not foundPrimary:
raise RuntimeError("No primary -set.xml found at:" + self._path) raise RuntimeError("No primary -set.xml found at:" + self._path)
def parsePrimarySetNode(self, sim): def parsePrimarySetNode(self, sim):
# basic / mandatory values # basic / mandatory values
self._node.addChild('id').value = d self._node.addChild('id').value = d
self._node.addChild('name').value = sim.getValue('description') self._node.addChild('name').value = sim.getValue('description')
longDesc = sim.getValue('long-description') longDesc = sim.getValue('long-description')
if longDesc is not None: if longDesc is not None:
self._node.addChild('description').value = longDesc self._node.addChild('description').value = longDesc
# copy all the standard values # copy all the standard values
for p in ['status', 'author', 'license']: for p in ['status', 'author', 'license']:
v = sim.getValue(p) v = sim.getValue(p)
if v is not None: if v is not None:
self._node.addChild(p).value = v self._node.addChild(p).value = v
# ratings # ratings
if sim.hasChild('rating'): if sim.hasChild('rating'):
pkgRatings = self._node.addChild('rating') pkgRatings = self._node.addChild('rating')
for r in ['FDM', 'systems', 'cockpit', 'model']: for r in ['FDM', 'systems', 'cockpit', 'model']:
pkgRatings.addChild(r).value = sim.getValue('rating/' + r, 0) pkgRatings.addChild(r).value = sim.getValue('rating/' + r, 0)
# copy tags # copy tags
if sim.hasChild('tags'): if sim.hasChild('tags'):
for c in sim.getChild('tags').getChildren('tag'): for c in sim.getChild('tags').getChildren('tag'):
@ -133,110 +144,137 @@ class PackageData:
print "Skipping non-standard tag:", c.value print "Skipping non-standard tag:", c.value
else: else:
self._node.addChild('tag').value = c.value self._node.addChild('tag').value = c.value
self._thumbnails = (t.value for t in self.getChildren("thumbnail")) self._thumbnails = (t.value for t in self.getChildren("thumbnail"))
def validate(self): def validate(self):
for t in self._thumbnails: for t in self._thumbnails:
if not os.path.exists(os.path.join(self._path, t)): if not os.path.exists(os.path.join(self._path, t)):
raise RuntimeError("missing thumbnail:" + t); raise RuntimeError("missing thumbnail:" + t);
def generateZip(self, outDir): def generateZip(self, outDir):
self._revision = self._previousRevision + 1 self._revision = self._previousRevision + 1
zipName = self.id zipName = self.id + ".zip"
zipFilePath = os.path.join(outDir, zipName) zipFilePath = os.path.join(outDir, zipName)
os.chdir(os.path.dirname(self.path))
print "Creating zip", zipFilePath
# TODO: exclude certain files # TODO: exclude certain files
subprocess.call(['zip', '-r', self.path, zipFilePath]) # anything we can do to make this faster?
subprocess.call(['zip', '--quiet', '-r', zipFilePath, self.id])
zipFile = open(zipFilePath + ".zip", 'r')
zipFile = open(zipFilePath, 'r')
self._md5 = hashlib.md5(zipFile.read()).hexdigest() self._md5 = hashlib.md5(zipFile.read()).hexdigest()
self._fileSize = os.path.getsize(zipFile) self._fileSize = os.path.getsize(zipFilePath)
@property def useExistingCatalogData(self):
def catalogNode(self, mirrorUrls, thumbnailUrl): self._md5 = self._previousMD5
def packageNode(self, mirrorUrls, thumbnailUrl):
self._node.getChild("md5", create = True).value = self._md5 self._node.getChild("md5", create = True).value = self._md5
self._node.getChild("file-size-bytes", create = True).value = self._fileSize self._node.getChild("file-size-bytes", create = True).value = self._fileSize
self._node.addChild("revision", create = True).value = self._revision self._node.getChild("revision", create = True).value = int(self._revision)
self._node.addChild("scm-revision", create = True).value = self._scm self._node.getChild("scm-revision", create = True).value = self.scmRevision
for m in mirrorUrls: for m in mirrorUrls:
self._node.addChild("url", m + "/" + self.id + ".zip") self._node.addChild("url", m + "/" + self.id + ".zip")
for t in self._thumbnails: for t in self._thumbnails:
self._node.addChild("thumbnail", thumbnailUrl + "/" + self.id + "_" + t) self._node.addChild("thumbnail", thumbnailUrl + "/" + self.id + "_" + t)
for pr in self._variants: for pr in self._variants:
for vr in self._variants[pr]: for vr in self._variants[pr]:
self._node.addChild(vr.catalogNode) self._node.addChild(vr.catalogNode)
return self._node return self._node
def extractThumnbails(self, thumbnailDir): def extractThumbnails(self, thumbnailDir):
for t in self._thumbnails: for t in self._thumbnails:
fullName = self.id + "_" + t fullName = self.id + "_" + t
os.file.copy(os.path.join(self._path, t), os.file.copy(os.path.join(self._path, t),
os.path.join(thumbnailDir, fullName) os.path.join(thumbnailDir, fullName)
) )
# TODO : verify image format, size and so on # TODO : verify image format, size and so on
def scanPackages(globPath): def scanPackages(globPath):
result = [] result = []
for d = in glob.glob(globPath): print "Scanning", globPath
print os.getcwd()
for d in glob.glob(globPath):
result.append(PackageData(d)) result.append(PackageData(d))
return result return result
def initScmRepository(node): def initScmRepository(node):
scmType = node.getValue("type") scmType = node.getValue("type")
if (scmType == "svn"): if (scmType == "svn"):
svnPath = node.getValue("path") svnPath = node.getValue("path")
return SVNCatalogRepository(svnPath) return svn_catalog_repository.SVNCatalogRepository(svnPath)
else if (scmType == "git"): elif (scmType == "git"):
gitPath = node.getValue("path") gitPath = node.getValue("path")
usesSubmodules = node.getValue("uses-submodules", False) usesSubmodules = node.getValue("uses-submodules", False)
return GitCatalogRepository(gitPath, usesSubmodules) return git_catalog_repository.GitCatalogRepository(gitPath, usesSubmodules)
else if (scmType == "git-discrete") elif (scmType == "git-discrete"):
return GitDiscreteSCM(node) return git_discrete_repository.GitDiscreteSCM(node)
else if (scmType == None): elif (scmType == None):
raise RuntimeError("No scm/type defined in catalog configuration") raise RuntimeError("No scm/type defined in catalog configuration")
else: else:
raise RuntimeError("Unspported SCM type:" + scmType) raise RuntimeError("Unspported SCM type:" + scmType)
def processUpload(node, outputPath): def processUpload(node, outputPath):
print "Enabled value is:", node.getValue("enabled")
if not node.getValue("enabled", True):
print "Upload disabled"
return
uploadType = node.getValue("type") uploadType = node.getValue("type")
if (type == "rsync"): if (uploadType == "rsync"):
subprocess.call(["rsync", node.getValue("args", "-az"), ".", subprocess.call(["rsync", node.getValue("args", "-az"), ".",
node.getValue("remote")], node.getValue("remote")],
cwd = outputPath) cwd = outputPath)
else if (type == "scp"): elif (uploadType == "scp"):
subprocess.call(["scp", node.getValue("args", "-r"), outputPath, subprocess.call(["scp", node.getValue("args", "-r"), outputPath,
node.getValue("remote")]) node.getValue("remote")])
else: else:
raise RuntimeError("Unsupported upload type:" + uploadType) raise RuntimeError("Unsupported upload type:" + uploadType)
# dictionary # dictionary
packages = {} packages = {}
if len(sys.argv) < 2:
raise RuntimeError("no root dir specified")
rootDir = sys.argv[1] rootDir = sys.argv[1]
os.path.chdir(rootDir) if not os.path.isabs(rootDir):
rootDir = os.path.abspath(rootDir)
os.chdir(rootDir)
print "Root path is:", rootDir
configPath = 'catalog.config.xml' configPath = 'catalog.config.xml'
if !os.path.exists(configPath): if not os.path.exists(configPath):
raise RuntimeError("no config file found at:" + configPath) raise RuntimeError("no config file found at:" + configPath)
config = readProps(configPath) config = sgprops.readProps(configPath)
# out path # out path
outPath = config.getValue('output-dir') outPath = config.getValue('output-dir')
if outPath is None: if outPath is None:
# default out path # default out path
outPath = "output" outPath = os.path.join(rootDir, "output")
elif not os.path.isabs(outPath):
outPath = os.path.join(rootDir, "output")
if not os.path.exists(outPath):
os.mkdir(outPath)
print "Output path is:" + outPath print "Output path is:" + outPath
thumbnailPath = os.path.join(outPath, config.getValue('thumbnail-dir', "thumbnails")) thumbnailPath = os.path.join(outPath, config.getValue('thumbnail-dir', "thumbnails"))
thumbnailUrl = config.getValue('thumbnail-url')
mirrorUrls = []
# contains existing catalog # contains existing catalog
existingCatalogPath = os.path.join(outPath, 'catalog.xml') existingCatalogPath = os.path.join(outPath, 'catalog.xml')
@ -244,33 +282,48 @@ existingCatalogPath = os.path.join(outPath, 'catalog.xml')
scmRepo = initScmRepository(config.getChild('scm')) scmRepo = initScmRepository(config.getChild('scm'))
# scan the directories in the aircraft paths # scan the directories in the aircraft paths
for g in config.getChildren("aircraft-dir"): for g in config.getChildren("aircraft-dir"):
for p in scanPackages(g): for p in scanPackages(g.value):
packages[p.id] = p packages[p.id] = p
previousCatalog = readProps(existingCatalogPath) if os.path.exists(existingCatalogPath):
for p in previousCatalog.getChildren("package"): try:
pkgId = p.getValue("id") previousCatalog = sgprops.readProps(existingCatalogPath)
if !packages.contains(pkgId): except:
print "Orphaned old package:", pkgId print "Previous catalog is malformed"
continue previousCatalog = sgprops.Node()
packages[pkgId].setPreviousData(p)
catalogNode = sgprops.Node() for p in previousCatalog.getChildren("package"):
pkgId = p.getValue("id")
if not pkgId in packages.keys():
print "Orphaned old package:", pkgId
continue
packages[pkgId].setPreviousData(p)
else:
print "No previous catalog"
catalogNode = sgprops.Node("catalog")
sgprops.copy(config.getChild("template"), catalogNode) sgprops.copy(config.getChild("template"), catalogNode)
packagesToGenerate = [] packagesToGenerate = []
for p in packages: for p in packages.values():
if (p.isSourceModified(scmRepo)): if (p.isSourceModified(scmRepo)):
packagesToGenerate.append(p) packagesToGenerate.append(p)
else:
p.useExistingCatalogData()
for p in packagesToGenerate: for p in packagesToGenerate:
p.generateZip(outPath) p.generateZip(outPath)
p.extractThumbnails(thumbnailPath) p.extractThumbnails(thumbnailPath)
catalogNode.addChild(p.catalogNode)
print "Creating catalog"
for p in packages.values():
catalogNode.addChild(p.packageNode(mirrorUrls, thumbnailUrl))
catalogNode.write(os.path.join(outPath, "catalog.xml"))
print "Uploading"
if config.hasChild("upload"): if config.hasChild("upload"):
processUpload(config.getChild("upload"), outPath) processUpload(config.getChild("upload"), outPath)

View file

@ -13,7 +13,7 @@ class Node(object):
self._value = None self._value = None
self._index = 0 self._index = 0
self._children = [] self._children = []
@property @property
def value(self): def value(self):
return self._value return self._value
@ -21,21 +21,21 @@ class Node(object):
@value.setter @value.setter
def value(self, v): def value(self, v):
self._value = v self._value = v
@property @property
def name(self): def name(self):
return self._name return self._name
@property @property
def index(self): def index(self):
return self._index return self._index
@property @property
def parent(self): def parent(self):
return self._parent return self._parent
def getChild(self, n, i=None, create = False): def getChild(self, n, i=None, create = False):
if i is None: if i is None:
i = 0 i = 0
# parse name as foo[999] if necessary # parse name as foo[999] if necessary
@ -43,18 +43,18 @@ class Node(object):
if m is not None: if m is not None:
n = m.group(1) n = m.group(1)
i = int(m.group(2)) i = int(m.group(2))
for c in self._children: for c in self._children:
if (c.name == n) and (c.index == i): if (c.name == n) and (c.index == i):
return c return c
if create: if create:
c = Node(n, i, self) c = Node(n, i, self)
self._children.append(c) self._children.append(c)
return c return c
else: else:
raise IndexError("no such child:" + str(n) + " index=" + str(i)) raise IndexError("no such child:" + str(n) + " index=" + str(i))
def addChild(self, n): def addChild(self, n):
# adding an existing instance # adding an existing instance
if isinstance(n, Node): if isinstance(n, Node):
@ -62,60 +62,64 @@ class Node(object):
n._index = self.firstUnusedIndex(n.name) n._index = self.firstUnusedIndex(n.name)
self._children.append(n) self._children.append(n)
return n return n
i = self.firstUnusedIndex(n) i = self.firstUnusedIndex(n)
# create it via getChild # create it via getChild
return self.getChild(n, i, create=True) return self.getChild(n, i, create=True)
def firstUnusedIndex(self, n): def firstUnusedIndex(self, n):
usedIndices = frozenset(c.index for c in self.getChildren(n)) usedIndices = frozenset(c.index for c in self.getChildren(n))
i = 0
while i < 1000: while i < 1000:
if i not in usedIndices: if i not in usedIndices:
return i return i
i += 1 i += 1
raise RuntimeException("too many children with name:" + n) raise RuntimeException("too many children with name:" + n)
def hasChild(self, nm): def hasChild(self, nm):
for c in self._children: for c in self._children:
if (c.name == nm): if (c.name == nm):
return True return True
return False return False
def getChildren(self, n = None): def getChildren(self, n = None):
if n is None: if n is None:
return self._children return self._children
return [c for c in self._children if c.name == n] return [c for c in self._children if c.name == n]
def getNode(self, path, cr = False): def getNode(self, path, cr = False):
axes = path.split('/') axes = path.split('/')
nd = self nd = self
for ax in axes: for ax in axes:
nd = nd.getChild(ax, create = cr) nd = nd.getChild(ax, create = cr)
return nd return nd
def getValue(self, path, default = None): def getValue(self, path, default = None):
try: try:
nd = self.getNode(path) nd = self.getNode(path)
return nd.value return nd.value
except: except:
return default return default
def write(self, path): def write(self, path):
root = self._createXMLElement('PropertyList') root = self._createXMLElement('PropertyList')
t = ET.ElementTree(root) t = ET.ElementTree(root)
t.write(path, 'utf-8')
ET.dump(root)
t.write(path, 'utf-8', xml_declaration = True)
def _createXMLElement(self, nm = None): def _createXMLElement(self, nm = None):
if nm is None: if nm is None:
nm = self.name nm = self.name
n = ET.Element(nm) n = ET.Element(nm)
# value and type specification # value and type specification
try: try:
if self._value is not None: if self._value is not None:
if isinstance(self._value, basestring): if isinstance(self._value, basestring):
@ -133,17 +137,17 @@ class Node(object):
n.set('type', "bool") n.set('type', "bool")
except UnicodeEncodeError: except UnicodeEncodeError:
print "Encoding error with", self._value, type(self._value) print "Encoding error with", self._value, type(self._value)
# index in parent # index in parent
if (self.index != 0): if (self.index != 0):
n.set('n', self.index) n.set('n', str(self.index))
# children # children
for c in self._children: for c in self._children:
n.append(c._createXMLElement()) n.append(c._createXMLElement())
return n; return n;
class PropsHandler(handler.ContentHandler): class PropsHandler(handler.ContentHandler):
def __init__(self, root = None, path = None, dataDirPath = None): def __init__(self, root = None, path = None, dataDirPath = None):
@ -152,50 +156,50 @@ class PropsHandler(handler.ContentHandler):
self._basePath = os.path.dirname(path) self._basePath = os.path.dirname(path)
self._dataDirPath = dataDirPath self._dataDirPath = dataDirPath
self._locator = None self._locator = None
if root is None: if root is None:
# make a nameless root node # make a nameless root node
self._root = Node("", 0) self._root = Node("", 0)
self._current = self._root self._current = self._root
def setDocumentLocator(self, loc): def setDocumentLocator(self, loc):
self._locator = loc self._locator = loc
def startElement(self, name, attrs): def startElement(self, name, attrs):
self._content = '' self._content = None
if (name == 'PropertyList'): if (name == 'PropertyList'):
return return
if 'n' in attrs.keys(): if 'n' in attrs.keys():
index = int(attrs['n']) index = int(attrs['n'])
self._current = self._current.getChild(name, index, create=True) self._current = self._current.getChild(name, index, create=True)
else: else:
self._current = self._current.addChild(name) self._current = self._current.addChild(name)
if 'include' in attrs.keys(): if 'include' in attrs.keys():
self.handleInclude(attrs['include']) self.handleInclude(attrs['include'])
self._currentTy = None; self._currentTy = None;
if 'type' in attrs.keys(): if 'type' in attrs.keys():
self._currentTy = attrs['type'] self._currentTy = attrs['type']
def handleInclude(self, includePath): def handleInclude(self, includePath):
if includePath.startswith('/'): if includePath.startswith('/'):
includePath = includePath[1:] includePath = includePath[1:]
p = os.path.join(self._basePath, includePath) p = os.path.join(self._basePath, includePath)
if not os.path.exists(p): if not os.path.exists(p):
p = os.path.join(self._dataDirPath, includePath) p = os.path.join(self._dataDirPath, includePath)
if not os.path.exists(p): if not os.path.exists(p):
raise RuntimeError("include file not found", includePath, "at line", self._locator.getLineNumber()) raise RuntimeError("include file not found", includePath, "at line", self._locator.getLineNumber())
readProps(p, self._current, self._dataDirPath) readProps(p, self._current, self._dataDirPath)
def endElement(self, name): def endElement(self, name):
if (name == 'PropertyList'): if (name == 'PropertyList'):
return return
try: try:
# convert and store value # convert and store value
self._current.value = self._content self._current.value = self._content
@ -207,20 +211,23 @@ class PropsHandler(handler.ContentHandler):
self._current.value = float(self._content) self._current.value = float(self._content)
except: except:
print "Parse error for value:", self._content, "at line:", self._locator.getLineNumber(), "of:", self._path print "Parse error for value:", self._content, "at line:", self._locator.getLineNumber(), "of:", self._path
self._current = self._current.parent self._current = self._current.parent
self._content = None
def characters(self, content): def characters(self, content):
if self._content is None:
self._content = ''
self._content += content self._content += content
def endDocument(self): def endDocument(self):
pass pass
@property @property
def root(self): def root(self):
return self._root return self._root
def readProps(path, root = None, dataDirPath = None): def readProps(path, root = None, dataDirPath = None):
parser = make_parser() parser = make_parser()
locator = expatreader.ExpatLocator( parser ) locator = expatreader.ExpatLocator( parser )
@ -229,11 +236,11 @@ def readProps(path, root = None, dataDirPath = None):
parser.setContentHandler(h) parser.setContentHandler(h)
parser.parse(path) parser.parse(path)
return h.root return h.root
def copy(src, dest): def copy(src, dest):
dest.value = src.value dest.value = src.value
# recurse over children # recurse over children
for c in src.children: for c in src.getChildren() :
dc = dest.getChild(c.name, i = c.index, create = True) dc = dest.getChild(c.name, i = c.index, create = True)
copy(c, dc) copy(c, dc)

View file

@ -7,7 +7,8 @@ class SVNCatalogRepository:
self._path = path self._path = path
xml = subprocess.check_output(["svn", "info", "--xml", path]) xml = subprocess.check_output(["svn", "info", "--xml", path])
root = ET.fromstring(xml) root = ET.fromstring(xml)
if (root.find("repository/root") == None):
if (root.find(".//repository/root") == None):
raise RuntimeError("Not an SVN repository:" + path) raise RuntimeError("Not an SVN repository:" + path)
def hasPathChanged(self, path, oldRevision): def hasPathChanged(self, path, oldRevision):
@ -16,7 +17,7 @@ class SVNCatalogRepository:
def scmRevisionForPath(self, path): def scmRevisionForPath(self, path):
xml = subprocess.check_output(["svn", "info", "--xml", path]) xml = subprocess.check_output(["svn", "info", "--xml", path])
root = ET.fromstring(xml) root = ET.fromstring(xml)
commit = root.find("entry/commit") commit = root.find(".//entry/commit")
return commit.get('revision', 0) return commit.get('revision', 0)
def update(self): def update(self):