comparison mercurial/obsolete.py @ 17469:fb72eec7efd8

obsolete: introduce caches for all meaningful sets This changeset introduces caches on the `obsstore` that keeps track of sets of revisions meaningful for obsolescence related logics. For now they are: - obsolete: changesets used as precursors (and not public), - extinct: obsolete changesets with osbolete descendants only, - unstable: non obsolete changesets with obsolete ancestors. The cache is accessed using the `getobscache(repo, '<set-name>')` function which builds the cache on demand. The `clearobscaches(repo)` function takes care of clearing the caches if any. Caches are cleared when one of these events happens: - a new marker is added, - a new changeset is added, - some changesets are made public, - some public changesets are demoted to draft or secret. Declaration of more sets is made easy because we will have to handle at least two other "troubles" (latecomer and conflicting). Caches are now used by revset and changectx. It is usually not much more expensive to compute the whole set than to check the property of a few elements. The performance boost is welcome in case we apply obsolescence logic on a lot of revisions. This makes the feature usable!
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Tue, 28 Aug 2012 20:52:04 +0200
parents 72fa4ef2245f
children f85816af6294
comparison
equal deleted inserted replaced
17460:a306837f8c87 17469:fb72eec7efd8
159 - precursors: old -> set(new) 159 - precursors: old -> set(new)
160 - successors: new -> set(old) 160 - successors: new -> set(old)
161 """ 161 """
162 162
163 def __init__(self, sopener): 163 def __init__(self, sopener):
164 # caches for various obsolescence related cache
165 self.caches = {}
164 self._all = [] 166 self._all = []
165 # new markers to serialize 167 # new markers to serialize
166 self.precursors = {} 168 self.precursors = {}
167 self.successors = {} 169 self.successors = {}
168 self.sopener = sopener 170 self.sopener = sopener
218 finally: 220 finally:
219 # XXX: f.close() == filecache invalidation == obsstore rebuilt. 221 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
220 # call 'filecacheentry.refresh()' here 222 # call 'filecacheentry.refresh()' here
221 f.close() 223 f.close()
222 self._load(new) 224 self._load(new)
225 # new marker *may* have changed several set. invalidate the cache.
226 self.caches.clear()
223 return len(new) 227 return len(new)
224 228
225 def mergemarkers(self, transation, data): 229 def mergemarkers(self, transation, data):
226 markers = _readmarkers(data) 230 markers = _readmarkers(data)
227 self.add(transation, markers) 231 self.add(transation, markers)
325 for mark in obsstore.precursors.get(current, ()): 329 for mark in obsstore.precursors.get(current, ()):
326 for suc in mark[1]: 330 for suc in mark[1]:
327 if suc not in seen: 331 if suc not in seen:
328 seen.add(suc) 332 seen.add(suc)
329 remaining.add(suc) 333 remaining.add(suc)
334
335 # mapping of 'set-name' -> <function to computer this set>
336 cachefuncs = {}
337 def cachefor(name):
338 """Decorator to register a function as computing the cache for a set"""
339 def decorator(func):
340 assert name not in cachefuncs
341 cachefuncs[name] = func
342 return func
343 return decorator
344
345 def getobscache(repo, name):
346 """Return the set of revision that belong to the <name> set
347
348 Such access may compute the set and cache it for future use"""
349 if not repo.obsstore:
350 return ()
351 if name not in repo.obsstore.caches:
352 repo.obsstore.caches[name] = cachefuncs[name](repo)
353 return repo.obsstore.caches[name]
354
355 # To be simple we need to invalidate obsolescence cache when:
356 #
357 # - new changeset is added:
358 # - public phase is changed
359 # - obsolescence marker are added
360 # - strip is used a repo
361 def clearobscaches(repo):
362 """Remove all obsolescence related cache from a repo
363
364 This remove all cache in obsstore is the obsstore already exist on the
365 repo.
366
367 (We could be smarter here given the exact event that trigger the cache
368 clearing)"""
369 # only clear cache is there is obsstore data in this repo
370 if 'obsstore' in repo._filecache:
371 repo.obsstore.caches.clear()
372
373 @cachefor('obsolete')
374 def _computeobsoleteset(repo):
375 """the set of obsolete revisions"""
376 obs = set()
377 nm = repo.changelog.nodemap
378 for prec in repo.obsstore.precursors:
379 rev = nm.get(prec)
380 if rev is not None:
381 obs.add(rev)
382 return set(repo.revs('%ld - public()', obs))
383
384 @cachefor('unstable')
385 def _computeunstableset(repo):
386 """the set of non obsolete revisions with obsolete parents"""
387 return set(repo.revs('(obsolete()::) - obsolete()'))
388
389 @cachefor('suspended')
390 def _computesuspendedset(repo):
391 """the set of obsolete parents with non obsolete descendants"""
392 return set(repo.revs('obsolete() and obsolete()::unstable()'))
393
394 @cachefor('extinct')
395 def _computeextinctset(repo):
396 """the set of obsolete parents without non obsolete descendants"""
397 return set(repo.revs('obsolete() - obsolete()::unstable()'))