Mercurial > hg
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()')) |