diff -r 137a08d82232 -r d13526333835 mercurial/phases.py --- a/mercurial/phases.py Fri Dec 08 01:23:34 2017 +0100 +++ b/mercurial/phases.py Wed Dec 06 15:46:41 2017 +0100 @@ -115,6 +115,7 @@ ) from . import ( error, + pycompat, smartset, txnutil, util, @@ -202,7 +203,7 @@ if _load: # Cheap trick to allow shallow-copy without copy module self.phaseroots, self.dirty = _readroots(repo, phasedefaults) - self._phaserevs = None + self._phasemaxrev = nullrev self._phasesets = None self.filterunknown(repo) self.opener = repo.svfs @@ -210,23 +211,30 @@ def getrevset(self, repo, phases): """return a smartset for the given phases""" self.loadphaserevs(repo) # ensure phase's sets are loaded - - if self._phasesets and all(self._phasesets[p] is not None - for p in phases): - # fast path - use _phasesets - revs = self._phasesets[phases[0]] - if len(phases) > 1: - revs = revs.copy() # only copy when needed - for p in phases[1:]: - revs.update(self._phasesets[p]) + phases = set(phases) + if public not in phases: + # fast path: _phasesets contains the interesting sets, + # might only need a union and post-filtering. + if len(phases) == 1: + [p] = phases + revs = self._phasesets[p] + else: + revs = set.union(*[self._phasesets[p] for p in phases]) if repo.changelog.filteredrevs: revs = revs - repo.changelog.filteredrevs return smartset.baseset(revs) else: - # slow path - enumerate all revisions - phase = self.phase - revs = (r for r in repo if phase(repo, r) in phases) - return smartset.generatorset(revs, iterasc=True) + phases = set(allphases).difference(phases) + if not phases: + return smartset.fullreposet(repo) + if len(phases) == 1: + [p] = phases + revs = self._phasesets[p] + else: + revs = set.union(*[self._phasesets[p] for p in phases]) + if not revs: + return smartset.fullreposet(repo) + return smartset.fullreposet(repo).filter(lambda r: r not in revs) def copy(self): # Shallow copy meant to ensure isolation in @@ -235,13 +243,14 @@ ph.phaseroots = self.phaseroots[:] ph.dirty = self.dirty ph.opener = self.opener - ph._phaserevs = self._phaserevs + ph._phasemaxrev = self._phasemaxrev ph._phasesets = self._phasesets return ph def replace(self, phcache): """replace all values in 'self' with content of phcache""" - for a in ('phaseroots', 'dirty', 'opener', '_phaserevs', '_phasesets'): + for a in ('phaseroots', 'dirty', 'opener', '_phasemaxrev', + '_phasesets'): setattr(self, a, getattr(phcache, a)) def _getphaserevsnative(self, repo): @@ -253,42 +262,38 @@ def _computephaserevspure(self, repo): repo = repo.unfiltered() - revs = [public] * len(repo.changelog) - self._phaserevs = revs - self._populatephaseroots(repo) - for phase in trackedphases: - roots = list(map(repo.changelog.rev, self.phaseroots[phase])) - if roots: - for rev in roots: - revs[rev] = phase - for rev in repo.changelog.descendants(roots): - revs[rev] = phase + cl = repo.changelog + self._phasesets = [set() for phase in allphases] + roots = pycompat.maplist(cl.rev, self.phaseroots[secret]) + if roots: + ps = set(cl.descendants(roots)) + for root in roots: + ps.add(root) + self._phasesets[secret] = ps + roots = pycompat.maplist(cl.rev, self.phaseroots[draft]) + if roots: + ps = set(cl.descendants(roots)) + for root in roots: + ps.add(root) + ps.difference_update(self._phasesets[secret]) + self._phasesets[draft] = ps + self._phasemaxrev = len(cl) def loadphaserevs(self, repo): """ensure phase information is loaded in the object""" - if self._phaserevs is None: + if self._phasesets is None: try: res = self._getphaserevsnative(repo) - self._phaserevs, self._phasesets = res + self._phasemaxrev, self._phasesets = res except AttributeError: self._computephaserevspure(repo) def invalidate(self): - self._phaserevs = None + self._phasemaxrev = nullrev self._phasesets = None - def _populatephaseroots(self, repo): - """Fills the _phaserevs cache with phases for the roots. - """ - cl = repo.changelog - phaserevs = self._phaserevs - for phase in trackedphases: - roots = map(cl.rev, self.phaseroots[phase]) - for root in roots: - phaserevs[root] = phase - def phase(self, repo, rev): - # We need a repo argument here to be able to build _phaserevs + # We need a repo argument here to be able to build _phasesets # if necessary. The repository instance is not stored in # phasecache to avoid reference cycles. The changelog instance # is not stored because it is a filecache() property and can @@ -297,10 +302,13 @@ return public if rev < nullrev: raise ValueError(_('cannot lookup negative revision')) - if self._phaserevs is None or rev >= len(self._phaserevs): + if rev >= self._phasemaxrev: self.invalidate() self.loadphaserevs(repo) - return self._phaserevs[rev] + for phase in trackedphases: + if rev in self._phasesets[phase]: + return phase + return public def write(self): if not self.dirty: @@ -455,10 +463,10 @@ if filtered: self.dirty = True # filterunknown is called by repo.destroyed, we may have no changes in - # root but phaserevs contents is certainly invalid (or at least we + # root but _phasesets contents is certainly invalid (or at least we # have not proper way to check that). related to issue 3858. # - # The other caller is __init__ that have no _phaserevs initialized + # The other caller is __init__ that have no _phasesets initialized # anyway. If this change we should consider adding a dedicated # "destroyed" function to phasecache or a proper cache key mechanism # (see branchmap one)