mercurial/phases.py
changeset 35318 d13526333835
parent 34876 eb1b964b354b
child 35339 0c1aff6d73a7
--- 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)