mercurial/phases.py
changeset 46644 77e129be10de
parent 45790 5d65e04b6a80
child 47012 d55b71393907
equal deleted inserted replaced
46643:eef13b940887 46644:77e129be10de
   125     smartset,
   125     smartset,
   126     txnutil,
   126     txnutil,
   127     util,
   127     util,
   128 )
   128 )
   129 
   129 
       
   130 if pycompat.TYPE_CHECKING:
       
   131     from typing import (
       
   132         Any,
       
   133         Callable,
       
   134         Dict,
       
   135         Iterable,
       
   136         List,
       
   137         Optional,
       
   138         Set,
       
   139         Tuple,
       
   140     )
       
   141     from . import (
       
   142         localrepo,
       
   143         ui as uimod,
       
   144     )
       
   145 
       
   146     Phaseroots = Dict[int, Set[bytes]]
       
   147     Phasedefaults = List[
       
   148         Callable[[localrepo.localrepository, Phaseroots], Phaseroots]
       
   149     ]
       
   150 
       
   151 
   130 _fphasesentry = struct.Struct(b'>i20s')
   152 _fphasesentry = struct.Struct(b'>i20s')
   131 
   153 
   132 # record phase index
   154 # record phase index
   133 public, draft, secret = range(3)
   155 public, draft, secret = range(3)  # type: int
   134 archived = 32  # non-continuous for compatibility
   156 archived = 32  # non-continuous for compatibility
   135 internal = 96  # non-continuous for compatibility
   157 internal = 96  # non-continuous for compatibility
   136 allphases = (public, draft, secret, archived, internal)
   158 allphases = (public, draft, secret, archived, internal)
   137 trackedphases = (draft, secret, archived, internal)
   159 trackedphases = (draft, secret, archived, internal)
   138 # record phase names
   160 # record phase names
   152 remotehiddenphases = (secret, archived, internal)
   174 remotehiddenphases = (secret, archived, internal)
   153 localhiddenphases = (internal, archived)
   175 localhiddenphases = (internal, archived)
   154 
   176 
   155 
   177 
   156 def supportinternal(repo):
   178 def supportinternal(repo):
       
   179     # type: (localrepo.localrepository) -> bool
   157     """True if the internal phase can be used on a repository"""
   180     """True if the internal phase can be used on a repository"""
   158     return requirements.INTERNAL_PHASE_REQUIREMENT in repo.requirements
   181     return requirements.INTERNAL_PHASE_REQUIREMENT in repo.requirements
   159 
   182 
   160 
   183 
   161 def _readroots(repo, phasedefaults=None):
   184 def _readroots(repo, phasedefaults=None):
       
   185     # type: (localrepo.localrepository, Optional[Phasedefaults]) -> Tuple[Phaseroots, bool]
   162     """Read phase roots from disk
   186     """Read phase roots from disk
   163 
   187 
   164     phasedefaults is a list of fn(repo, roots) callable, which are
   188     phasedefaults is a list of fn(repo, roots) callable, which are
   165     executed if the phase roots file does not exist. When phases are
   189     executed if the phase roots file does not exist. When phases are
   166     being initialized on an existing repository, this could be used to
   190     being initialized on an existing repository, this could be used to
   189         dirty = True
   213         dirty = True
   190     return roots, dirty
   214     return roots, dirty
   191 
   215 
   192 
   216 
   193 def binaryencode(phasemapping):
   217 def binaryencode(phasemapping):
       
   218     # type: (Dict[int, List[bytes]]) -> bytes
   194     """encode a 'phase -> nodes' mapping into a binary stream
   219     """encode a 'phase -> nodes' mapping into a binary stream
   195 
   220 
   196     The revision lists are encoded as (phase, root) pairs.
   221     The revision lists are encoded as (phase, root) pairs.
   197     """
   222     """
   198     binarydata = []
   223     binarydata = []
   201             binarydata.append(_fphasesentry.pack(phase, head))
   226             binarydata.append(_fphasesentry.pack(phase, head))
   202     return b''.join(binarydata)
   227     return b''.join(binarydata)
   203 
   228 
   204 
   229 
   205 def binarydecode(stream):
   230 def binarydecode(stream):
       
   231     # type: (...) -> Dict[int, List[bytes]]
   206     """decode a binary stream into a 'phase -> nodes' mapping
   232     """decode a binary stream into a 'phase -> nodes' mapping
   207 
   233 
   208     The (phase, root) pairs are turned back into a dictionary with
   234     The (phase, root) pairs are turned back into a dictionary with
   209     the phase as index and the aggregated roots of that phase as value."""
   235     the phase as index and the aggregated roots of that phase as value."""
   210     headsbyphase = {i: [] for i in allphases}
   236     headsbyphase = {i: [] for i in allphases}
   319         data.insert(low + 1, (pycompat.xrange(rev, rev + 1), t))
   345         data.insert(low + 1, (pycompat.xrange(rev, rev + 1), t))
   320 
   346 
   321 
   347 
   322 class phasecache(object):
   348 class phasecache(object):
   323     def __init__(self, repo, phasedefaults, _load=True):
   349     def __init__(self, repo, phasedefaults, _load=True):
       
   350         # type: (localrepo.localrepository, Optional[Phasedefaults], bool) -> None
   324         if _load:
   351         if _load:
   325             # Cheap trick to allow shallow-copy without copy module
   352             # Cheap trick to allow shallow-copy without copy module
   326             self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
   353             self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
   327             self._loadedrevslen = 0
   354             self._loadedrevslen = 0
   328             self._phasesets = None
   355             self._phasesets = None
   329             self.filterunknown(repo)
   356             self.filterunknown(repo)
   330             self.opener = repo.svfs
   357             self.opener = repo.svfs
   331 
   358 
   332     def hasnonpublicphases(self, repo):
   359     def hasnonpublicphases(self, repo):
       
   360         # type: (localrepo.localrepository) -> bool
   333         """detect if there are revisions with non-public phase"""
   361         """detect if there are revisions with non-public phase"""
   334         repo = repo.unfiltered()
   362         repo = repo.unfiltered()
   335         cl = repo.changelog
   363         cl = repo.changelog
   336         if len(cl) >= self._loadedrevslen:
   364         if len(cl) >= self._loadedrevslen:
   337             self.invalidate()
   365             self.invalidate()
   341             for phase, revs in pycompat.iteritems(self.phaseroots)
   369             for phase, revs in pycompat.iteritems(self.phaseroots)
   342             if phase != public
   370             if phase != public
   343         )
   371         )
   344 
   372 
   345     def nonpublicphaseroots(self, repo):
   373     def nonpublicphaseroots(self, repo):
       
   374         # type: (localrepo.localrepository) -> Set[bytes]
   346         """returns the roots of all non-public phases
   375         """returns the roots of all non-public phases
   347 
   376 
   348         The roots are not minimized, so if the secret revisions are
   377         The roots are not minimized, so if the secret revisions are
   349         descendants of draft revisions, their roots will still be present.
   378         descendants of draft revisions, their roots will still be present.
   350         """
   379         """
   360                 if phase != public
   389                 if phase != public
   361             ]
   390             ]
   362         )
   391         )
   363 
   392 
   364     def getrevset(self, repo, phases, subset=None):
   393     def getrevset(self, repo, phases, subset=None):
       
   394         # type: (localrepo.localrepository, Iterable[int], Optional[Any]) -> Any
       
   395         # TODO: finish typing this
   365         """return a smartset for the given phases"""
   396         """return a smartset for the given phases"""
   366         self.loadphaserevs(repo)  # ensure phase's sets are loaded
   397         self.loadphaserevs(repo)  # ensure phase's sets are loaded
   367         phases = set(phases)
   398         phases = set(phases)
   368         publicphase = public in phases
   399         publicphase = public in phases
   369 
   400 
   455                 lowerroots.update(ps)
   486                 lowerroots.update(ps)
   456                 self._phasesets[phase] = ps
   487                 self._phasesets[phase] = ps
   457         self._loadedrevslen = len(cl)
   488         self._loadedrevslen = len(cl)
   458 
   489 
   459     def loadphaserevs(self, repo):
   490     def loadphaserevs(self, repo):
       
   491         # type: (localrepo.localrepository) -> None
   460         """ensure phase information is loaded in the object"""
   492         """ensure phase information is loaded in the object"""
   461         if self._phasesets is None:
   493         if self._phasesets is None:
   462             try:
   494             try:
   463                 res = self._getphaserevsnative(repo)
   495                 res = self._getphaserevsnative(repo)
   464                 self._loadedrevslen, self._phasesets = res
   496                 self._loadedrevslen, self._phasesets = res
   468     def invalidate(self):
   500     def invalidate(self):
   469         self._loadedrevslen = 0
   501         self._loadedrevslen = 0
   470         self._phasesets = None
   502         self._phasesets = None
   471 
   503 
   472     def phase(self, repo, rev):
   504     def phase(self, repo, rev):
       
   505         # type: (localrepo.localrepository, int) -> int
   473         # We need a repo argument here to be able to build _phasesets
   506         # We need a repo argument here to be able to build _phasesets
   474         # if necessary. The repository instance is not stored in
   507         # if necessary. The repository instance is not stored in
   475         # phasecache to avoid reference cycles. The changelog instance
   508         # phasecache to avoid reference cycles. The changelog instance
   476         # is not stored because it is a filecache() property and can
   509         # is not stored because it is a filecache() property and can
   477         # be replaced without us being notified.
   510         # be replaced without us being notified.
   650             )
   683             )
   651             return True
   684             return True
   652         return False
   685         return False
   653 
   686 
   654     def filterunknown(self, repo):
   687     def filterunknown(self, repo):
       
   688         # type: (localrepo.localrepository) -> None
   655         """remove unknown nodes from the phase boundary
   689         """remove unknown nodes from the phase boundary
   656 
   690 
   657         Nothing is lost as unknown nodes only hold data for their descendants.
   691         Nothing is lost as unknown nodes only hold data for their descendants.
   658         """
   692         """
   659         filtered = False
   693         filtered = False
   727     phcache.registernew(repo, tr, targetphase, revs)
   761     phcache.registernew(repo, tr, targetphase, revs)
   728     repo._phasecache.replace(phcache)
   762     repo._phasecache.replace(phcache)
   729 
   763 
   730 
   764 
   731 def listphases(repo):
   765 def listphases(repo):
       
   766     # type: (localrepo.localrepository) -> Dict[bytes, bytes]
   732     """List phases root for serialization over pushkey"""
   767     """List phases root for serialization over pushkey"""
   733     # Use ordered dictionary so behavior is deterministic.
   768     # Use ordered dictionary so behavior is deterministic.
   734     keys = util.sortdict()
   769     keys = util.sortdict()
   735     value = b'%i' % draft
   770     value = b'%i' % draft
   736     cl = repo.unfiltered().changelog
   771     cl = repo.unfiltered().changelog
   758         keys[b'publishing'] = b'True'
   793         keys[b'publishing'] = b'True'
   759     return keys
   794     return keys
   760 
   795 
   761 
   796 
   762 def pushphase(repo, nhex, oldphasestr, newphasestr):
   797 def pushphase(repo, nhex, oldphasestr, newphasestr):
       
   798     # type: (localrepo.localrepository, bytes, bytes, bytes) -> bool
   763     """List phases root for serialization over pushkey"""
   799     """List phases root for serialization over pushkey"""
   764     repo = repo.unfiltered()
   800     repo = repo.unfiltered()
   765     with repo.lock():
   801     with repo.lock():
   766         currentphase = repo[nhex].phase()
   802         currentphase = repo[nhex].phase()
   767         newphase = abs(int(newphasestr))  # let's avoid negative index surprise
   803         newphase = abs(int(newphasestr))  # let's avoid negative index surprise
   907 
   943 
   908     return pycompat.maplist(cl.node, sorted(new_heads))
   944     return pycompat.maplist(cl.node, sorted(new_heads))
   909 
   945 
   910 
   946 
   911 def newcommitphase(ui):
   947 def newcommitphase(ui):
       
   948     # type: (uimod.ui) -> int
   912     """helper to get the target phase of new commit
   949     """helper to get the target phase of new commit
   913 
   950 
   914     Handle all possible values for the phases.new-commit options.
   951     Handle all possible values for the phases.new-commit options.
   915 
   952 
   916     """
   953     """
   922             _(b"phases.new-commit: not a valid phase name ('%s')") % v
   959             _(b"phases.new-commit: not a valid phase name ('%s')") % v
   923         )
   960         )
   924 
   961 
   925 
   962 
   926 def hassecret(repo):
   963 def hassecret(repo):
       
   964     # type: (localrepo.localrepository) -> bool
   927     """utility function that check if a repo have any secret changeset."""
   965     """utility function that check if a repo have any secret changeset."""
   928     return bool(repo._phasecache.phaseroots[secret])
   966     return bool(repo._phasecache.phaseroots[secret])
   929 
   967 
   930 
   968 
   931 def preparehookargs(node, old, new):
   969 def preparehookargs(node, old, new):
       
   970     # type: (bytes, Optional[int], Optional[int]) -> Dict[bytes, bytes]
   932     if old is None:
   971     if old is None:
   933         old = b''
   972         old = b''
   934     else:
   973     else:
   935         old = phasenames[old]
   974         old = phasenames[old]
   936     return {b'node': node, b'oldphase': old, b'phase': phasenames[new]}
   975     return {b'node': node, b'oldphase': old, b'phase': phasenames[new]}