changeset 52970:42f78c859dd1

branching: merge with stable
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 18 Feb 2025 22:49:43 +0100
parents 6a8a1792aab7 (current diff) e16065bb7f42 (diff)
children 469b9a628b51
files mercurial/bundlecaches.py mercurial/dirstatemap.py mercurial/extensions.py mercurial/upgrade_utils/actions.py rust/hg-core/src/sparse.rs rust/rhg/src/error.rs tests/test-stream-bundle-v2.t
diffstat 11 files changed, 136 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/bundlecaches.py	Tue Feb 18 16:57:50 2025 +0100
+++ b/mercurial/bundlecaches.py	Tue Feb 18 22:49:43 2025 +0100
@@ -317,10 +317,10 @@
             % compression
         )
 
-    # The specification for packed1 can optionally declare the data formats
+    # The specification for stream bundles can optionally declare the data formats
     # required to apply it. If we see this metadata, compare against what the
     # repo supports and error if the bundle isn't compatible.
-    if version == b'packed1' and b'requirements' in params:
+    if b'requirements' in params:
         requirements = set(cast(bytes, params[b'requirements']).split(b','))
         missingreqs = requirements - requirementsmod.STREAM_FIXED_REQUIREMENTS
         if missingreqs:
--- a/mercurial/dirstatemap.py	Tue Feb 18 16:57:50 2025 +0100
+++ b/mercurial/dirstatemap.py	Tue Feb 18 22:49:43 2025 +0100
@@ -197,6 +197,7 @@
                 )
             else:
                 raise error.CorruptedDirstate(b"dirstate is not in v2 format")
+            testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
         return self._docket
 
     def _read_v2_data(self):
@@ -673,7 +674,6 @@
             Fills the Dirstatemap when called.
             """
             # ignore HG_PENDING because identity is used only for writing
-            self._set_identity()
 
             testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
             if self._use_dirstate_v2:
@@ -713,7 +713,6 @@
             return self._map
 
         def _get_rust_identity(self):
-            self._set_identity()
             identity = None
             if self.identity is not None and self.identity.stat is not None:
                 stat_info = self.identity.stat
@@ -733,8 +732,9 @@
             return identity
 
         def _v1_map(self, from_v2_exception=None):
-            identity = self._get_rust_identity()
             try:
+                self._set_identity()
+                identity = self._get_rust_identity()
                 self._map, parents = rustmod.DirstateMap.new_v1(
                     self._readdirstatefile(), identity
                 )
--- a/mercurial/extensions.py	Tue Feb 18 16:57:50 2025 +0100
+++ b/mercurial/extensions.py	Tue Feb 18 22:49:43 2025 +0100
@@ -620,11 +620,13 @@
 class wrappedfunction:
     '''context manager for temporarily wrapping a function'''
 
-    def __init__(self, container, funcname, wrapper):
+    def __init__(self, container, funcname: str, wrapper):
         assert callable(wrapper)
         if not isinstance(funcname, str):
-            msg = b"wrappedfunction target name should be `str`, not `bytes`"
-            raise TypeError(msg)
+            # Keep this compat shim around for older/unmaintained extensions
+            msg = b"pass wrappedfunction target name as `str`, not `bytes`"
+            util.nouideprecwarn(msg, b"6.6", stacklevel=2)
+            funcname = pycompat.sysstr(funcname)
         self._container = container
         self._funcname = funcname
         self._wrapper = wrapper
@@ -636,7 +638,7 @@
         unwrapfunction(self._container, self._funcname, self._wrapper)
 
 
-def wrapfunction(container, funcname, wrapper):
+def wrapfunction(container, funcname: str, wrapper):
     """Wrap the function named funcname in container
 
     Replace the funcname member in the given container with the specified
@@ -672,8 +674,10 @@
     assert callable(wrapper)
 
     if not isinstance(funcname, str):
-        msg = b"wrapfunction target name should be `str`, not `bytes`"
-        raise TypeError(msg)
+        # Keep this compat shim around for older/unmaintained extensions
+        msg = b"pass wrapfunction target name as `str`, not `bytes`"
+        util.nouideprecwarn(msg, b"6.6", stacklevel=2)
+        funcname = pycompat.sysstr(funcname)
 
     origfn = getattr(container, funcname)
     assert callable(origfn)
--- a/mercurial/narrowspec.py	Tue Feb 18 16:57:50 2025 +0100
+++ b/mercurial/narrowspec.py	Tue Feb 18 22:49:43 2025 +0100
@@ -68,12 +68,27 @@
     if _numlines(pat) > 1:
         raise error.Abort(_(b'newlines are not allowed in narrowspec paths'))
 
+    # patterns are stripped on load (see sparse.parseconfig),
+    # so a pattern ending in whitespace doesn't work correctly
+    if pat.strip() != pat:
+        raise error.Abort(
+            _(
+                b'leading or trailing whitespace is not allowed '
+                b'in narrowspec paths'
+            )
+        )
+
     components = pat.split(b'/')
     if b'.' in components or b'..' in components:
         raise error.Abort(
             _(b'"." and ".." are not allowed in narrowspec paths')
         )
 
+    if pat != b'' and b'' in components:
+        raise error.Abort(
+            _(b'empty path components are not allowed in narrowspec paths')
+        )
+
 
 def normalizepattern(pattern, defaultkind=b'path'):
     """Returns the normalized version of a text-format pattern.
--- a/mercurial/upgrade_utils/actions.py	Tue Feb 18 16:57:50 2025 +0100
+++ b/mercurial/upgrade_utils/actions.py	Tue Feb 18 22:49:43 2025 +0100
@@ -929,7 +929,7 @@
 
     def has_upgrade_action(self, name):
         """Check whether the upgrade operation will perform this action"""
-        return name in self._upgrade_actions_names
+        return name in self.upgrade_actions_names
 
     def print_post_op_messages(self):
         """print post upgrade operation warning messages"""
--- a/rust/hg-core/src/narrow.rs	Tue Feb 18 16:57:50 2025 +0100
+++ b/rust/hg-core/src/narrow.rs	Tue Feb 18 22:49:43 2025 +0100
@@ -97,19 +97,38 @@
     Ok((m, warnings))
 }
 
+fn is_whitespace(b: &u8) -> bool {
+    // should match what .strip() in Python does
+    b.is_ascii_whitespace() || *b == 0x0b
+}
+
+fn starts_or_ends_with_whitespace(s: &[u8]) -> bool {
+    let w = |b: Option<&u8>| b.map(is_whitespace).unwrap_or(false);
+    w(s.first()) || w(s.last())
+}
+
+fn validate_pattern(pattern: &[u8]) -> Result<(), SparseConfigError> {
+    if starts_or_ends_with_whitespace(pattern) {
+        return Err(SparseConfigError::WhitespaceAtEdgeOfPattern(
+            pattern.to_owned(),
+        ));
+    }
+    for prefix in VALID_PREFIXES.iter() {
+        if pattern.starts_with(prefix.as_bytes()) {
+            return Ok(());
+        }
+    }
+    Err(SparseConfigError::InvalidNarrowPrefix(pattern.to_owned()))
+}
+
 fn validate_patterns(patterns: &[u8]) -> Result<(), SparseConfigError> {
     for pattern in patterns.split(|c| *c == b'\n') {
         if pattern.is_empty() {
+            // TODO: probably not intentionally allowed (only because `split`
+            // produces "fake" empty line at the end)
             continue;
         }
-        for prefix in VALID_PREFIXES.iter() {
-            if pattern.starts_with(prefix.as_bytes()) {
-                return Ok(());
-            }
-        }
-        return Err(SparseConfigError::InvalidNarrowPrefix(
-            pattern.to_owned(),
-        ));
+        validate_pattern(pattern)?
     }
     Ok(())
 }
--- a/rust/hg-core/src/sparse.rs	Tue Feb 18 16:57:50 2025 +0100
+++ b/rust/hg-core/src/sparse.rs	Tue Feb 18 22:49:43 2025 +0100
@@ -89,6 +89,10 @@
     /// An invalid pattern prefix was given to the narrow spec. Includes the
     /// entire pattern for context.
     InvalidNarrowPrefix(Vec<u8>),
+    /// Narrow/sparse patterns can not begin or end in whitespace
+    /// because the Python parser strips the whitespace when parsing
+    /// the config file.
+    WhitespaceAtEdgeOfPattern(Vec<u8>),
     #[from]
     HgError(HgError),
     #[from]
@@ -138,6 +142,20 @@
                     VALID_PREFIXES.join(", ")
                 )),
             },
+            SparseConfigError::WhitespaceAtEdgeOfPattern(vec) => {
+                HgError::Abort {
+                    message: String::from_utf8_lossy(&format_bytes!(
+                        b"narrow pattern with whitespace at the edge: {}",
+                        vec
+                    ))
+                    .to_string(),
+                    detailed_exit_code: STATE_ERROR,
+                    hint: Some(
+                        "narrow patterns can't begin or end in whitespace"
+                            .to_string(),
+                    ),
+                }
+            }
             SparseConfigError::HgError(hg_error) => hg_error,
             SparseConfigError::PatternError(pattern_error) => HgError::Abort {
                 message: pattern_error.to_string(),
--- a/rust/rhg/src/error.rs	Tue Feb 18 16:57:50 2025 +0100
+++ b/rust/rhg/src/error.rs	Tue Feb 18 22:49:43 2025 +0100
@@ -285,6 +285,15 @@
                     exit_codes::ABORT,
                 )
             }
+            SparseConfigError::WhitespaceAtEdgeOfPattern(prefix) => {
+                Self::abort_with_exit_code_bytes(
+                    format_bytes!(
+                        b"narrow pattern with whitespace at the edge: {}",
+                        &prefix
+                    ),
+                    exit_codes::ABORT,
+                )
+            }
             SparseConfigError::IncludesInNarrow => Self::abort(
                 "including other spec files using '%include' \
                     is not supported in narrowspec",
--- a/tests/test-dirstate-read-race.t	Tue Feb 18 16:57:50 2025 +0100
+++ b/tests/test-dirstate-read-race.t	Tue Feb 18 22:49:43 2025 +0100
@@ -231,20 +231,16 @@
 
 The status process should return a consistent result and not crash.
 
-#if dirstate-v1
+(The "pre-commit" state is only visible to (any) rust variant because the pure
+python implementation always rewrites, so we are never really in the "-append"
+case).
+
   $ cat $TESTTMP/status-race-lock.out
+  A dir/o (dirstate-v2-append pre-some-read rust !)
+  R dir/nested/m (dirstate-v2-append pre-some-read rust !)
   ? dir/n
   ? p
   ? q
-#endif
-#if dirstate-v2
-  $ cat $TESTTMP/status-race-lock.out
-  A dir/o
-  R dir/nested/m
-  ? dir/n
-  ? p
-  ? q
-#endif
 
 final cleanup
 
@@ -277,10 +273,19 @@
   |
   o  4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
   
+(double check the working copy location before and after the update+concurrent status)
+  $ hg log -T '{node|short}\n' --rev "."
+  9a86dcbfb938
+(update destination)
+  $ hg log -T '{node|short}\n' --rev ".~1"
+  4f23db756b09
   $ hg $d2args update --merge ".~1"
   0 files updated, 0 files merged, 6 files removed, 0 files unresolved
   $ touch $TESTTMP/status-race-lock
   $ wait
+(the working copy should have been updated)
+  $ hg log -T '{node|short}\n' --rev "."
+  4f23db756b09
   $ hg log -GT '{node|short} {desc}\n'
   o  9a86dcbfb938 more files to have two commit
   |
--- a/tests/test-narrow.t	Tue Feb 18 16:57:50 2025 +0100
+++ b/tests/test-narrow.t	Tue Feb 18 22:49:43 2025 +0100
@@ -59,6 +59,12 @@
   $ hg clone --narrow ssh://user@dummy/master foo --include a/./c
   abort: "." and ".." are not allowed in narrowspec paths
   [255]
+  $ hg clone --narrow ssh://user@dummy/master foo --include ' '
+  abort: leading or trailing whitespace is not allowed in narrowspec paths
+  [255]
+  $ hg clone --narrow ssh://user@dummy/master foo --include 'a//c'
+  abort: empty path components are not allowed in narrowspec paths
+  [255]
 
 Names with '.' in them are OK.
   $ hg clone --narrow ./master should-work --include a/.b/c
--- a/tests/test-stream-bundle-v2.t	Tue Feb 18 16:57:50 2025 +0100
+++ b/tests/test-stream-bundle-v2.t	Tue Feb 18 22:49:43 2025 +0100
@@ -105,20 +105,44 @@
   none-v2;stream=v3-exp;requirements%3Dgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog (stream-v3 zstd no-rust !)
   none-v2;stream=v3-exp;requirements%3Dgeneraldelta%2Crevlog-compression-zstd%2Crevlogv1%2Csparserevlog (stream-v3 rust !)
 
-Test that we can apply the bundle as a stream clone bundle
-
-  $ cat > .hg/clonebundles.manifest << EOF
-  > http://localhost:$HGPORT1/bundle.hg BUNDLESPEC=`hg debugbundle --spec bundle.hg`
-  > EOF
-
   $ hg serve -d -p $HGPORT --pid-file hg.pid --accesslog access.log
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ "$PYTHON" $TESTDIR/dumbhttp.py -p $HGPORT1 --pid http.pid
   $ cat http.pid >> $DAEMON_PIDS
 
+Stream bundle spec with unknown requirements should be filtered out
+
+#if stream-v2
+  $ cat > .hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/bundle.hg BUNDLESPEC=none-v2;stream=v2;requirements%3Drevlogv42
+  > EOF
+#endif
+#if stream-v3
+  $ cat > .hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/bundle.hg BUNDLESPEC=none-v2;stream=v3-exp;requirements%3Drevlogv42
+  > EOF
+#endif
+
   $ cd ..
 
+  $ hg clone -U http://localhost:$HGPORT stream-clone-unsupported-requirements
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 5 files
+  new changesets 426bada5c675:9bc730a19041 (5 drafts)
+
+Test that we can apply the bundle as a stream clone bundle
+
+  $ cat > main/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/bundle.hg BUNDLESPEC=`hg debugbundle --spec main/bundle.hg`
+  > EOF
+
+
 #if stream-v2
   $ hg clone http://localhost:$HGPORT stream-clone-implicit --debug
   using http://localhost:$HGPORT/