Patchwork D1606: phases: drop the list with phase of each rev, always comput phase sets

login
register
mail settings
Submitter phabricator
Date Dec. 8, 2017, 3:29 p.m.
Message ID <1826080020a230cddb9ae742b5d2b86d@localhost.localdomain>
Download mbox | patch
Permalink /patch/26107/
State Not Applicable
Headers show

Comments

phabricator - Dec. 8, 2017, 3:29 p.m.
This revision was automatically updated to reflect the committed changes.
Closed by commit rHGd13526333835: phases: drop the list with phase of each rev, always comput phase sets (authored by joerg.sonnenberger, committed by ).

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D1606?vs=4228&id=4254#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D1606?vs=4228&id=4254

REVISION DETAIL
  https://phab.mercurial-scm.org/D1606

AFFECTED FILES
  mercurial/cext/parsers.c
  mercurial/cext/revlog.c
  mercurial/localrepo.py
  mercurial/phases.py
  mercurial/policy.py

CHANGE DETAILS




To: joerg.sonnenberger, #hg-reviewers, quark, yuja
Cc: yuja, durin42, quark, mercurial-devel

Patch

diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -75,7 +75,7 @@ 
     (r'cext', r'diffhelpers'): 1,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 1,
-    (r'cext', r'parsers'): 3,
+    (r'cext', r'parsers'): 4,
 }
 
 # map import request to other package or module
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -115,6 +115,7 @@ 
 )
 from . import (
     error,
+    pycompat,
     smartset,
     txnutil,
     util,
@@ -202,46 +203,54 @@ 
         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
 
     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
         # advance/retractboundary(), nothing more.
         ph = self.__class__(None, None, _load=False)
         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,54 +262,53 @@ 
 
     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
         # be replaced without us being notified.
         if rev == nullrev:
             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)
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -688,8 +688,8 @@ 
     def _activebookmark(self):
         return self._bookmarks.active
 
-    # _phaserevs and _phasesets depend on changelog. what we need is to
-    # call _phasecache.invalidate() if '00changelog.i' was changed, but it
+    # _phasesets depend on changelog. what we need is to call
+    # _phasecache.invalidate() if '00changelog.i' was changed, but it
     # can't be easily expressed in filecache mechanism.
     @storecache('phaseroots', '00changelog.i')
     def _phasecache(self):
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -628,7 +628,7 @@ 
 {
 	PyObject *roots = Py_None;
 	PyObject *ret = NULL;
-	PyObject *phaseslist = NULL;
+	PyObject *phasessize = NULL;
 	PyObject *phaseroots = NULL;
 	PyObject *phaseset = NULL;
 	PyObject *phasessetlist = NULL;
@@ -685,12 +685,10 @@ 
 		}
 	}
 	/* Transform phase list to a python list */
-	phaseslist = PyList_New(len);
-	if (phaseslist == NULL)
+	phasessize = PyInt_FromLong(len);
+	if (phasessize == NULL)
 		goto release;
 	for (i = 0; i < len; i++) {
-		PyObject *phaseval;
-
 		phase = phases[i];
 		/* We only store the sets of phase for non public phase, the public phase
 		 * is computed as a difference */
@@ -702,15 +700,11 @@ 
 			PySet_Add(phaseset, rev);
 			Py_XDECREF(rev);
 		}
-		phaseval = PyInt_FromLong(phase);
-		if (phaseval == NULL)
-			goto release;
-		PyList_SET_ITEM(phaseslist, i, phaseval);
 	}
-	ret = PyTuple_Pack(2, phaseslist, phasessetlist);
+	ret = PyTuple_Pack(2, phasessize, phasessetlist);
 
 release:
-	Py_XDECREF(phaseslist);
+	Py_XDECREF(phasessize);
 	Py_XDECREF(phasessetlist);
 done:
 	free(phases);
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -710,7 +710,7 @@ 
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 3;
+static const int version = 4;
 
 static void module_init(PyObject *mod)
 {