Mercurial > public > mercurial-scm > hg-stable
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/