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 |
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. |
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]} |