--- 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/