remotenames: introduce a class to lazily resolve remotnames
remotenames may take time to load and in next patch we are going to introduce
namespaces related to them. So let's introduce a class making them load lazily.
This is a part of moving hgremotenames extension to core.
hgremotenames: https://bitbucket.org/seanfarley/hgremotenames
Differential Revision: https://phab.mercurial-scm.org/D1757
--- a/hgext/remotenames.py Sat Dec 23 00:19:09 2017 +0530
+++ b/hgext/remotenames.py Sat Dec 23 17:50:42 2017 +0530
@@ -10,6 +10,11 @@
from __future__ import absolute_import
+import UserDict
+
+from mercurial.node import (
+ bin,
+)
from mercurial import (
logexchange,
)
@@ -20,34 +25,102 @@
# leave the attribute unspecified.
testedwith = 'ships-with-hg-core'
+class lazyremotenamedict(UserDict.DictMixin):
+ """
+ Read-only dict-like Class to lazily resolve remotename entries
+
+ We are doing that because remotenames startup was slow.
+ We lazily read the remotenames file once to figure out the potential entries
+ and store them in self.potentialentries. Then when asked to resolve an
+ entry, if it is not in self.potentialentries, then it isn't there, if it
+ is in self.potentialentries we resolve it and store the result in
+ self.cache. We cannot be lazy is when asked all the entries (keys).
+ """
+ def __init__(self, kind, repo):
+ self.cache = {}
+ self.potentialentries = {}
+ self._kind = kind # bookmarks or branches
+ self._repo = repo
+ self.loaded = False
+
+ def _load(self):
+ """ Read the remotenames file, store entries matching selected kind """
+ self.loaded = True
+ repo = self._repo
+ for node, rpath, rname in logexchange.readremotenamefile(repo,
+ self._kind):
+ name = rpath + '/' + rname
+ self.potentialentries[name] = (node, rpath, name)
+
+ def _resolvedata(self, potentialentry):
+ """ Check that the node for potentialentry exists and return it """
+ if not potentialentry in self.potentialentries:
+ return None
+ node, remote, name = self.potentialentries[potentialentry]
+ repo = self._repo
+ binnode = bin(node)
+ # if the node doesn't exist, skip it
+ try:
+ repo.changelog.rev(binnode)
+ except LookupError:
+ return None
+ # Skip closed branches
+ if (self._kind == 'branches' and repo[binnode].closesbranch()):
+ return None
+ return [binnode]
+
+ def __getitem__(self, key):
+ if not self.loaded:
+ self._load()
+ val = self._fetchandcache(key)
+ if val is not None:
+ return val
+ else:
+ raise KeyError()
+
+ def _fetchandcache(self, key):
+ if key in self.cache:
+ return self.cache[key]
+ val = self._resolvedata(key)
+ if val is not None:
+ self.cache[key] = val
+ return val
+ else:
+ return None
+
+ def keys(self):
+ """ Get a list of bookmark or branch names """
+ if not self.loaded:
+ self._load()
+ return self.potentialentries.keys()
+
+ def iteritems(self):
+ """ Iterate over (name, node) tuples """
+
+ if not self.loaded:
+ self._load()
+
+ for k, vtup in self.potentialentries.iteritems():
+ yield (k, [bin(vtup[0])])
+
class remotenames(dict):
"""
This class encapsulates all the remotenames state. It also contains
- methods to access that state in convenient ways.
+ methods to access that state in convenient ways. Remotenames are lazy
+ loaded. Whenever client code needs to ensure the freshest copy of
+ remotenames, use the `clearnames` method to force an eventual load.
"""
def __init__(self, repo, *args):
dict.__init__(self, *args)
self._repo = repo
- self['bookmarks'] = {}
- self['branches'] = {}
- self.loadnames()
- self._loadednames = True
-
- def loadnames(self):
- """ loads the remotenames information from the remotenames file """
- for rtype in ('bookmarks', 'branches'):
- for node, rpath, name in logexchange.readremotenamefile(self._repo,
- rtype):
- rname = rpath + '/' + name
- self[rtype][rname] = [node]
+ self.clearnames()
def clearnames(self):
""" Clear all remote names state """
- self['bookmarks'] = {}
- self['branches'] = {}
+ self['bookmarks'] = lazyremotenamedict("bookmarks", self._repo)
+ self['branches'] = lazyremotenamedict("branches", self._repo)
self._invalidatecache()
- self._loadednames = False
def _invalidatecache(self):
self._nodetobmarks = None