Patchwork D9750: node: introduce nodeconstants class

login
register
mail settings
Submitter phabricator
Date Jan. 13, 2021, 4 p.m.
Message ID <differential-rev-PHID-DREV-sjjw2utqr3eyqj527s5i-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/48061/
State New
Headers show

Comments

phabricator - Jan. 13, 2021, 4 p.m.
joerg.sonnenberger created this revision.
Herald added a reviewer: indygreg.
Herald added a reviewer: durin42.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.
joerg.sonnenberger added a comment.


  This is the API changing part of D9465 <https://phab.mercurial-scm.org/D9465> without all the followup changes.

REVISION SUMMARY
  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.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D9750

AFFECTED 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

CHANGE DETAILS




To: joerg.sonnenberger, indygreg, durin42, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/tests/test-manifest.py b/tests/test-manifest.py
--- a/tests/test-manifest.py
+++ b/tests/test-manifest.py
@@ -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)
diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py
--- a/tests/test-check-interfaces.py
+++ b/tests/test-check-interfaces.py
@@ -243,7 +243,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)
@@ -258,7 +261,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)
diff --git a/tests/simplestorerepo.py b/tests/simplestorerepo.py
--- a/tests/simplestorerepo.py
+++ b/tests/simplestorerepo.py
@@ -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
 
@@ -687,7 +689,7 @@ 
 
     class simplestorerepo(repo.__class__):
         def file(self, f):
-            return filestorage(self.svfs, f)
+            return filestorage(repo, self.svfs, f)
 
     repo.__class__ = simplestorerepo
 
diff --git a/relnotes/next b/relnotes/next
--- a/relnotes/next
+++ b/relnotes/next
@@ -57,4 +57,6 @@ 
 
 == Internal API Changes ==
 
-
+ * `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.
diff --git a/mercurial/upgrade_utils/engine.py b/mercurial/upgrade_utils/engine.py
--- a/mercurial/upgrade_utils/engine.py
+++ b/mercurial/upgrade_utils/engine.py
@@ -35,7 +35,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])
diff --git a/mercurial/unionrepo.py b/mercurial/unionrepo.py
--- a/mercurial/unionrepo.py
+++ b/mercurial/unionrepo.py
@@ -152,9 +152,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
         )
@@ -204,7 +204,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()
diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -433,7 +433,7 @@ 
         return changelog.changelog(self.vfs, trypending=trypending)
 
     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):
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -12,6 +12,7 @@ 
 import errno
 
 from .i18n import _
+from .node import sha1nodeconstants
 from . import (
     branchmap,
     changelog,
@@ -197,6 +198,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)
@@ -206,7 +209,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()
         )
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -28,6 +28,7 @@ 
     nullhex,
     nullid,
     nullrev,
+    sha1nodeconstants,
     short,
     wdirfilenodeids,
     wdirhex,
@@ -615,6 +616,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
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -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')
diff --git a/mercurial/node.py b/mercurial/node.py
--- a/mercurial/node.py
+++ b/mercurial/node.py
@@ -21,29 +21,46 @@ 
         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):
+    # In hex, this is '0000000000000000000000000000000000000000'
+    nullid = b"\0" * 20
+    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" * 20
+    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
diff --git a/mercurial/manifest.py b/mercurial/manifest.py
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -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.
@@ -1653,7 +1660,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]
@@ -1909,6 +1920,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
 
@@ -1947,7 +1959,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:
@@ -2110,7 +2122,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'')
@@ -2160,17 +2172,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)
@@ -2190,7 +2204,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
 
@@ -2227,7 +2243,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
@@ -2270,8 +2286,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/")
@@ -2290,12 +2306,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 write(self, *args):
         raise error.ProgrammingError(
@@ -2313,7 +2330,8 @@ 
     outside the narrowspec.
     """
 
-    def __init__(self, dir):
+    def __init__(self, nodeconstants, dir):
+        self.nodeconstants = nodeconstants
         self._dir = dir
 
     def __len__(self):
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -21,6 +21,7 @@ 
     hex,
     nullid,
     nullrev,
+    sha1nodeconstants,
     short,
 )
 from .pycompat import (
@@ -1218,6 +1219,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
@@ -1557,7 +1560,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):
diff --git a/mercurial/interfaces/repository.py b/mercurial/interfaces/repository.py
--- a/mercurial/interfaces/repository.py
+++ b/mercurial/interfaces/repository.py
@@ -519,6 +519,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."""
 
@@ -1132,6 +1136,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.
 
@@ -1355,6 +1363,10 @@ 
     tree manifests.
     """
 
+    nodeconstants = interfaceutil.Attribute(
+        """nodeconstants used by the current repository."""
+    )
+
     def __getitem__(node):
         """Obtain a manifest instance for a given binary node.
 
@@ -1423,6 +1435,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.
 
diff --git a/mercurial/interfaces/dirstate.py b/mercurial/interfaces/dirstate.py
--- a/mercurial/interfaces/dirstate.py
+++ b/mercurial/interfaces/dirstate.py
@@ -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
diff --git a/mercurial/filelog.py b/mercurial/filelog.py
--- a/mercurial/filelog.py
+++ b/mercurial/filelog.py
@@ -32,6 +32,7 @@ 
         # Full name of the user visible file, relative to the repository root.
         # Used by LFS.
         self._revlog.filename = path
+        self.nullid = self._revlog.nullid
 
     def __len__(self):
         return len(self._revlog)
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -814,7 +814,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)
 
 
@@ -1007,7 +1007,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):
@@ -2393,7 +2393,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)
 
diff --git a/mercurial/discovery.py b/mercurial/discovery.py
--- a/mercurial/discovery.py
+++ b/mercurial/discovery.py
@@ -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):
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -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
diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -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))
@@ -218,9 +218,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__.
@@ -515,7 +515,7 @@ 
         """
         d, s = self._revisiondata(node)
         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)
 
@@ -523,7 +523,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, node):
diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -559,7 +559,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
@@ -932,7 +932,9 @@ 
             cl, clnodes, generate=changelog
         )
         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
 
@@ -987,7 +989,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
@@ -1025,7 +1029,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
diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -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(
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -2117,7 +2117,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 '
@@ -2347,7 +2347,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'
diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py
--- a/mercurial/branchmap.py
+++ b/mercurial/branchmap.py
@@ -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,
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
--- a/mercurial/bookmarks.py
+++ b/mercurial/bookmarks.py
@@ -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:
diff --git a/hgext/sqlitestore.py b/hgext/sqlitestore.py
--- a/hgext/sqlitestore.py
+++ b/hgext/sqlitestore.py
@@ -54,6 +54,7 @@ 
 from mercurial.node import (
     nullid,
     nullrev,
+    sha1nodeconstants,
     short,
 )
 from mercurial.thirdparty import attr
@@ -304,6 +305,7 @@ 
     """Implements storage for an individual tracked path."""
 
     def __init__(self, db, path, compression):
+        self.nullid = sha1nodeconstants.nullid
         self._db = db
         self._path = path
 
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -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
diff --git a/hgext/git/gitlog.py b/hgext/git/gitlog.py
--- a/hgext/git/gitlog.py
+++ b/hgext/git/gitlog.py
@@ -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)
 
diff --git a/hgext/absorb.py b/hgext/absorb.py
--- a/hgext/absorb.py
+++ b/hgext/absorb.py
@@ -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
diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -3668,7 +3668,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()