diff mercurial/localrepo.py @ 47759:d7515d29761d stable 5.9rc0

branching: merge default into stable This mark the start of the 5.9 freeze.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 21 Jul 2021 22:52:09 +0200
parents e33c6dd346e7
children 2813d406b036
line wrap: on
line diff
--- a/mercurial/localrepo.py	Fri Jul 09 00:25:14 2021 +0530
+++ b/mercurial/localrepo.py	Wed Jul 21 22:52:09 2021 +0200
@@ -19,7 +19,6 @@
 from .node import (
     bin,
     hex,
-    nullid,
     nullrev,
     sha1nodeconstants,
     short,
@@ -50,7 +49,6 @@
     match as matchmod,
     mergestate as mergestatemod,
     mergeutil,
-    metadata as metadatamod,
     namespaces,
     narrowspec,
     obsolete,
@@ -91,6 +89,7 @@
 from .revlogutils import (
     concurrency_checker as revlogchecker,
     constants as revlogconst,
+    sidedata as sidedatamod,
 )
 
 release = lockmod.release
@@ -738,6 +737,14 @@
     storevfs = store.vfs
     storevfs.options = resolvestorevfsoptions(ui, requirements, features)
 
+    if (
+        requirementsmod.REVLOGV2_REQUIREMENT in requirements
+        or requirementsmod.CHANGELOGV2_REQUIREMENT in requirements
+    ):
+        features.add(repository.REPO_FEATURE_SIDE_DATA)
+        # the revlogv2 docket introduced race condition that we need to fix
+        features.discard(repository.REPO_FEATURE_STREAM_CLONE)
+
     # The cache vfs is used to manage cache files.
     cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
     cachevfs.createmode = store.createmode
@@ -880,6 +887,9 @@
     # Start with all requirements supported by this file.
     supported = set(localrepository._basesupported)
 
+    if dirstate.SUPPORTS_DIRSTATE_V2:
+        supported.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
+
     # Execute ``featuresetupfuncs`` entries if they belong to an extension
     # relevant to this ui instance.
     modules = {m.__name__ for n, m in extensions.extensions(ui)}
@@ -1017,6 +1027,8 @@
         options[b'revlogv1'] = True
     if requirementsmod.REVLOGV2_REQUIREMENT in requirements:
         options[b'revlogv2'] = True
+    if requirementsmod.CHANGELOGV2_REQUIREMENT in requirements:
+        options[b'changelogv2'] = True
 
     if requirementsmod.GENERALDELTA_REQUIREMENT in requirements:
         options[b'generaldelta'] = True
@@ -1064,9 +1076,6 @@
     if sparserevlog:
         options[b'generaldelta'] = True
 
-    sidedata = requirementsmod.SIDEDATA_REQUIREMENT in requirements
-    options[b'side-data'] = sidedata
-
     maxchainlen = None
     if sparserevlog:
         maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
@@ -1219,7 +1228,7 @@
         requirementsmod.TREEMANIFEST_REQUIREMENT,
         requirementsmod.COPIESSDC_REQUIREMENT,
         requirementsmod.REVLOGV2_REQUIREMENT,
-        requirementsmod.SIDEDATA_REQUIREMENT,
+        requirementsmod.CHANGELOGV2_REQUIREMENT,
         requirementsmod.SPARSEREVLOG_REQUIREMENT,
         requirementsmod.NODEMAP_REQUIREMENT,
         bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
@@ -1408,7 +1417,7 @@
 
         self._wanted_sidedata = set()
         self._sidedata_computers = {}
-        metadatamod.set_sidedata_spec_for_repo(self)
+        sidedatamod.set_sidedata_spec_for_repo(self)
 
     def _getvfsward(self, origfunc):
         """build a ward for self.vfs"""
@@ -1681,6 +1690,8 @@
     def _makedirstate(self):
         """Extension point for wrapping the dirstate per-repo."""
         sparsematchfn = lambda: sparse.matcher(self)
+        v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
+        use_dirstate_v2 = v2_req in self.requirements
 
         return dirstate.dirstate(
             self.vfs,
@@ -1689,6 +1700,7 @@
             self._dirstatevalidate,
             sparsematchfn,
             self.nodeconstants,
+            use_dirstate_v2,
         )
 
     def _dirstatevalidate(self, node):
@@ -1702,7 +1714,7 @@
                     _(b"warning: ignoring unknown working parent %s!\n")
                     % short(node)
                 )
-            return nullid
+            return self.nullid
 
     @storecache(narrowspec.FILENAME)
     def narrowpats(self):
@@ -1753,9 +1765,9 @@
     @unfilteredpropertycache
     def _quick_access_changeid_null(self):
         return {
-            b'null': (nullrev, nullid),
-            nullrev: (nullrev, nullid),
-            nullid: (nullrev, nullid),
+            b'null': (nullrev, self.nodeconstants.nullid),
+            nullrev: (nullrev, self.nodeconstants.nullid),
+            self.nullid: (nullrev, self.nullid),
         }
 
     @unfilteredpropertycache
@@ -1765,7 +1777,7 @@
         quick = self._quick_access_changeid_null.copy()
         cl = self.unfiltered().changelog
         for node in self.dirstate.parents():
-            if node == nullid:
+            if node == self.nullid:
                 continue
             rev = cl.index.get_rev(node)
             if rev is None:
@@ -1785,7 +1797,7 @@
                 quick[r] = pair
                 quick[n] = pair
         p1node = self.dirstate.p1()
-        if p1node != nullid:
+        if p1node != self.nullid:
             quick[b'.'] = quick[p1node]
         return quick
 
@@ -1841,7 +1853,7 @@
                 # when we know that '.' won't be hidden
                 node = self.dirstate.p1()
                 rev = self.unfiltered().changelog.rev(node)
-            elif len(changeid) == 20:
+            elif len(changeid) == self.nodeconstants.nodelen:
                 try:
                     node = changeid
                     rev = self.changelog.rev(changeid)
@@ -1862,7 +1874,7 @@
                     changeid = hex(changeid)  # for the error message
                     raise
 
-            elif len(changeid) == 40:
+            elif len(changeid) == 2 * self.nodeconstants.nodelen:
                 node = bin(changeid)
                 rev = self.changelog.rev(node)
             else:
@@ -2037,7 +2049,7 @@
         # local encoding.
         tags = {}
         for (name, (node, hist)) in pycompat.iteritems(alltags):
-            if node != nullid:
+            if node != self.nullid:
                 tags[encoding.tolocal(name)] = node
         tags[b'tip'] = self.changelog.tip()
         tagtypes = {
@@ -2161,7 +2173,9 @@
     def wjoin(self, f, *insidef):
         return self.vfs.reljoin(self.root, f, *insidef)
 
-    def setparents(self, p1, p2=nullid):
+    def setparents(self, p1, p2=None):
+        if p2 is None:
+            p2 = self.nullid
         self[None].setparents(p1, p2)
         self._quick_access_changeid_invalidate()
 
@@ -2718,7 +2732,7 @@
         return updater
 
     @unfilteredmethod
-    def updatecaches(self, tr=None, full=False):
+    def updatecaches(self, tr=None, full=False, caches=None):
         """warm appropriate caches
 
         If this function is called after a transaction closed. The transaction
@@ -2738,40 +2752,61 @@
             # later call to `destroyed` will refresh them.
             return
 
-        if tr is None or tr.changes[b'origrepolen'] < len(self):
-            # accessing the 'served' branchmap should refresh all the others,
-            self.ui.debug(b'updating the branch cache\n')
-            self.filtered(b'served').branchmap()
-            self.filtered(b'served.hidden').branchmap()
+        unfi = self.unfiltered()
 
         if full:
-            unfi = self.unfiltered()
-
+            msg = (
+                "`full` argument for `repo.updatecaches` is deprecated\n"
+                "(use `caches=repository.CACHE_ALL` instead)"
+            )
+            self.ui.deprecwarn(msg, b"5.9")
+            caches = repository.CACHES_ALL
+            if full == b"post-clone":
+                caches = repository.CACHES_POST_CLONE
+            caches = repository.CACHES_ALL
+        elif caches is None:
+            caches = repository.CACHES_DEFAULT
+
+        if repository.CACHE_BRANCHMAP_SERVED in caches:
+            if tr is None or tr.changes[b'origrepolen'] < len(self):
+                # accessing the 'served' branchmap should refresh all the others,
+                self.ui.debug(b'updating the branch cache\n')
+                self.filtered(b'served').branchmap()
+                self.filtered(b'served.hidden').branchmap()
+
+        if repository.CACHE_CHANGELOG_CACHE in caches:
             self.changelog.update_caches(transaction=tr)
+
+        if repository.CACHE_MANIFESTLOG_CACHE in caches:
             self.manifestlog.update_caches(transaction=tr)
 
+        if repository.CACHE_REV_BRANCH in caches:
             rbc = unfi.revbranchcache()
             for r in unfi.changelog:
                 rbc.branchinfo(r)
             rbc.write()
 
+        if repository.CACHE_FULL_MANIFEST in caches:
             # ensure the working copy parents are in the manifestfulltextcache
             for ctx in self[b'.'].parents():
                 ctx.manifest()  # accessing the manifest is enough
 
-            if not full == b"post-clone":
-                # accessing fnode cache warms the cache
-                tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
+        if repository.CACHE_FILE_NODE_TAGS in caches:
+            # accessing fnode cache warms the cache
+            tagsmod.fnoderevs(self.ui, unfi, unfi.changelog.revs())
+
+        if repository.CACHE_TAGS_DEFAULT in caches:
             # accessing tags warm the cache
             self.tags()
+        if repository.CACHE_TAGS_SERVED in caches:
             self.filtered(b'served').tags()
 
-            # The `full` arg is documented as updating even the lazily-loaded
-            # caches immediately, so we're forcing a write to cause these caches
-            # to be warmed up even if they haven't explicitly been requested
-            # yet (if they've never been used by hg, they won't ever have been
-            # written, even if they're a subset of another kind of cache that
-            # *has* been used).
+        if repository.CACHE_BRANCHMAP_ALL in caches:
+            # The CACHE_BRANCHMAP_ALL updates lazily-loaded caches immediately,
+            # so we're forcing a write to cause these caches to be warmed up
+            # even if they haven't explicitly been requested yet (if they've
+            # never been used by hg, they won't ever have been written, even if
+            # they're a subset of another kind of cache that *has* been used).
             for filt in repoview.filtertable.keys():
                 filtered = self.filtered(filt)
                 filtered.branchmap().write(filtered)
@@ -3100,7 +3135,7 @@
                 subrepoutil.writestate(self, newstate)
 
             p1, p2 = self.dirstate.parents()
-            hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or b'')
+            hookp1, hookp2 = hex(p1), (p2 != self.nullid and hex(p2) or b'')
             try:
                 self.hook(
                     b"precommit", throw=True, parent1=hookp1, parent2=hookp2
@@ -3273,7 +3308,7 @@
             t = n
             while True:
                 p = self.changelog.parents(n)
-                if p[1] != nullid or p[0] == nullid:
+                if p[1] != self.nullid or p[0] == self.nullid:
                     b.append((t, n, p[0], p[1]))
                     break
                 n = p[0]
@@ -3286,7 +3321,7 @@
             n, l, i = top, [], 0
             f = 1
 
-            while n != bottom and n != nullid:
+            while n != bottom and n != self.nullid:
                 p = self.changelog.parents(n)[0]
                 if i == f:
                     l.append(n)
@@ -3370,20 +3405,32 @@
         return self.pathto(fp.name[len(self.root) + 1 :])
 
     def register_wanted_sidedata(self, category):
+        if repository.REPO_FEATURE_SIDE_DATA not in self.features:
+            # Only revlogv2 repos can want sidedata.
+            return
         self._wanted_sidedata.add(pycompat.bytestr(category))
 
-    def register_sidedata_computer(self, kind, category, keys, computer):
-        if kind not in (b"changelog", b"manifest", b"filelog"):
+    def register_sidedata_computer(
+        self, kind, category, keys, computer, flags, replace=False
+    ):
+        if kind not in revlogconst.ALL_KINDS:
             msg = _(b"unexpected revlog kind '%s'.")
             raise error.ProgrammingError(msg % kind)
         category = pycompat.bytestr(category)
-        if category in self._sidedata_computers.get(kind, []):
+        already_registered = category in self._sidedata_computers.get(kind, [])
+        if already_registered and not replace:
             msg = _(
                 b"cannot register a sidedata computer twice for category '%s'."
             )
             raise error.ProgrammingError(msg % category)
+        if replace and not already_registered:
+            msg = _(
+                b"cannot replace a sidedata computer that isn't registered "
+                b"for category '%s'."
+            )
+            raise error.ProgrammingError(msg % category)
         self._sidedata_computers.setdefault(kind, {})
-        self._sidedata_computers[kind][category] = (keys, computer)
+        self._sidedata_computers[kind][category] = (keys, computer, flags)
 
 
 # used to avoid circular references so destructors work
@@ -3398,8 +3445,9 @@
             vfs.tryunlink(dest)
             try:
                 vfs.rename(src, dest)
-            except OSError:  # journal file does not yet exist
-                pass
+            except OSError as exc:  # journal file does not yet exist
+                if exc.errno != errno.ENOENT:
+                    raise
 
     return a
 
@@ -3437,6 +3485,24 @@
     return createopts
 
 
+def clone_requirements(ui, createopts, srcrepo):
+    """clone the requirements of a local repo for a local clone
+
+    The store requirements are unchanged while the working copy requirements
+    depends on the configuration
+    """
+    target_requirements = set()
+    createopts = defaultcreateopts(ui, createopts=createopts)
+    for r in newreporequirements(ui, createopts):
+        if r in requirementsmod.WORKING_DIR_REQUIREMENTS:
+            target_requirements.add(r)
+
+    for r in srcrepo.requirements:
+        if r not in requirementsmod.WORKING_DIR_REQUIREMENTS:
+            target_requirements.add(r)
+    return target_requirements
+
+
 def newreporequirements(ui, createopts):
     """Determine the set of requirements for a new local repository.
 
@@ -3507,25 +3573,33 @@
         if ui.configbool(b'format', b'sparse-revlog'):
             requirements.add(requirementsmod.SPARSEREVLOG_REQUIREMENT)
 
-    # experimental config: format.exp-use-side-data
-    if ui.configbool(b'format', b'exp-use-side-data'):
-        requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
-        requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
-        requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
+    # experimental config: format.exp-dirstate-v2
+    # Keep this logic in sync with `has_dirstate_v2()` in `tests/hghave.py`
+    if ui.configbool(b'format', b'exp-dirstate-v2'):
+        if dirstate.SUPPORTS_DIRSTATE_V2:
+            requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
+        else:
+            raise error.Abort(
+                _(
+                    b"dirstate v2 format requested by config "
+                    b"but not supported (requires Rust extensions)"
+                )
+            )
+
     # experimental config: format.exp-use-copies-side-data-changeset
     if ui.configbool(b'format', b'exp-use-copies-side-data-changeset'):
-        requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
-        requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
-        requirements.add(requirementsmod.SIDEDATA_REQUIREMENT)
+        requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
         requirements.add(requirementsmod.COPIESSDC_REQUIREMENT)
     if ui.configbool(b'experimental', b'treemanifest'):
         requirements.add(requirementsmod.TREEMANIFEST_REQUIREMENT)
 
+    changelogv2 = ui.config(b'format', b'exp-use-changelog-v2')
+    if changelogv2 == b'enable-unstable-format-and-corrupt-my-data':
+        requirements.add(requirementsmod.CHANGELOGV2_REQUIREMENT)
+
     revlogv2 = ui.config(b'experimental', b'revlogv2')
     if revlogv2 == b'enable-unstable-format-and-corrupt-my-data':
         requirements.discard(requirementsmod.REVLOGV1_REQUIREMENT)
-        # generaldelta is implied by revlogv2.
-        requirements.discard(requirementsmod.GENERALDELTA_REQUIREMENT)
         requirements.add(requirementsmod.REVLOGV2_REQUIREMENT)
     # experimental config: format.internal-phase
     if ui.configbool(b'format', b'internal-phase'):
@@ -3621,11 +3695,13 @@
     return {k: v for k, v in createopts.items() if k not in known}
 
 
-def createrepository(ui, path, createopts=None):
+def createrepository(ui, path, createopts=None, requirements=None):
     """Create a new repository in a vfs.
 
     ``path`` path to the new repo's working directory.
     ``createopts`` options for the new repository.
+    ``requirement`` predefined set of requirements.
+                    (incompatible with ``createopts``)
 
     The following keys for ``createopts`` are recognized:
 
@@ -3648,27 +3724,34 @@
        Indicates that storage for files should be shallow (not all ancestor
        revisions are known).
     """
-    createopts = defaultcreateopts(ui, createopts=createopts)
-
-    unknownopts = filterknowncreateopts(ui, createopts)
-
-    if not isinstance(unknownopts, dict):
-        raise error.ProgrammingError(
-            b'filterknowncreateopts() did not return a dict'
-        )
-
-    if unknownopts:
-        raise error.Abort(
-            _(
-                b'unable to create repository because of unknown '
-                b'creation option: %s'
+
+    if requirements is not None:
+        if createopts is not None:
+            msg = b'cannot specify both createopts and requirements'
+            raise error.ProgrammingError(msg)
+        createopts = {}
+    else:
+        createopts = defaultcreateopts(ui, createopts=createopts)
+
+        unknownopts = filterknowncreateopts(ui, createopts)
+
+        if not isinstance(unknownopts, dict):
+            raise error.ProgrammingError(
+                b'filterknowncreateopts() did not return a dict'
             )
-            % b', '.join(sorted(unknownopts)),
-            hint=_(b'is a required extension not loaded?'),
-        )
-
-    requirements = newreporequirements(ui, createopts=createopts)
-    requirements -= checkrequirementscompat(ui, requirements)
+
+        if unknownopts:
+            raise error.Abort(
+                _(
+                    b'unable to create repository because of unknown '
+                    b'creation option: %s'
+                )
+                % b', '.join(sorted(unknownopts)),
+                hint=_(b'is a required extension not loaded?'),
+            )
+
+        requirements = newreporequirements(ui, createopts=createopts)
+        requirements -= checkrequirementscompat(ui, requirements)
 
     wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)