[Updated] [+-- ] D8654: phases: make phase list dense or dictionaries [PoC]
joerg.sonnenberger (Joerg Sonnenberger)
phabricator at mercurial-scm.org
Wed Jun 24 19:05:06 UTC 2020
joerg.sonnenberger updated this revision to Diff 21700.
REPOSITORY
rHG Mercurial
CHANGES SINCE LAST UPDATE
https://phab.mercurial-scm.org/D8654?vs=21693&id=21700
BRANCH
default
CHANGES SINCE LAST ACTION
https://phab.mercurial-scm.org/D8654/new/
REVISION DETAIL
https://phab.mercurial-scm.org/D8654
AFFECTED FILES
mercurial/bundle2.py
mercurial/exchange.py
mercurial/exchangev2.py
mercurial/phases.py
mercurial/repoview.py
CHANGE DETAILS
diff --git a/mercurial/repoview.py b/mercurial/repoview.py
--- a/mercurial/repoview.py
+++ b/mercurial/repoview.py
@@ -129,7 +129,7 @@
def computemutable(repo, visibilityexceptions=None):
assert not repo.changelog.filteredrevs
# fast check to avoid revset call on huge repo
- if any(repo._phasecache.phaseroots[1:]):
+ if repo._phasecache.hasnonpublicphases(repo):
getphase = repo._phasecache.phase
maymutable = filterrevs(repo, b'base')
return frozenset(r for r in maymutable if getphase(repo, r))
@@ -154,9 +154,9 @@
assert not repo.changelog.filteredrevs
cl = repo.changelog
firstmutable = len(cl)
- for roots in repo._phasecache.phaseroots[1:]:
- if roots:
- firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
+ roots = repo._phasecache.nonpublicphaseroots(repo)
+ if roots:
+ firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
# protect from nullrev root
firstmutable = max(0, firstmutable)
return frozenset(pycompat.xrange(firstmutable, len(cl)))
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -128,25 +128,25 @@
_fphasesentry = struct.Struct(b'>i20s')
-INTERNAL_FLAG = 64 # Phases for mercurial internal usage only
-HIDEABLE_FLAG = 32 # Phases that are hideable
-
# record phase index
public, draft, secret = range(3)
-internal = INTERNAL_FLAG | HIDEABLE_FLAG
-archived = HIDEABLE_FLAG
-allphases = list(range(internal + 1))
-trackedphases = allphases[1:]
+internal = 96
+archived = 32
+allphases = [public, draft, secret, archived, internal]
+trackedphases = allphases[draft:]
# record phase names
cmdphasenames = [b'public', b'draft', b'secret'] # known to `hg phase` command
-phasenames = [None] * len(allphases)
-phasenames[: len(cmdphasenames)] = cmdphasenames
+phasenames = dict(enumerate(cmdphasenames))
phasenames[archived] = b'archived'
phasenames[internal] = b'internal'
+phasenumber = {name: phase for phase, name in phasenames.items()}
+phasenumber2 = phasenumber.copy()
+phasenumber2.update({phase: phase for phase in phasenames})
+phasenumber2.update({b'%i' % phase: phase for phase in phasenames})
# record phase property
mutablephases = tuple(allphases[1:])
remotehiddenphases = tuple(allphases[2:])
-localhiddenphases = tuple(p for p in allphases if p & HIDEABLE_FLAG)
+localhiddenphases = (internal, archived)
def supportinternal(repo):
@@ -167,7 +167,7 @@
"""
repo = repo.unfiltered()
dirty = False
- roots = [set() for i in allphases]
+ roots = {i: set() for i in allphases}
try:
f, pending = txnutil.trypending(repo.root, repo.svfs, b'phaseroots')
try:
@@ -189,11 +189,10 @@
def binaryencode(phasemapping):
"""encode a 'phase -> nodes' mapping into a binary stream
- Since phases are integer the mapping is actually a python list:
- [[PUBLIC_HEADS], [DRAFTS_HEADS], [SECRET_HEADS]]
+ The revision lists are encoded as (phase, root) pairs.
"""
binarydata = []
- for phase, nodes in enumerate(phasemapping):
+ for phase, nodes in pycompat.iteritems(phasemapping):
for head in nodes:
binarydata.append(_fphasesentry.pack(phase, head))
return b''.join(binarydata)
@@ -202,8 +201,9 @@
def binarydecode(stream):
"""decode a binary stream into a 'phase -> nodes' mapping
- Since phases are integer the mapping is actually a python list."""
- headsbyphase = [[] for i in allphases]
+ The (phase, root) pairs are turned back into a dictionary with
+ the phase as index and the aggregated roots of that phase as value."""
+ headsbyphase = {i: [] for i in allphases}
entrysize = _fphasesentry.size
while True:
entry = stream.read(entrysize)
@@ -323,6 +323,38 @@
self.filterunknown(repo)
self.opener = repo.svfs
+ def hasnonpublicphases(self, repo):
+ """detect if there are revisions with non-public phase"""
+ repo = repo.unfiltered()
+ cl = repo.changelog
+ if len(cl) >= self._loadedrevslen:
+ self.invalidate()
+ self.loadphaserevs(repo)
+ return any(
+ revs
+ for phase, revs in pycompat.iteritems(self.phaseroots)
+ if phase != public
+ )
+
+ def nonpublicphaseroots(self, repo):
+ """returns the roots of all non-public phases
+
+ The roots are not minimized, so if the secret revisions are
+ descendants of draft revisions, their roots will still be present.
+ """
+ repo = repo.unfiltered()
+ cl = repo.changelog
+ if len(cl) >= self._loadedrevslen:
+ self.invalidate()
+ self.loadphaserevs(repo)
+ return set().union(
+ *[
+ revs
+ for phase, revs in pycompat.iteritems(self.phaseroots)
+ if phase != public
+ ]
+ )
+
def getrevset(self, repo, phases, subset=None):
"""return a smartset for the given phases"""
self.loadphaserevs(repo) # ensure phase's sets are loaded
@@ -380,7 +412,7 @@
# Shallow copy meant to ensure isolation in
# advance/retractboundary(), nothing more.
ph = self.__class__(None, None, _load=False)
- ph.phaseroots = self.phaseroots[:]
+ ph.phaseroots = self.phaseroots.copy()
ph.dirty = self.dirty
ph.opener = self.opener
ph._loadedrevslen = self._loadedrevslen
@@ -400,17 +432,19 @@
def _getphaserevsnative(self, repo):
repo = repo.unfiltered()
- nativeroots = []
+ nativeroots = [[] for phase in range(internal)]
for phase in trackedphases:
- nativeroots.append(
- pycompat.maplist(repo.changelog.rev, self.phaseroots[phase])
+ nativeroots[phase - 1] = pycompat.maplist(
+ repo.changelog.rev, self.phaseroots[phase]
)
- return repo.changelog.computephases(nativeroots)
+ revslen, phaseset = repo.changelog.computephases(nativeroots)
+ phaseset = {phase: phaseset[phase] or set() for phase in trackedphases}
+ return revslen, phaseset
def _computephaserevspure(self, repo):
repo = repo.unfiltered()
cl = repo.changelog
- self._phasesets = [set() for phase in allphases]
+ self._phasesets = {phase: set() for phase in allphases}
lowerroots = set()
for phase in reversed(trackedphases):
roots = pycompat.maplist(cl.rev, self.phaseroots[phase])
@@ -464,7 +498,7 @@
f.close()
def _write(self, fp):
- for phase, roots in enumerate(self.phaseroots):
+ for phase, roots in pycompat.iteritems(self.phaseroots):
for h in sorted(roots):
fp.write(b'%i %s\n' % (phase, hex(h)))
self.dirty = False
@@ -511,7 +545,7 @@
changes = set() # set of revisions to be changed
delroots = [] # set of root deleted by this path
- for phase in pycompat.xrange(targetphase + 1, len(allphases)):
+ for phase in (phase for phase in allphases if phase > targetphase):
# filter nodes that are not in a compatible phase already
nodes = [
n for n in nodes if self.phase(repo, repo[n].rev()) >= phase
@@ -546,7 +580,11 @@
return changes
def retractboundary(self, repo, tr, targetphase, nodes):
- oldroots = self.phaseroots[: targetphase + 1]
+ oldroots = {
+ phase: revs
+ for phase, revs in pycompat.iteritems(self.phaseroots)
+ if phase <= targetphase
+ }
if tr is None:
phasetracking = None
else:
@@ -565,7 +603,7 @@
# find the phase of the affected revision
for phase in pycompat.xrange(targetphase, -1, -1):
if phase:
- roots = oldroots[phase]
+ roots = oldroots.get(phase, [])
revs = set(repo.revs(b'%ln::%ld', roots, affected))
affected -= revs
else: # public phase
@@ -619,7 +657,7 @@
"""
filtered = False
has_node = repo.changelog.index.has_node # to filter unknown nodes
- for phase, nodes in enumerate(self.phaseroots):
+ for phase, nodes in pycompat.iteritems(self.phaseroots):
missing = sorted(node for node in nodes if not has_node(node))
if missing:
for mnode in missing:
@@ -744,7 +782,7 @@
"""
cl = repo.changelog
- headsbyphase = [[] for i in allphases]
+ headsbyphase = {i: [] for i in allphases}
# No need to keep track of secret phase; any heads in the subset that
# are not mentioned are implicitly secret.
for phase in allphases[:secret]:
@@ -755,12 +793,12 @@
def updatephases(repo, trgetter, headsbyphase):
"""Updates the repo with the given phase heads"""
- # Now advance phase boundaries of all but secret phase
+ # Now advance phase boundaries of all phases
#
# run the update (and fetch transaction) only if there are actually things
# to update. This avoid creating empty transaction during no-op operation.
- for phase in allphases[:-1]:
+ for phase in allphases:
revset = b'%ln - _phase(%s)'
heads = [c.node() for c in repo.set(revset, headsbyphase[phase], phase)]
if heads:
@@ -875,18 +913,16 @@
"""
v = ui.config(b'phases', b'new-commit')
try:
- return phasenames.index(v)
- except ValueError:
- try:
- return int(v)
- except ValueError:
- msg = _(b"phases.new-commit: not a valid phase name ('%s')")
- raise error.ConfigError(msg % v)
+ return phasenumber2[v]
+ except KeyError:
+ raise error.ConfigError(
+ _(b"phases.new-commit: not a valid phase name ('%s')") % v
+ )
def hassecret(repo):
"""utility function that check if a repo have any secret changeset."""
- return bool(repo._phasecache.phaseroots[2])
+ return bool(repo._phasecache.phaseroots[secret])
def preparehookargs(node, old, new):
diff --git a/mercurial/exchangev2.py b/mercurial/exchangev2.py
--- a/mercurial/exchangev2.py
+++ b/mercurial/exchangev2.py
@@ -82,14 +82,14 @@
phases.registernew(repo, tr, phases.draft, csetres[b'added'])
# And adjust the phase of all changesets accordingly.
- for phase in phases.phasenames:
+ for phasenumber, phase in phases.phasenames.items():
if phase == b'secret' or not csetres[b'nodesbyphase'][phase]:
continue
phases.advanceboundary(
repo,
tr,
- phases.phasenames.index(phase),
+ phasenumber,
csetres[b'nodesbyphase'][phase],
)
@@ -361,7 +361,7 @@
# so we can set the linkrev accordingly when manifests are added.
manifestnodes[cl.rev(node)] = revision.manifest
- nodesbyphase = {phase: set() for phase in phases.phasenames}
+ nodesbyphase = {phase: set() for phase in phases.phasenames.values()}
remotebookmarks = {}
# addgroup() expects a 7-tuple describing revisions. This normalizes
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1024,12 +1024,12 @@
hasphaseheads = b'heads' in b2caps.get(b'phases', ())
if pushop.remotephases is not None and hasphaseheads:
# check that the remote phase has not changed
- checks = [[] for p in phases.allphases]
+ checks = {p: [] for p in phases.allphases}
checks[phases.public].extend(pushop.remotephases.publicheads)
checks[phases.draft].extend(pushop.remotephases.draftroots)
- if any(checks):
- for nodes in checks:
- nodes.sort()
+ if any(pycompat.itervalues(checks)):
+ for phase in checks:
+ checks[phase].sort()
checkdata = phases.binaryencode(checks)
bundler.newpart(b'check:phases', data=checkdata)
@@ -1104,7 +1104,7 @@
"""push phase information through a bundle2 - binary part"""
pushop.stepsdone.add(b'phases')
if pushop.outdatedphases:
- updates = [[] for p in phases.allphases]
+ updates = {p: [] for p in phases.allphases}
updates[0].extend(h.node() for h in pushop.outdatedphases)
phasedata = phases.binaryencode(updates)
bundler.newpart(b'phase-heads', data=phasedata)
@@ -2658,9 +2658,9 @@
headsbyphase[phases.public].add(node(r))
# transform data in a format used by the encoding function
- phasemapping = []
- for phase in phases.allphases:
- phasemapping.append(sorted(headsbyphase[phase]))
+ phasemapping = {
+ phase: sorted(headsbyphase[phase]) for phase in phases.allphases
+ }
# generate the actual part
phasedata = phases.binaryencode(phasemapping)
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -2207,7 +2207,7 @@
b'remote repository changed while pushing - please try again '
b'(%s is %s expected %s)'
)
- for expectedphase, nodes in enumerate(phasetonodes):
+ for expectedphase, nodes in pycompat.iteritems(phasetonodes):
for n in nodes:
actualphase = phasecache.phase(unfi, cl.rev(n))
if actualphase != expectedphase:
To: joerg.sonnenberger, #hg-reviewers
Cc: mjacob, mercurial-patches
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurial-scm.org/pipermail/mercurial-patches/attachments/20200624/68ccbfeb/attachment-0002.html>
More information about the Mercurial-patches
mailing list