changeset 46780:6266d19556ad

node: introduce nodeconstants class In preparing for moving from SHA1 hashes to a modern hash function, place nullid and other constant magic vules in a class. Provide the active set of constants in the repository and push it down. Provide nullid directly in strategic places like the repository as it is accessed very often. This changeset introduces the API change, but not the mechanical replacement of the node.py attributes itself. Differential Revision: https://phab.mercurial-scm.org/D9750
author Joerg Sonnenberger <joerg@bec.de>
date Wed, 13 Jan 2021 16:14:58 +0100
parents 49fd21f32695
children 1099ca176ba1
files contrib/perf.py hgext/absorb.py hgext/git/gitlog.py hgext/largefiles/lfutil.py hgext/sqlitestore.py mercurial/bookmarks.py mercurial/branchmap.py mercurial/bundle2.py mercurial/bundlerepo.py mercurial/changegroup.py mercurial/changelog.py mercurial/dirstate.py mercurial/discovery.py mercurial/exchange.py mercurial/filelog.py mercurial/interfaces/dirstate.py mercurial/interfaces/repository.py mercurial/localrepo.py mercurial/manifest.py mercurial/node.py mercurial/obsolete.py mercurial/revlog.py mercurial/statichttprepo.py mercurial/store.py mercurial/unionrepo.py mercurial/upgrade_utils/engine.py relnotes/next tests/simplestorerepo.py tests/test-check-interfaces.py tests/test-manifest.py
diffstat 30 files changed, 215 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/perf.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/contrib/perf.py	Wed Jan 13 16:14:58 2021 +0100
@@ -3672,7 +3672,7 @@
     Result is the number of markers in the repo."""
     timer, fm = gettimer(ui)
     svfs = getsvfs(repo)
-    timer(lambda: len(obsolete.obsstore(svfs)))
+    timer(lambda: len(obsolete.obsstore(repo, svfs)))
     fm.end()
 
 
--- a/hgext/absorb.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/hgext/absorb.py	Wed Jan 13 16:14:58 2021 +0100
@@ -102,6 +102,9 @@
 class emptyfilecontext(object):
     """minimal filecontext representing an empty file"""
 
+    def __init__(self, repo):
+        self._repo = repo
+
     def data(self):
         return b''
 
@@ -212,7 +215,7 @@
         if path in pctx:
             fctxs.append(pctx[path])
         else:
-            fctxs.append(emptyfilecontext())
+            fctxs.append(emptyfilecontext(pctx.repo()))
 
     fctxs.reverse()
     # note: we rely on a property of hg: filerev is not reused for linear
--- a/hgext/git/gitlog.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/hgext/git/gitlog.py	Wed Jan 13 16:14:58 2021 +0100
@@ -8,6 +8,7 @@
     nullhex,
     nullid,
     nullrev,
+    sha1nodeconstants,
     wdirhex,
 )
 from mercurial import (
@@ -422,6 +423,8 @@
 
 
 class manifestlog(baselog):
+    nodeconstants = sha1nodeconstants
+
     def __getitem__(self, node):
         return self.get(b'', node)
 
--- a/hgext/largefiles/lfutil.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/hgext/largefiles/lfutil.py	Wed Jan 13 16:14:58 2021 +0100
@@ -206,6 +206,7 @@
         repo.root,
         repo.dirstate._validate,
         lambda: sparse.matcher(repo),
+        repo.nodeconstants,
     )
 
     # If the largefiles dirstate does not exist, populate and create
--- a/hgext/sqlitestore.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/hgext/sqlitestore.py	Wed Jan 13 16:14:58 2021 +0100
@@ -54,6 +54,7 @@
 from mercurial.node import (
     nullid,
     nullrev,
+    sha1nodeconstants,
     short,
 )
 from mercurial.thirdparty import attr
@@ -305,6 +306,7 @@
     """Implements storage for an individual tracked path."""
 
     def __init__(self, db, path, compression):
+        self.nullid = sha1nodeconstants.nullid
         self._db = db
         self._path = path
 
--- a/mercurial/bookmarks.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/bookmarks.py	Wed Jan 13 16:14:58 2021 +0100
@@ -623,7 +623,7 @@
 _binaryentry = struct.Struct(b'>20sH')
 
 
-def binaryencode(bookmarks):
+def binaryencode(repo, bookmarks):
     """encode a '(bookmark, node)' iterable into a binary stream
 
     the binary format is:
@@ -645,7 +645,7 @@
     return b''.join(binarydata)
 
 
-def binarydecode(stream):
+def binarydecode(repo, stream):
     """decode a binary stream into an '(bookmark, node)' iterable
 
     the binary format is:
--- a/mercurial/branchmap.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/branchmap.py	Wed Jan 13 16:14:58 2021 +0100
@@ -97,7 +97,7 @@
                 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
             else:
                 # nothing to fall back on, start empty.
-                bcache = branchcache()
+                bcache = branchcache(repo)
 
         revs.extend(cl.revs(start=bcache.tiprev + 1))
         if revs:
@@ -129,6 +129,7 @@
         if rbheads:
             rtiprev = max((int(clrev(node)) for node in rbheads))
             cache = branchcache(
+                repo,
                 remotebranchmap,
                 repo[rtiprev].node(),
                 rtiprev,
@@ -184,6 +185,7 @@
 
     def __init__(
         self,
+        repo,
         entries=(),
         tipnode=nullid,
         tiprev=nullrev,
@@ -195,6 +197,7 @@
         """hasnode is a function which can be used to verify whether changelog
         has a given node or not. If it's not provided, we assume that every node
         we have exists in changelog"""
+        self._repo = repo
         self.tipnode = tipnode
         self.tiprev = tiprev
         self.filteredhash = filteredhash
@@ -280,6 +283,7 @@
             if len(cachekey) > 2:
                 filteredhash = bin(cachekey[2])
             bcache = cls(
+                repo,
                 tipnode=last,
                 tiprev=lrev,
                 filteredhash=filteredhash,
@@ -388,6 +392,7 @@
     def copy(self):
         """return an deep copy of the branchcache object"""
         return type(self)(
+            self._repo,
             self._entries,
             self.tipnode,
             self.tiprev,
--- a/mercurial/bundle2.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/bundle2.py	Wed Jan 13 16:14:58 2021 +0100
@@ -2146,7 +2146,7 @@
     contains binary encoded (bookmark, node) tuple. If the local state does
     not marks the one in the part, a PushRaced exception is raised
     """
-    bookdata = bookmarks.binarydecode(inpart)
+    bookdata = bookmarks.binarydecode(op.repo, inpart)
 
     msgstandard = (
         b'remote repository changed while pushing - please try again '
@@ -2376,7 +2376,7 @@
     When mode is 'records', the information is recorded into the 'bookmarks'
     records of the bundle operation. This behavior is suitable for pulling.
     """
-    changes = bookmarks.binarydecode(inpart)
+    changes = bookmarks.binarydecode(op.repo, inpart)
 
     pushkeycompat = op.repo.ui.configbool(
         b'server', b'bookmarks-pushkey-compat'
--- a/mercurial/bundlerepo.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/bundlerepo.py	Wed Jan 13 16:14:58 2021 +0100
@@ -175,9 +175,15 @@
 
 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
     def __init__(
-        self, opener, cgunpacker, linkmapper, dirlogstarts=None, dir=b''
+        self,
+        nodeconstants,
+        opener,
+        cgunpacker,
+        linkmapper,
+        dirlogstarts=None,
+        dir=b'',
     ):
-        manifest.manifestrevlog.__init__(self, opener, tree=dir)
+        manifest.manifestrevlog.__init__(self, nodeconstants, opener, tree=dir)
         bundlerevlog.__init__(
             self, opener, self.indexfile, cgunpacker, linkmapper
         )
@@ -192,6 +198,7 @@
         if d in self._dirlogstarts:
             self.bundle.seek(self._dirlogstarts[d])
             return bundlemanifest(
+                self.nodeconstants,
                 self.opener,
                 self.bundle,
                 self._linkmapper,
@@ -368,7 +375,9 @@
         # consume the header if it exists
         self._cgunpacker.manifestheader()
         linkmapper = self.unfiltered().changelog.rev
-        rootstore = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
+        rootstore = bundlemanifest(
+            self.nodeconstants, self.svfs, self._cgunpacker, linkmapper
+        )
         self.filestart = self._cgunpacker.tell()
 
         return manifest.manifestlog(
--- a/mercurial/changegroup.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/changegroup.py	Wed Jan 13 16:14:58 2021 +0100
@@ -662,7 +662,7 @@
         return readexactly(self._fh, n)
 
 
-def _revisiondeltatochunks(delta, headerfn):
+def _revisiondeltatochunks(repo, delta, headerfn):
     """Serialize a revisiondelta to changegroup chunks."""
 
     # The captured revision delta may be encoded as a delta against
@@ -1065,7 +1065,9 @@
             sidedata_helpers=sidedata_helpers,
         )
         for delta in deltas:
-            for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
+            for chunk in _revisiondeltatochunks(
+                self._repo, delta, self._builddeltaheader
+            ):
                 size += len(chunk)
                 yield chunk
 
@@ -1121,7 +1123,9 @@
                 yield chunk
 
             for delta in deltas:
-                chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
+                chunks = _revisiondeltatochunks(
+                    self._repo, delta, self._builddeltaheader
+                )
                 for chunk in chunks:
                     size += len(chunk)
                     yield chunk
@@ -1160,7 +1164,9 @@
             yield h
 
             for delta in deltas:
-                chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
+                chunks = _revisiondeltatochunks(
+                    self._repo, delta, self._builddeltaheader
+                )
                 for chunk in chunks:
                     size += len(chunk)
                     yield chunk
--- a/mercurial/changelog.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/changelog.py	Wed Jan 13 16:14:58 2021 +0100
@@ -191,7 +191,7 @@
     # Extensions might modify _defaultextra, so let the constructor below pass
     # it in
     extra = attr.ib()
-    manifest = attr.ib(default=nullid)
+    manifest = attr.ib()
     user = attr.ib(default=b'')
     date = attr.ib(default=(0, 0))
     files = attr.ib(default=attr.Factory(list))
@@ -219,9 +219,9 @@
         '_changes',
     )
 
-    def __new__(cls, text, sidedata, cpsd):
+    def __new__(cls, cl, text, sidedata, cpsd):
         if not text:
-            return _changelogrevision(extra=_defaultextra)
+            return _changelogrevision(extra=_defaultextra, manifest=nullid)
 
         self = super(changelogrevision, cls).__new__(cls)
         # We could return here and implement the following as an __init__.
@@ -526,7 +526,7 @@
         """
         d, s = self._revisiondata(nodeorrev)
         c = changelogrevision(
-            d, s, self._copiesstorage == b'changeset-sidedata'
+            self, d, s, self._copiesstorage == b'changeset-sidedata'
         )
         return (c.manifest, c.user, c.date, c.files, c.description, c.extra)
 
@@ -534,7 +534,7 @@
         """Obtain a ``changelogrevision`` for a node or revision."""
         text, sidedata = self._revisiondata(nodeorrev)
         return changelogrevision(
-            text, sidedata, self._copiesstorage == b'changeset-sidedata'
+            self, text, sidedata, self._copiesstorage == b'changeset-sidedata'
         )
 
     def readfiles(self, nodeorrev):
--- a/mercurial/dirstate.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/dirstate.py	Wed Jan 13 16:14:58 2021 +0100
@@ -73,13 +73,16 @@
 
 @interfaceutil.implementer(intdirstate.idirstate)
 class dirstate(object):
-    def __init__(self, opener, ui, root, validate, sparsematchfn):
+    def __init__(
+        self, opener, ui, root, validate, sparsematchfn, nodeconstants
+    ):
         """Create a new dirstate object.
 
         opener is an open()-like callable that can be used to open the
         dirstate file; root is the root of the directory tracked by
         the dirstate.
         """
+        self._nodeconstants = nodeconstants
         self._opener = opener
         self._validate = validate
         self._root = root
@@ -136,7 +139,9 @@
     @propertycache
     def _map(self):
         """Return the dirstate contents (see documentation for dirstatemap)."""
-        self._map = self._mapcls(self._ui, self._opener, self._root)
+        self._map = self._mapcls(
+            self._ui, self._opener, self._root, self._nodeconstants
+        )
         return self._map
 
     @property
@@ -1420,12 +1425,13 @@
       denormalized form that they appear as in the dirstate.
     """
 
-    def __init__(self, ui, opener, root):
+    def __init__(self, ui, opener, root, nodeconstants):
         self._ui = ui
         self._opener = opener
         self._root = root
         self._filename = b'dirstate'
         self._nodelen = 20
+        self._nodeconstants = nodeconstants
 
         self._parents = None
         self._dirtyparents = False
@@ -1724,7 +1730,8 @@
 if rustmod is not None:
 
     class dirstatemap(object):
-        def __init__(self, ui, opener, root):
+        def __init__(self, ui, opener, root, nodeconstants):
+            self._nodeconstants = nodeconstants
             self._ui = ui
             self._opener = opener
             self._root = root
--- a/mercurial/discovery.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/discovery.py	Wed Jan 13 16:14:58 2021 +0100
@@ -270,9 +270,12 @@
     # C. Update newmap with outgoing changes.
     # This will possibly add new heads and remove existing ones.
     newmap = branchmap.remotebranchcache(
-        (branch, heads[1])
-        for branch, heads in pycompat.iteritems(headssum)
-        if heads[0] is not None
+        repo,
+        (
+            (branch, heads[1])
+            for branch, heads in pycompat.iteritems(headssum)
+            if heads[0] is not None
+        ),
     )
     newmap.update(repo, (ctx.rev() for ctx in missingctx))
     for branch, newheads in pycompat.iteritems(newmap):
--- a/mercurial/exchange.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/exchange.py	Wed Jan 13 16:14:58 2021 +0100
@@ -827,7 +827,7 @@
     data = []
     for book, old, new in pushop.outbookmarks:
         data.append((book, old))
-    checkdata = bookmod.binaryencode(data)
+    checkdata = bookmod.binaryencode(pushop.repo, data)
     bundler.newpart(b'check:bookmarks', data=checkdata)
 
 
@@ -1027,7 +1027,7 @@
         _abortonsecretctx(pushop, new, book)
         data.append((book, new))
         allactions.append((book, _bmaction(old, new)))
-    checkdata = bookmod.binaryencode(data)
+    checkdata = bookmod.binaryencode(pushop.repo, data)
     bundler.newpart(b'bookmarks', data=checkdata)
 
     def handlereply(op):
@@ -2455,7 +2455,7 @@
     if not b2caps or b'bookmarks' not in b2caps:
         raise error.Abort(_(b'no common bookmarks exchange method'))
     books = bookmod.listbinbookmarks(repo)
-    data = bookmod.binaryencode(books)
+    data = bookmod.binaryencode(repo, books)
     if data:
         bundler.newpart(b'bookmarks', data=data)
 
--- a/mercurial/filelog.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/filelog.py	Wed Jan 13 16:14:58 2021 +0100
@@ -33,6 +33,7 @@
         # Used by LFS.
         self._revlog.filename = path
         self._revlog.revlog_kind = b'filelog'
+        self.nullid = self._revlog.nullid
 
     def __len__(self):
         return len(self._revlog)
--- a/mercurial/interfaces/dirstate.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/interfaces/dirstate.py	Wed Jan 13 16:14:58 2021 +0100
@@ -8,7 +8,7 @@
 
 
 class idirstate(interfaceutil.Interface):
-    def __init__(opener, ui, root, validate, sparsematchfn):
+    def __init__(opener, ui, root, validate, sparsematchfn, nodeconstants):
         """Create a new dirstate object.
 
         opener is an open()-like callable that can be used to open the
--- a/mercurial/interfaces/repository.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/interfaces/repository.py	Wed Jan 13 16:14:58 2021 +0100
@@ -523,6 +523,10 @@
     * Metadata to facilitate storage.
     """
 
+    nullid = interfaceutil.Attribute(
+        """node for the null revision for use as delta base."""
+    )
+
     def __len__():
         """Obtain the number of revisions stored for this file."""
 
@@ -1143,6 +1147,10 @@
 class imanifeststorage(interfaceutil.Interface):
     """Storage interface for manifest data."""
 
+    nodeconstants = interfaceutil.Attribute(
+        """nodeconstants used by the current repository."""
+    )
+
     tree = interfaceutil.Attribute(
         """The path to the directory this manifest tracks.
 
@@ -1366,6 +1374,10 @@
     tree manifests.
     """
 
+    nodeconstants = interfaceutil.Attribute(
+        """nodeconstants used by the current repository."""
+    )
+
     def __getitem__(node):
         """Obtain a manifest instance for a given binary node.
 
@@ -1434,6 +1446,13 @@
     This currently captures the reality of things - not how things should be.
     """
 
+    nodeconstants = interfaceutil.Attribute(
+        """Constant nodes matching the hash function used by the repository."""
+    )
+    nullid = interfaceutil.Attribute(
+        """null revision for the hash function used by the repository."""
+    )
+
     supportedformats = interfaceutil.Attribute(
         """Set of requirements that apply to stream clone.
 
--- a/mercurial/localrepo.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/localrepo.py	Wed Jan 13 16:14:58 2021 +0100
@@ -21,6 +21,7 @@
     hex,
     nullid,
     nullrev,
+    sha1nodeconstants,
     short,
 )
 from .pycompat import (
@@ -1330,6 +1331,8 @@
         self.vfs = hgvfs
         self.path = hgvfs.base
         self.requirements = requirements
+        self.nodeconstants = sha1nodeconstants
+        self.nullid = self.nodeconstants.nullid
         self.supported = supportedrequirements
         self.sharedpath = sharedpath
         self.store = store
@@ -1676,7 +1679,12 @@
         sparsematchfn = lambda: sparse.matcher(self)
 
         return dirstate.dirstate(
-            self.vfs, self.ui, self.root, self._dirstatevalidate, sparsematchfn
+            self.vfs,
+            self.ui,
+            self.root,
+            self._dirstatevalidate,
+            sparsematchfn,
+            self.nodeconstants,
         )
 
     def _dirstatevalidate(self, node):
--- a/mercurial/manifest.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/manifest.py	Wed Jan 13 16:14:58 2021 +0100
@@ -792,8 +792,9 @@
 
 @interfaceutil.implementer(repository.imanifestdict)
 class treemanifest(object):
-    def __init__(self, dir=b'', text=b''):
+    def __init__(self, nodeconstants, dir=b'', text=b''):
         self._dir = dir
+        self.nodeconstants = nodeconstants
         self._node = nullid
         self._loadfunc = _noop
         self._copyfunc = _noop
@@ -1051,7 +1052,9 @@
         if dir:
             self._loadlazy(dir)
             if dir not in self._dirs:
-                self._dirs[dir] = treemanifest(self._subpath(dir))
+                self._dirs[dir] = treemanifest(
+                    self.nodeconstants, self._subpath(dir)
+                )
             self._dirs[dir].__setitem__(subpath, n)
         else:
             # manifest nodes are either 20 bytes or 32 bytes,
@@ -1078,14 +1081,16 @@
         if dir:
             self._loadlazy(dir)
             if dir not in self._dirs:
-                self._dirs[dir] = treemanifest(self._subpath(dir))
+                self._dirs[dir] = treemanifest(
+                    self.nodeconstants, self._subpath(dir)
+                )
             self._dirs[dir].setflag(subpath, flags)
         else:
             self._flags[f] = flags
         self._dirty = True
 
     def copy(self):
-        copy = treemanifest(self._dir)
+        copy = treemanifest(self.nodeconstants, self._dir)
         copy._node = self._node
         copy._dirty = self._dirty
         if self._copyfunc is _noop:
@@ -1215,7 +1220,7 @@
         visit = match.visitchildrenset(self._dir[:-1])
         if visit == b'all':
             return self.copy()
-        ret = treemanifest(self._dir)
+        ret = treemanifest(self.nodeconstants, self._dir)
         if not visit:
             return ret
 
@@ -1272,7 +1277,7 @@
             m2 = m2._matches(match)
             return m1.diff(m2, clean=clean)
         result = {}
-        emptytree = treemanifest()
+        emptytree = treemanifest(self.nodeconstants)
 
         def _iterativediff(t1, t2, stack):
             """compares two tree manifests and append new tree-manifests which
@@ -1368,7 +1373,7 @@
         self._load()  # for consistency; should never have any effect here
         m1._load()
         m2._load()
-        emptytree = treemanifest()
+        emptytree = treemanifest(self.nodeconstants)
 
         def getnode(m, d):
             ld = m._lazydirs.get(d)
@@ -1551,6 +1556,7 @@
 
     def __init__(
         self,
+        nodeconstants,
         opener,
         tree=b'',
         dirlogcache=None,
@@ -1567,6 +1573,7 @@
         option takes precedence, so if it is set to True, we ignore whatever
         value is passed in to the constructor.
         """
+        self.nodeconstants = nodeconstants
         # During normal operations, we expect to deal with not more than four
         # revs at a time (such as during commit --amend). When rebasing large
         # stacks of commits, the number can go up, hence the config knob below.
@@ -1654,7 +1661,11 @@
             assert self._treeondisk
         if d not in self._dirlogcache:
             mfrevlog = manifestrevlog(
-                self.opener, d, self._dirlogcache, treemanifest=self._treeondisk
+                self.nodeconstants,
+                self.opener,
+                d,
+                self._dirlogcache,
+                treemanifest=self._treeondisk,
             )
             self._dirlogcache[d] = mfrevlog
         return self._dirlogcache[d]
@@ -1917,6 +1928,7 @@
     they receive (i.e. tree or flat or lazily loaded, etc)."""
 
     def __init__(self, opener, repo, rootstore, narrowmatch):
+        self.nodeconstants = repo.nodeconstants
         usetreemanifest = False
         cachesize = 4
 
@@ -1955,7 +1967,7 @@
 
         if not self._narrowmatch.always():
             if not self._narrowmatch.visitdir(tree[:-1]):
-                return excludeddirmanifestctx(tree, node)
+                return excludeddirmanifestctx(self.nodeconstants, tree, node)
         if tree:
             if self._rootstore._treeondisk:
                 if verify:
@@ -2118,7 +2130,7 @@
     def __init__(self, manifestlog, dir=b''):
         self._manifestlog = manifestlog
         self._dir = dir
-        self._treemanifest = treemanifest()
+        self._treemanifest = treemanifest(manifestlog.nodeconstants)
 
     def _storage(self):
         return self._manifestlog.getstorage(b'')
@@ -2168,17 +2180,19 @@
         narrowmatch = self._manifestlog._narrowmatch
         if not narrowmatch.always():
             if not narrowmatch.visitdir(self._dir[:-1]):
-                return excludedmanifestrevlog(self._dir)
+                return excludedmanifestrevlog(
+                    self._manifestlog.nodeconstants, self._dir
+                )
         return self._manifestlog.getstorage(self._dir)
 
     def read(self):
         if self._data is None:
             store = self._storage()
             if self._node == nullid:
-                self._data = treemanifest()
+                self._data = treemanifest(self._manifestlog.nodeconstants)
             # TODO accessing non-public API
             elif store._treeondisk:
-                m = treemanifest(dir=self._dir)
+                m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
 
                 def gettext():
                     return store.revision(self._node)
@@ -2198,7 +2212,9 @@
                     text = store.revision(self._node)
                     arraytext = bytearray(text)
                     store.fulltextcache[self._node] = arraytext
-                self._data = treemanifest(dir=self._dir, text=text)
+                self._data = treemanifest(
+                    self._manifestlog.nodeconstants, dir=self._dir, text=text
+                )
 
         return self._data
 
@@ -2235,7 +2251,7 @@
             r0 = store.deltaparent(store.rev(self._node))
             m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
             m1 = self.read()
-            md = treemanifest(dir=self._dir)
+            md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
             for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
                 if n1:
                     md[f] = n1
@@ -2278,8 +2294,8 @@
     whose contents are unknown.
     """
 
-    def __init__(self, dir, node):
-        super(excludeddir, self).__init__(dir)
+    def __init__(self, nodeconstants, dir, node):
+        super(excludeddir, self).__init__(nodeconstants, dir)
         self._node = node
         # Add an empty file, which will be included by iterators and such,
         # appearing as the directory itself (i.e. something like "dir/")
@@ -2298,12 +2314,13 @@
 class excludeddirmanifestctx(treemanifestctx):
     """context wrapper for excludeddir - see that docstring for rationale"""
 
-    def __init__(self, dir, node):
+    def __init__(self, nodeconstants, dir, node):
+        self.nodeconstants = nodeconstants
         self._dir = dir
         self._node = node
 
     def read(self):
-        return excludeddir(self._dir, self._node)
+        return excludeddir(self.nodeconstants, self._dir, self._node)
 
     def readfast(self, shallow=False):
         # special version of readfast since we don't have underlying storage
@@ -2325,7 +2342,8 @@
     outside the narrowspec.
     """
 
-    def __init__(self, dir):
+    def __init__(self, nodeconstants, dir):
+        self.nodeconstants = nodeconstants
         self._dir = dir
 
     def __len__(self):
--- a/mercurial/node.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/node.py	Wed Jan 13 16:14:58 2021 +0100
@@ -21,29 +21,48 @@
         raise TypeError(e)
 
 
-nullrev = -1
-# In hex, this is '0000000000000000000000000000000000000000'
-nullid = b"\0" * 20
-nullhex = hex(nullid)
+def short(node):
+    return hex(node[:6])
+
 
-# Phony node value to stand-in for new files in some uses of
-# manifests.
-# In hex, this is '2121212121212121212121212121212121212121'
-newnodeid = b'!!!!!!!!!!!!!!!!!!!!'
-# In hex, this is '3030303030303030303030303030306164646564'
-addednodeid = b'000000000000000added'
-# In hex, this is '3030303030303030303030306d6f646966696564'
-modifiednodeid = b'000000000000modified'
+nullrev = -1
 
-wdirfilenodeids = {newnodeid, addednodeid, modifiednodeid}
-
-# pseudo identifiers for working directory
-# (they are experimental, so don't add too many dependencies on them)
+# pseudo identifier for working directory
+# (experimental, so don't add too many dependencies on it)
 wdirrev = 0x7FFFFFFF
-# In hex, this is 'ffffffffffffffffffffffffffffffffffffffff'
-wdirid = b"\xff" * 20
-wdirhex = hex(wdirid)
 
 
-def short(node):
-    return hex(node[:6])
+class sha1nodeconstants(object):
+    nodelen = 20
+
+    # In hex, this is '0000000000000000000000000000000000000000'
+    nullid = b"\0" * nodelen
+    nullhex = hex(nullid)
+
+    # Phony node value to stand-in for new files in some uses of
+    # manifests.
+    # In hex, this is '2121212121212121212121212121212121212121'
+    newnodeid = b'!!!!!!!!!!!!!!!!!!!!'
+    # In hex, this is '3030303030303030303030303030306164646564'
+    addednodeid = b'000000000000000added'
+    # In hex, this is '3030303030303030303030306d6f646966696564'
+    modifiednodeid = b'000000000000modified'
+
+    wdirfilenodeids = {newnodeid, addednodeid, modifiednodeid}
+
+    # pseudo identifier for working directory
+    # (experimental, so don't add too many dependencies on it)
+    # In hex, this is 'ffffffffffffffffffffffffffffffffffffffff'
+    wdirid = b"\xff" * nodelen
+    wdirhex = hex(wdirid)
+
+
+# legacy starting point for porting modules
+nullid = sha1nodeconstants.nullid
+nullhex = sha1nodeconstants.nullhex
+newnodeid = sha1nodeconstants.newnodeid
+addednodeid = sha1nodeconstants.addednodeid
+modifiednodeid = sha1nodeconstants.modifiednodeid
+wdirfilenodeids = sha1nodeconstants.wdirfilenodeids
+wdirid = sha1nodeconstants.wdirid
+wdirhex = sha1nodeconstants.wdirhex
--- a/mercurial/obsolete.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/obsolete.py	Wed Jan 13 16:14:58 2021 +0100
@@ -560,10 +560,11 @@
     # parents: (tuple of nodeid) or None, parents of predecessors
     #          None is used when no data has been recorded
 
-    def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
+    def __init__(self, repo, svfs, defaultformat=_fm1version, readonly=False):
         # caches for various obsolescence related cache
         self.caches = {}
         self.svfs = svfs
+        self.repo = repo
         self._defaultformat = defaultformat
         self._readonly = readonly
 
@@ -806,7 +807,7 @@
     if defaultformat is not None:
         kwargs['defaultformat'] = defaultformat
     readonly = not isenabled(repo, createmarkersopt)
-    store = obsstore(repo.svfs, readonly=readonly, **kwargs)
+    store = obsstore(repo, repo.svfs, readonly=readonly, **kwargs)
     if store and readonly:
         ui.warn(
             _(b'obsolete feature not enabled but %i markers found!\n')
--- a/mercurial/revlog.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/revlog.py	Wed Jan 13 16:14:58 2021 +0100
@@ -28,6 +28,7 @@
     nullhex,
     nullid,
     nullrev,
+    sha1nodeconstants,
     short,
     wdirfilenodeids,
     wdirhex,
@@ -651,6 +652,10 @@
             raise error.RevlogError(
                 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
             )
+
+        self.nodeconstants = sha1nodeconstants
+        self.nullid = self.nodeconstants.nullid
+
         # sparse-revlog can't be on without general-delta (issue6056)
         if not self._generaldelta:
             self._sparserevlog = False
--- a/mercurial/statichttprepo.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/statichttprepo.py	Wed Jan 13 16:14:58 2021 +0100
@@ -12,6 +12,7 @@
 import errno
 
 from .i18n import _
+from .node import sha1nodeconstants
 from . import (
     branchmap,
     changelog,
@@ -198,6 +199,8 @@
             requirements, supportedrequirements
         )
         localrepo.ensurerequirementscompatible(ui, requirements)
+        self.nodeconstants = sha1nodeconstants
+        self.nullid = self.nodeconstants.nullid
 
         # setup store
         self.store = localrepo.makestore(requirements, self.path, vfsclass)
@@ -207,7 +210,7 @@
         self._filecache = {}
         self.requirements = requirements
 
-        rootmanifest = manifest.manifestrevlog(self.svfs)
+        rootmanifest = manifest.manifestrevlog(self.nodeconstants, self.svfs)
         self.manifestlog = manifest.manifestlog(
             self.svfs, self, rootmanifest, self.narrowmatch()
         )
--- a/mercurial/store.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/store.py	Wed Jan 13 16:14:58 2021 +0100
@@ -441,7 +441,7 @@
         )
 
     def manifestlog(self, repo, storenarrowmatch):
-        rootstore = manifest.manifestrevlog(self.vfs)
+        rootstore = manifest.manifestrevlog(repo.nodeconstants, self.vfs)
         return manifest.manifestlog(self.vfs, repo, rootstore, storenarrowmatch)
 
     def datafiles(self, matcher=None):
--- a/mercurial/unionrepo.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/unionrepo.py	Wed Jan 13 16:14:58 2021 +0100
@@ -153,9 +153,9 @@
 
 
 class unionmanifest(unionrevlog, manifest.manifestrevlog):
-    def __init__(self, opener, opener2, linkmapper):
-        manifest.manifestrevlog.__init__(self, opener)
-        manifest2 = manifest.manifestrevlog(opener2)
+    def __init__(self, nodeconstants, opener, opener2, linkmapper):
+        manifest.manifestrevlog.__init__(self, nodeconstants, opener)
+        manifest2 = manifest.manifestrevlog(nodeconstants, opener2)
         unionrevlog.__init__(
             self, opener, self.indexfile, manifest2, linkmapper
         )
@@ -205,7 +205,10 @@
     @localrepo.unfilteredpropertycache
     def manifestlog(self):
         rootstore = unionmanifest(
-            self.svfs, self.repo2.svfs, self.unfiltered()._clrev
+            self.nodeconstants,
+            self.svfs,
+            self.repo2.svfs,
+            self.unfiltered()._clrev,
         )
         return manifest.manifestlog(
             self.svfs, self, rootstore, self.narrowmatch()
--- a/mercurial/upgrade_utils/engine.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/mercurial/upgrade_utils/engine.py	Wed Jan 13 16:14:58 2021 +0100
@@ -36,7 +36,9 @@
         return changelog.changelog(repo.svfs)
     elif path.endswith(b'00manifest.i'):
         mandir = path[: -len(b'00manifest.i')]
-        return manifest.manifestrevlog(repo.svfs, tree=mandir)
+        return manifest.manifestrevlog(
+            repo.nodeconstants, repo.svfs, tree=mandir
+        )
     else:
         # reverse of "/".join(("data", path + ".i"))
         return filelog.filelog(repo.svfs, path[5:-2])
--- a/relnotes/next	Wed Mar 10 18:09:21 2021 +0100
+++ b/relnotes/next	Wed Jan 13 16:14:58 2021 +0100
@@ -43,3 +43,7 @@
    now get a revision number as argument instead of a node.
 
  * revlog.addrevision returns the revision number instead of the node.
+
+ * `nodes.nullid` and related constants are being phased out as part of
+   the deprecation of SHA1. Repository instances and related classes
+   provide access via `nodeconstants` and in some cases `nullid` attributes.
--- a/tests/simplestorerepo.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/tests/simplestorerepo.py	Wed Jan 13 16:14:58 2021 +0100
@@ -106,7 +106,9 @@
 
     _flagserrorclass = simplestoreerror
 
-    def __init__(self, svfs, path):
+    def __init__(self, repo, svfs, path):
+        self.nullid = repo.nullid
+        self._repo = repo
         self._svfs = svfs
         self._path = path
 
@@ -689,7 +691,7 @@
 
     class simplestorerepo(repo.__class__):
         def file(self, f):
-            return filestorage(self.svfs, f)
+            return filestorage(repo, self.svfs, f)
 
     repo.__class__ = simplestorerepo
 
--- a/tests/test-check-interfaces.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/tests/test-check-interfaces.py	Wed Jan 13 16:14:58 2021 +0100
@@ -248,7 +248,10 @@
 
     # Conforms to imanifestlog.
     ml = manifest.manifestlog(
-        vfs, repo, manifest.manifestrevlog(repo.svfs), repo.narrowmatch()
+        vfs,
+        repo,
+        manifest.manifestrevlog(repo.nodeconstants, repo.svfs),
+        repo.narrowmatch(),
     )
     checkzobject(ml)
     checkzobject(repo.manifestlog)
@@ -263,7 +266,7 @@
     # Conforms to imanifestdict.
     checkzobject(mctx.read())
 
-    mrl = manifest.manifestrevlog(vfs)
+    mrl = manifest.manifestrevlog(repo.nodeconstants, vfs)
     checkzobject(mrl)
 
     ziverify.verifyClass(repository.irevisiondelta, revlog.revlogrevisiondelta)
--- a/tests/test-manifest.py	Wed Mar 10 18:09:21 2021 +0100
+++ b/tests/test-manifest.py	Wed Jan 13 16:14:58 2021 +0100
@@ -6,6 +6,8 @@
 import unittest
 import zlib
 
+from mercurial.node import sha1nodeconstants
+
 from mercurial import (
     manifest as manifestmod,
     match as matchmod,
@@ -436,7 +438,7 @@
 
 class testtreemanifest(unittest.TestCase, basemanifesttests):
     def parsemanifest(self, text):
-        return manifestmod.treemanifest(b'', text)
+        return manifestmod.treemanifest(sha1nodeconstants, b'', text)
 
     def testWalkSubtrees(self):
         m = self.parsemanifest(A_DEEPER_MANIFEST)