D12317: dirstate-tree: optimize HashMap lookups with raw_entry_mut
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This switches to using `HashMap` from the hashbrown crate, in order to use its `raw_entry_mut` method. The standard library’s `HashMap` is also based on this same crate, but `raw_entry_mut` is not yet stable there: https://github.com/rust-lang/rust/issues/56167 Using version 0.9 because 0.10 is yanked and 0.11 requires Rust 1.49 This replaces in `DirstateMap::get_or_insert_node` a call to `HashMap::entry` with `K = WithBasename>`. `entry` takes and consumes an "owned" `key: K` parameter, in case a new entry ends up inserted. This key is converted by `to_cow` from a value that borrows the `'path` lifetime. When this function is called by `Dirstate::new_v1`, `'path` is in fact the same as `'on_disk` so `to_cow` can return an owned key that contains `Cow::Borrowed`. For other callers, `to_cow` needs to create a `Cow::Owned` and thus make a costly heap memory allocation. This is wasteful if this key was already present in the map. Even when inserting a new node this is typically the case for its ancestor nodes (assuming most directories have numerous descendants). REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12317 AFFECTED FILES rust/Cargo.lock rust/hg-core/Cargo.toml rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/lib.rs CHANGE DETAILS diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -56,6 +56,11 @@ /// write access to your repository, you have other issues. pub type FastHashMap = HashMap; +// TODO: should this be the default `FastHashMap` for all of hg-core, not just +// dirstate_tree? How does XxHash compare with AHash, hashbrown’s default? +pub type FastHashbrownMap = +hashbrown::HashMap; + #[derive(Debug, PartialEq)] pub enum DirstateMapError { PathNotFound(HgPathBuf), diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -22,7 +22,7 @@ use crate::DirstateParents; use crate::DirstateStatus; use crate::EntryState; -use crate::FastHashMap; +use crate::FastHashbrownMap as FastHashMap; use crate::PatternFileWarning; use crate::StatusError; use crate::StatusOptions; @@ -585,13 +585,11 @@ .next() .expect("expected at least one inclusive ancestor"); loop { -// TODO: can we avoid allocating an owned key in cases where the -// map already contains that key, without introducing double -// lookup? -let child_node = child_nodes +let (_, child_node) = child_nodes .make_mut(on_disk, unreachable_bytes)? -.entry(to_cow(ancestor_path)) -.or_default(); +.raw_entry_mut() +.from_key(ancestor_path.base_name()) +.or_insert_with(|| (to_cow(ancestor_path), Node::default())); if let Some(next) = inclusive_ancestor_paths.next() { each_ancestor(child_node); ancestor_path = next; diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml +++ b/rust/hg-core/Cargo.toml @@ -13,6 +13,7 @@ bytes-cast = "0.2" byteorder = "1.3.4" derive_more = "0.99" +hashbrown = {version = "0.9.1", features = ["rayon"]} home = "0.5" im-rc = "15.0.*" itertools = "0.9" diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -9,6 +9,12 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index; +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] name = "aho-corasick" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index; @@ -372,6 +378,16 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index; +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", + "rayon", +] + +[[package]] name = "hermit-abi" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index; @@ -398,6 +414,7 @@ "derive_more", "flate2", "format-
D12316: rust: enable Python 3 support unconditionally
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Note: `cpython/python3-sys` is a default feature. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12316 AFFECTED FILES Makefile rust/README.rst rust/hg-cpython/Cargo.toml setup.py CHANGE DETAILS diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -1380,15 +1380,9 @@ cargocmd = ['cargo', 'rustc', '--release'] -feature_flags = ['python3'] - -cargocmd.append('--no-default-features') - rust_features = env.get("HG_RUST_FEATURES") if rust_features: -feature_flags.append(rust_features) - -cargocmd.extend(('--features', " ".join(feature_flags))) +cargocmd.extend(('--features', rust_features)) cargocmd.append('--') if sys.platform == 'darwin': diff --git a/rust/hg-cpython/Cargo.toml b/rust/hg-cpython/Cargo.toml --- a/rust/hg-cpython/Cargo.toml +++ b/rust/hg-cpython/Cargo.toml @@ -8,18 +8,8 @@ name='rusthg' crate-type = ["cdylib"] -[features] -default = ["python3"] - -# Features to build an extension module: -python3 = ["cpython/python3-sys", "cpython/extension-module"] - -# Enable this feature to build a test executable linked to libpython: -# e.g. cargo test --no-default-features --features python3-bin -python3-bin = ["cpython/python3-sys"] - [dependencies] -cpython = { version = "0.7.0", default-features = false } +cpython = { version = "0.7.0", features = ["extension-module"] } crossbeam-channel = "0.4" hg-core = { path = "../hg-core"} libc = "0.2" diff --git a/rust/README.rst b/rust/README.rst --- a/rust/README.rst +++ b/rust/README.rst @@ -40,8 +40,8 @@ Special features -You might want to check the `features` section in ``hg-cpython/Cargo.toml``. -It may contain features that might be interesting to try out. +In the future, compile-time opt-ins may be added +to the `features` section in ``hg-cpython/Cargo.toml``. To use features from the Makefile, use the `HG_RUST_FEATURES` environment variable: for instance `HG_RUST_FEATURES="some-feature other-feature"` diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -151,12 +151,9 @@ $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python ) cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS) -rust-tests: py_feature = $(shell $(PYTHON) -c \ - 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])') rust-tests: cd $(HGROOT)/rust/hg-cpython \ - && $(CARGO) test --quiet --all \ - --no-default-features --features "$(py_feature) $(HG_RUST_FEATURES)" + && $(CARGO) test --quiet --all --features "$(HG_RUST_FEATURES)" check-code: hg manifest | xargs python contrib/check-code.py To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12175: status: prefer relative paths in Rust code
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY … when the repository root is under the current directory, so the kernel needs to traverse fewer directory in every call to `read_dir` or `symlink_metadata`. Better yet would be to use libc functions like `openat` and `fstatat` to remove such repeated traversals entirely, but the standard library does not provide APIs based on those. Maybe with a crate like https://crates.io/crates/openat instead? Benchmarks of `rhg status` show that this patch is neutral in some configurations, and makes the command up to ~20% faster in others. Below is semi-arbitrary subset of results. The four numeric columns are: time (in seconds) with this changeset’s parent, time with this changeset, time difference (negative is better), time ratio (less than 1 is better). mercurial-dirstate-v1 | default-plain-clean.no-iu.pbr| 0.0061 -> 0.0059: -0.0002 (0.97) mercurial-dirstate-v2 | default-plain-clean.no-iu.pbr| 0.0029 -> 0.0028: -0.0001 (0.97) mozilla-dirstate-v1 | default-plain-clean.no-iu.pbr| 0.2110 -> 0.2102: -0.0007 (1.00) mozilla-dirstate-v2 | default-copies-clean.ignored.pbr | 0.0489 -> 0.0401: -0.0088 (0.82) mozilla-dirstate-v2 | default-copies-clean.no-iu.pbr | 0.0479 -> 0.0393: -0.0085 (0.82) mozilla-dirstate-v2 | default-copies-large.all.pbr | 0.1262 -> 0.1210: -0.0051 (0.96) mozilla-dirstate-v2 | default-copies-small.ignored-unknown.pbr | 0.1262 -> 0.1200: -0.0062 (0.95) mozilla-dirstate-v2 | default-copies-small.ignored.pbr | 0.0536 -> 0.0417: -0.0119 (0.78) mozilla-dirstate-v2 | default-copies-small.no-iu.pbr | 0.0482 -> 0.0393: -0.0089 (0.81) mozilla-dirstate-v2 | default-plain-clean.ignored.pbr | 0.0518 -> 0.0402: -0.0116 (0.78) mozilla-dirstate-v2 | default-plain-clean.no-iu.pbr| 0.0481 -> 0.0392: -0.0088 (0.82) mozilla-dirstate-v2 | default-plain-large.all.pbr | 0.1271 -> 0.1218: -0.0052 (0.96) mozilla-dirstate-v2 | default-plain-small.ignored-unknown.pbr | 0.1225 -> 0.1202: -0.0022 (0.98) mozilla-dirstate-v2 | default-plain-small.ignored.pbr | 0.0510 -> 0.0418: -0.0092 (0.82) mozilla-dirstate-v2 | default-plain-small.no-iu.pbr| 0.0480 -> 0.0394: -0.0086 (0.82) netbeans-dirstate-v1 | default-plain-clean.no-iu.pbr| 0.1442 -> 0.1422: -0.0020 (0.99) netbeans-dirstate-v2 | default-plain-clean.no-iu.pbr| 0.0325 -> 0.0282: -0.0043 (0.87) REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12175 AFFECTED FILES rust/hg-core/src/dirstate_tree/status.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -65,6 +65,26 @@ let filesystem_time_at_status_start = filesystem_now(_dir).ok().map(TruncatedTimestamp::from); + +// If the repository is under the current directory, prefer using a +// relative path, so the kernel needs to traverse fewer directory in every +// call to `read_dir` or `symlink_metadata`. +// This is effective in the common case where the current directory is the +// repository root. + +// TODO: Better yet would be to use libc functions like `openat` and +// `fstatat` to remove such repeated traversals entirely, but the standard +// library does not provide APIs based on those. +// Maybe with a crate like https://crates.io/crates/openat instead? +let root_dir = if let Some(relative) = std::env::current_dir() +.ok() +.and_then(|cwd| root_dir.strip_prefix(cwd).ok()) +{ +relative +} else { +_dir +}; + let outcome = DirstateStatus { filesystem_time_at_status_start, ..Default::default() @@ -752,13 +772,16 @@ /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty /// list instead. fn read_dir(path: , is_at_repo_root: bool) -> io::Result> { +// `read_dir` returns a "not found" error for the empty path +let at_cwd = path == Path::new(""); +let read_dir_path = if at_cwd { Path::new(".") } else { path }; let mut results = Vec::new(); -for entry in path.read_dir()? { +for entry in read_dir_path.read_dir()? { let entry = entry?; let metadata = entry.metadata()?; -let name = get_bytes_from_os_string(entry.file_name()); +let file_name = entry.file_name(); // FIXME don't do this when cached -if na
D12174: rust: remove unused `StatusError::IO` enum variant
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY All `io::Error` cases are now handled through PatternFileWarning or BadMatch REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12174 AFFECTED FILES rust/hg-core/src/dirstate/status.rs rust/hg-cpython/src/dirstate/status.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/status.rs b/rust/hg-cpython/src/dirstate/status.rs --- a/rust/hg-cpython/src/dirstate/status.rs +++ b/rust/hg-cpython/src/dirstate/status.rs @@ -10,7 +10,6 @@ //! `rustext.dirstate.status`. use crate::{dirstate::DirstateMap, exceptions::FallbackError}; -use cpython::exc::OSError; use cpython::{ exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject, PyResult, PyTuple, Python, PythonObject, ToPyObject, @@ -95,7 +94,6 @@ PyErr::new::(py, _string) } -StatusError::IO(e) => PyErr::new::(py, e.to_string()), e => PyErr::new::(py, e.to_string()), } } diff --git a/rust/hg-core/src/dirstate/status.rs b/rust/hg-core/src/dirstate/status.rs --- a/rust/hg-core/src/dirstate/status.rs +++ b/rust/hg-core/src/dirstate/status.rs @@ -128,8 +128,6 @@ #[derive(Debug, derive_more::From)] pub enum StatusError { -/// Generic IO error -IO(std::io::Error), /// An invalid path that cannot be represented in Mercurial was found Path(HgPathError), /// An invalid "ignore" pattern was found @@ -143,7 +141,6 @@ impl fmt::Display for StatusError { fn fmt(, f: fmt::Formatter) -> fmt::Result { match self { -StatusError::IO(error) => error.fmt(f), StatusError::Path(error) => error.fmt(f), StatusError::Pattern(error) => error.fmt(f), StatusError::DirstateV2ParseError(_) => { To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12173: rust: fix code formatting
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12173 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -443,7 +443,8 @@ mut paths: Vec>, ) -> Result<(), CommandError> { paths.sort_unstable(); -// TODO: get the stdout lock once for the whole loop instead of in each write +// TODO: get the stdout lock once for the whole loop +// instead of in each write for StatusPath { path, copy_source } in paths { let relative; let path = if let Some(relativize) = { To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12167: [WIP] rhg: Add support for colored output
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The same "label" system is used as in Python code REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12167 AFFECTED FILES rust/Cargo.lock rust/hg-core/src/config.rs rust/hg-core/src/config/config.rs rust/hg-core/src/config/layer.rs rust/rhg/Cargo.toml rust/rhg/src/ui.rs CHANGE DETAILS diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -1,16 +1,19 @@ use format_bytes::format_bytes; +use format_bytes::write_bytes; use hg::config::Config; +use hg::config::ConfigOrigin; use hg::errors::HgError; use hg::utils::files::get_bytes_from_os_string; use std::borrow::Cow; +use std::collections::HashMap; use std::env; use std::io; use std::io::{ErrorKind, Write}; -#[derive(Debug)] pub struct Ui { stdout: std::io::Stdout, stderr: std::io::Stderr, +colors: Option, } /// The kind of user interface error @@ -23,20 +26,26 @@ /// The commandline user interface impl Ui { -pub fn new(_config: ) -> Result { +pub fn new(config: ) -> Result { Ok(Ui { +// If using something else, also adapt `isatty()` below. stdout: std::io::stdout(), + stderr: std::io::stderr(), +colors: ColorConfig::new(config)?, }) } /// Default to no color if color configuration errors. /// /// Useful when we’re already handling another error. -pub fn new_infallible(_config: ) -> Self { +pub fn new_infallible(config: ) -> Self { Ui { +// If using something else, also adapt `isatty()` below. stdout: std::io::stdout(), + stderr: std::io::stderr(), +colors: ColorConfig::new(config).unwrap_or(None), } } @@ -48,6 +57,11 @@ /// Write bytes to stdout pub fn write_stdout(, bytes: &[u8]) -> Result<(), UiError> { +// Hack to silence "unused" warnings +if false { +return self.write_stdout_labelled(bytes, ""); +} + let mut stdout = self.stdout.lock(); stdout.write_all(bytes).or_else(handle_stdout_error)?; @@ -64,6 +78,25 @@ stderr.flush().or_else(handle_stderr_error) } +pub fn write_stdout_labelled( +, +bytes: &[u8], +label: , +) -> Result<(), UiError> { +if let Some(colors) = { +let mut stdout = self.stdout.lock(); +(|| { +colors.write_start(label, stdout)?; +stdout.write_all(bytes)?; +colors.write_end(label, stdout)?; +stdout.flush() +})() +.or_else(handle_stdout_error) +} else { +self.write_stdout(bytes) +} +} + /// Return whether plain mode is active. /// /// Plain mode means that all configuration variables which affect @@ -154,3 +187,279 @@ let bytes = s.as_bytes(); Cow::Borrowed(bytes) } + +struct ColorConfig { +styles: EffectsMap, +} + +impl ColorConfig { +// Similar to _modesetup in mercurial/color.py +fn new(config: ) -> Result, HgError> { +Ok(match ColorMode::get(config)? { +None => None, +Some(ColorMode::Ansi) => { +let mut styles = default_styles(); +for (key, _value) in config.iter_section(b"color") { +if !key.contains('.') +|| key.starts_with(b"color.") +|| key.starts_with(b"terminfo.") +{ +continue; +} +// `unwrap` shouldn’t panic since we just got this key from +// iteration +styles.insert( +key.to_owned(), +config +.get_list(b"color", key) +.unwrap() +.iter() +// TODO: warn for unknown effect/color names +// (when `effect` returns `None`) +.filter_map(|name| effect(name)) +.collect(), +); +} +Some(ColorConfig { styles }) +} +}) +} + +fn write_start( +, +label: , +stream: impl Write, +) -> io::Result<()> { +if let Some(effects) = self.styles.get(label.as_bytes()) { +let mut iter = effects.iter(); +if let Some(first) = iter.next() { +write_bytes!(stream, b"\0o33[{}", first)?; +
D12168: [WIP] rhg: Colorize `rhg status` output when appropriate
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12168 AFFECTED FILES rust/rhg/src/commands/status.rs rust/rhg/src/main.rs rust/rhg/src/ui.rs CHANGE DETAILS diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -57,11 +57,6 @@ /// Write bytes to stdout pub fn write_stdout(, bytes: &[u8]) -> Result<(), UiError> { -// Hack to silence "unused" warnings -if false { -return self.write_stdout_labelled(bytes, ""); -} - let mut stdout = self.stdout.lock(); stdout.write_all(bytes).or_else(handle_stdout_error)?; @@ -233,7 +228,7 @@ if let Some(effects) = self.styles.get(label.as_bytes()) { let mut iter = effects.iter(); if let Some(first) = iter.next() { -write_bytes!(stream, b"\0o33[{}", first)?; +write_bytes!(stream, b"{}[{}", ASCII_ESCAPE, first)?; for next in iter { write_bytes!(stream, b";{}", next)?; } @@ -250,13 +245,16 @@ ) -> io::Result<()> { if let Some(effects) = self.styles.get(label.as_bytes()) { if !effects.is_empty() { -write_bytes!(stream, b"\0o33[0m")?; +write_bytes!(stream, b"{}[0m", ASCII_ESCAPE)?; } } Ok(()) } } +/// 0x1B == 27 == 0o33 +const ASCII_ESCAPE: &[u8] = b"\x1b"; + enum ColorMode { // TODO: support other modes Ansi, diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -29,7 +29,7 @@ repo: Result<, >, config: , ) -> Result<(), CommandError> { -check_unsupported(config, repo, ui)?; +check_unsupported(config, repo)?; let app = App::new("rhg") .global_setting(AppSettings::AllowInvalidUtf8) @@ -678,7 +678,6 @@ fn check_unsupported( config: , repo: Result<, >, -ui: ::Ui, ) -> Result<(), CommandError> { check_extensions(config)?; @@ -702,13 +701,5 @@ Err(CommandError::unsupported("[decode] config"))? } -if let Some(color) = config.get(b"ui", b"color") { -if (color == b"always" || color == b"debug") -&& !ui.plain(Some("color")) -{ -Err(CommandError::unsupported("colored output"))? -} -} - Ok(()) } diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -326,25 +326,25 @@ }, }; if display_states.modified { -output.display(b"M", ds_status.modified)?; +output.display(b"M", "status.modified", ds_status.modified)?; } if display_states.added { -output.display(b"A", ds_status.added)?; +output.display(b"A", "status.added", ds_status.added)?; } if display_states.removed { -output.display(b"R", ds_status.removed)?; +output.display(b"R", "status.removed", ds_status.removed)?; } if display_states.deleted { -output.display(b"!", ds_status.deleted)?; +output.display(b"!", "status.deleted", ds_status.deleted)?; } if display_states.unknown { -output.display(b"?", ds_status.unknown)?; +output.display(b"?", "status.unknown", ds_status.unknown)?; } if display_states.ignored { -output.display(b"I", ds_status.ignored)?; +output.display(b"I", "status.ignored", ds_status.ignored)?; } if display_states.clean { -output.display(b"C", ds_status.clean)?; +output.display(b"C", "status.clean", ds_status.clean)?; } let mut dirstate_write_needed = ds_status.dirty; @@ -448,6 +448,7 @@ fn display( , status_prefix: &[u8], +label: &'static str, mut paths: Vec>, ) -> Result<(), CommandError> { paths.sort_unstable(); @@ -459,22 +460,26 @@ } else { path.as_bytes() }; -// TODO optim, probably lots of unneeded copies here, especially -// if out stream is buffered +// TODO: Add a way to use `write_bytes!` instead of `format_bytes!` +// in order to stream to stdout instead of allocating an +// itermediate `Vec`. i
D12166: rhg: Add parsing for the --color global CLI argument
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12166 AFFECTED FILES rust/hg-core/src/config/config.rs rust/hg-core/src/config/layer.rs rust/rhg/src/main.rs CHANGE DETAILS diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -66,6 +66,14 @@ .takes_value(true) .global(true), ) +.arg( +Arg::with_name("color") +.help("when to colorize (boolean, always, auto, never, or debug)") +.long("--color") +.value_name("TYPE") +.takes_value(true) +.global(true), +) .version("0.0.1"); let app = add_subcommand_args(app); @@ -179,7 +187,7 @@ }); non_repo_config -.load_cli_args_config(early_args.config) +.load_cli_args(early_args.config, early_args.color) .unwrap_or_else(|error| { exit( _current_dir, @@ -526,6 +534,8 @@ struct EarlyArgs { /// Values of all `--config` arguments. (Possibly none) config: Vec>, +/// Value of all the `--color` argument, if any. +color: Option>, /// Value of the `-R` or `--repository` argument, if any. repo: Option>, /// Value of the `--cwd` argument, if any. @@ -536,6 +546,7 @@ fn parse(args: impl IntoIterator) -> Self { let mut args = args.into_iter().map(get_bytes_from_os_str); let mut config = Vec::new(); +let mut color = None; let mut repo = None; let mut cwd = None; // Use `while let` instead of `for` so that we can also call @@ -549,6 +560,14 @@ config.push(value.to_owned()) } +if arg == b"--color" { +if let Some(value) = args.next() { +color = Some(value) +} +} else if let Some(value) = arg.drop_prefix(b"--color=") { +color = Some(value.to_owned()) +} + if arg == b"--cwd" { if let Some(value) = args.next() { cwd = Some(value) @@ -567,7 +586,12 @@ repo = Some(value.to_owned()) } } -Self { config, repo, cwd } +Self { +config, +color, +repo, +cwd, +} } } diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -301,6 +301,8 @@ File(PathBuf), /// From a `--config` CLI argument CommandLine, +/// From a `--color` CLI argument +CommandLineColor, /// From environment variables like `$PAGER` or `$EDITOR` Environment(Vec), /* TODO cli @@ -318,6 +320,7 @@ match self { ConfigOrigin::File(p) => out.write_all(_bytes_from_path(p)), ConfigOrigin::CommandLine => out.write_all(b"--config"), +ConfigOrigin::CommandLineColor => out.write_all(b"--color"), ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e), } } diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -139,13 +139,19 @@ Ok(config) } -pub fn load_cli_args_config( +pub fn load_cli_args( self, cli_config_args: impl IntoIterator>, +color_arg: Option>, ) -> Result<(), ConfigError> { if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? { self.layers.push(layer) } +if let Some(arg) = color_arg { +let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor); +layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None); +self.layers.push(layer) +} Ok(()) } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12164: rhg: Pass a to Ui::new
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY When a Ui object is needed to print errors about configuration-loading errors, an empty (default) configuration is used. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12164 AFFECTED FILES rust/hg-core/src/config/config.rs rust/rhg/src/main.rs rust/rhg/src/ui.rs CHANGE DETAILS diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -1,4 +1,5 @@ use format_bytes::format_bytes; +use hg::config::Config; use hg::utils::files::get_bytes_from_os_string; use std::borrow::Cow; use std::env; @@ -21,7 +22,7 @@ /// The commandline user interface impl Ui { -pub fn new() -> Self { +pub fn new(_config: ) -> Self { Ui { stdout: std::io::stdout(), stderr: std::io::stderr(), diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -137,7 +137,6 @@ let process_start_time = blackbox::ProcessStartTime::now(); env_logger::init(); -let ui = ui::Ui::new(); let early_args = EarlyArgs::parse(std::env::args_os()); @@ -151,7 +150,7 @@ .unwrap_or_else(|error| { exit( , -, +::new(::empty()), OnUnsupported::Abort, Err(CommandError::abort(format!( "abort: {}: '{}'", @@ -172,7 +171,7 @@ exit( _current_dir, -, +::new(::empty()), on_unsupported, Err(error.into()), false, @@ -184,7 +183,7 @@ .unwrap_or_else(|error| { exit( _current_dir, -, +::new(_repo_config), OnUnsupported::from_config(_repo_config), Err(error.into()), non_repo_config @@ -202,7 +201,7 @@ if SCHEME_RE.is_match(_path_bytes) { exit( _current_dir, -, +::new(_repo_config), OnUnsupported::from_config(_repo_config), Err(CommandError::UnsupportedFeature { message: format_bytes!( @@ -292,7 +291,7 @@ } Err(error) => exit( _current_dir, -, +::new(_repo_config), OnUnsupported::from_config(_repo_config), Err(error.into()), // TODO: show a warning or combine with original error if @@ -308,6 +307,7 @@ } else { _repo_config }; +let ui = Ui::new(); let on_unsupported = OnUnsupported::from_config(config); let result = main_with_result( diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -84,6 +84,11 @@ } impl Config { +/// The configuration to use when printing configuration-loading errors +pub fn empty() -> Self { +Self { layers: Vec::new() } +} + /// Load system and user configuration from various files. /// /// This is also affected by some environment variables. To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12165: rhg: Make Ui::new falliable, add Ui::new_infallible
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This allows propagating color configuration errors REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12165 AFFECTED FILES rust/rhg/src/main.rs rust/rhg/src/ui.rs CHANGE DETAILS diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -1,5 +1,6 @@ use format_bytes::format_bytes; use hg::config::Config; +use hg::errors::HgError; use hg::utils::files::get_bytes_from_os_string; use std::borrow::Cow; use std::env; @@ -22,7 +23,17 @@ /// The commandline user interface impl Ui { -pub fn new(_config: ) -> Self { +pub fn new(_config: ) -> Result { +Ok(Ui { +stdout: std::io::stdout(), +stderr: std::io::stderr(), +}) +} + +/// Default to no color if color configuration errors. +/// +/// Useful when we’re already handling another error. +pub fn new_infallible(_config: ) -> Self { Ui { stdout: std::io::stdout(), stderr: std::io::stderr(), diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -150,7 +150,7 @@ .unwrap_or_else(|error| { exit( , -::new(::empty()), +::new_infallible(::empty()), OnUnsupported::Abort, Err(CommandError::abort(format!( "abort: {}: '{}'", @@ -171,7 +171,7 @@ exit( _current_dir, -::new(::empty()), +::new_infallible(::empty()), on_unsupported, Err(error.into()), false, @@ -183,7 +183,7 @@ .unwrap_or_else(|error| { exit( _current_dir, -::new(_repo_config), +::new_infallible(_repo_config), OnUnsupported::from_config(_repo_config), Err(error.into()), non_repo_config @@ -201,7 +201,7 @@ if SCHEME_RE.is_match(_path_bytes) { exit( _current_dir, -::new(_repo_config), +::new_infallible(_repo_config), OnUnsupported::from_config(_repo_config), Err(CommandError::UnsupportedFeature { message: format_bytes!( @@ -291,7 +291,7 @@ } Err(error) => exit( _current_dir, -::new(_repo_config), +::new_infallible(_repo_config), OnUnsupported::from_config(_repo_config), Err(error.into()), // TODO: show a warning or combine with original error if @@ -307,7 +307,17 @@ } else { _repo_config }; -let ui = Ui::new(); +let ui = Ui::new().unwrap_or_else(|error| { +exit( +_current_dir, +::new_infallible(), +OnUnsupported::from_config(), +Err(error.into()), +config +.get_bool(b"ui", b"detailed-exit-code") +.unwrap_or(false), +) +}); let on_unsupported = OnUnsupported::from_config(config); let result = main_with_result( To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12163: rhg: Add support for HGPLAINEXPECT
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12163 AFFECTED FILES rust/rhg/src/commands/status.rs rust/rhg/src/main.rs rust/rhg/src/ui.rs CHANGE DETAILS diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -1,4 +1,5 @@ use format_bytes::format_bytes; +use hg::utils::files::get_bytes_from_os_string; use std::borrow::Cow; use std::env; use std::io; @@ -65,8 +66,19 @@ /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT /// - False if feature is disabled by default and not included in HGPLAIN /// - True otherwise -pub fn plain() -> bool { -// TODO: add support for HGPLAINEXCEPT +pub fn plain(, feature: Option<>) -> bool { +plain(feature) +} +} + +fn plain(opt_feature: Option<>) -> bool { +if let Some(except) = env::var_os("HGPLAINEXCEPT") { +opt_feature.map_or(true, |feature| { +get_bytes_from_os_string(except) +.split(|| byte == b',') +.any(|exception| exception == feature.as_bytes()) +}) +} else { env::var_os("HGPLAIN").is_some() } } diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -669,7 +669,9 @@ } if let Some(color) = config.get(b"ui", b"color") { -if (color == b"always" || color == b"debug") && !ui.plain() { +if (color == b"always" || color == b"debug") +&& !ui.plain(Some("color")) +{ Err(CommandError::unsupported("colored output"))? } } diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -183,7 +183,7 @@ let config = invocation.config; let args = invocation.subcommand_args; -let verbose = !ui.plain() +let verbose = !ui.plain(None) && !args.is_present("print0") && (config.get_bool(b"ui", b"verbose")? || config.get_bool(b"commands", b"status.verbose")?); @@ -312,7 +312,7 @@ } } } -let relative_paths = (!ui.plain()) +let relative_paths = (!ui.plain(None)) && config .get_option(b"commands", b"status.relative")? .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12162: rhg: enable `rhg status` by default, without config or env opt-in
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The full test suite now passes with `hg` pointing to rhg. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12162 AFFECTED FILES rust/hg-core/src/config/config.rs rust/rhg/src/commands/status.rs tests/run-tests.py CHANGE DETAILS diff --git a/tests/run-tests.py b/tests/run-tests.py --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -3228,7 +3228,6 @@ # output. osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback' osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg -osenvironb[b'RHG_STATUS'] = b'1' else: # drop flag for hghave osenvironb.pop(b'RHG_INSTALLED_AS_HG', None) diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -149,15 +149,6 @@ } pub fn run(invocation: ::CliInvocation) -> Result<(), CommandError> { -let status_enabled_default = false; -let status_enabled = invocation.config.get_option(b"rhg", b"status")?; -if !status_enabled.unwrap_or(status_enabled_default) { -return Err(CommandError::unsupported( -"status is experimental in rhg (enable it with 'rhg.status = true' \ -or enable fallback with 'rhg.on-unsupported = fallback')" -)); -} - // TODO: lift these limitations if invocation.config.get_bool(b"ui", b"tweakdefaults")? { return Err(CommandError::unsupported( diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -119,7 +119,6 @@ b"rhg", b"fallback-executable", ); -config.add_for_environment_variable("RHG_STATUS", b"rhg", b"status"); // HGRCPATH replaces user config if opt_rc_path.is_none() { To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D12003: rust: Fix outdated comments in OwningDirstateMap
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY OwningDirstateMap used to own a PyBytes, but was generalized to be more generic when it was moved from hg-cpython to hg-core. This fixes some comments that were still referencing PyBytes. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D12003 AFFECTED FILES rust/hg-core/src/dirstate_tree/owning.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/owning.rs b/rust/hg-core/src/dirstate_tree/owning.rs --- a/rust/hg-core/src/dirstate_tree/owning.rs +++ b/rust/hg-core/src/dirstate_tree/owning.rs @@ -21,7 +21,7 @@ /// language cannot represent a lifetime referencing a sibling field. /// This is not quite a self-referencial struct (moving this struct is not /// a problem as it doesn’t change the address of the bytes buffer owned -/// by `PyBytes`) but touches similar borrow-checker limitations. +/// by `on_disk`) but touches similar borrow-checker limitations. ptr: *mut (), } @@ -50,13 +50,13 @@ // SAFETY: We cast the type-erased pointer back to the same type it had // in `new`, except with a different lifetime parameter. This time we // connect the lifetime to that of `self`. This cast is valid because -// `self` owns the same `PyBytes` whose buffer `DirstateMap` -// references. That buffer has a stable memory address because the byte -// string value of a `PyBytes` is immutable. +// `self` owns the same `on_disk` whose buffer `DirstateMap` +// references. That buffer has a stable memory address because our +// `Self::new_empty` counstructor requires `StableDeref`. let ptr: *mut DirstateMap<'a> = self.ptr.cast(); // SAFETY: we dereference that pointer, connecting the lifetime of the -// new `` to that of `self`. This is valid because the -// raw pointer is to a boxed value, and `self` owns that box. +// new `` to that of `self`. This is valid because the +// raw pointer is to a boxed value, and `self` owns that box. (_disk, unsafe { *ptr }) } @@ -65,7 +65,7 @@ } pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> { -// SAFETY: same reasoning as in `get_mut` above. +// SAFETY: same reasoning as in `get_pair_mut` above. let ptr: *mut DirstateMap<'a> = self.ptr.cast(); unsafe { &*ptr } } @@ -79,13 +79,13 @@ fn drop( self) { // Silence a "field is never read" warning, and demonstrate that this // value is still alive. -let _ = _disk; +let _: + Send> = _disk; // SAFETY: this cast is the same as in `get_mut`, and is valid for the // same reason. `self.on_disk` still exists at this point, drop glue // will drop it implicitly after this `drop` method returns. let ptr: *mut DirstateMap<'_> = self.ptr.cast(); // SAFETY: `Box::from_raw` takes ownership of the box away from `self`. -// This is fine because drop glue does nothig for `*mut ()` and we’re +// This is fine because drop glue does nothing for `*mut ()` and we’re // in `drop`, so `get` and `get_mut` cannot be called again. unsafe { drop(Box::from_raw(ptr)) } } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11965: rhg: desambiguate status without decompressing filelog if possible
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY When status is unsure based on `stat()` and the dirstate if a file is clean or modified, we need to compare it against the filelog. This comparison can skip looking at contents if the lengths differ. This changeset optimize this further to deduce what we can about the length if the filelog without decompressing it or resolving deltas. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11965 AFFECTED FILES mercurial/revlogutils/flagutil.py rust/hg-core/src/revlog/filelog.rs rust/hg-core/src/revlog/index.rs rust/hg-core/src/revlog/revlog.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -516,16 +516,16 @@ filelog.entry_for_node(entry.node_id()?).map_err(|_| { HgError::corrupted("filelog missing node from manifest") })?; -// TODO: check `fs_len` here like below, but based on -// `RevlogEntry::uncompressed_len` without decompressing the full filelog -// contents where possible. This is only valid if the revlog data does not -// contain metadata. See how Python’s `revlog.rawsize` calls -// `storageutil.filerevisioncopied`. -// (Maybe also check for content-modifying flags? See `revlog.size`.) -let filelog_data = filelog_entry.data()?; -let contents_in_p1 = filelog_data.file_data()?; -if contents_in_p1.len() as u64 != fs_len { -// No need to read the file contents: +if filelog_entry.file_data_len_not_equal_to(fs_len) { +// No need to read file contents: +// it cannot be equal if it has a different length. +return Ok(true); +} + +let p1_filelog_data = filelog_entry.data()?; +let p1_contents = p1_filelog_data.file_data()?; +if p1_contents.len() as u64 != fs_len { +// No need to read file contents: // it cannot be equal if it has a different length. return Ok(true); } @@ -535,5 +535,5 @@ } else { vfs.read(fs_path)? }; -Ok(contents_in_p1 != &*fs_contents) +Ok(p1_contents != &*fs_contents) } diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -20,6 +20,18 @@ use crate::revlog::Revision; use crate::{Node, NULL_REVISION}; +const REVISION_FLAG_CENSORED: u16 = 1 << 15; +const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14; +const REVISION_FLAG_EXTSTORED: u16 = 1 << 13; +const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12; + +// Keep this in sync with REVIDX_KNOWN_FLAGS in +// mercurial/revlogutils/flagutil.py +const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED +| REVISION_FLAG_ELLIPSIS +| REVISION_FLAG_EXTSTORED +| REVISION_FLAG_HASCOPIESINFO; + #[derive(derive_more::From)] pub enum RevlogError { InvalidRevision, @@ -282,6 +294,7 @@ }, p1: index_entry.p1(), p2: index_entry.p2(), +flags: index_entry.flags(), hash: *index_entry.hash(), }; Ok(entry) @@ -309,6 +322,7 @@ base_rev_or_base_of_delta_chain: Option, p1: Revision, p2: Revision, +flags: u16, hash: Node, } @@ -321,6 +335,20 @@ u32::try_from(self.uncompressed_len).ok() } +pub fn has_p1() -> bool { +self.p1 != NULL_REVISION +} + +pub fn is_cencored() -> bool { +(self.flags & REVISION_FLAG_CENSORED) != 0 +} + +pub fn has_length_affecting_flag_processor() -> bool { +// Relevant Python code: revlog.size() +// note: ELLIPSIS is known to not change the content +(self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0 +} + /// The data for this entry, after resolving deltas if any. pub fn data() -> Result, HgError> { let mut entry = self.clone(); diff --git a/rust/hg-core/src/revlog/index.rs b/rust/hg-core/src/revlog/index.rs --- a/rust/hg-core/src/revlog/index.rs +++ b/rust/hg-core/src/revlog/index.rs @@ -260,6 +260,10 @@ } } +pub fn flags() -> u16 { +BigEndian::read_u16([6..=7]) +} + /// Return the compressed length of the data. pub fn compressed_len() -> u32 { BigEndian::read_u32([8..=11]) diff --git a/rust/hg-core/src/revlog/filelog.rs b/rust/hg-core/src/revlog/filelog.rs --- a/rust/hg-core/src/revlog/filelog.rs +++ b/rust/hg-core/src/revlog/filelog.rs @@ -73,6 +73,89 @@ pub struct FilelogEntry<'a>(RevlogEntry<'a>); impl FilelogEntry<'_> { +/// `self.data()` can be expensive, with decompression and delta +/// resolution. +/// +/// *Wit
D11963: rhg: Store p1, p2, and hash in RevlogEntry
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This avoids a duplicate index lookup REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11963 AFFECTED FILES rust/hg-core/src/revlog/revlog.rs CHANGE DETAILS diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -280,6 +280,9 @@ } else { Some(index_entry.base_revision_or_base_of_delta_chain()) }, +p1: index_entry.p1(), +p2: index_entry.p2(), +hash: *index_entry.hash(), }; Ok(entry) } @@ -304,6 +307,9 @@ compressed_len: u32, uncompressed_len: i32, base_rev_or_base_of_delta_chain: Option, +p1: Revision, +p2: Revision, +hash: Node, } impl<'a> RevlogEntry<'a> { @@ -335,13 +341,6 @@ entry = self.revlog.get_entry_internal(base_rev)?; } -// TODO do not look twice in the index -let index_entry = self -.revlog -.index -.get_entry(self.rev) -.ok_or_else(corrupted)?; - let data = if delta_chain.is_empty() { entry.data_chunk()? } else { @@ -349,9 +348,9 @@ }; if self.revlog.check_hash( -index_entry.p1(), -index_entry.p2(), -index_entry.hash().as_bytes(), +self.p1, +self.p2, +self.hash.as_bytes(), , ) { Ok(data) To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11960: rhg: Add RevlogEntry::data that does delta resolution
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This requires keeping a `` reference inside the `RevlogEntry` struct. This struct already had the appropriate lifetime parameter. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11960 AFFECTED FILES rust/hg-core/src/operations/debugdata.rs rust/hg-core/src/revlog/changelog.rs rust/hg-core/src/revlog/filelog.rs rust/hg-core/src/revlog/manifest.rs rust/hg-core/src/revlog/revlog.rs CHANGE DETAILS diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -184,51 +184,14 @@ /// retrieved as needed, and the deltas will be applied to the inital /// snapshot to rebuild the final data. #[timed] -pub fn get_rev_data(, rev: Revision) -> Result, RevlogError> { +pub fn get_rev_data( +, +rev: Revision, +) -> Result, RevlogError> { if rev == NULL_REVISION { -return Ok(vec![]); +return Ok(Cow::Borrowed(&[])); }; -// Todo return -> Cow -let mut entry = self.get_entry(rev)?; -let mut delta_chain = vec![]; - -// The meaning of `base_rev_or_base_of_delta_chain` depends on -// generaldelta. See the doc on `ENTRY_DELTA_BASE` in -// `mercurial/revlogutils/constants.py` and the code in -// [_chaininfo] and in [index_deltachain]. -let uses_generaldelta = self.index.uses_generaldelta(); -while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain { -let base_rev = if uses_generaldelta { -base_rev -} else { -entry.rev - 1 -}; -delta_chain.push(entry); -entry = self.get_entry_internal(base_rev)?; -} - -// TODO do not look twice in the index -let index_entry = self -.index -.get_entry(rev) -.ok_or(RevlogError::InvalidRevision)?; - -let data: Vec = if delta_chain.is_empty() { -entry.data_chunk()?.into() -} else { -Revlog::build_data_from_deltas(entry, _chain)? -}; - -if self.check_hash( -index_entry.p1(), -index_entry.p2(), -index_entry.hash().as_bytes(), -, -) { -Ok(data) -} else { -Err(RevlogError::corrupted()) -} +self.get_entry(rev)?.data() } /// Check the hash of some given data against the recorded hash. @@ -296,6 +259,7 @@ ()[start..end] }; let entry = RevlogEntry { +revlog: self, rev, bytes: data, compressed_len: index_entry.compressed_len(), @@ -324,8 +288,9 @@ /// The revlog entry's bytes and the necessary informations to extract /// the entry's data. -#[derive(Debug)] +#[derive(Clone)] pub struct RevlogEntry<'a> { +revlog: &'a Revlog, rev: Revision, bytes: &'a [u8], compressed_len: usize, @@ -338,9 +303,54 @@ self.rev } +/// The data for this entry, after resolving deltas if any. +pub fn data() -> Result, RevlogError> { +let mut entry = self.clone(); +let mut delta_chain = vec![]; + +// The meaning of `base_rev_or_base_of_delta_chain` depends on +// generaldelta. See the doc on `ENTRY_DELTA_BASE` in +// `mercurial/revlogutils/constants.py` and the code in +// [_chaininfo] and in [index_deltachain]. +let uses_generaldelta = self.revlog.index.uses_generaldelta(); +while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain { +let base_rev = if uses_generaldelta { +base_rev +} else { +entry.rev - 1 +}; +delta_chain.push(entry); +entry = self.revlog.get_entry_internal(base_rev)?; +} + +// TODO do not look twice in the index +let index_entry = self +.revlog +.index +.get_entry(self.rev) +.ok_or(RevlogError::InvalidRevision)?; + +let data = if delta_chain.is_empty() { +entry.data_chunk()? +} else { +Revlog::build_data_from_deltas(entry, _chain)?.into() +}; + +if self.revlog.check_hash( +index_entry.p1(), +index_entry.p2(), +index_entry.hash().as_bytes(), +, +) { +Ok(data) +} else { +Err(RevlogError::corrupted()) +} +} + /// Extract the data contained in the entry. /// This may be a delta. (See `is_delta`.) -fn data_chunk() -> Result, RevlogError>
D11964: filelog: add a comment explaining a fast path in filerevisioncopied()
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11964 AFFECTED FILES mercurial/utils/storageutil.py CHANGE DETAILS diff --git a/mercurial/utils/storageutil.py b/mercurial/utils/storageutil.py --- a/mercurial/utils/storageutil.py +++ b/mercurial/utils/storageutil.py @@ -112,6 +112,13 @@ 2-tuple of the source filename and node. """ if store.parents(node)[0] != sha1nodeconstants.nullid: +# When creating a copy or move we set filelog parents to null, +# because contents are probably unrelated and making a delta +# would not be useful. +# Conversely, if filelog p1 is non-null we know +# there is no copy metadata. +# In the presence of merges, this reasoning becomes invalid +# if we reorder parents. See tests/test-issue6528.t. return False meta = parsemeta(store.revision(node))[0] To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11959: rhg: Rename some revlog-related types and methods
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Use "data chunck" and "data" for a revlog entry’s data before and after resolving deltas (if any), repsectively. The former `FilelogEntry` actually only contains data, rename it to `FilelogRevisionData` accordingly. This leaves room to later have a `FilelogEntry` type that wraps `RevlogEntry`. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11959 AFFECTED FILES rust/hg-core/src/operations/cat.rs rust/hg-core/src/revlog/changelog.rs rust/hg-core/src/revlog/filelog.rs rust/hg-core/src/revlog/revlog.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -522,7 +522,7 @@ filelog.data_for_node(entry.node_id()?).map_err(|_| { HgError::corrupted("filelog missing node from manifest") })?; -let contents_in_p1 = filelog_entry.data()?; +let contents_in_p1 = filelog_entry.file_data()?; if contents_in_p1.len() as u64 != fs_len { // No need to read the file contents: // it cannot be equal if it has a different length. diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -214,7 +214,7 @@ .ok_or(RevlogError::InvalidRevision)?; let data: Vec = if delta_chain.is_empty() { -entry.data()?.into() +entry.data_chunk()?.into() } else { Revlog::build_data_from_deltas(entry, _chain)? }; @@ -260,11 +260,11 @@ snapshot: RevlogEntry, deltas: &[RevlogEntry], ) -> Result, RevlogError> { -let snapshot = snapshot.data()?; +let snapshot = snapshot.data_chunk()?; let deltas = deltas .iter() .rev() -.map(RevlogEntry::data) +.map(RevlogEntry::data_chunk) .collect::>, RevlogError>>()?; let patches: Vec<_> = deltas.iter().map(|d| patch::PatchList::new(d)).collect(); @@ -339,7 +339,8 @@ } /// Extract the data contained in the entry. -pub fn data() -> Result, RevlogError> { +/// This may be a delta. (See `is_delta`.) +fn data_chunk() -> Result, RevlogError> { if self.bytes.is_empty() { return Ok(Cow::Borrowed(&[])); } diff --git a/rust/hg-core/src/revlog/filelog.rs b/rust/hg-core/src/revlog/filelog.rs --- a/rust/hg-core/src/revlog/filelog.rs +++ b/rust/hg-core/src/revlog/filelog.rs @@ -28,7 +28,7 @@ pub fn data_for_node( , file_node: impl Into, -) -> Result { +) -> Result { let file_rev = self.revlog.rev_from_node(file_node.into())?; self.data_for_rev(file_rev) } @@ -38,9 +38,9 @@ pub fn data_for_rev( , file_rev: Revision, -) -> Result { +) -> Result { let data: Vec = self.revlog.get_rev_data(file_rev)?; -Ok(FilelogEntry(data.into())) +Ok(FilelogRevisionData(data.into())) } } @@ -50,9 +50,10 @@ get_path_from_bytes(_bytes).into() } -pub struct FilelogEntry(Vec); +/// The data for one revision in a filelog, uncompressed and delta-resolved. +pub struct FilelogRevisionData(Vec); -impl FilelogEntry { +impl FilelogRevisionData { /// Split into metadata and data pub fn split() -> Result<(Option<&[u8]>, &[u8]), HgError> { const DELIMITER: &[u8; 2] = &[b'\x01', b'\n']; @@ -71,14 +72,14 @@ } /// Returns the file contents at this revision, stripped of any metadata -pub fn data() -> Result<&[u8], HgError> { +pub fn file_data() -> Result<&[u8], HgError> { let (_metadata, data) = self.split()?; Ok(data) } /// Consume the entry, and convert it into data, discarding any metadata, /// if present. -pub fn into_data(self) -> Result, HgError> { +pub fn into_file_data(self) -> Result, HgError> { if let (Some(_metadata), data) = self.split()? { Ok(data.to_owned()) } else { diff --git a/rust/hg-core/src/revlog/changelog.rs b/rust/hg-core/src/revlog/changelog.rs --- a/rust/hg-core/src/revlog/changelog.rs +++ b/rust/hg-core/src/revlog/changelog.rs @@ -22,7 +22,7 @@ pub fn data_for_node( , node: NodePrefix, -) -> Result { +) -> Result { let rev = self.revlog.rev_from_node(node)?; self.data_for_rev(rev) } @@ -31,9 +31,9 @@ pub fn data_for_rev( , rev: Revision, -) -> Result { +) -> Result {
D11961: rhg: Expose FilelogEntry that wraps RevlogEntry
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This can be later extended to access metadata such as `uncompressed_len` without necessarily resolving deltas. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11961 AFFECTED FILES rust/hg-core/src/revlog/filelog.rs rust/hg-core/src/revlog/revlog.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -512,17 +512,18 @@ } let filelog = repo.filelog(hg_path)?; let fs_len = fs_metadata.len(); +let filelog_entry = +filelog.entry_for_node(entry.node_id()?).map_err(|_| { +HgError::corrupted("filelog missing node from manifest") +})?; // TODO: check `fs_len` here like below, but based on // `RevlogEntry::uncompressed_len` without decompressing the full filelog // contents where possible. This is only valid if the revlog data does not // contain metadata. See how Python’s `revlog.rawsize` calls // `storageutil.filerevisioncopied`. // (Maybe also check for content-modifying flags? See `revlog.size`.) -let filelog_entry = -filelog.data_for_node(entry.node_id()?).map_err(|_| { -HgError::corrupted("filelog missing node from manifest") -})?; -let contents_in_p1 = filelog_entry.file_data()?; +let filelog_data = filelog_entry.data()?; +let contents_in_p1 = filelog_data.file_data()?; if contents_in_p1.len() as u64 != fs_len { // No need to read the file contents: // it cannot be equal if it has a different length. diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -39,9 +39,13 @@ } } +fn corrupted() -> HgError { +HgError::corrupted("corrupted revlog") +} + impl RevlogError { fn corrupted() -> Self { -RevlogError::Other(HgError::corrupted("corrupted revlog")) +RevlogError::Other(corrupted()) } } @@ -191,7 +195,7 @@ if rev == NULL_REVISION { return Ok(Cow::Borrowed(&[])); }; -self.get_entry(rev)?.data() +Ok(self.get_entry(rev)?.data()?) } /// Check the hash of some given data against the recorded hash. @@ -222,13 +226,13 @@ fn build_data_from_deltas( snapshot: RevlogEntry, deltas: &[RevlogEntry], -) -> Result, RevlogError> { +) -> Result, HgError> { let snapshot = snapshot.data_chunk()?; let deltas = deltas .iter() .rev() .map(RevlogEntry::data_chunk) -.collect::>, RevlogError>>()?; +.collect::, _>>()?; let patches: Vec<_> = deltas.iter().map(|d| patch::PatchList::new(d)).collect(); let patch = patch::fold_patch_lists(); @@ -246,7 +250,10 @@ } /// Get an entry of the revlog. -fn get_entry(, rev: Revision) -> Result { +pub fn get_entry( +, +rev: Revision, +) -> Result { let index_entry = self .index .get_entry(rev) @@ -281,8 +288,8 @@ fn get_entry_internal( , rev: Revision, -) -> Result { -return self.get_entry(rev).map_err(|_| RevlogError::corrupted()); +) -> Result { +return self.get_entry(rev).map_err(|_| corrupted()); } } @@ -304,7 +311,7 @@ } /// The data for this entry, after resolving deltas if any. -pub fn data() -> Result, RevlogError> { +pub fn data() -> Result, HgError> { let mut entry = self.clone(); let mut delta_chain = vec![]; @@ -328,7 +335,7 @@ .revlog .index .get_entry(self.rev) -.ok_or(RevlogError::InvalidRevision)?; +.ok_or_else(corrupted)?; let data = if delta_chain.is_empty() { entry.data_chunk()? @@ -344,13 +351,13 @@ ) { Ok(data) } else { -Err(RevlogError::corrupted()) +Err(corrupted()) } } /// Extract the data contained in the entry. /// This may be a delta. (See `is_delta`.) -fn data_chunk() -> Result, RevlogError> { +fn data_chunk() -> Result, HgError> { if self.bytes.is_empty() { return Ok(Cow::Borrowed(&[])); } @@ -365,39 +372,35 @@ // zstd data. b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)), // A proper new format should have had a repo/store requirement. -_format_type => Err(RevlogError::cor
D11962: rhg: RevlogEntry::uncompressed_len is signed
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The corresponding Python code appears to explicitly check for non-negative values. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11962 AFFECTED FILES rust/hg-core/src/revlog/index.rs rust/hg-core/src/revlog/revlog.rs CHANGE DETAILS diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::convert::TryFrom; use std::io::Read; use std::ops::Deref; use std::path::Path; @@ -259,7 +260,7 @@ .get_entry(rev) .ok_or(RevlogError::InvalidRevision)?; let start = index_entry.offset(); -let end = start + index_entry.compressed_len(); +let end = start + index_entry.compressed_len() as usize; let data = if self.index.is_inline() { self.index.data(start, end) } else { @@ -300,8 +301,8 @@ revlog: &'a Revlog, rev: Revision, bytes: &'a [u8], -compressed_len: usize, -uncompressed_len: usize, +compressed_len: u32, +uncompressed_len: i32, base_rev_or_base_of_delta_chain: Option, } @@ -310,6 +311,10 @@ self.rev } +pub fn uncompressed_len() -> Option { +u32::try_from(self.uncompressed_len).ok() +} + /// The data for this entry, after resolving deltas if any. pub fn data() -> Result, HgError> { let mut entry = self.clone(); @@ -379,11 +384,12 @@ fn uncompressed_zlib_data() -> Result, HgError> { let mut decoder = ZlibDecoder::new(self.bytes); if self.is_delta() { -let mut buf = Vec::with_capacity(self.compressed_len); +let mut buf = Vec::with_capacity(self.compressed_len as usize); decoder.read_to_end( buf).map_err(|_| corrupted())?; Ok(buf) } else { -let mut buf = vec![0; self.uncompressed_len]; +let cap = self.uncompressed_len.max(0) as usize; +let mut buf = vec![0; cap]; decoder.read_exact( buf).map_err(|_| corrupted())?; Ok(buf) } @@ -391,15 +397,16 @@ fn uncompressed_zstd_data() -> Result, HgError> { if self.is_delta() { -let mut buf = Vec::with_capacity(self.compressed_len); +let mut buf = Vec::with_capacity(self.compressed_len as usize); zstd::stream::copy_decode(self.bytes, buf) .map_err(|_| corrupted())?; Ok(buf) } else { -let mut buf = vec![0; self.uncompressed_len]; +let cap = self.uncompressed_len.max(0) as usize; +let mut buf = vec![0; cap]; let len = zstd::block::decompress_to_buffer(self.bytes, buf) .map_err(|_| corrupted())?; -if len != self.uncompressed_len { +if len != self.uncompressed_len as usize { Err(corrupted()) } else { Ok(buf) diff --git a/rust/hg-core/src/revlog/index.rs b/rust/hg-core/src/revlog/index.rs --- a/rust/hg-core/src/revlog/index.rs +++ b/rust/hg-core/src/revlog/index.rs @@ -118,7 +118,7 @@ offset_override: None, }; -offset += INDEX_ENTRY_SIZE + entry.compressed_len(); +offset += INDEX_ENTRY_SIZE + entry.compressed_len() as usize; } if offset == bytes.len() { @@ -261,13 +261,13 @@ } /// Return the compressed length of the data. -pub fn compressed_len() -> usize { -BigEndian::read_u32([8..=11]) as usize +pub fn compressed_len() -> u32 { +BigEndian::read_u32([8..=11]) } /// Return the uncompressed length of the data. -pub fn uncompressed_len() -> usize { -BigEndian::read_u32([12..=15]) as usize +pub fn uncompressed_len() -> i32 { +BigEndian::read_i32([12..=15]) } /// Return the revision upon which the data has been derived. To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11943: rhg: Fall back to Python if verbose status is requested by config
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11943 AFFECTED FILES rust/rhg/src/commands/status.rs rust/rhg/src/ui.rs tests/test-conflict.t CHANGE DETAILS diff --git a/tests/test-conflict.t b/tests/test-conflict.t --- a/tests/test-conflict.t +++ b/tests/test-conflict.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init $ cat << EOF > a > Small Mathematical Series. diff --git a/rust/rhg/src/ui.rs b/rust/rhg/src/ui.rs --- a/rust/rhg/src/ui.rs +++ b/rust/rhg/src/ui.rs @@ -51,7 +51,7 @@ stderr.flush().or_else(handle_stderr_error) } -/// is plain mode active +/// Return whether plain mode is active. /// /// Plain mode means that all configuration variables which affect /// the behavior and output of Mercurial should be diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -181,6 +181,17 @@ let ui = invocation.ui; let config = invocation.config; let args = invocation.subcommand_args; + +let verbose = !ui.plain() +&& !args.is_present("print0") +&& (config.get_bool(b"ui", b"verbose")? +|| config.get_bool(b"commands", b"status.verbose")?); +if verbose { +return Err(CommandError::unsupported( +"verbose status is not supported yet", +)); +} + let all = args.is_present("all"); let display_states = if all { // TODO when implementing `--quiet`: it excludes clean files To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11942: rhg: Accept different "invalid ignore pattern" error message formatting
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY At the moment rhg compiles all patterns into a single big regular expression, so it’s not practical to find out which file the invalid bit of syntax came from. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11942 AFFECTED FILES tests/test-hgignore.t CHANGE DETAILS diff --git a/tests/test-hgignore.t b/tests/test-hgignore.t --- a/tests/test-hgignore.t +++ b/tests/test-hgignore.t @@ -9,10 +9,6 @@ > EOF #endif -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init ignorerepo $ cd ignorerepo @@ -63,9 +59,19 @@ ? syntax $ echo "*.o" > .hgignore +#if no-rhg $ hg status abort: $TESTTMP/ignorerepo/.hgignore: invalid pattern (relre): *.o (glob) [255] +#endif +#if rhg + $ hg status + Unsupported syntax regex parse error: + ^(?:*.o) + ^ + error: repetition operator missing expression + [255] +#endif Ensure given files are relative to cwd To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11940: rhg: Sub-repositories are not supported
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11940 AFFECTED FILES rust/hg-core/src/repo.rs rust/rhg/src/main.rs tests/test-subrepo-deep-nested-change.t tests/test-subrepo-missing.t CHANGE DETAILS diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t --- a/tests/test-subrepo-missing.t +++ b/tests/test-subrepo-missing.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init repo $ cd repo $ hg init subrepo diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t --- a/tests/test-subrepo-deep-nested-change.t +++ b/tests/test-subrepo-deep-nested-change.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ cat >> $HGRCPATH < [extdiff] > # for portability: diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -29,7 +29,7 @@ repo: Result<, >, config: , ) -> Result<(), CommandError> { -check_unsupported(config, ui)?; +check_unsupported(config, repo, ui)?; let app = App::new("rhg") .global_setting(AppSettings::AllowInvalidUtf8) @@ -643,6 +643,7 @@ fn check_unsupported( config: , +repo: Result<, >, ui: ::Ui, ) -> Result<(), CommandError> { check_extensions(config)?; @@ -653,6 +654,12 @@ Err(CommandError::unsupported("$HG_PENDING"))? } +if let Ok(repo) = repo { +if repo.has_subrepos()? { +Err(CommandError::unsupported("sub-repositories"))? +} +} + if config.has_non_empty_section(b"encode") { Err(CommandError::unsupported("[encode] config"))? } diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -417,6 +417,14 @@ ) } +pub fn has_subrepos() -> Result { +if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? { +Ok(entry.state().is_tracked()) +} else { +Ok(false) + } +} + pub fn filelog(, path: ) -> Result { Filelog::open(self, path) } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11941: rhg: Properly format warnings related to ignore patterns
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11941 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -25,7 +25,7 @@ use hg::utils::files::get_path_from_bytes; use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; use hg::StatusOptions; -use log::{info, warn}; +use log::info; use std::io; use std::path::PathBuf; @@ -233,8 +233,29 @@ ignore_files(repo, config), options, )?; -if !pattern_warnings.is_empty() { -warn!("Pattern warnings: {:?}", _warnings); +for warning in pattern_warnings { +match warning { +hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui +.write_stderr(_bytes!( +b"{}: ignoring invalid syntax '{}'\n", +format_bytes::Utf8(path.display()), +&*syntax +))?, +hg::PatternFileWarning::NoSuchFile(path) => { +let path = if let Ok(relative) = +path.strip_prefix(repo.working_directory_path()) +{ +relative +} else { +&*path +}; +ui.write_stderr(_bytes!( +b"skipping unreadable pattern file '{}': \ + No such file or directory\n", +format_bytes::Utf8(path.display()), +))? +} +} } for (path, error) in ds_status.bad { To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11939: dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This would only be relevant in contrived scenarios such as a dirstate file being written with a libc that supports sub-second precision in mtimes, then transfered (at the filesystem level, not `hg clone`) to another system where libc *doesn’t* have sub-second precision and truncates the stored mtime of a directory to integer seconds. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11939 AFFECTED FILES rust/hg-core/src/dirstate/status.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-core/src/dirstate_tree/status.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -315,9 +315,8 @@ } let mut dirstate_write_needed = ds_status.dirty; -let filesystem_time_at_status_start = ds_status -.filesystem_time_at_status_start -.map(TruncatedTimestamp::from); +let filesystem_time_at_status_start = +ds_status.filesystem_time_at_status_start; if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) && !dirstate_write_needed diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -63,7 +63,8 @@ (Box::new(|&_| true), vec![], None) }; -let filesystem_time_at_status_start = filesystem_now(_dir).ok(); +let filesystem_time_at_status_start = +filesystem_now(_dir).ok().map(TruncatedTimestamp::from); let outcome = DirstateStatus { filesystem_time_at_status_start, ..Default::default() @@ -145,7 +146,7 @@ /// The current time at the start of the `status()` algorithm, as measured /// and possibly truncated by the filesystem. -filesystem_time_at_status_start: Option, +filesystem_time_at_status_start: Option, } enum Outcome { @@ -472,71 +473,86 @@ directory_metadata: ::fs::Metadata, dirstate_node: NodeRef<'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { -if children_all_have_dirstate_node_or_are_ignored { -// All filesystem directory entries from `read_dir` have a -// corresponding node in the dirstate, so we can reconstitute the -// names of those entries without calling `read_dir` again. -if let (Some(status_start), Ok(directory_mtime)) = ( -_time_at_status_start, -directory_metadata.modified(), +if !children_all_have_dirstate_node_or_are_ignored { +return Ok(()); +} +// All filesystem directory entries from `read_dir` have a +// corresponding node in the dirstate, so we can reconstitute the +// names of those entries without calling `read_dir` again. + +// TODO: use let-else here and below when available: +// https://github.com/rust-lang/rust/issues/87335 +let status_start = if let Some(status_start) = +_time_at_status_start +{ +status_start +} else { +return Ok(()); +}; + +// Although the Rust standard library’s `SystemTime` type +// has nanosecond precision, the times reported for a +// directory’s (or file’s) modified time may have lower +// resolution based on the filesystem (for example ext3 +// only stores integer seconds), kernel (see +// https://stackoverflow.com/a/14393315/1162888), etc. +let directory_mtime = if let Ok(option) = +TruncatedTimestamp::for_reliable_mtime_of( +directory_metadata, +status_start, ) { -// Although the Rust standard library’s `SystemTime` type -// has nanosecond precision, the times reported for a -// directory’s (or file’s) modified time may have lower -// resolution based on the filesystem (for example ext3 -// only stores integer seconds), kernel (see -// https://stackoverflow.com/a/14393315/1162888), etc. -if _mtime >= status_start { -// The directory was modified too recently, don’t cache its -// `read_dir` results. -// -// A timeline like this is possible: -// -// 1. A change to this directory (direct child was -//added or removed) cause its mtime to be set -//(possibly truncated) to `directory_mtime` -// 2. This `status` algorithm calls `read_dir` -
D11938: rust: Upgrade to format-bytes 0.3
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This removes use of the proc-macro-hack crate, which is possible now that we’ve don’td support Rust 1.41 to 1.44 anymore. This in turn fixes spurious errors reported by rust-analyser: https://github.com/rust-analyzer/rust-analyzer/issues/9606#issuecomment-919240134 REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11938 AFFECTED FILES rust/Cargo.lock rust/hg-core/Cargo.toml rust/rhg/Cargo.toml CHANGE DETAILS diff --git a/rust/rhg/Cargo.toml b/rust/rhg/Cargo.toml --- a/rust/rhg/Cargo.toml +++ b/rust/rhg/Cargo.toml @@ -18,5 +18,5 @@ micro-timer = "0.3.1" regex = "1.3.9" env_logger = "0.7.1" -format-bytes = "0.2.1" +format-bytes = "0.3.0" users = "0.11.0" diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml +++ b/rust/hg-core/Cargo.toml @@ -33,7 +33,7 @@ log = "0.4.8" memmap2 = {version = "0.4", features = ["stable_deref_trait"]} zstd = "0.5.3" -format-bytes = "0.2.2" +format-bytes = "0.3.0" # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until # we have a clearer view of which backend is the fastest. diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -314,21 +314,19 @@ [[package]] name = "format-bytes" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index; -checksum = "1c4e89040c7fd7b4e6ba2820ac705a45def8a0c098ec78d170ae88f1ef1d5762" +checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd" dependencies = [ "format-bytes-macros", - "proc-macro-hack", ] [[package]] name = "format-bytes-macros" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index; -checksum = "b05089e341a0460449e2210c3bf7b61597860b07f0deae58da38dbed0a4c6b6d" +checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f" dependencies = [ - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -637,12 +635,6 @@ ] [[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index; -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index; To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11932: rhg: Use binary search in manifest lookup
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY … instead of linear scan, when looking for a single entry based on its path. Manifest entries are sorted by path, but are variable-size so we can’t use the standard library’s `[T]::binary_search`. We can still jump to a byte index and then look around for entry boundaries. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11932 AFFECTED FILES rust/hg-core/src/revlog/manifest.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -473,7 +473,7 @@ }; let entry = manifest -.find_file(hg_path)? +.find_by_path(hg_path)? .expect("ambgious file not in p1"); if entry.flags != fs_flags { return Ok(true); diff --git a/rust/hg-core/src/revlog/manifest.rs b/rust/hg-core/src/revlog/manifest.rs --- a/rust/hg-core/src/revlog/manifest.rs +++ b/rust/hg-core/src/revlog/manifest.rs @@ -52,6 +52,15 @@ /// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. #[derive(Debug)] pub struct Manifest { +/// Format for a manifest: flat sequence of variable-size entries, +/// sorted by path, each as: +/// +/// ```text +/// \0 \n +/// ``` +/// +/// The last entry is also terminated by a newline character. +/// Flags is one of `b""` (the empty string), `b"x"`, `b"l"`, or `b"t"`. bytes: Vec, } @@ -62,44 +71,84 @@ self.bytes .split(|b| b == '\n') .filter(|line| !line.is_empty()) -.map(|line| { -let (path, rest) = line.split_2(b'\0').ok_or_else(|| { -HgError::corrupted("manifest line should contain \\0") -})?; -let path = HgPath::new(path); -let (hex_node_id, flags) = match rest.split_last() { -Some(('x', rest)) => (rest, Some(b'x')), -Some(('l', rest)) => (rest, Some(b'l')), -Some(('t', rest)) => (rest, Some(b't')), -_ => (rest, None), -}; -Ok(ManifestEntry { -path, -hex_node_id, -flags, -}) -}) +.map(ManifestEntry::from_raw) } /// If the given path is in this manifest, return its filelog node ID -pub fn find_file( +pub fn find_by_path( , path: , ) -> Result, HgError> { -// TODO: use binary search instead of linear scan. This may involve -// building (and caching) an index of the byte indicex of each manifest -// line. +use std::cmp::Ordering::*; +let path = path.as_bytes(); +// Both boundaries of this `&[u8]` slice are always at the boundary of +// an entry +let mut bytes = &*self.bytes; -// TODO: use try_find when available (if still using linear scan) -// https://github.com/rust-lang/rust/issues/63178 -for entry in self.iter() { -let entry = entry?; -if entry.path == path { -return Ok(Some(entry)); +// Binary search algorithm derived from `[T]::binary_search_by` +// <https://github.com/rust-lang/rust/blob/1.57.0/library/core/src/slice/mod.rs#L2221> +// except we don’t have a slice of entries. Instead we jump to the +// middle of the byte slice and look around for entry delimiters +// (newlines). +while let Some(entry_range) = Self::find_entry_near_middle_of(bytes)? { +let (entry_path, rest) = +ManifestEntry::split_path([entry_range.clone()])?; +let cmp = entry_path.cmp(path); +if cmp == Less { +let after_newline = entry_range.end + 1; +bytes = [after_newline..]; +} else if cmp == Greater { +bytes = [..entry_range.start]; +} else { +return Ok(Some(ManifestEntry::from_path_and_rest( +entry_path, rest, +))); } } Ok(None) } + +/// If there is at least one, return the byte range of an entry *excluding* +/// the final newline. +fn find_entry_near_middle_of( +bytes: &[u8], +) -> Result>, HgError> { +let len = bytes.len(); +if len > 0 { +let middle = bytes.len() / 2; +// Integer division rounds down, so `middle < len`. +let (before, after) = bytes.split_at(middle); +let is_newline = |: | byte == b'\n
D11910: rhg: Skip reading the contents of ambiguous files in some cases
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY If the size of the file in the working directory does not match the length of the filelog data, we know its contents will be different and don’t need to read it. rhg still decodes the filelog revision, which is not needed in some cases. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11910 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -479,11 +479,23 @@ return Ok(true); } let filelog = repo.filelog(hg_path)?; +let fs_len = fs_metadata.len(); +// TODO: check `fs_len` here like below, but based on +// `RevlogEntry::uncompressed_len` without decompressing the full filelog +// contents where possible. This is only valid if the revlog data does not +// contain metadata. See how Python’s `revlog.rawsize` calls +// `storageutil.filerevisioncopied`. +// (Maybe also check for content-modifying flags? See `revlog.size`.) let filelog_entry = filelog.data_for_node(entry.node_id()?).map_err(|_| { HgError::corrupted("filelog missing node from manifest") })?; let contents_in_p1 = filelog_entry.data()?; +if contents_in_p1.len() as u64 != fs_len { +// No need to read the file contents: +// it cannot be equal if it has a different length. +return Ok(true); +} let fs_contents = if is_symlink { get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string()) To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11909: rhg: Mark it as expected that the issue6528 bug is not reproduced
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11909 AFFECTED FILES tests/test-issue6528.t CHANGE DETAILS diff --git a/tests/test-issue6528.t b/tests/test-issue6528.t --- a/tests/test-issue6528.t +++ b/tests/test-issue6528.t @@ -2,10 +2,6 @@ Test non-regression on the corruption associated with issue6528 === -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - Setup = @@ -191,9 +187,14 @@ #endif Check that the issue is present +(It is currently not present with rhg but will be when optimizations are added +to resolve ambiguous files at the end of status without reading their content +if the size differs, and reading the expected size without resolving filelog +deltas where possible.) + $ hg st - M D.txt - M b.txt + M D.txt (no-rhg !) + M b.txt (no-rhg !) $ hg debugrevlogindex b.txt rev linkrev nodeid p1 p2 0 2 05b806ebe5ea @@ -211,8 +212,8 @@ found affected revision 1 for filelog 'data/b.txt.i' found affected revision 3 for filelog 'data/b.txt.i' $ hg st - M D.txt - M b.txt + M D.txt (no-rhg !) + M b.txt (no-rhg !) $ hg debugrevlogindex b.txt rev linkrev nodeid p1 p2 0 2 05b806ebe5ea @@ -230,8 +231,8 @@ found affected revision 1 for filelog 'data/b.txt.i' found affected revision 3 for filelog 'data/b.txt.i' $ hg st - M D.txt - M b.txt + M D.txt (no-rhg !) + M b.txt (no-rhg !) $ hg debugrevlogindex b.txt rev linkrev nodeid p1 p2 0 2 05b806ebe5ea @@ -307,8 +308,8 @@ found affected revision 1 for filelog 'b.txt' found affected revision 3 for filelog 'b.txt' $ hg st - M D.txt - M b.txt + M D.txt (no-rhg !) + M b.txt (no-rhg !) $ hg debugrevlogindex b.txt rev linkrev nodeid p1 p2 0 2 05b806ebe5ea To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11908: rhg: Print "bad match" errors in rhg status
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Previously these would only be visible if enabled with some RUST_LOG environment variable. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11908 AFFECTED FILES rust/rhg/src/commands/status.rs tests/test-permissions.t CHANGE DETAILS diff --git a/tests/test-permissions.t b/tests/test-permissions.t --- a/tests/test-permissions.t +++ b/tests/test-permissions.t @@ -11,10 +11,6 @@ > EOF #endif -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init t $ cd t @@ -82,7 +78,7 @@ (fsmonitor makes "hg status" avoid accessing to "dir") $ hg status - dir: Permission denied + dir: Permission denied * (glob) M a #endif diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -237,8 +237,20 @@ warn!("Pattern warnings: {:?}", _warnings); } -if !ds_status.bad.is_empty() { -warn!("Bad matches {:?}", &(ds_status.bad)) +for (path, error) in ds_status.bad { +let error = match error { +hg::BadMatch::OsError(code) => { +std::io::Error::from_raw_os_error(code).to_string() +} +hg::BadMatch::BadType(ty) => { +format!("unsupported file type (type is {})", ty) +} +}; +ui.write_stderr(_bytes!( +b"{}: {}\n", +path.as_bytes(), +error.as_bytes() +))? } if !ds_status.unsure.is_empty() { info!( To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11907: rhg: Add support for ui.ignore and ui.ignore.* config
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This fixes some but not all failures in `tests/test-hgignore.t` when running with `rhg status` enabled. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11907 AFFECTED FILES rust/hg-core/src/config/config.rs rust/hg-core/src/config/layer.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -21,10 +21,12 @@ use hg::matchers::AlwaysMatcher; use hg::repo::Repo; use hg::utils::files::get_bytes_from_os_string; +use hg::utils::files::get_path_from_bytes; use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; use hg::{HgPathCow, StatusOptions}; use log::{info, warn}; use std::io; +use std::path::PathBuf; pub const HELP_TEXT: = " Show changed files in the working directory @@ -213,11 +215,10 @@ list_ignored: display_states.ignored, collect_traversed_dirs: false, }; -let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded let (mut ds_status, pattern_warnings) = dmap.status( , repo.working_directory_path().to_owned(), -vec![ignore_file], +ignore_files(repo, config), options, )?; if !pattern_warnings.is_empty() { @@ -396,6 +397,25 @@ Ok(()) } +fn ignore_files(repo: , config: ) -> Vec { +let mut ignore_files = Vec::new(); +let repo_ignore = repo.working_directory_vfs().join(".hgignore"); +if repo_ignore.exists() { +ignore_files.push(repo_ignore) +} +for (key, value) in config.iter_section(b"ui") { +if key == b"ignore" || key.starts_with(b"ignore.") { +let path = get_path_from_bytes(value); +// TODO: expand "~/" and environment variable here, like Python +// does with `os.path.expanduser` and `os.path.expandvars` + +let joined = repo.working_directory_path().join(path); +ignore_files.push(joined); +} +} +ignore_files +} + // Probably more elegant to use a Deref or Borrow trait rather than // harcode HgPathBuf, but probably not really useful at this point fn display_status_paths( diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -127,6 +127,17 @@ .flat_map(|section| section.keys().map(|vec| &**vec)) } +/// Returns the (key, value) pairs defined in the given section +pub fn iter_section<'layer>( +&'layer self, +section: &[u8], +) -> impl Iterator { +self.sections +.get(section) +.into_iter() +.flat_map(|section| section.iter().map(|(k, v)| (&**k, &*v.bytes))) +} + /// Returns whether any key is defined in the given section pub fn has_non_empty_section(, section: &[u8]) -> bool { self.sections diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -419,6 +419,59 @@ .any(|layer| layer.has_non_empty_section(section)) } +/// Yields (key, value) pairs for everything in the given section +pub fn iter_section<'a>( +&'a self, +section: &'a [u8], +) -> impl Iterator + 'a { +// TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is +// available: +// https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut +struct Peekable { +iter: I, +/// Remember a peeked value, even if it was None. +peeked: Option>, +} + +impl Peekable { +fn new(iter: I) -> Self { +Self { iter, peeked: None } +} + +fn next( self) { +self.peeked = None +} + +fn peek_mut( self) -> Option< I::Item> { +let iter = self.iter; +self.peeked.get_or_insert_with(|| iter.next()).as_mut() +} +} + +// Deduplicate keys redefined in multiple layers +let mut keys_already_seen = HashSet::new(); +let mut key_is_new = +move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool { +keys_already_seen.insert(key) +}; +// This is similar to `flat_map` + `filter_map`, except with a single +// closure that owns `key_is_new` (and therefore the +// `keys_already_seen` set): +let mut layer_iters = Peekable::new( +s
D11899: rhg: Add support for `rhg status --copies`
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Copy sources are collected during `status()` rather than after the fact like in Python, because `status()` takes a `` exclusive reference to the dirstate map (in order to potentially mutate it for directory mtimes) and returns `Cow<'_, HgPath>` that borrow the dirstate map. Even though with `Cow` only some shared borrows remain, the still extend the same lifetime of the initial `` so the dirstate map cannot be borrowed again to access copy sources after the fact: https://doc.rust-lang.org/nomicon/lifetime-mismatch.html#limits-of-lifetimes Additionally, collecting copy sources during the dirstate tree traversal that `status()` already does avoids the cost of another traversal or other lookups (though I haven’t benchmarked that cost). REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11899 AFFECTED FILES rust/hg-core/src/dirstate/status.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/status.rs rust/hg-cpython/src/dirstate/status.rs rust/rhg/src/commands/status.rs tests/test-rename-dir-merge.t tests/test-status.t CHANGE DETAILS diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -9,10 +9,6 @@ > EOF #endif -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init repo1 $ cd repo1 $ mkdir a b a/1 b/1 b/2 @@ -223,7 +219,7 @@ ? unknown hg status -n: - $ env RHG_STATUS=1 RHG_ON_UNSUPPORTED=abort hg status -n + $ env RHG_ON_UNSUPPORTED=abort hg status -n added removed deleted diff --git a/tests/test-rename-dir-merge.t b/tests/test-rename-dir-merge.t --- a/tests/test-rename-dir-merge.t +++ b/tests/test-rename-dir-merge.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init t $ cd t diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -13,6 +13,7 @@ use hg; use hg::config::Config; use hg::dirstate::has_exec_bit; +use hg::dirstate::status::StatusPath; use hg::dirstate::TruncatedTimestamp; use hg::dirstate::RANGE_MASK_31BIT; use hg::errors::{HgError, IoResultExt}; @@ -23,7 +24,7 @@ use hg::utils::files::get_bytes_from_os_string; use hg::utils::files::get_path_from_bytes; use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; -use hg::{HgPathCow, StatusOptions}; +use hg::StatusOptions; use log::{info, warn}; use std::io; use std::path::PathBuf; @@ -89,6 +90,12 @@ .long("--ignored"), ) .arg( +Arg::with_name("copies") +.help("show source of copied files (DEFAULT: ui.statuscopies)") +.short("-C") +.long("--copies"), +) +.arg( Arg::with_name("no-status") .help("hide status prefix") .short("-n") @@ -174,7 +181,8 @@ let ui = invocation.ui; let config = invocation.config; let args = invocation.subcommand_args; -let display_states = if args.is_present("all") { +let all = args.is_present("all"); +let display_states = if all { // TODO when implementing `--quiet`: it excludes clean files // from `--all` ALL_DISPLAY_STATES @@ -195,6 +203,9 @@ } }; let no_status = args.is_present("no-status"); +let list_copies = all +|| args.is_present("copies") +|| config.get_bool(b"ui", b"statuscopies")?; let repo = invocation.repo?; @@ -213,6 +224,7 @@ list_clean: display_states.clean, list_unknown: display_states.unknown, list_ignored: display_states.ignored, +list_copies, collect_traversed_dirs: false, }; let (mut ds_status, pattern_warnings) = dmap.status( @@ -231,7 +243,7 @@ if !ds_status.unsure.is_empty() { info!( "Files to be rechecked by retrieval from filelog: {:?}", -_status.unsure +ds_status.unsure.iter().map(|s| ).collect::>() ); } let mut fixup = Vec::new(); @@ -243,7 +255,7 @@ CommandError::from((e, &*format!("{:x}", p1.short( })?; for to_check in ds_status.unsure { -if unsure_is_modified(repo, , _check)? { +if unsure_is_modified(repo, , _check.path)? { if display_states.modified { ds_status.modified.push(to_check); } @@ -251,7 +263,7 @@ if display_states.clean {
D11900: tests: add a short `sleep` in test-status.t
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY With dirstate-v2 and rhg both enabled, this test would sometimes fail for me with: --- tests/test-status.t +++ tests/test-status.t#dirstate-v2.err @@ -943,7 +943,7 @@ $ rm subdir/unknown $ hg status $ hg debugdirstate --all --no-dates | grep '^ ' - 0 -1 set subdir + 0 -1 unset subdir Meaning that `status` did not write a directory mtime in the dirstate as expected. This can happen if the observed mtime of the directory is the same as "current time" at the start of `status`. This current time is obtained by creating a temporary file and checking its mtime. Even with ext4 on my system being able to store nanosecond precision, identical mtime for successive but separate operations is still possible becuse the kernel may cache the current time: https://stackoverflow.com/a/14393315/1162888 0.1 second should be enough for this cache to be updated, without significantly slowing down the test. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11900 AFFECTED FILES tests/test-status.t CHANGE DETAILS diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -941,6 +941,7 @@ Now the directory is eligible for caching, so its mtime is save in the dirstate $ rm subdir/unknown + $ sleep 0.1 # ensure the kernel’s internal clock for mtimes has ticked $ hg status $ hg debugdirstate --all --no-dates | grep '^ ' 0 -1 set subdir To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11898: rhg: refactor relativize_path into a struct + method
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY … instead of a function that takes an iterator and a callback. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11898 AFFECTED FILES rust/rhg/src/commands/files.rs rust/rhg/src/commands/status.rs rust/rhg/src/utils/path_utils.rs CHANGE DETAILS diff --git a/rust/rhg/src/utils/path_utils.rs b/rust/rhg/src/utils/path_utils.rs --- a/rust/rhg/src/utils/path_utils.rs +++ b/rust/rhg/src/utils/path_utils.rs @@ -3,8 +3,6 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -use crate::error::CommandError; -use crate::ui::UiError; use hg::errors::HgError; use hg::repo::Repo; use hg::utils::current_dir; @@ -13,37 +11,45 @@ use hg::utils::hg_path::HgPathBuf; use std::borrow::Cow; -pub fn relativize_paths( -repo: , -paths: impl IntoIterator, HgError>>, -mut callback: impl FnMut(Cow<[u8]>) -> Result<(), UiError>, -) -> Result<(), CommandError> { -let cwd = current_dir()?; -let repo_root = repo.working_directory_path(); -let repo_root = cwd.join(repo_root); // Make it absolute -let repo_root_hgpath = -HgPathBuf::from(get_bytes_from_path(repo_root.to_owned())); -let outside_repo: bool; -let cwd_hgpath: HgPathBuf; +pub struct RelativizePaths { +repo_root: HgPathBuf, +cwd: HgPathBuf, +outside_repo: bool, +} + +impl RelativizePaths { +pub fn new(repo: ) -> Result { +let cwd = current_dir()?; +let repo_root = repo.working_directory_path(); +let repo_root = cwd.join(repo_root); // Make it absolute +let repo_root_hgpath = +HgPathBuf::from(get_bytes_from_path(repo_root.to_owned())); -if let Ok(cwd_relative_to_repo) = cwd.strip_prefix(_root) { -// The current directory is inside the repo, so we can work with -// relative paths -outside_repo = false; -cwd_hgpath = -HgPathBuf::from(get_bytes_from_path(cwd_relative_to_repo)); -} else { -outside_repo = true; -cwd_hgpath = HgPathBuf::from(get_bytes_from_path(cwd)); +if let Ok(cwd_relative_to_repo) = cwd.strip_prefix(_root) { +// The current directory is inside the repo, so we can work with +// relative paths +Ok(Self { +repo_root: repo_root_hgpath, +cwd: HgPathBuf::from(get_bytes_from_path( +cwd_relative_to_repo, +)), +outside_repo: false, +}) +} else { +Ok(Self { +repo_root: repo_root_hgpath, +cwd: HgPathBuf::from(get_bytes_from_path(cwd)), +outside_repo: true, +}) +} } -for file in paths { -if outside_repo { -let file = repo_root_hgpath.join(file?.as_ref()); -callback(relativize_path(, _hgpath))?; +pub fn relativize<'a>(, path: &'a HgPath) -> Cow<'a, [u8]> { +if self.outside_repo { +let joined = self.repo_root.join(path); +Cow::Owned(relativize_path(, ).into_owned()) } else { -callback(relativize_path(file?.as_ref(), _hgpath))?; +relativize_path(path, ) } } -Ok(()) } diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -7,7 +7,7 @@ use crate::error::CommandError; use crate::ui::Ui; -use crate::utils::path_utils::relativize_paths; +use crate::utils::path_utils::RelativizePaths; use clap::{Arg, SubCommand}; use format_bytes::format_bytes; use hg; @@ -261,9 +261,12 @@ .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); let output = DisplayStatusPaths { ui, -repo, no_status, -relative_paths, +relativize: if relative_paths { +Some(RelativizePaths::new(repo)?) +} else { +None +}, }; if display_states.modified { output.display(b"M", ds_status.modified)?; @@ -379,9 +382,8 @@ struct DisplayStatusPaths<'a> { ui: &'a Ui, -repo: &'a Repo, no_status: bool, -relative_paths: bool, +relativize: Option, } impl DisplayStatusPaths<'_> { @@ -393,27 +395,24 @@ mut paths: Vec, ) -> Result<(), CommandError> { paths.sort_unstable(); -let print_path = |path: &[u8]| { +for path in paths { +let relative; +let path = if let Some(relativize) = { +relative = relativize.relativize(); +&*relative +} else { +
D11897: rhg: refactor display_status_paths with a struct for common arguments
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11897 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -255,75 +255,36 @@ } } } +let relative_paths = (!ui.plain()) +&& config +.get_option(b"commands", b"status.relative")? +.unwrap_or(config.get_bool(b"ui", b"relative-paths")?); +let output = DisplayStatusPaths { +ui, +repo, +no_status, +relative_paths, +}; if display_states.modified { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.modified, -b"M", -)?; +output.display(b"M", ds_status.modified)?; } if display_states.added { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.added, -b"A", -)?; +output.display(b"A", ds_status.added)?; } if display_states.removed { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.removed, -b"R", -)?; +output.display(b"R", ds_status.removed)?; } if display_states.deleted { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.deleted, -b"!", -)?; +output.display(b"!", ds_status.deleted)?; } if display_states.unknown { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.unknown, -b"?", -)?; +output.display(b"?", ds_status.unknown)?; } if display_states.ignored { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.ignored, -b"I", -)?; +output.display(b"I", ds_status.ignored)?; } if display_states.clean { -display_status_paths( -ui, -repo, -config, -no_status, - ds_status.clean, -b"C", -)?; +output.display(b"C", ds_status.clean)?; } let mut dirstate_write_needed = ds_status.dirty; @@ -416,41 +377,47 @@ ignore_files } -// Probably more elegant to use a Deref or Borrow trait rather than -// harcode HgPathBuf, but probably not really useful at this point -fn display_status_paths( -ui: , -repo: , -config: , +struct DisplayStatusPaths<'a> { +ui: &'a Ui, +repo: &'a Repo, no_status: bool, -paths: [HgPathCow], -status_prefix: &[u8], -) -> Result<(), CommandError> { -paths.sort_unstable(); -let mut relative: bool = config.get_bool(b"ui", b"relative-paths")?; -relative = config -.get_option(b"commands", b"status.relative")? -.unwrap_or(relative); -let print_path = |path: &[u8]| { -// TODO optim, probably lots of unneeded copies here, especially -// if out stream is buffered -if no_status { -ui.write_stdout(_bytes!(b"{}\n", path)) +relative_paths: bool, +} + +impl DisplayStatusPaths<'_> { +// Probably more elegant to use a Deref or Borrow trait rather than +// harcode HgPathBuf, but probably not really useful at this point +fn display( +, +status_prefix: &[u8], +mut paths: Vec, +) -> Result<(), CommandError> { +paths.sort_unstable(); +let print_path = |path: &[u8]| { +// TODO optim, probably lots of unneeded copies here, especially +// if out stream is buffered +if self.no_status { +self.ui.write_stdout(_bytes!(b"{}\n", path)) +} else { +self.ui.write_stdout(_bytes!( +b"{} {}\n", +status_prefix, +path +)) +} +}; + +if self.relative_paths { +relativize_paths(self.repo, paths.iter().map(Ok), |path| { +print_path() +})?; } else { -ui.write_stdout(_bytes!(b"{} {}\n", status_prefix, pat
D11890: rhg: Add support for ui.ignore and ui.ignore.* config
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This fixes some but not all failures in `tests/test-hgignore.t` when running with `rhg status` enabled. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11890 AFFECTED FILES rust/hg-core/src/config/config.rs rust/hg-core/src/config/layer.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -21,10 +21,12 @@ use hg::matchers::AlwaysMatcher; use hg::repo::Repo; use hg::utils::files::get_bytes_from_os_string; +use hg::utils::files::get_path_from_bytes; use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; use hg::{HgPathCow, StatusOptions}; use log::{info, warn}; use std::io; +use std::path::PathBuf; pub const HELP_TEXT: = " Show changed files in the working directory @@ -213,11 +215,10 @@ list_ignored: display_states.ignored, collect_traversed_dirs: false, }; -let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded let (mut ds_status, pattern_warnings) = dmap.status( , repo.working_directory_path().to_owned(), -vec![ignore_file], +ignore_files(repo, config), options, )?; if !pattern_warnings.is_empty() { @@ -396,6 +397,25 @@ Ok(()) } +fn ignore_files(repo: , config: ) -> Vec { +let mut ignore_files = Vec::new(); +let repo_ignore = repo.working_directory_vfs().join(".hgignore"); +if repo_ignore.exists() { +ignore_files.push(repo_ignore) +} +for (key, value) in config.iter_section(b"ui") { +if key == b"ignore" || key.starts_with(b"ignore.") { +let path = get_path_from_bytes(value); +// TODO: expand "~/" and environment variable here, like Python +// does with `os.path.expanduser` and `os.path.expandvars` + +let joined = repo.working_directory_path().join(path); +ignore_files.push(joined); +} +} +ignore_files +} + // Probably more elegant to use a Deref or Borrow trait rather than // harcode HgPathBuf, but probably not really useful at this point fn display_status_paths( diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -127,6 +127,17 @@ .flat_map(|section| section.keys().map(|vec| &**vec)) } +/// Returns the (key, value) pairs defined in the given section +pub fn iter_section<'layer>( +&'layer self, +section: &[u8], +) -> impl Iterator { +self.sections +.get(section) +.into_iter() +.flat_map(|section| section.iter().map(|(k, v)| (&**k, &*v.bytes))) +} + /// Returns whether any key is defined in the given section pub fn has_non_empty_section(, section: &[u8]) -> bool { self.sections diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -419,6 +419,35 @@ .any(|layer| layer.has_non_empty_section(section)) } +/// Yields (key, value) pairs for everything in the given section +pub fn iter_section<'a>( +&'a self, +section: &'a [u8], +) -> impl Iterator + 'a { +// Deduplicate keys redefined in multiple layers +let mut keys_already_seen = HashSet::new(); +let mut key_is_new = +move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool { +keys_already_seen.insert(key) +}; +// This is similar to `flat_map` + `filter_map`, except with a single +// closure that owns `key_is_new` (and therefore the +// `keys_already_seen` set): +let mut layer_iters = self +.layers +.iter() +.rev() +.map(move |layer| layer.iter_section(section)) +.peekable(); +std::iter::from_fn(move || loop { +if let Some(pair) = layer_iters.peek_mut()?.find( key_is_new) { +return Some(pair); +} else { +layer_iters.next(); +} + }) +} + /// Get raw values bytes from all layers (even untrusted ones) in order /// of precedence. #[cfg(test)] To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11889: rhg: Set second_ambiguous as needed in post-status fixup
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This fixes an intermittent bug that manifested only in test-revert.t, and unfortunately not on CI. On a fast enough machine we could have: 1. A file is modified 2. `rhg status` writes an updated dirstate-v1 3. The same file is modified again … all within the same integer second. Because the dirstate-v1 file format does not store sub-second precision, step 2 must write the file’s mtime as "unknown" because of the possibility of step 3. However, most of the code now handles timestamps with nanosecond precision in order to take advantage of it in dirstate-v2. `second_ambiguous` must be set for timestamps that become ambiguous if sub-second precision is dropped (such as through serialization in dirstate-v1 format). REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11889 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -352,9 +352,13 @@ let fs_metadata = repo .working_directory_vfs() .symlink_metadata(_path)?; -let mtime = TruncatedTimestamp::for_mtime_of(_metadata) -.when_reading_file(_path)?; -if mtime.is_reliable_mtime(_boundary) { +if let Some(mtime) = +TruncatedTimestamp::for_reliable_mtime_of( +_metadata, +_boundary, +) +.when_reading_file(_path)? +{ let mode = fs_metadata.mode(); let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT; let mut entry = dmap diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -87,6 +87,10 @@ } } +/// Returns a `TruncatedTimestamp` for the modification time of `metadata`. +/// +/// Propagates errors from `std` on platforms where modification time +/// is not available at all. pub fn for_mtime_of(metadata: ::Metadata) -> io::Result { #[cfg(unix)] { @@ -102,13 +106,18 @@ } } -/// Returns whether this timestamp is reliable as the "mtime" of a file. +/// Like `for_mtime_of`, but may return `None` or a value with +/// `second_ambiguous` set if the mtime is not "reliable". /// /// A modification time is reliable if it is older than `boundary` (or /// sufficiently in the future). /// /// Otherwise a concurrent modification might happens with the same mtime. -pub fn is_reliable_mtime(, boundary: ) -> bool { +pub fn for_reliable_mtime_of( +metadata: ::Metadata, +boundary: , +) -> io::Result> { +let mut mtime = Self::for_mtime_of(metadata)?; // If the mtime of the ambiguous file is younger (or equal) to the // starting point of the `status` walk, we cannot garantee that // another, racy, write will not happen right after with the same mtime @@ -118,16 +127,23 @@ // mismatch between the current clock and previous file system // operation. So mtime more than one days in the future are considered // fine. -if self.truncated_seconds == boundary.truncated_seconds { -self.nanoseconds != 0 +let reliable = if mtime.truncated_seconds == boundary.truncated_seconds +{ +mtime.second_ambiguous = true; +mtime.nanoseconds != 0 && boundary.nanoseconds != 0 -&& self.nanoseconds < boundary.nanoseconds +&& mtime.nanoseconds < boundary.nanoseconds } else { // `truncated_seconds` is less than 2**31, // so this does not overflow `u32`: let one_day_later = boundary.truncated_seconds + 24 * 3600; -self.truncated_seconds < boundary.truncated_seconds -|| self.truncated_seconds > one_day_later +mtime.truncated_seconds < boundary.truncated_seconds +|| mtime.truncated_seconds > one_day_later +}; +if reliable { + Ok(Some(mtime)) +} else { +Ok(None) } } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11888: dirstate: Document Timestamp.second_ambiguous
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11888 AFFECTED FILES mercurial/dirstateutils/timestamp.py CHANGE DETAILS diff --git a/mercurial/dirstateutils/timestamp.py b/mercurial/dirstateutils/timestamp.py --- a/mercurial/dirstateutils/timestamp.py +++ b/mercurial/dirstateutils/timestamp.py @@ -21,13 +21,16 @@ A Unix timestamp with optional nanoseconds precision, modulo 2**31 seconds. -A 2-tuple containing: +A 3-tuple containing: `truncated_seconds`: seconds since the Unix epoch, truncated to its lower 31 bits `subsecond_nanoseconds`: number of nanoseconds since `truncated_seconds`. When this is zero, the sub-second precision is considered unknown. + +`second_ambiguous`: whether this timestamp is still "reliable" +(see `reliable_mtime_of`) if we drop its sub-second component. """ def __new__(cls, value): @@ -93,7 +96,8 @@ def reliable_mtime_of(stat_result, present_mtime): -"""same as `mtime_of`, but return None if the date might be ambiguous +"""Same as `mtime_of`, but return `None` or a `Timestamp` with +`second_ambiguous` set if the date might be ambiguous. A modification time is reliable if it is older than "present_time" (or sufficiently in the future). To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11840: rhg: Update the dirstate on disk after status
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11840 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/rhg/src/commands/status.rs tests/test-backout.t tests/test-dirstate-race2.t tests/test-dirstate.t CHANGE DETAILS diff --git a/tests/test-dirstate.t b/tests/test-dirstate.t --- a/tests/test-dirstate.t +++ b/tests/test-dirstate.t @@ -9,10 +9,6 @@ > EOF #endif -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - -- Test dirstate._dirs refcounting $ hg init t diff --git a/tests/test-dirstate-race2.t b/tests/test-dirstate-race2.t --- a/tests/test-dirstate-race2.t +++ b/tests/test-dirstate-race2.t @@ -9,9 +9,6 @@ > EOF #endif -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - Checking the size/permissions/file-type of files stored in the dirstate after an update where the files are changed concurrently outside of hg's control. diff --git a/tests/test-backout.t b/tests/test-backout.t --- a/tests/test-backout.t +++ b/tests/test-backout.t @@ -172,8 +172,7 @@ $ hg status -A C c $ hg debugstate --no-dates - n 644 12 set c (no-rhg !) - n 0 -1 unset c (rhg known-bad-output !) + n 644 12 set c $ hg backout -d '6 0' -m 'to be rollback-ed soon' -r . removing c adding b diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -13,14 +13,18 @@ use hg; use hg::config::Config; use hg::dirstate::has_exec_bit; -use hg::errors::HgError; +use hg::dirstate::TruncatedTimestamp; +use hg::dirstate::RANGE_MASK_31BIT; +use hg::errors::{HgError, IoResultExt}; +use hg::lock::LockError; use hg::manifest::Manifest; use hg::matchers::AlwaysMatcher; use hg::repo::Repo; use hg::utils::files::get_bytes_from_os_string; -use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; +use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; use hg::{HgPathCow, StatusOptions}; use log::{info, warn}; +use std::io; pub const HELP_TEXT: = " Show changed files in the working directory @@ -222,6 +226,7 @@ _status.unsure ); } +let mut fixup = Vec::new(); if !ds_status.unsure.is_empty() && (display_states.modified || display_states.clean) { @@ -230,14 +235,17 @@ CommandError::from((e, &*format!("{:x}", p1.short( })?; for to_check in ds_status.unsure { -if unsure_is_modified(repo, , _check)? { +if let Some((mode, size, mtime)) = +unsure_is_clean(repo, , _check)? +{ +if display_states.clean { +ds_status.clean.push(to_check.clone()); +} +fixup.push((to_check.into_owned(), mode, size, mtime)) +} else { if display_states.modified { ds_status.modified.push(to_check); } -} else { -if display_states.clean { -ds_status.clean.push(to_check); -} } } } @@ -311,6 +319,38 @@ b"C", )?; } +if !fixup.is_empty() { +// Update the dirstate on disk if we can +let with_lock_result = +repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> { +for (path, mode, size, mtime) in fixup { +let mut entry = dmap +.get()? +.expect("ambiguous file not in dirstate"); +entry.set_clean(mode, size, mtime); +dmap.add_file(, entry)? +} +drop(dmap); // Avoid "already mutably borrowed" RefCell panics +repo.write_dirstate()?; +Ok(()) +}); +match with_lock_result { +Ok(closure_result) => closure_result?, +Err(LockError::AlreadyHeld) => { +// Not updating the dirstate is not ideal but not critical: +// don’t keep our caller waiting until some other Mercurial process releases the lock. +} +Err(LockError::Other(HgError::IoError { error, .. })) +if error.kind() == io::ErrorKind::PermissionDenied => +{ +// `hg status` on a read-only repository is fine +} +Err(LockError::Other(error)) => { +// Report other I/O errors +Err(error)? +} +} +} Ok(()) } @@ -355,13 +395,15 @@ /// /// This meant to be used
D11839: rhg: Add Repo::write_dirstate
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This method is not used yet. It saves to disk any mutation that was done to the `Repo`’s dirstate through `Repo::dirstate_map_mut`. It takes care of dirstate-v1 v.s. dirstate-v2, dockets, data files, appending when possible, etc. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11839 AFFECTED FILES rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-core/src/repo.rs rust/hg-core/src/revlog/node.rs rust/hg-cpython/src/dirstate/dirstate_map.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -222,7 +222,7 @@ match result { Ok((packed, tree_metadata, append)) => { let packed = PyBytes::new(py, ); -let tree_metadata = PyBytes::new(py, _metadata); +let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes()); let tuple = (packed, tree_metadata, append); Ok(tuple.to_py_object(py).into_object()) }, diff --git a/rust/hg-core/src/revlog/node.rs b/rust/hg-core/src/revlog/node.rs --- a/rust/hg-core/src/revlog/node.rs +++ b/rust/hg-core/src/revlog/node.rs @@ -174,6 +174,12 @@ data: self.data, } } + +pub fn pad_to_256_bits() -> [u8; 32] { +let mut bits = [0; 32]; +bits[..NODE_BYTES_LENGTH].copy_from_slice(); +bits +} } /// The beginning of a binary revision SHA. diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -2,9 +2,10 @@ use crate::config::{Config, ConfigError, ConfigParseError}; use crate::dirstate::DirstateParents; use crate::dirstate_tree::dirstate_map::DirstateMap; +use crate::dirstate_tree::on_disk::Docket as DirstateDocket; use crate::dirstate_tree::owning::OwningDirstateMap; -use crate::errors::HgError; use crate::errors::HgResultExt; +use crate::errors::{HgError, IoResultExt}; use crate::exit_codes; use crate::lock::{try_with_lock_no_wait, LockError}; use crate::manifest::{Manifest, Manifestlog}; @@ -16,8 +17,13 @@ use crate::vfs::{is_dir, is_file, Vfs}; use crate::{requirements, NodePrefix}; use crate::{DirstateError, Revision}; +use rand::Rng; use std::cell::{Ref, RefCell, RefMut}; use std::collections::HashSet; +use std::fmt::Write; +use std::io::Seek; +use std::io::SeekFrom; +use std::io::Write as IoWrite; use std::path::{Path, PathBuf}; /// A repository on disk @@ -408,6 +414,79 @@ pub fn filelog(, path: ) -> Result { Filelog::open(self, path) } + +/// Write to disk any updates that were made through `dirstate_map_mut`. +/// +/// The "wlock" must be held while calling this. +/// See for example `try_with_wlock_no_wait`. +/// +/// TODO: have a `WritableRepo` type only accessible while holding the +/// lock? +pub fn write_dirstate() -> Result<(), DirstateError> { +let map = self.dirstate_map()?; +// TODO: Maintain a `DirstateMap::dirty` flag, and return early here if +// it’s unset +let parents = self.dirstate_parents()?; +let packed_dirstate = if self.has_dirstate_v2() { +let uuid = self.dirstate_data_file_uuid.get_or_init(self)?; +let mut uuid = uuid.as_ref(); +let can_append = uuid.is_some(); +let (data, tree_metadata, append) = map.pack_v2(can_append)?; +if !append { +uuid = None +} +let uuid = if let Some(uuid) = uuid { +std::str::from_utf8(uuid) +.map_err(|_| { +HgError::corrupted("non-UTF-8 dirstate data file ID") +})? +.to_owned() +} else { +const ID_LENGTH: usize = 8; +let mut id = String::with_capacity(ID_LENGTH); +let mut rng = rand::thread_rng(); +for _ in 0..ID_LENGTH { +// One random hexadecimal digit. +// `unwrap` never panics because `impl Write for String` +// never returns an error. +write!( id, "{:x}", rng.gen_range(0, 16)).unwrap(); +} +id +}; +let data_filename = format!("dirstate.{}", uuid); +let data_filename = self.hg_vfs().join(data_filename); +let mut options = std::fs::OpenOptions::new(); +if append { +options.append(true); +} else { +options.wri
D11835: rhg: Initial repository locking
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Initial Rust implementation of locking based on the `.hg/wlock` symlink (or file), with lock breaking when the recorded pid and hostname show that a lock was left by a local process that is not running anymore (as it might have been killed). REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11835 AFFECTED FILES rust/hg-core/src/lib.rs rust/hg-core/src/lock.rs rust/hg-core/src/repo.rs rust/hg-core/src/utils.rs rust/hg-core/src/vfs.rs CHANGE DETAILS diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs --- a/rust/hg-core/src/vfs.rs +++ b/rust/hg-core/src/vfs.rs @@ -87,6 +87,26 @@ std::fs::rename(, ) .with_context(|| IoErrorContext::RenamingFile { from, to }) } + +pub fn remove_file( +, +relative_path: impl AsRef, +) -> Result<(), HgError> { +let path = self.join(relative_path); +std::fs::remove_file() +.with_context(|| IoErrorContext::RemovingFile(path)) +} + +#[cfg(unix)] +pub fn create_symlink( +, +relative_link_path: impl AsRef, +target_path: impl AsRef, +) -> Result<(), HgError> { +let link_path = self.join(relative_link_path); +std::os::unix::fs::symlink(target_path, _path) +.with_context(|| IoErrorContext::WritingFile(link_path)) +} } fn fs_metadata( diff --git a/rust/hg-core/src/utils.rs b/rust/hg-core/src/utils.rs --- a/rust/hg-core/src/utils.rs +++ b/rust/hg-core/src/utils.rs @@ -145,6 +145,21 @@ } } +pub trait StrExt { +// TODO: Use https://doc.rust-lang.org/nightly/std/primitive.str.html#method.split_once +// once we require Rust 1.52+ +fn split_2(, separator: char) -> Option<(, )>; +} + +impl StrExt for str { +fn split_2(, separator: char) -> Option<(, )> { +let mut iter = self.splitn(2, separator); +let a = iter.next()?; +let b = iter.next()?; +Some((a, b)) +} +} + pub trait Escaped { /// Return bytes escaped for display to the user fn escaped_bytes() -> Vec; diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -6,6 +6,7 @@ use crate::errors::HgError; use crate::errors::HgResultExt; use crate::exit_codes; +use crate::lock::{try_with_lock_no_wait, LockError}; use crate::manifest::{Manifest, Manifestlog}; use crate::revlog::filelog::Filelog; use crate::revlog::revlog::RevlogError; @@ -243,6 +244,13 @@ } } +pub fn try_with_wlock_no_wait( +, +f: impl FnOnce() -> R, +) -> Result { +try_with_lock_no_wait(self.hg_vfs(), "wlock", f) +} + pub fn has_dirstate_v2() -> bool { self.requirements .contains(requirements::DIRSTATE_V2_REQUIREMENT) diff --git a/rust/hg-core/src/lock.rs b/rust/hg-core/src/lock.rs new file mode 100644 --- /dev/null +++ b/rust/hg-core/src/lock.rs @@ -0,0 +1,182 @@ +//! Filesystem-based locks for local repositories + +use crate::errors::HgError; +use crate::errors::HgResultExt; +use crate::utils::StrExt; +use crate::vfs::Vfs; +use std::io; +use std::io::ErrorKind; + +#[derive(derive_more::From)] +pub enum LockError { +AlreadyHeld, +#[from] +Other(HgError), +} + +/// Try to call `f` with the lock acquired, without waiting. +/// +/// If the lock is aready held, `f` is not called and `LockError::AlreadyHeld` +/// is returned. `LockError::Io` is returned for any unexpected I/O error +/// accessing the lock file, including for removing it after `f` was called. +/// The return value of `f` is dropped in that case. If all is successful, the +/// return value of `f` is forwarded. +pub fn try_with_lock_no_wait( +hg_vfs: Vfs, +lock_filename: , +f: impl FnOnce() -> R, +) -> Result { +let our_lock_data = &*OUR_LOCK_DATA; +for _retry in 0..5 { +match make_lock(hg_vfs, lock_filename, our_lock_data) { +Ok(()) => { +let result = f(); +unlock(hg_vfs, lock_filename)?; +return Ok(result); +} +Err(HgError::IoError { error, .. }) +if error.kind() == ErrorKind::AlreadyExists => +{ +let lock_data = read_lock(hg_vfs, lock_filename)?; +if lock_data.is_none() { +// Lock was apparently just released, retry acquiring it +continue; +} +if !lock_should_be_broken(_data) { +return Err(LockError::AlreadyHeld); +} +// The lock file is left over from a process not running +// anymore. Break it, but with another lock to +
D11834: rust: Serializing a DirstateMap does not mutate it anymore
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11834 AFFECTED FILES rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-cpython/src/dirstate/dirstate_map.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -195,7 +195,7 @@ p1: PyObject, p2: PyObject, ) -> PyResult { -let mut inner = self.inner(py).borrow_mut(); +let inner = self.inner(py).borrow(); let parents = DirstateParents { p1: extract_node_id(py, )?, p2: extract_node_id(py, )?, @@ -217,7 +217,7 @@ , can_append: bool, ) -> PyResult { -let mut inner = self.inner(py).borrow_mut(); +let inner = self.inner(py).borrow(); let result = inner.pack_v2(can_append); match result { Ok((packed, tree_metadata, append)) => { diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -549,7 +549,7 @@ /// `dirstate_map.on_disk` (true), instead of written to a new data file /// (false). pub(super) fn write( -dirstate_map: DirstateMap, +dirstate_map: , can_append: bool, ) -> Result<(Vec, Vec, bool), DirstateError> { let append = can_append && dirstate_map.write_should_append(); diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -909,10 +909,10 @@ #[timed] pub fn pack_v1( - self, +, parents: DirstateParents, ) -> Result, DirstateError> { -let map = self.get_map_mut(); +let map = self.get_map(); // Optizimation (to be measured?): pre-compute size to avoid `Vec` // reallocations let mut size = parents.as_bytes().len(); @@ -949,10 +949,10 @@ /// (false). #[timed] pub fn pack_v2( - self, +, can_append: bool, ) -> Result<(Vec, Vec, bool), DirstateError> { -let map = self.get_map_mut(); +let map = self.get_map(); on_disk::write(map, can_append) } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11836: rust: Add Vfs::write_atomic
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This method writes to a temporary file then renames in place REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11836 AFFECTED FILES rust/hg-core/src/errors.rs rust/hg-core/src/vfs.rs CHANGE DETAILS diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs --- a/rust/hg-core/src/vfs.rs +++ b/rust/hg-core/src/vfs.rs @@ -1,6 +1,6 @@ use crate::errors::{HgError, IoErrorContext, IoResultExt}; use memmap2::{Mmap, MmapOptions}; -use std::io::ErrorKind; +use std::io::{ErrorKind, Write}; use std::path::{Path, PathBuf}; /// Filesystem access abstraction for the contents of a given "base" diretory @@ -105,7 +105,28 @@ ) -> Result<(), HgError> { let link_path = self.join(relative_link_path); std::os::unix::fs::symlink(target_path, _path) -.with_context(|| IoErrorContext::WritingFile(link_path)) +.when_writing_file(_path) +} + +/// Write `contents` into a temporary file, then rename to `relative_path`. +/// This makes writing to a file "atomic": a reader opening that path will +/// see either the previous contents of the file or the complete new +/// content, never a partial write. +pub fn atomic_write( +, +relative_path: impl AsRef, +contents: &[u8], +) -> Result<(), HgError> { +let mut tmp = tempfile::NamedTempFile::new_in(self.base) +.when_writing_file(self.base)?; +tmp.write_all(contents) +.and_then(|()| tmp.flush()) +.when_writing_file(tmp.path())?; +let path = self.join(relative_path); +tmp.persist() +.map_err(|e| e.error) +.when_writing_file()?; +Ok(()) } } diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs --- a/rust/hg-core/src/errors.rs +++ b/rust/hg-core/src/errors.rs @@ -151,6 +151,8 @@ /// Converts a `Result` with `std::io::Error` into one with `HgError`. fn when_reading_file(self, path: ::path::Path) -> Result; +fn when_writing_file(self, path: ::path::Path) -> Result; + fn with_context( self, context: impl FnOnce() -> IoErrorContext, @@ -162,6 +164,10 @@ self.with_context(|| IoErrorContext::ReadingFile(path.to_owned())) } +fn when_writing_file(self, path: ::path::Path) -> Result { +self.with_context(|| IoErrorContext::WritingFile(path.to_owned())) +} + fn with_context( self, context: impl FnOnce() -> IoErrorContext, To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11837: rhg: Make Repo::dirstate_parents a LazyCell
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Unify with the same abstraction used for other lazily-initialized components REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11837 AFFECTED FILES rust/hg-core/src/repo.rs CHANGE DETAILS diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -16,7 +16,7 @@ use crate::vfs::{is_dir, is_file, Vfs}; use crate::{requirements, NodePrefix}; use crate::{DirstateError, Revision}; -use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::HashSet; use std::path::{Path, PathBuf}; @@ -27,8 +27,7 @@ store: PathBuf, requirements: HashSet, config: Config, -// None means not known/initialized yet -dirstate_parents: Cell>, +dirstate_parents: LazyCell, dirstate_map: LazyCell, changelog: LazyCell, manifestlog: LazyCell, @@ -203,7 +202,7 @@ store: store_path, dot_hg, config: repo_config, -dirstate_parents: Cell::new(None), +dirstate_parents: LazyCell::new(Self::read_dirstate_parents), dirstate_map: LazyCell::new(Self::new_dirstate_map), changelog: LazyCell::new(Changelog::open), manifestlog: LazyCell::new(Manifestlog::open), @@ -265,9 +264,10 @@ } pub fn dirstate_parents() -> Result { -if let Some(parents) = self.dirstate_parents.get() { -return Ok(parents); -} +Ok(*self.dirstate_parents.get_or_init(self)?) +} + +fn read_dirstate_parents() -> Result { let dirstate = self.dirstate_file_contents()?; let parents = if dirstate.is_empty() { DirstateParents::NULL @@ -277,20 +277,20 @@ crate::dirstate::parsers::parse_dirstate_parents()? .clone() }; -self.dirstate_parents.set(Some(parents)); +self.dirstate_parents.set(parents); Ok(parents) } fn new_dirstate_map() -> Result { let dirstate_file_contents = self.dirstate_file_contents()?; if dirstate_file_contents.is_empty() { -self.dirstate_parents.set(Some(DirstateParents::NULL)); +self.dirstate_parents.set(DirstateParents::NULL); Ok(OwningDirstateMap::new_empty(Vec::new())) } else if self.has_dirstate_v2() { let docket = crate::dirstate_tree::on_disk::read_docket( _file_contents, )?; -self.dirstate_parents.set(Some(docket.parents())); +self.dirstate_parents.set(docket.parents()); let data_size = docket.data_size(); let metadata = docket.tree_metadata(); let mut map = if let Some(data_mmap) = self @@ -310,7 +310,7 @@ let (on_disk, placeholder) = map.get_pair_mut(); let (inner, parents) = DirstateMap::new_v1(on_disk)?; self.dirstate_parents -.set(Some(parents.unwrap_or(DirstateParents::NULL))); +.set(parents.unwrap_or(DirstateParents::NULL)); *placeholder = inner; Ok(map) } @@ -394,6 +394,10 @@ } } +fn set(, value: T) { +*self.value.borrow_mut() = Some(value) +} + fn get_or_init(, repo: ) -> Result, E> { let mut borrowed = self.value.borrow(); if borrowed.is_none() { @@ -407,7 +411,7 @@ Ok(Ref::map(borrowed, |option| option.as_ref().unwrap())) } -pub fn get_mut_or_init(, repo: ) -> Result, E> { +fn get_mut_or_init(, repo: ) -> Result, E> { let mut borrowed = self.value.borrow_mut(); if borrowed.is_none() { *borrowed = Some((self.init)(repo)?); To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11815: rhg: Add support for `rhg status -n`
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The `RHG_STATUS=1` bit added here can be removed when `unset RHG_STATUS` near the top of the file is removed. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11815 AFFECTED FILES rust/rhg/src/commands/status.rs tests/test-status.t CHANGE DETAILS diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -222,6 +222,13 @@ ! deleted ? unknown +hg status -n: + $ env RHG_STATUS=1 RHG_ON_UNSUPPORTED=abort hg status -n + added + removed + deleted + unknown + hg status modified added removed deleted unknown never-existed ignored: $ hg status modified added removed deleted unknown never-existed ignored diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -6,9 +6,10 @@ // GNU General Public License version 2 or any later version. use crate::error::CommandError; -use crate::ui::{Ui, UiError}; +use crate::ui::Ui; use crate::utils::path_utils::relativize_paths; use clap::{Arg, SubCommand}; +use format_bytes::format_bytes; use hg; use hg::config::Config; use hg::dirstate::{has_exec_bit, TruncatedTimestamp}; @@ -20,7 +21,6 @@ use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; use hg::{HgPathCow, StatusOptions}; use log::{info, warn}; -use std::borrow::Cow; pub const HELP_TEXT: = " Show changed files in the working directory @@ -82,6 +82,12 @@ .short("-i") .long("--ignored"), ) +.arg( +Arg::with_name("no-status") +.help("hide status prefix") +.short("-n") +.long("--no-status"), +) } /// Pure data type allowing the caller to specify file states to display @@ -182,6 +188,7 @@ requested } }; +let no_status = args.is_present("no-status"); let repo = invocation.repo?; let mut dmap = repo.dirstate_map_mut()?; @@ -240,25 +247,74 @@ } } if display_states.modified { -display_status_paths(ui, repo, config, ds_status.modified, b"M")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.modified, +b"M", +)?; } if display_states.added { -display_status_paths(ui, repo, config, ds_status.added, b"A")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.added, +b"A", +)?; } if display_states.removed { -display_status_paths(ui, repo, config, ds_status.removed, b"R")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.removed, +b"R", +)?; } if display_states.deleted { -display_status_paths(ui, repo, config, ds_status.deleted, b"!")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.deleted, +b"!", +)?; } if display_states.unknown { -display_status_paths(ui, repo, config, ds_status.unknown, b"?")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.unknown, +b"?", +)?; } if display_states.ignored { -display_status_paths(ui, repo, config, ds_status.ignored, b"I")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.ignored, +b"I", +)?; } if display_states.clean { -display_status_paths(ui, repo, config, ds_status.clean, b"C")?; +display_status_paths( +ui, +repo, +config, +no_status, + ds_status.clean, +b"C", +)?; } Ok(()) } @@ -269,6 +325,7 @@ ui: , repo: , config: , +no_status: bool, paths: [HgPathCow], status_prefix: &[u8], ) -> Result<(), CommandError> { @@ -277,23 +334,23 @@ relative = config .get_option(b"commands", b"status.relative")? .unwrap_or(relative); +let print_path = |path: &[u8]| { +// TODO optim, probably lots of unneeded copies here, especially +// if out stream is buffered +if n
D11774: rhg: Fix status desambiguation of symlinks and executable files
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11774 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/vfs.rs rust/rhg/src/commands/status.rs tests/test-execute-bit.t tests/test-merge-exec.t tests/test-merge-types.t tests/test-symlinks.t CHANGE DETAILS diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t --- a/tests/test-symlinks.t +++ b/tests/test-symlinks.t @@ -11,10 +11,6 @@ > EOF #endif -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - == tests added in 0.7 == $ hg init test-symlinks-0.7; cd test-symlinks-0.7; diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t --- a/tests/test-merge-types.t +++ b/tests/test-merge-types.t @@ -1,9 +1,5 @@ #require symlink execbit -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ tellmeabout() { > if [ -h $1 ]; then > echo $1 is a symlink: diff --git a/tests/test-merge-exec.t b/tests/test-merge-exec.t --- a/tests/test-merge-exec.t +++ b/tests/test-merge-exec.t @@ -4,10 +4,6 @@ #require execbit -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - Initial setup == diff --git a/tests/test-execute-bit.t b/tests/test-execute-bit.t --- a/tests/test-execute-bit.t +++ b/tests/test-execute-bit.t @@ -1,9 +1,5 @@ #require execbit -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init $ echo a > a $ hg ci -Am'not executable' diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -11,11 +11,12 @@ use clap::{Arg, SubCommand}; use hg; use hg::config::Config; -use hg::dirstate::TruncatedTimestamp; +use hg::dirstate::{has_exec_bit, TruncatedTimestamp}; use hg::errors::HgError; use hg::manifest::Manifest; use hg::matchers::AlwaysMatcher; use hg::repo::Repo; +use hg::utils::files::get_bytes_from_os_string; use hg::utils::hg_path::{hg_path_to_os_string, HgPath}; use hg::{HgPathCow, StatusOptions}; use log::{info, warn}; @@ -302,16 +303,29 @@ /// /// This meant to be used for those that the dirstate cannot resolve, due /// to time resolution limits. -/// -/// TODO: detect permission bits and similar metadata modifications fn unsure_is_modified( repo: , manifest: , hg_path: , ) -> Result { +let vfs = repo.working_directory_vfs(); +let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); +let fs_metadata = vfs.symlink_metadata(_path)?; +let is_symlink = fs_metadata.file_type().is_symlink(); +let fs_flags = if is_symlink { +Some(b'l') +} else if has_exec_bit(_metadata) { +Some(b'x') +} else { +None +}; + let entry = manifest .find_file(hg_path)? .expect("ambgious file not in p1"); +if entry.flags != fs_flags { +return Ok(true); +} let filelog = repo.filelog(hg_path)?; let filelog_entry = filelog.data_for_node(entry.node_id()?).map_err(|_| { @@ -319,7 +333,10 @@ })?; let contents_in_p1 = filelog_entry.data()?; -let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); -let fs_contents = repo.working_directory_vfs().read(fs_path)?; +let fs_contents = if is_symlink { +get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string()) +} else { +vfs.read(fs_path)? +}; return Ok(contents_in_p1 != &*fs_contents); } diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs --- a/rust/hg-core/src/vfs.rs +++ b/rust/hg-core/src/vfs.rs @@ -16,6 +16,22 @@ self.base.join(relative_path) } +pub fn symlink_metadata( +, +relative_path: impl AsRef, +) -> Result { +let path = self.join(relative_path); +std::fs::symlink_metadata().when_reading_file() +} + +pub fn read_link( +, +relative_path: impl AsRef, +) -> Result { +let path = self.join(relative_path); +std::fs::read_link().when_reading_file() +} + pub fn read( , relative_path: impl AsRef, diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -580,10 +580,8 @@ , filesystem_metadata: ::fs::Metadata, ) -> bool { -use std::os::unix::fs::MetadataExt; -const EXEC_BIT_MASK: u32 = 0o100; -let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK; -let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK; +
D11771: rhg: Propogate manifest parse errors instead of panicking
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The Rust parser for the manifest file format is an iterator. Now every item from that iterator is a `Result`, which makes error handling required in multiple new places. This makes better recovery on errors possible, compare to a run time panic. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11771 AFFECTED FILES rust/hg-core/src/operations/cat.rs rust/hg-core/src/operations/list_tracked_files.rs rust/hg-core/src/revlog/manifest.rs rust/rhg/src/commands/files.rs rust/rhg/src/commands/status.rs rust/rhg/src/utils/path_utils.rs CHANGE DETAILS diff --git a/rust/rhg/src/utils/path_utils.rs b/rust/rhg/src/utils/path_utils.rs --- a/rust/rhg/src/utils/path_utils.rs +++ b/rust/rhg/src/utils/path_utils.rs @@ -5,6 +5,7 @@ use crate::error::CommandError; use crate::ui::UiError; +use hg::errors::HgError; use hg::repo::Repo; use hg::utils::current_dir; use hg::utils::files::{get_bytes_from_path, relativize_path}; @@ -14,7 +15,7 @@ pub fn relativize_paths( repo: , -paths: impl IntoIterator>, +paths: impl IntoIterator, HgError>>, mut callback: impl FnMut(Cow<[u8]>) -> Result<(), UiError>, ) -> Result<(), CommandError> { let cwd = current_dir()?; @@ -38,10 +39,10 @@ for file in paths { if outside_repo { -let file = repo_root_hgpath.join(file.as_ref()); +let file = repo_root_hgpath.join(file?.as_ref()); callback(relativize_path(, _hgpath))?; } else { -callback(relativize_path(file.as_ref(), _hgpath))?; +callback(relativize_path(file?.as_ref(), _hgpath))?; } } Ok(()) diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -279,7 +279,7 @@ if relative && !ui.plain() { relativize_paths( repo, -paths, +paths.iter().map(Ok), |path: Cow<[u8]>| -> Result<(), UiError> { ui.write_stdout( &[status_prefix, b" ", path.as_ref(), b"\n"].concat(), diff --git a/rust/rhg/src/commands/files.rs b/rust/rhg/src/commands/files.rs --- a/rust/rhg/src/commands/files.rs +++ b/rust/rhg/src/commands/files.rs @@ -3,6 +3,7 @@ use crate::ui::UiError; use crate::utils::path_utils::relativize_paths; use clap::Arg; +use hg::errors::HgError; use hg::operations::list_rev_tracked_files; use hg::operations::Dirstate; use hg::repo::Repo; @@ -45,14 +46,14 @@ } else { let distate = Dirstate::new(repo)?; let files = distate.tracked_files()?; -display_files(invocation.ui, repo, files) +display_files(invocation.ui, repo, files.into_iter().map(Ok)) } } fn display_files<'a>( ui: , repo: , -files: impl IntoIterator, +files: impl IntoIterator>, ) -> Result<(), CommandError> { let mut stdout = ui.stdout_buffer(); let mut any = false; diff --git a/rust/hg-core/src/revlog/manifest.rs b/rust/hg-core/src/revlog/manifest.rs --- a/rust/hg-core/src/revlog/manifest.rs +++ b/rust/hg-core/src/revlog/manifest.rs @@ -63,26 +63,28 @@ } /// Return an iterator over the files of the entry. -pub fn files() -> impl Iterator { +pub fn files() -> impl Iterator> { self.lines().filter(|line| !line.is_empty()).map(|line| { -let pos = line -.iter() -.position(|x| x == '\0') -.expect("manifest line should contain \\0"); -HgPath::new([..pos]) +let pos = +line.iter().position(|x| x == '\0').ok_or_else(|| { +HgError::corrupted("manifest line should contain \\0") +})?; +Ok(HgPath::new([..pos])) }) } /// Return an iterator over the files of the entry. -pub fn files_with_nodes() -> impl Iterator { +pub fn files_with_nodes( +, +) -> impl Iterator> { self.lines().filter(|line| !line.is_empty()).map(|line| { -let pos = line -.iter() -.position(|x| x == '\0') -.expect("manifest line should contain \\0"); +let pos = +line.iter().position(|x| x == '\0').ok_or_else(|| { +HgError::corrupted("manifest line should contain \\0") +})?; let hash_start = pos + 1; let hash_end = hash_start + 40; -(HgPath::new([..pos]), [hash_start..hash_end]) +Ok((HgPath::new([..pos]), [hash_start..hash_end])) }) } @@ -91,7 +93,8 @@
D11772: rhg: Also parse flags in the manifest parser
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11772 AFFECTED FILES rust/hg-core/src/operations/cat.rs rust/hg-core/src/operations/list_tracked_files.rs rust/hg-core/src/revlog/manifest.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -309,13 +309,14 @@ manifest: , hg_path: , ) -> Result { -let file_node = manifest +let entry = manifest .find_file(hg_path)? .expect("ambgious file not in p1"); let filelog = repo.filelog(hg_path)?; -let filelog_entry = filelog.data_for_node(file_node).map_err(|_| { -HgError::corrupted("filelog missing node from manifest") -})?; +let filelog_entry = +filelog.data_for_node(entry.node_id()?).map_err(|_| { +HgError::corrupted("filelog missing node from manifest") +})?; let contents_in_p1 = filelog_entry.data()?; let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); diff --git a/rust/hg-core/src/revlog/manifest.rs b/rust/hg-core/src/revlog/manifest.rs --- a/rust/hg-core/src/revlog/manifest.rs +++ b/rust/hg-core/src/revlog/manifest.rs @@ -4,6 +4,7 @@ use crate::revlog::Revision; use crate::revlog::{Node, NodePrefix}; use crate::utils::hg_path::HgPath; +use crate::utils::SliceExt; /// A specialized `Revlog` to work with `manifest` data format. pub struct Manifestlog { @@ -55,50 +56,64 @@ } impl Manifest { -/// Return an iterator over the lines of the entry. -pub fn lines() -> impl Iterator { +pub fn iter( +, +) -> impl Iterator> { self.bytes .split(|b| b == '\n') .filter(|line| !line.is_empty()) -} - -/// Return an iterator over the files of the entry. -pub fn files() -> impl Iterator> { -self.lines().filter(|line| !line.is_empty()).map(|line| { -let pos = -line.iter().position(|x| x == '\0').ok_or_else(|| { +.map(|line| { +let (path, rest) = line.split_2(b'\0').ok_or_else(|| { HgError::corrupted("manifest line should contain \\0") })?; -Ok(HgPath::new([..pos])) -}) -} - -/// Return an iterator over the files of the entry. -pub fn files_with_nodes( -, -) -> impl Iterator> { -self.lines().filter(|line| !line.is_empty()).map(|line| { -let pos = -line.iter().position(|x| x == '\0').ok_or_else(|| { -HgError::corrupted("manifest line should contain \\0") -})?; -let hash_start = pos + 1; -let hash_end = hash_start + 40; -Ok((HgPath::new([..pos]), [hash_start..hash_end])) -}) +let path = HgPath::new(path); +let (hex_node_id, flags) = match rest.split_last() { +Some(('x', rest)) => (rest, Some(b'x')), +Some(('l', rest)) => (rest, Some(b'l')), +Some(('t', rest)) => (rest, Some(b't')), +_ => (rest, None), +}; +Ok(ManifestEntry { +path, +hex_node_id, +flags, +}) +}) } /// If the given path is in this manifest, return its filelog node ID -pub fn find_file(, path: ) -> Result, HgError> { +pub fn find_file( +, +path: , +) -> Result, HgError> { // TODO: use binary search instead of linear scan. This may involve // building (and caching) an index of the byte indicex of each manifest // line. -for entry in self.files_with_nodes() { -let (manifest_path, node) = entry?; -if manifest_path == path { -return Ok(Some(Node::from_hex_for_repo(node)?)); + +// TODO: use try_find when available (if still using linear scan) +// https://github.com/rust-lang/rust/issues/63178 +for entry in self.iter() { +let entry = entry?; +if entry.path == path { +return Ok(Some(entry)); } } Ok(None) } } + +/// `Manifestlog` entry which knows how to interpret the `manifest` data bytes. +#[derive(Debug)] +pub struct ManifestEntry<'manifest> { +pub path: &'manifest HgPath, +pub hex_node_id: &'manifest [u8], + +/// `Some` values are b'x', b'l', or 't' +pub flags: Option, +} + +impl ManifestEntry<'_> { +pub fn node_id() -> Result { +Nod
D11773: rhg: Rename cat_file_is_modified
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY It hasn’t been based on the "cat operation" for some time already. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11773 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -227,7 +227,7 @@ CommandError::from((e, &*format!("{:x}", p1.short( })?; for to_check in ds_status.unsure { -if cat_file_is_modified(repo, , _check)? { +if unsure_is_modified(repo, , _check)? { if display_states.modified { ds_status.modified.push(to_check); } @@ -304,7 +304,7 @@ /// to time resolution limits. /// /// TODO: detect permission bits and similar metadata modifications -fn cat_file_is_modified( +fn unsure_is_modified( repo: , manifest: , hg_path: , To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11769: tests: Adapt test-basic.t expected output for rhg
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11769 AFFECTED FILES tests/test-basic.t CHANGE DETAILS diff --git a/tests/test-basic.t b/tests/test-basic.t --- a/tests/test-basic.t +++ b/tests/test-basic.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - Create a repository: #if no-extraextensions @@ -13,6 +9,7 @@ format.exp-rc-dirstate-v2=1 (dirstate-v2 !) largefiles.usercache=$TESTTMP/.cache/largefiles lfs.usercache=$TESTTMP/.cache/lfs + rhg.status=1 (rhg !) ui.slash=True ui.interactive=False ui.detailed-exit-code=True @@ -44,7 +41,7 @@ A a $ hg status >/dev/full - abort: No space left on device + abort: No space left on device* (glob) [255] #endif To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11762: rhg: Colored output is not supported
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Fallback if it is requested explicitly. The default is documented as use color "whenever it seems possible". rhg proceeds without color in that case. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11762 AFFECTED FILES rust/rhg/src/main.rs CHANGE DETAILS diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -28,7 +28,7 @@ repo: Result<, >, config: , ) -> Result<(), CommandError> { -check_unsupported(config)?; +check_unsupported(config, ui)?; let app = App::new("rhg") .global_setting(AppSettings::AllowInvalidUtf8) @@ -615,7 +615,10 @@ } } -fn check_unsupported(config: ) -> Result<(), CommandError> { +fn check_unsupported( +config: , +ui: ::Ui, +) -> Result<(), CommandError> { check_extensions(config)?; if std::env::var_os("HG_PENDING").is_some() { @@ -632,5 +635,11 @@ Err(CommandError::unsupported("[decode] config"))? } +if let Some(color) = config.get(b"ui", b"color") { +if (color == b"always" || color == b"debug") && !ui.plain() { +Err(CommandError::unsupported("colored output"))? +} +} + Ok(()) } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11761: rhg: [encode] and [decode] config sections are not supported
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11761 AFFECTED FILES rust/hg-core/src/config/config.rs rust/hg-core/src/config/layer.rs rust/rhg/src/main.rs tests/test-encode.t CHANGE DETAILS diff --git a/tests/test-encode.t b/tests/test-encode.t --- a/tests/test-encode.t +++ b/tests/test-encode.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - Test encode/decode filters $ hg init diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -624,5 +624,13 @@ Err(CommandError::unsupported("$HG_PENDING"))? } +if config.has_non_empty_section(b"encode") { +Err(CommandError::unsupported("[encode] config"))? +} + +if config.has_non_empty_section(b"decode") { +Err(CommandError::unsupported("[decode] config"))? +} + Ok(()) } diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -127,6 +127,13 @@ .flat_map(|section| section.keys().map(|vec| &**vec)) } +/// Returns whether any key is defined in the given section +pub fn has_non_empty_section(, section: &[u8]) -> bool { +self.sections +.get(section) +.map_or(false, |section| !section.is_empty()) +} + pub fn is_empty() -> bool { self.sections.is_empty() } diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -403,6 +403,13 @@ .collect() } +/// Returns whether any key is defined in the given section +pub fn has_non_empty_section(, section: &[u8]) -> bool { +self.layers +.iter() +.any(|layer| layer.has_non_empty_section(section)) +} + /// Get raw values bytes from all layers (even untrusted ones) in order /// of precedence. #[cfg(test)] To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11760: rhg: Config commands.status.terse is not supported
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11760 AFFECTED FILES rust/rhg/src/commands/status.rs tests/test-status-terse.t CHANGE DETAILS diff --git a/tests/test-status-terse.t b/tests/test-status-terse.t --- a/tests/test-status-terse.t +++ b/tests/test-status-terse.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ mkdir folder $ cd folder $ hg init diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -148,6 +148,15 @@ "ui.statuscopies is not yet supported with rhg status", )); } +if invocation +.config +.get(b"commands", b"status.terse") +.is_some() +{ +return Err(CommandError::unsupported( +"status.terse is not yet supported with rhg status", +)); +} let ui = invocation.ui; let config = invocation.config; To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11759: rhg: Propagate config errors in `rhg status`
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This code was calling `Result::unwrap_or` instead of `Option::unwrap_or` as was presumably intended. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11759 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -138,12 +138,12 @@ } // TODO: lift these limitations -if invocation.config.get_bool(b"ui", b"tweakdefaults").ok() == Some(true) { +if invocation.config.get_bool(b"ui", b"tweakdefaults")? { return Err(CommandError::unsupported( "ui.tweakdefaults is not yet supported with rhg status", )); } -if invocation.config.get_bool(b"ui", b"statuscopies").ok() == Some(true) { +if invocation.config.get_bool(b"ui", b"statuscopies")? { return Err(CommandError::unsupported( "ui.statuscopies is not yet supported with rhg status", )); @@ -263,10 +263,9 @@ status_prefix: &[u8], ) -> Result<(), CommandError> { paths.sort_unstable(); -let mut relative: bool = -config.get_bool(b"ui", b"relative-paths").unwrap_or(false); +let mut relative: bool = config.get_bool(b"ui", b"relative-paths")?; relative = config -.get_bool(b"commands", b"status.relative") +.get_option(b"commands", b"status.relative")? .unwrap_or(relative); if relative && !ui.plain() { relativize_paths( To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11758: rhg: $HG_PENDING is not supported
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Trigger fallback in that case, if configured to do so. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11758 AFFECTED FILES rust/rhg/src/main.rs tests/test-import.t CHANGE DETAILS diff --git a/tests/test-import.t b/tests/test-import.t --- a/tests/test-import.t +++ b/tests/test-import.t @@ -1,7 +1,3 @@ -TODO: fix rhg bugs that make this test fail when status is enabled - $ unset RHG_STATUS - - $ hg init a $ mkdir a/d1 $ mkdir a/d1/d2 diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -1,4 +1,5 @@ extern crate log; +use crate::error::CommandError; use crate::ui::Ui; use clap::App; use clap::AppSettings; @@ -20,7 +21,6 @@ pub mod utils { pub mod path_utils; } -use error::CommandError; fn main_with_result( process_start_time: ::ProcessStartTime, @@ -28,7 +28,7 @@ repo: Result<, >, config: , ) -> Result<(), CommandError> { -check_extensions(config)?; +check_unsupported(config)?; let app = App::new("rhg") .global_setting(AppSettings::AllowInvalidUtf8) @@ -608,11 +608,21 @@ if unsupported.is_empty() { Ok(()) } else { -Err(CommandError::UnsupportedFeature { -message: format_bytes!( -b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)", -join(unsupported, b", ") -), -}) +Err(CommandError::UnsupportedFeature { message: format_bytes!( +b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)", +join(unsupported, b", ") +)}) } } + +fn check_unsupported(config: ) -> Result<(), CommandError> { +check_extensions(config)?; + +if std::env::var_os("HG_PENDING").is_some() { +// TODO: only if the value is `== repo.working_directory`? +// What about relative v.s. absolute paths? + Err(CommandError::unsupported("$HG_PENDING"))? +} + +Ok(()) +} To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11756: rhg: Enable `rhg status` in most tests
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This subcommand is disabled by default because of bugs that make some test fail. Enable it in the rest of the tests in order to avoid regressing them. As with `RHG_ON_UNSUPPORTED`, an environment variable is used instead of a configuration file and `HGRCPATH` because some tests override `HGRCPATH`. Running `unset RHG_STATUS` at the start of a test restores the default of `rhg status` being disabled. Hopefully it can be increasingly removed from test files as bugs are fixed. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11756 AFFECTED FILES rust/hg-core/src/config/config.rs tests/run-tests.py tests/test-basic.t tests/test-conflict.t tests/test-dirstate.t tests/test-encode.t tests/test-execute-bit.t tests/test-hgignore.t tests/test-import.t tests/test-issue6528.t tests/test-merge-exec.t tests/test-merge-types.t tests/test-permissions.t tests/test-rename-dir-merge.t tests/test-status-color.t tests/test-status-terse.t tests/test-status.t tests/test-subrepo-deep-nested-change.t tests/test-subrepo-missing.t tests/test-symlinks.t CHANGE DETAILS diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t --- a/tests/test-symlinks.t +++ b/tests/test-symlinks.t @@ -11,6 +11,10 @@ > EOF #endif +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + == tests added in 0.7 == $ hg init test-symlinks-0.7; cd test-symlinks-0.7; diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t --- a/tests/test-subrepo-missing.t +++ b/tests/test-subrepo-missing.t @@ -1,3 +1,7 @@ +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ hg init repo $ cd repo $ hg init subrepo diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t --- a/tests/test-subrepo-deep-nested-change.t +++ b/tests/test-subrepo-deep-nested-change.t @@ -1,3 +1,7 @@ +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ cat >> $HGRCPATH < [extdiff] > # for portability: diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -9,6 +9,10 @@ > EOF #endif +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ hg init repo1 $ cd repo1 $ mkdir a b a/1 b/1 b/2 diff --git a/tests/test-status-terse.t b/tests/test-status-terse.t --- a/tests/test-status-terse.t +++ b/tests/test-status-terse.t @@ -1,3 +1,7 @@ +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ mkdir folder $ cd folder $ hg init diff --git a/tests/test-status-color.t b/tests/test-status-color.t --- a/tests/test-status-color.t +++ b/tests/test-status-color.t @@ -1,3 +1,7 @@ +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ cat <> $HGRCPATH > [ui] > color = always diff --git a/tests/test-rename-dir-merge.t b/tests/test-rename-dir-merge.t --- a/tests/test-rename-dir-merge.t +++ b/tests/test-rename-dir-merge.t @@ -1,3 +1,7 @@ +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ hg init t $ cd t diff --git a/tests/test-permissions.t b/tests/test-permissions.t --- a/tests/test-permissions.t +++ b/tests/test-permissions.t @@ -11,6 +11,10 @@ > EOF #endif +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ hg init t $ cd t diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t --- a/tests/test-merge-types.t +++ b/tests/test-merge-types.t @@ -1,5 +1,9 @@ #require symlink execbit +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + $ tellmeabout() { > if [ -h $1 ]; then > echo $1 is a symlink: diff --git a/tests/test-merge-exec.t b/tests/test-merge-exec.t --- a/tests/test-merge-exec.t +++ b/tests/test-merge-exec.t @@ -4,6 +4,9 @@ #require execbit +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + Initial setup == diff --git a/tests/test-issue6528.t b/tests/test-issue6528.t --- a/tests/test-issue6528.t +++ b/tests/test-issue6528.t @@ -2,6 +2,10 @@ Test non-regression on the corruption associated with issue6528 === +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_STATUS + + Setup = diff --git a/tests/test-import.t b/tests/test-import.t --- a/tests/test-import.t +++ b/tests/test-import.t @@ -1,3 +1,7 @@ +TODO: fix rhg bugs that make this test fail when status is enabled + $ unset RHG_ST
D11743: rhg: Fix `rhg status` file content comparison
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This is only used when a file’s metadata make its status ambiguous, which depends on timing of previous command executions. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11743 AFFECTED FILES rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -312,5 +312,5 @@ let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion"); let fs_contents = repo.working_directory_vfs().read(fs_path)?; -return Ok(contents_in_p1 == &*fs_contents); +return Ok(contents_in_p1 != &*fs_contents); } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11696: rust: Remove now-unused DirstateEntry::clear_ambiguous_mtime method
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11696 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -597,23 +597,6 @@ false } } - -pub fn clear_ambiguous_mtime( self, now: TruncatedTimestamp) -> bool { -let ambiguous = self.mtime_is_ambiguous(now); -if ambiguous { -// The file was last modified "simultaneously" with the current -// write to dirstate (i.e. within the same second for file- -// systems with a granularity of 1 sec). This commonly happens -// for at least a couple of files on 'update'. -// The user could change the file without changing its size -// within the same second. Invalidate the file's mtime in -// dirstate, forcing future 'status' calls to compare the -// contents of the file if the size is the same. This prevents -// mistakenly treating such files as clean. -self.set_possibly_dirty() -} -ambiguous -} } impl EntryState { To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11691: dirstate: rename a `very_likely_equal` method to `likely_equal`
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY No need to oversell it. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11691 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/status.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -200,7 +200,7 @@ // directory eligible for `read_dir` caching. if let Some(meta) = directory_metadata { if cached_mtime -.very_likely_equal_to_mtime_of(meta) +.likely_equal_to_mtime_of(meta) .unwrap_or(false) { // The mtime of that directory has not changed @@ -471,7 +471,7 @@ let is_up_to_date = if let Some(cached) = dirstate_node.cached_directory_mtime()? { -cached.very_likely_equal(truncated) +cached.likely_equal(truncated) } else { false }; diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -110,16 +110,16 @@ /// If someone is manipulating the modification times of some files to /// intentionally make `hg status` return incorrect results, not truncating /// wouldn’t help much since they can set exactly the expected timestamp. -pub fn very_likely_equal(self, other: Self) -> bool { +pub fn likely_equal(self, other: Self) -> bool { self.truncated_seconds == other.truncated_seconds && self.nanoseconds == other.nanoseconds } -pub fn very_likely_equal_to_mtime_of( +pub fn likely_equal_to_mtime_of( self, metadata: ::Metadata, ) -> io::Result { -Ok(self.very_likely_equal(Self::for_mtime_of(metadata)?)) +Ok(self.likely_equal(Self::for_mtime_of(metadata)?)) } } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11684: dirstate: Store mtimes with nanosecond precision in memory
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Keep integer seconds since the Unix epoch, together with integer nanoseconds in the `0 <= n < 1e9` range. For now, nanoseconds are still always zero. This commit is about data structure changes. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11684 AFFECTED FILES mercurial/cext/parsers.c mercurial/cext/util.h mercurial/dirstate.py mercurial/dirstatemap.py mercurial/dirstateutils/timestamp.py mercurial/dirstateutils/v2.py mercurial/merge.py mercurial/pure/parsers.py rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate/parsers.rs rust/hg-core/src/dirstate/status.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-core/src/dirstate_tree/status.rs rust/hg-cpython/src/dirstate.rs rust/hg-cpython/src/dirstate/dirstate_map.rs rust/hg-cpython/src/dirstate/item.rs rust/hg-cpython/src/dirstate/status.rs rust/rhg/src/commands/status.rs tests/fakedirstatewritetime.py CHANGE DETAILS diff --git a/tests/fakedirstatewritetime.py b/tests/fakedirstatewritetime.py --- a/tests/fakedirstatewritetime.py +++ b/tests/fakedirstatewritetime.py @@ -15,6 +15,7 @@ policy, registrar, ) +from mercurial.dirstateutils import timestamp from mercurial.utils import dateutil try: @@ -40,9 +41,8 @@ def pack_dirstate(fakenow, orig, dmap, copymap, pl, now): # execute what original parsers.pack_dirstate should do actually # for consistency -actualnow = int(now) for f, e in dmap.items(): -if e.need_delay(actualnow): +if e.need_delay(now): e.set_possibly_dirty() return orig(dmap, copymap, pl, fakenow) @@ -62,6 +62,7 @@ # parsing 'fakenow' in mmddHHMM format makes comparison between # 'fakenow' value and 'touch -t mmddHHMM' argument easy fakenow = dateutil.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] +fakenow = timestamp.timestamp((fakenow, 0)) if has_rust_dirstate: # The Rust implementation does not use public parse/pack dirstate diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -11,6 +11,7 @@ use clap::{Arg, SubCommand}; use hg; use hg::config::Config; +use hg::dirstate::TruncatedTimestamp; use hg::errors::HgError; use hg::manifest::Manifest; use hg::matchers::AlwaysMatcher; @@ -180,7 +181,7 @@ // hence be stored on dmap. Using a value that assumes we aren't // below the time resolution granularity of the FS and the // dirstate. -last_normal_time: 0, +last_normal_time: TruncatedTimestamp::new_truncate(0, 0), // we're currently supporting file systems with exec flags only // anyway check_exec: true, diff --git a/rust/hg-cpython/src/dirstate/status.rs b/rust/hg-cpython/src/dirstate/status.rs --- a/rust/hg-cpython/src/dirstate/status.rs +++ b/rust/hg-cpython/src/dirstate/status.rs @@ -9,6 +9,7 @@ //! `hg-core` crate. From Python, this will be seen as //! `rustext.dirstate.status`. +use crate::dirstate::item::timestamp; use crate::{dirstate::DirstateMap, exceptions::FallbackError}; use cpython::exc::OSError; use cpython::{ @@ -102,12 +103,13 @@ root_dir: PyObject, ignore_files: PyList, check_exec: bool, -last_normal_time: i64, +last_normal_time: (u32, u32), list_clean: bool, list_ignored: bool, list_unknown: bool, collect_traversed_dirs: bool, ) -> PyResult { +let last_normal_time = timestamp(py, last_normal_time)?; let bytes = root_dir.extract::(py)?; let root_dir = get_path_from_bytes(bytes.data(py)); diff --git a/rust/hg-cpython/src/dirstate/item.rs b/rust/hg-cpython/src/dirstate/item.rs --- a/rust/hg-cpython/src/dirstate/item.rs +++ b/rust/hg-cpython/src/dirstate/item.rs @@ -8,6 +8,7 @@ use cpython::PythonObject; use hg::dirstate::DirstateEntry; use hg::dirstate::EntryState; +use hg::dirstate::TruncatedTimestamp; use std::cell::Cell; use std::convert::TryFrom; @@ -21,7 +22,7 @@ p2_info: bool = false, has_meaningful_data: bool = true, has_meaningful_mtime: bool = true, -parentfiledata: Option<(u32, u32, u32)> = None, +parentfiledata: Option<(u32, u32, (u32, u32))> = None, ) -> PyResult { let mut mode_size_opt = None; @@ -31,7 +32,7 @@ mode_size_opt = Some((mode, size)) } if has_meaningful_mtime { -mtime_opt = Some(mtime) +mtime_opt = Some(timestamp(py, mtime)?) } } let entry = DirstateEntry::from_v2_data( @@ -118,10 +119,19 @@ Ok(mtime) } -def need_delay(, now: i32) -> PyResult { +
D11678: rust: reformat Rust code
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY (cd rust && cargo +nightly fmt) REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11678 AFFECTED FILES rust/hg-core/src/operations/cat.rs CHANGE DETAILS diff --git a/rust/hg-core/src/operations/cat.rs b/rust/hg-core/src/operations/cat.rs --- a/rust/hg-core/src/operations/cat.rs +++ b/rust/hg-core/src/operations/cat.rs @@ -104,10 +104,8 @@ bytes.extend(file_log.data_for_node(file_node)?.data()?); } -let missing: Vec = missing -.iter() -.map(|file| (*file).to_owned()) -.collect(); +let missing: Vec = +missing.iter().map(|file| (*file).to_owned()).collect(); Ok(CatOutput { found_any, concatenated: bytes, To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11662: dirstate-v2: Separate HAS_FILE_MTIME and HAS_DIRECTORY_MTIME flags
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Previously the same flag was used, with its meaning based on whether the node otherwise identifies a file tracked anywhere. In addition to being more explicit, this enables storing a directory mtime if a given path used to be tracked in a parent commit (so the dirstate still has data about it) but became a directory in the working copy. (However this is not done yet as it would require a larger change, replacing the `dirstate_map::NodeData` enum with struct fields.) REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11662 AFFECTED FILES mercurial/cext/parsers.c mercurial/cext/util.h mercurial/helptext/internals/dirstate-v2.txt mercurial/pure/parsers.py rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -106,9 +106,10 @@ const P1_TRACKED = 1 << 1; const P2_INFO = 1 << 2; const HAS_MODE_AND_SIZE = 1 << 3; -const HAS_MTIME = 1 << 4; -const MODE_EXEC_PERM = 1 << 5; -const MODE_IS_SYMLINK = 1 << 6; +const HAS_FILE_MTIME = 1 << 4; +const HAS_DIRECTORY_MTIME = 1 << 5; +const MODE_EXEC_PERM = 1 << 6; +const MODE_IS_SYMLINK = 1 << 7; } } @@ -320,13 +321,15 @@ pub(super) fn cached_directory_mtime( , ) -> Result, DirstateV2ParseError> { -Ok( -if self.flags().contains(Flags::HAS_MTIME) && !self.has_entry() { -Some(self.mtime.try_into()?) +if self.flags().contains(Flags::HAS_DIRECTORY_MTIME) { +if self.flags().contains(Flags::HAS_FILE_MTIME) { +Err(DirstateV2ParseError) } else { -None -}, -) +Ok(Some(self.mtime.try_into()?)) +} +} else { +Ok(None) +} } fn synthesize_unix_mode() -> u32 { @@ -353,7 +356,7 @@ } else { None }; -let mtime = if self.flags().contains(Flags::HAS_MTIME) { +let mtime = if self.flags().contains(Flags::HAS_FILE_MTIME) { Some(self.mtime.truncated_seconds.into()) } else { None @@ -422,7 +425,7 @@ 0.into() }; let mtime = if let Some(m) = mtime_opt { -flags.insert(Flags::HAS_MTIME); +flags.insert(Flags::HAS_FILE_MTIME); PackedTruncatedTimestamp { truncated_seconds: m.into(), nanoseconds: 0.into(), @@ -580,9 +583,11 @@ dirstate_map::NodeData::Entry(entry) => { Node::from_dirstate_entry(entry) } -dirstate_map::NodeData::CachedDirectory { mtime } => { -(Flags::HAS_MTIME, 0.into(), (*mtime).into()) -} +dirstate_map::NodeData::CachedDirectory { mtime } => ( +Flags::HAS_DIRECTORY_MTIME, +0.into(), +(*mtime).into(), +), dirstate_map::NodeData::None => ( Flags::empty(), 0.into(), diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py +++ b/mercurial/pure/parsers.py @@ -49,9 +49,10 @@ DIRSTATE_V2_P1_TRACKED = 1 << 1 DIRSTATE_V2_P2_INFO = 1 << 2 DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 3 -DIRSTATE_V2_HAS_MTIME = 1 << 4 -DIRSTATE_V2_MODE_EXEC_PERM = 1 << 5 -DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 6 +DIRSTATE_V2_HAS_FILE_MTIME = 1 << 4 +_DIRSTATE_V2_HAS_DIRCTORY_MTIME = 1 << 5 # Unused when Rust is not available +DIRSTATE_V2_MODE_EXEC_PERM = 1 << 6 +DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 7 @attr.s(slots=True, init=False) @@ -138,7 +139,7 @@ p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), p2_info=bool(flags & DIRSTATE_V2_P2_INFO), has_meaningful_data=has_mode_size, -has_meaningful_mtime=bool(flags & DIRSTATE_V2_HAS_MTIME), +has_meaningful_mtime=bool(flags & DIRSTATE_V2_HAS_FILE_MTIME), parentfiledata=(mode, size, mtime), ) @@ -329,7 +330,7 @@ if stat.S_ISLNK(self.mode): flags |= DIRSTATE_V2_MODE_IS_SYMLINK if self._mtime is not None: -flags |= DIRSTATE_V2_HAS_MTIME +flags |= DIRSTATE_V2_HAS_FILE_MTIME r
D11661: dirstate-v2: Extend node flags to 16 bits
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Only 7 out of 8 available bits are used right now. Reserve some more. Future versions of Mercurial may assign meaning to some of these bits, with the limitation that then-older versions will always reset those bits to unset when writing nodes. (A new node is written for any mutation in its subtree, leaving the bytes of the old node unreachable until the data file is rewritten entirely.) REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11661 AFFECTED FILES mercurial/dirstateutils/v2.py mercurial/helptext/internals/dirstate-v2.txt rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -33,7 +33,7 @@ /// Must match constants of the same names in `mercurial/dirstateutils/v2.py` const TREE_METADATA_SIZE: usize = 44; -const NODE_SIZE: usize = 43; +const NODE_SIZE: usize = 44; /// Make sure that size-affecting changes are made knowingly #[allow(unused)] @@ -94,15 +94,14 @@ children: ChildNodes, pub(super) descendants_with_entry_count: Size, pub(super) tracked_descendants_count: Size, -flags: Flags, +flags: U16Be, size: U32Be, mtime: PackedTruncatedTimestamp, } bitflags! { -#[derive(BytesCast)] #[repr(C)] -struct Flags: u8 { +struct Flags: u16 { const WDIR_TRACKED = 1 << 0; const P1_TRACKED = 1 << 1; const P2_INFO = 1 << 2; @@ -296,8 +295,12 @@ }) } +fn flags() -> Flags { +Flags::from_bits_truncate(self.flags.get()) +} + fn has_entry() -> bool { -self.flags.intersects( +self.flags().intersects( Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO, ) } @@ -318,7 +321,7 @@ , ) -> Result, DirstateV2ParseError> { Ok( -if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() { +if self.flags().contains(Flags::HAS_MTIME) && !self.has_entry() { Some(self.mtime.try_into()?) } else { None @@ -327,12 +330,12 @@ } fn synthesize_unix_mode() -> u32 { -let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) { +let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) { libc::S_IFLNK } else { libc::S_IFREG }; -let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) { +let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) { 0o755 } else { 0o644 @@ -342,15 +345,15 @@ fn assume_entry() -> DirstateEntry { // TODO: convert through raw bits instead? -let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); -let p1_tracked = self.flags.contains(Flags::P1_TRACKED); -let p2_info = self.flags.contains(Flags::P2_INFO); -let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) { +let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); +let p1_tracked = self.flags().contains(Flags::P1_TRACKED); +let p2_info = self.flags().contains(Flags::P2_INFO); +let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE) { Some((self.synthesize_unix_mode(), self.size.into())) } else { None }; -let mtime = if self.flags.contains(Flags::HAS_MTIME) { +let mtime = if self.flags().contains(Flags::HAS_MTIME) { Some(self.mtime.truncated_seconds.into()) } else { None @@ -600,7 +603,7 @@ tracked_descendants_count: node .tracked_descendants_count .into(), -flags, +flags: flags.bits().into(), size, mtime, } diff --git a/mercurial/helptext/internals/dirstate-v2.txt b/mercurial/helptext/internals/dirstate-v2.txt --- a/mercurial/helptext/internals/dirstate-v2.txt +++ b/mercurial/helptext/internals/dirstate-v2.txt @@ -372,7 +372,7 @@ This counter is used to implement `has_tracked_dir`. * Offset 30: - A single `flags` byte that packs some boolean values as bits. + A `flags` fields that packs some boolean values as bits of a 16-bit integer. Starting from least-significant, bit masks are:: WDIR_TRACKED = 1 << 0 @@ -384,22 +384,29 @@ MODE_IS_SYMLINK = 1 << 6 The meaning of each bit is described below. - Other bits are unset. -* Offset 31: + Oth
D11660: dirstate-v2: Use attributes as intended instead of properties in v2_data()
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The property return other integer values instead of None, so `is not None` does not work. This fixes test-dirstate-race.t in pure-Python mode, which currently fails on the default branch. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11660 AFFECTED FILES mercurial/pure/parsers.py CHANGE DETAILS diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py +++ b/mercurial/pure/parsers.py @@ -322,15 +322,15 @@ flags |= DIRSTATE_V2_P1_TRACKED if self._p2_info: flags |= DIRSTATE_V2_P2_INFO -if self.mode is not None and self.size is not None: +if self._mode is not None and self._size is not None: flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE if self.mode & stat.S_IXUSR: flags |= DIRSTATE_V2_MODE_EXEC_PERM if stat.S_ISLNK(self.mode): flags |= DIRSTATE_V2_MODE_IS_SYMLINK -if self.mtime is not None: +if self._mtime is not None: flags |= DIRSTATE_V2_HAS_MTIME -return (flags, self.size or 0, self.mtime or 0) +return (flags, self._size or 0, self._mtime or 0) def v1_state(self): """return a "state" suitable for v1 serialization""" To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11658: dirstate: Remove unused variable
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This fixes test-check-pyflakes.t which is currently failing on the default branch. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11658 AFFECTED FILES mercurial/dirstateutils/v2.py CHANGE DETAILS diff --git a/mercurial/dirstateutils/v2.py b/mercurial/dirstateutils/v2.py --- a/mercurial/dirstateutils/v2.py +++ b/mercurial/dirstateutils/v2.py @@ -149,7 +149,6 @@ else: # There are no mtime-cached directories in the Python implementation flags = 0 -mode = 0 size = 0 mtime_s = 0 mtime_ns = 0 To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11657: rust: Reformat source code
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This fixes test-check-rust-format.t which is currently failing on the default branch. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11657 AFFECTED FILES rust/hg-core/src/revlog/revlog.rs CHANGE DETAILS diff --git a/rust/hg-core/src/revlog/revlog.rs b/rust/hg-core/src/revlog/revlog.rs --- a/rust/hg-core/src/revlog/revlog.rs +++ b/rust/hg-core/src/revlog/revlog.rs @@ -76,7 +76,8 @@ Some(index_mmap) => { let version = get_version(_mmap)?; if version != 1 { -// A proper new version should have had a repo/store requirement. +// A proper new version should have had a repo/store +// requirement. return Err(HgError::corrupted("corrupted revlog")); } @@ -424,6 +425,6 @@ .with_version(1) .build(); -assert_eq!(get_version().map_err(|_err|()), Ok(1)) +assert_eq!(get_version().map_err(|_err| ()), Ok(1)) } } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11655: dirstate-v2: Add storage space for nanoseconds precision in file mtimes
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY For now the sub-second component is always set to zero for tracked files and symlinks. (The mtime of directories for the `readdir`-skipping optimization is a different code path and already uses the full precision available.) This extra storage uses the space previously freed by replacing the 32-bit `mode` field by two bits in the existing `flags` field, so the overall size of nodes is unchanged. (This space had been left as padding for this purpose.) Also move things around in the node layout and documentation to have less duplication. Now that they have the same representation, directory mtime and file mtime are kept in the same field. (Only either one can exist for a given node.) REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11655 AFFECTED FILES mercurial/helptext/internals/dirstate-v2.txt rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -97,7 +97,8 @@ pub(super) descendants_with_entry_count: Size, pub(super) tracked_descendants_count: Size, flags: Flags, -data: Entry, +size: U32Be, +mtime: PackedTruncatedTimestamp, } bitflags! { @@ -110,23 +111,14 @@ const HAS_MODE_AND_SIZE = 1 << 3; const HAS_MTIME = 1 << 4; const MODE_EXEC_PERM = 1 << 5; -const MODE_IS_SYMLINK = 1 << 7; +const MODE_IS_SYMLINK = 1 << 6; } } -#[derive(BytesCast, Copy, Clone, Debug)] -#[repr(C)] -struct Entry { -_padding: U32Be, -size: U32Be, -mtime: U32Be, -} - /// Duration since the Unix epoch #[derive(BytesCast, Copy, Clone)] #[repr(C)] -struct PackedTimestamp { -_padding: U32Be, +struct PackedTruncatedTimestamp { truncated_seconds: U32Be, nanoseconds: U32Be, } @@ -329,7 +321,7 @@ ) -> Result, DirstateV2ParseError> { Ok( if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() { -Some(self.data.as_timestamp()?) +Some(self.mtime.try_into()?) } else { None }, @@ -356,12 +348,12 @@ let p1_tracked = self.flags.contains(Flags::P1_TRACKED); let p2_info = self.flags.contains(Flags::P2_INFO); let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) { -Some((self.synthesize_unix_mode(), self.data.size.into())) +Some((self.synthesize_unix_mode(), self.size.into())) } else { None }; let mtime = if self.flags.contains(Flags::HAS_MTIME) { -Some(self.data.mtime.into()) +Some(self.mtime.truncated_seconds.into()) } else { None }; @@ -407,10 +399,10 @@ tracked_descendants_count: self.tracked_descendants_count.get(), }) } -} -impl Entry { -fn from_dirstate_entry(entry: ) -> (Flags, Self) { +fn from_dirstate_entry( +entry: , +) -> (Flags, U32Be, PackedTruncatedTimestamp) { let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) = entry.v2_data(); // TODO: convert throug raw flag bits instead? @@ -418,53 +410,26 @@ flags.set(Flags::WDIR_TRACKED, wdir_tracked); flags.set(Flags::P1_TRACKED, p1_tracked); flags.set(Flags::P2_INFO, p2_info); -let (size, mtime); -if let Some((m, s)) = mode_size_opt { +let size = if let Some((m, s)) = mode_size_opt { let exec_perm = m & libc::S_IXUSR != 0; let is_symlink = m & libc::S_IFMT == libc::S_IFLNK; flags.set(Flags::MODE_EXEC_PERM, exec_perm); flags.set(Flags::MODE_IS_SYMLINK, is_symlink); -size = s; -flags.insert(Flags::HAS_MODE_AND_SIZE) +flags.insert(Flags::HAS_MODE_AND_SIZE); +s.into() } else { -size = 0; -} -if let Some(m) = mtime_opt { -mtime = m; -flags.insert(Flags::HAS_MTIME); -} else { -mtime = 0; -} -let raw_entry = Entry { -_padding: 0.into(), -size: size.into(), -mtime: mtime.into(), +0.into() }; -(flags, raw_entry) -} - -fn from_timestamp(timestamp: TruncatedTimestamp) -> Self { -let packed = PackedTimestamp { -_padding: 0.into(), -truncated_seconds: timestamp.truncated_seconds().into(), -nanoseconds: timestamp.nanoseconds().into(), +let mtime = if let Some(m) = mtime_opt { +flags.in
D11654: status: Extract TruncatedTimestamp from fs::Metadata without SystemTime
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY On Unix, the Rust standard library exposes `mtime` and `mtime_nsec` methods for `std::fs::Metada` whih is exactly what we need to construct a `TruncatedTimestamp`. This skips the computation in the conversion through `SystemTime` and `Result`. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11654 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/status.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -199,15 +199,14 @@ // by a previous run of the `status` algorithm which found this // directory eligible for `read_dir` caching. if let Some(meta) = directory_metadata { -if let Ok(current_mtime) = meta.modified() { -let truncated = -TruncatedTimestamp::from(current_mtime); -if truncated.very_likely_equal(_mtime) { -// The mtime of that directory has not changed -// since then, which means that the results of -// `read_dir` should also be unchanged. -return true; -} +if cached_mtime +.very_likely_equal_to_mtime_of(meta) +.unwrap_or(false) +{ +// The mtime of that directory has not changed +// since then, which means that the results of +// `read_dir` should also be unchanged. +return true; } } } @@ -472,7 +471,7 @@ let is_up_to_date = if let Some(cached) = dirstate_node.cached_directory_mtime()? { -cached.very_likely_equal() +cached.very_likely_equal(truncated) } else { false }; diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -1,7 +1,9 @@ use crate::dirstate_tree::on_disk::DirstateV2ParseError; use crate::errors::HgError; use bitflags::bitflags; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; +use std::fs; +use std::io; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -69,6 +71,21 @@ } } +pub fn for_mtime_of(metadata: ::Metadata) -> io::Result { +#[cfg(unix)] +{ +use std::os::unix::fs::MetadataExt; +let seconds = metadata.mtime(); +// i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range +let nanoseconds = metadata.mtime_nsec().try_into().unwrap(); +Ok(Self::new_truncate(seconds, nanoseconds)) +} +#[cfg(not(unix))] +{ +metadata.modified().map(Self::from) +} +} + /// The lower 31 bits of the number of seconds since the epoch. pub fn truncated_seconds() -> u32 { self.truncated_seconds @@ -93,10 +110,17 @@ /// If someone is manipulating the modification times of some files to /// intentionally make `hg status` return incorrect results, not truncating /// wouldn’t help much since they can set exactly the expected timestamp. -pub fn very_likely_equal(, other: ) -> bool { +pub fn very_likely_equal(self, other: Self) -> bool { self.truncated_seconds == other.truncated_seconds && self.nanoseconds == other.nanoseconds } + +pub fn very_likely_equal_to_mtime_of( +self, +metadata: ::Metadata, +) -> io::Result { +Ok(self.very_likely_equal(Self::for_mtime_of(metadata)?)) +} } impl From for TruncatedTimestamp { To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11633: dirstate-v2: Truncate directory mtimes to 31 bits of seconds
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY … instead of 64 bits, while keeping the sub-second presision. This brings the size of one timestamp from 12 bytes to 8 bytes. 31 bits is chosen instead of 32 because that’s already what happens for the mtime of files and symlinks, because dirstate-v1 uses negative i32 values as markers. Later we’ll add sub-second precision for file/symlink mtimes, making their dirstate-v2 representation the same as for directories. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11633 AFFECTED FILES mercurial/helptext/internals/dirstate-v2.txt rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-core/src/dirstate_tree/status.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -1,4 +1,4 @@ -use crate::dirstate::entry::Timestamp; +use crate::dirstate::entry::TruncatedTimestamp; use crate::dirstate::status::IgnoreFnType; use crate::dirstate_tree::dirstate_map::BorrowedPath; use crate::dirstate_tree::dirstate_map::ChildNodesRef; @@ -126,7 +126,8 @@ matcher: &'a (dyn Matcher + Sync), ignore_fn: IgnoreFnType<'a>, outcome: Mutex>, -new_cachable_directories: Mutex, Timestamp)>>, +new_cachable_directories: +Mutex, TruncatedTimestamp)>>, outated_cached_directories: Mutex>>, /// Whether ignore files like `.hgignore` have changed since the previous @@ -165,7 +166,7 @@ dirstate_node: <'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { if self.ignore_patterns_have_changed == Some(true) -&& dirstate_node.cached_directory_mtime().is_some() +&& dirstate_node.cached_directory_mtime()?.is_some() { self.outated_cached_directories.lock().unwrap().push( dirstate_node @@ -182,7 +183,7 @@ fn can_skip_fs_readdir( , directory_metadata: Option<::fs::Metadata>, -cached_directory_mtime: Option, +cached_directory_mtime: Option, ) -> bool { if !self.options.list_unknown && !self.options.list_ignored { // All states that we care about listing have corresponding @@ -199,8 +200,9 @@ // directory eligible for `read_dir` caching. if let Some(meta) = directory_metadata { if let Ok(current_mtime) = meta.modified() { -let current_mtime = Timestamp::from(current_mtime); -if current_mtime == cached_mtime { +let truncated = +TruncatedTimestamp::from(current_mtime); +if truncated.very_likely_equal(_mtime) { // The mtime of that directory has not changed // since then, which means that the results of // `read_dir` should also be unchanged. @@ -222,7 +224,7 @@ directory_hg_path: <'tree, 'on_disk>, directory_fs_path: , directory_metadata: Option<::fs::Metadata>, -cached_directory_mtime: Option, +cached_directory_mtime: Option, is_at_repo_root: bool, ) -> Result { if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime) @@ -363,7 +365,7 @@ hg_path, fs_path, Some(fs_metadata), -dirstate_node.cached_directory_mtime(), +dirstate_node.cached_directory_mtime()?, is_at_repo_root, )?; self.maybe_save_directory_mtime( @@ -466,16 +468,22 @@ // // We deem this scenario (unlike the previous one) to be // unlikely enough in practice. -let timestamp = directory_mtime.into(); -let cached = dirstate_node.cached_directory_mtime(); -if cached != Some(timestamp) { +let truncated = TruncatedTimestamp::from(directory_mtime); +let is_up_to_date = if let Some(cached) = +dirstate_node.cached_directory_mtime()? +{ +cached.very_likely_equal() +} else { +false +}; +if !is_up_to_date { let hg_path = dirstate_node .full_path_borrowed(self.dmap.on_disk)? .detach_from_tre
D11635: dirstate-v2: Replace the 32-bit `mode` field with two bits
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Previously we stored the entire value from `stat_result.st_mode`, like dirstate-v1 does. However only the executable permission and type of file (only symbolic links and normal files are supported) are relevant to Mecurial. So replace this field with two bits in the existing bitfield byte. For now the unused space is left as padding, as it will be used for something else soon. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11635 AFFECTED FILES mercurial/helptext/internals/dirstate-v2.txt rust/Cargo.lock rust/hg-core/Cargo.toml rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-cpython/Cargo.toml CHANGE DETAILS diff --git a/rust/hg-cpython/Cargo.toml b/rust/hg-cpython/Cargo.toml --- a/rust/hg-cpython/Cargo.toml +++ b/rust/hg-cpython/Cargo.toml @@ -23,7 +23,7 @@ [dependencies] crossbeam-channel = "0.4" hg-core = { path = "../hg-core"} -libc = '*' +libc = "0.2" log = "0.4.8" env_logger = "0.7.1" stable_deref_trait = "1.2.0" diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -107,13 +107,15 @@ const P2_INFO = 1 << 2; const HAS_MODE_AND_SIZE = 1 << 3; const HAS_MTIME = 1 << 4; +const MODE_EXEC_PERM = 1 << 5; +const MODE_IS_SYMLINK = 1 << 7; } } #[derive(BytesCast, Copy, Clone, Debug)] #[repr(C)] struct Entry { -mode: U32Be, +_padding: U32Be, size: U32Be, mtime: U32Be, } @@ -332,13 +334,27 @@ ) } +fn synthesize_unix_mode() -> u32 { +let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) { +libc::S_IFLNK +} else { +libc::S_IFREG +}; +let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) { +0o755 +} else { +0o644 +}; +file_type | permisions +} + fn assume_entry() -> DirstateEntry { // TODO: convert through raw bits instead? let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED); let p1_tracked = self.flags.contains(Flags::P1_TRACKED); let p2_info = self.flags.contains(Flags::P2_INFO); let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) { -Some((self.data.mode.into(), self.data.size.into())) +Some((self.synthesize_unix_mode(), self.data.size.into())) } else { None }; @@ -400,13 +416,15 @@ flags.set(Flags::WDIR_TRACKED, wdir_tracked); flags.set(Flags::P1_TRACKED, p1_tracked); flags.set(Flags::P2_INFO, p2_info); -let (mode, size, mtime); +let (size, mtime); if let Some((m, s)) = mode_size_opt { -mode = m; +let exec_perm = m & libc::S_IXUSR != 0; +let is_symlink = m & libc::S_IFMT == libc::S_IFLNK; +flags.set(Flags::MODE_EXEC_PERM, exec_perm); +flags.set(Flags::MODE_IS_SYMLINK, is_symlink); size = s; flags.insert(Flags::HAS_MODE_AND_SIZE) } else { -mode = 0; size = 0; } if let Some(m) = mtime_opt { @@ -416,7 +434,7 @@ mtime = 0; } let raw_entry = Entry { -mode: mode.into(), +_padding: 0.into(), size: size.into(), mtime: mtime.into(), }; @@ -600,7 +618,7 @@ dirstate_map::NodeData::None => ( Flags::empty(), Entry { -mode: 0.into(), +_padding: 0.into(), size: 0.into(), mtime: 0.into(), }, diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml +++ b/rust/hg-core/Cargo.toml @@ -17,6 +17,7 @@ im-rc = "15.0.*" itertools = "0.9" lazy_static = "1.4.0" +libc = "0.2" rand = "0.7.3" rand_pcg = "0.2.1" rand_distr = "0.2.2" diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler" version = "0.2.3" @@ -386,6 +388,7 @@ "im-rc", "itertools", "lazy_static", + "libc", "log", "memmap2", "micro-timer", diff --git a/m
D11632: dirstate-v2: Separate Rust structs for Timestamp and PackedTimestamp
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY PackedTimestamp is now exclusively for dirstate-v2 serialization purpose. It contains unaligned big-endian integers. Timestamp is used everywhere else and contains native Rust integers. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11632 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-core/src/dirstate_tree/status.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -1,3 +1,4 @@ +use crate::dirstate::entry::Timestamp; use crate::dirstate::status::IgnoreFnType; use crate::dirstate_tree::dirstate_map::BorrowedPath; use crate::dirstate_tree::dirstate_map::ChildNodesRef; @@ -5,7 +6,6 @@ use crate::dirstate_tree::dirstate_map::NodeData; use crate::dirstate_tree::dirstate_map::NodeRef; use crate::dirstate_tree::on_disk::DirstateV2ParseError; -use crate::dirstate_tree::on_disk::Timestamp; use crate::matchers::get_ignore_function; use crate::matchers::Matcher; use crate::utils::files::get_bytes_from_os_string; @@ -182,7 +182,7 @@ fn can_skip_fs_readdir( , directory_metadata: Option<::fs::Metadata>, -cached_directory_mtime: Option<>, +cached_directory_mtime: Option, ) -> bool { if !self.options.list_unknown && !self.options.list_ignored { // All states that we care about listing have corresponding @@ -200,7 +200,7 @@ if let Some(meta) = directory_metadata { if let Ok(current_mtime) = meta.modified() { let current_mtime = Timestamp::from(current_mtime); -if current_mtime == *cached_mtime { +if current_mtime == cached_mtime { // The mtime of that directory has not changed // since then, which means that the results of // `read_dir` should also be unchanged. @@ -222,7 +222,7 @@ directory_hg_path: <'tree, 'on_disk>, directory_fs_path: , directory_metadata: Option<::fs::Metadata>, -cached_directory_mtime: Option<>, +cached_directory_mtime: Option, is_at_repo_root: bool, ) -> Result { if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime) @@ -468,7 +468,7 @@ // unlikely enough in practice. let timestamp = directory_mtime.into(); let cached = dirstate_node.cached_directory_mtime(); -if cached != Some() { +if cached != Some(timestamp) { let hg_path = dirstate_node .full_path_borrowed(self.dmap.on_disk)? .detach_from_tree(); diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -2,6 +2,7 @@ //! //! See `mercurial/helptext/internals/dirstate-v2.txt` +use crate::dirstate::Timestamp; use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef}; use crate::dirstate_tree::path_with_basename::WithBasename; use crate::errors::HgError; @@ -15,7 +16,6 @@ use format_bytes::format_bytes; use std::borrow::Cow; use std::convert::{TryFrom, TryInto}; -use std::time::{SystemTime, UNIX_EPOCH}; /// Added at the start of `.hg/dirstate` when the "v2" format is used. /// This a redundant sanity check more than an actual "magic number" since @@ -119,9 +119,9 @@ } /// Duration since the Unix epoch -#[derive(BytesCast, Copy, Clone, PartialEq)] +#[derive(BytesCast, Copy, Clone)] #[repr(C)] -pub(super) struct Timestamp { +struct PackedTimestamp { seconds: I64Be, /// In `0 .. 1_000_000_000`. @@ -316,14 +316,14 @@ ) -> Result { if self.has_entry() { Ok(dirstate_map::NodeData::Entry(self.assume_entry())) -} else if let Some() = self.cached_directory_mtime() { +} else if let Some(mtime) = self.cached_directory_mtime() { Ok(dirstate_map::NodeData::CachedDirectory { mtime }) } else { Ok(dirstate_map::NodeData::None) } } -pub(super) fn cached_directory_mtime() -> Option<> { +pub(super) fn cached_directory_mtime() -> Option { if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() { Some(self.data.as_timestamp()) } else { @@ -423,58 +423,23 @@ }
D11634: dirstate-v2: Store unsigned integers inside DirstateEntry
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The negative marker values are not used anymore. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11634 AFFECTED FILES rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-cpython/src/dirstate/item.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/item.rs b/rust/hg-cpython/src/dirstate/item.rs --- a/rust/hg-cpython/src/dirstate/item.rs +++ b/rust/hg-cpython/src/dirstate/item.rs @@ -21,7 +21,7 @@ p2_info: bool = false, has_meaningful_data: bool = true, has_meaningful_mtime: bool = true, -parentfiledata: Option<(i32, i32, i32)> = None, +parentfiledata: Option<(u32, u32, u32)> = None, ) -> PyResult { let mut mode_size_opt = None; @@ -145,9 +145,9 @@ def set_clean( , -mode: i32, -size: i32, -mtime: i32, +mode: u32, +size: u32, +mtime: u32, ) -> PyResult { self.update(py, |entry| entry.set_clean(mode, size, mtime)); Ok(PyNone) diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -11,7 +11,7 @@ use crate::DirstateError; use crate::DirstateParents; use bitflags::bitflags; -use bytes_cast::unaligned::{I32Be, U16Be, U32Be}; +use bytes_cast::unaligned::{U16Be, U32Be}; use bytes_cast::BytesCast; use format_bytes::format_bytes; use std::borrow::Cow; @@ -113,9 +113,9 @@ #[derive(BytesCast, Copy, Clone, Debug)] #[repr(C)] struct Entry { -mode: I32Be, -size: I32Be, -mtime: I32Be, +mode: U32Be, +size: U32Be, +mtime: U32Be, } /// Duration since the Unix epoch diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -18,8 +18,8 @@ #[derive(Debug, PartialEq, Copy, Clone)] pub struct DirstateEntry { pub(crate) flags: Flags, -mode_size: Option<(i32, i32)>, -mtime: Option, +mode_size: Option<(u32, u32)>, +mtime: Option, } bitflags! { @@ -153,9 +153,17 @@ wdir_tracked: bool, p1_tracked: bool, p2_info: bool, -mode_size: Option<(i32, i32)>, -mtime: Option, +mode_size: Option<(u32, u32)>, +mtime: Option, ) -> Self { +if let Some((mode, size)) = mode_size { +// TODO: return an error for out of range values? +assert!(mode & !RANGE_MASK_31BIT == 0); +assert!(size & !RANGE_MASK_31BIT == 0); +} +if let Some(mtime) = mtime { +assert!(mtime & !RANGE_MASK_31BIT == 0); +} let mut flags = Flags::empty(); flags.set(Flags::WDIR_TRACKED, wdir_tracked); flags.set(Flags::P1_TRACKED, p1_tracked); @@ -189,12 +197,19 @@ mtime: None, } } else if mtime == MTIME_UNSET { +// TODO: return an error for negative values? +let mode = u32::try_from(mode).unwrap(); +let size = u32::try_from(size).unwrap(); Self { flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, mode_size: Some((mode, size)), mtime: None, } } else { +// TODO: return an error for negative values? +let mode = u32::try_from(mode).unwrap(); +let size = u32::try_from(size).unwrap(); +let mtime = u32::try_from(mtime).unwrap(); Self { flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED, mode_size: Some((mode, size)), @@ -282,7 +297,7 @@ /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)` pub(crate) fn v2_data( , -) -> (bool, bool, bool, Option<(i32, i32)>, Option) { +) -> (bool, bool, bool, Option<(u32, u32)>, Option) { if !self.any_tracked() { // TODO: return an Option instead? panic!("Accessing v1_state of an untracked DirstateEntry") @@ -316,7 +331,7 @@ fn v1_mode() -> i32 { if let Some((mode, _size)) = self.mode_size { -mode +i32::try_from(mode).unwrap() } else { 0 } @@ -338,7 +353,7 @@ } else if self.added() { SIZE_NON_NORMAL } else if let Some((_mode, size)) = self.mode_size { -size +i32::try_from(size).unwrap() } else {
D11631: dirstate-v2: Only convert from SystemTime to Timestamp and not back
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Converting from Timestamp back to SystemTime was only used for equality comparison, but this can also be done on Timestamp values. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11631 AFFECTED FILES rust/hg-core/src/dirstate_tree/on_disk.rs rust/hg-core/src/dirstate_tree/status.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/status.rs b/rust/hg-core/src/dirstate_tree/status.rs --- a/rust/hg-core/src/dirstate_tree/status.rs +++ b/rust/hg-core/src/dirstate_tree/status.rs @@ -199,7 +199,8 @@ // directory eligible for `read_dir` caching. if let Some(meta) = directory_metadata { if let Ok(current_mtime) = meta.modified() { -if current_mtime == cached_mtime.into() { +let current_mtime = Timestamp::from(current_mtime); +if current_mtime == *cached_mtime { // The mtime of that directory has not changed // since then, which means that the results of // `read_dir` should also be unchanged. diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -15,7 +15,7 @@ use format_bytes::format_bytes; use std::borrow::Cow; use std::convert::{TryFrom, TryInto}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::{SystemTime, UNIX_EPOCH}; /// Added at the start of `.hg/dirstate` when the "v2" format is used. /// This a redundant sanity check more than an actual "magic number" since @@ -462,18 +462,6 @@ } } -impl From<&'_ Timestamp> for SystemTime { -fn from(timestamp: &'_ Timestamp) -> Self { -let secs = timestamp.seconds.get(); -let nanos = timestamp.nanoseconds.get(); -if secs >= 0 { -UNIX_EPOCH + Duration::new(secs as u64, nanos) -} else { -UNIX_EPOCH - Duration::new((-secs) as u64, nanos) -} -} -} - fn read_hg_path( on_disk: &[u8], slice: PathSlice, To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11630: rust: update the rust-cpython crate to 0.7.0
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This notably brings support for Python 3.10, and includes the panic message when propagating a Rust panic as a Python exception. https://github.com/dgrunwald/rust-cpython/blob/master/CHANGELOG.md#070---2021-10-09 REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11630 AFFECTED FILES rust/Cargo.lock rust/hg-cpython/Cargo.toml CHANGE DETAILS diff --git a/rust/hg-cpython/Cargo.toml b/rust/hg-cpython/Cargo.toml --- a/rust/hg-cpython/Cargo.toml +++ b/rust/hg-cpython/Cargo.toml @@ -21,13 +21,10 @@ python3-bin = ["cpython/python3-sys"] [dependencies] +cpython = { version = "0.7.0", default-features = false } crossbeam-channel = "0.4" hg-core = { path = "../hg-core"} libc = '*' log = "0.4.8" env_logger = "0.7.1" stable_deref_trait = "1.2.0" - -[dependencies.cpython] -version = "0.6.0" -default-features = false diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "adler" version = "0.2.3" @@ -157,9 +159,9 @@ [[package]] name = "cpython" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index; -checksum = "8094679a4e9bfc8035572162624bc800eda35b5f9eff2537b9cd9aacc3d9782e" +checksum = "b7d46ba8ace7f3a1d204ac5060a706d0a68de6b42eafb6a586cc08bebcffe664" dependencies = [ "libc", "num-traits", @@ -652,9 +654,9 @@ [[package]] name = "python27-sys" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index; -checksum = "5826ddbc5366eb0b0492040fdc25bf50bb49092c192bd45e80fb7a24dc6832ab" +checksum = "94670354e264300dde81a5864cbb6bfc9d56ac3dcf3a278c32cb52f816f4dfd1" dependencies = [ "libc", "regex", @@ -662,9 +664,9 @@ [[package]] name = "python3-sys" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index; -checksum = "b78af21b29594951a47fc3dac9b9eff0a3f077dec2f780ee943ae16a668f3b6a" +checksum = "b18b32e64c103d5045f44644d7d65336f7a0521f6fde673240a9ecceb77e" dependencies = [ "libc", "regex", To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11629: dirstate-v2: Change the representation of negative directory mtime
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Change it from how I previously thought C’s `timespec` works to how it actually works. The previous behavior was also buggy for timestamps strictly before the epoch but less than one second away from it, because two’s complement does not distinguish negative zero from positive zero. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11629 AFFECTED FILES mercurial/helptext/internals/dirstate-v2.txt rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -126,8 +126,7 @@ /// In `0 .. 1_000_000_000`. /// -/// This timestamp is later or earlier than `(seconds, 0)` by this many -/// nanoseconds, if `seconds` is non-negative or negative, respectively. +/// This timestamp is after `(seconds, 0)` by this many nanoseconds. nanoseconds: U32Be, } @@ -444,15 +443,33 @@ } } +const NSEC_PER_SEC: u32 = 1_000_000_000; + impl From for Timestamp { fn from(system_time: SystemTime) -> Self { +// On Unix, `SystemTime` is a wrapper for the `timespec` C struct: +// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec +// We want to effectively access its fields, but the Rust standard +// library does not expose them. The best we can do is: let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) { Ok(duration) => { (duration.as_secs() as i64, duration.subsec_nanos()) } Err(error) => { +// `system_time` is before `UNIX_EPOCH`. +// We need to undo this algorithm: +// https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41 let negative = error.duration(); -(-(negative.as_secs() as i64), negative.subsec_nanos()) +let negative_secs = negative.as_secs() as i64; +let negative_nanos = negative.subsec_nanos(); +if negative_nanos == 0 { +(-negative_secs, 0) +} else { +// For example if `system_time` was 4.3 seconds before +// the Unix epoch we get a Duration that represents +// `(-4, -0.3)` but we want `(-5, +0.7)`: +(-1 - negative_secs, NSEC_PER_SEC - negative_nanos) +} } }; Timestamp { @@ -466,10 +483,17 @@ fn from(timestamp: &'_ Timestamp) -> Self { let secs = timestamp.seconds.get(); let nanos = timestamp.nanoseconds.get(); -if secs >= 0 { +if secs < 0 { +let (s_to_subtract, ns_to_subtract) = if nanos == 0 { +(-secs, 0) +} else { +// Example: `(-5, +0.7)` → `(-4, -0.3)` +// See `impl From for Timestamp` above +(1 - secs, NSEC_PER_SEC - nanos) +}; +UNIX_EPOCH - Duration::new(s_to_subtract as u64, ns_to_subtract) +} else { UNIX_EPOCH + Duration::new(secs as u64, nanos) -} else { -UNIX_EPOCH - Duration::new((-secs) as u64, nanos) } } } diff --git a/mercurial/helptext/internals/dirstate-v2.txt b/mercurial/helptext/internals/dirstate-v2.txt --- a/mercurial/helptext/internals/dirstate-v2.txt +++ b/mercurial/helptext/internals/dirstate-v2.txt @@ -443,20 +443,19 @@ If an untracked node `HAS_MTIME` *set*, what follows is the modification time of a directory - represented with separated second and sub-second components - since the Unix epoch: + represented similarly to the C `timespec` struct: * Offset 31: -The number of seconds as a signed (two’s complement) 64-bit integer. +The number of seconds elapsed since the Unix epoch, +as a signed (two’s complement) 64-bit integer. * Offset 39: -The number of nanoseconds as 32-bit integer. +The number of nanoseconds elapsed since +the instant specified by the previous field alone, +as 32-bit integer. Always greater than or equal to zero, and strictly less than a billion. Increasing this component makes the modification time -go forward or backward in time dependening -on the sign of the integral seconds components. -(Note: this is buggy because there is no negative zero integer, -but will be changed soon.) +go forward in time regardless of the sign of the seconds component. The presence of a directory modification time means that at some point, this path in
D11625: dirstate-v2: Document flags/mode/size/mtime fields of tree nodes
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This file format modification was previously left incomplete because of planned upcoming changes. Not all of these changes have been made yet, but documenting what exists today will help talking more widely about it. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11625 AFFECTED FILES mercurial/helptext/internals/dirstate-v2.txt mercurial/pure/parsers.py rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -64,44 +64,24 @@ uuid: &'on_disk [u8], } +/// Fields are documented in the *Tree metadata in the docket file* +/// section of `mercurial/helptext/internals/dirstate-v2.txt` #[derive(BytesCast)] #[repr(C)] struct TreeMetadata { root_nodes: ChildNodes, nodes_with_entry_count: Size, nodes_with_copy_source_count: Size, - -/// How many bytes of this data file are not used anymore unreachable_bytes: Size, - -/// Current version always sets these bytes to zero when creating or -/// updating a dirstate. Future versions could assign some bits to signal -/// for example "the version that last wrote/updated this dirstate did so -/// in such and such way that can be relied on by versions that know to." unused: [u8; 4], -/// If non-zero, a hash of ignore files that were used for some previous -/// run of the `status` algorithm. -/// -/// We define: -/// -/// * "Root" ignore files are `.hgignore` at the root of the repository if -/// it exists, and files from `ui.ignore.*` config. This set of files is -/// then sorted by the string representation of their path. -/// * The "expanded contents" of an ignore files is the byte string made -/// by concatenating its contents with the "expanded contents" of other -/// files included with `include:` or `subinclude:` files, in inclusion -/// order. This definition is recursive, as included files can -/// themselves include more files. -/// -/// This hash is defined as the SHA-1 of the concatenation (in sorted -/// order) of the "expanded contents" of each "root" ignore file. -/// (Note that computing this does not require actually concatenating byte -/// strings into contiguous memory, instead SHA-1 hashing can be done -/// incrementally.) +/// See *Optional hash of ignore patterns* section of +/// `mercurial/helptext/internals/dirstate-v2.txt` ignore_patterns_hash: IgnorePatternsHash, } +/// Fields are documented in the *The data file format* +/// section of `mercurial/helptext/internals/dirstate-v2.txt` #[derive(BytesCast)] #[repr(C)] pub(super) struct Node { @@ -114,45 +94,6 @@ children: ChildNodes, pub(super) descendants_with_entry_count: Size, pub(super) tracked_descendants_count: Size, - -/// Depending on the bits in `flags`: -/// -/// * If any of `WDIR_TRACKED`, `P1_TRACKED`, or `P2_INFO` are set, the -/// node has an entry. -/// -/// - If `HAS_MODE_AND_SIZE` is set, `data.mode` and `data.size` are -/// meaningful. Otherwise they are set to zero -/// - If `HAS_MTIME` is set, `data.mtime` is meaningful. Otherwise it is -/// set to zero. -/// -/// * If none of `WDIR_TRACKED`, `P1_TRACKED`, `P2_INFO`, or `HAS_MTIME` -/// are set, the node does not have an entry and `data` is set to all -/// zeros. -/// -/// * If none of `WDIR_TRACKED`, `P1_TRACKED`, `P2_INFO` are set, but -/// `HAS_MTIME` is set, the bytes of `data` should instead be -/// interpreted as the `Timestamp` for the mtime of a cached directory. -/// -/// The presence of this combination of flags means that at some point, -/// this path in the working directory was observed: -/// -/// - To be a directory -/// - With the modification time as given by `Timestamp` -/// - That timestamp was already strictly in the past when observed, -/// meaning that later changes cannot happen in the same clock tick -/// and must cause a different modification time (unless the system -/// clock jumps back and we get unlucky, which is not impossible but -/// but deemed unlikely enough). -/// - All direct children of this directory (as returned by -/// `std::fs::read_dir`) either have a corresponding dirstate node, or -/// are ignored by ignore patterns whose hash is in -/// `TreeMetadata::ignore_patterns_hash`. -/// -/// This means that if `std::fs::symlink_metadata` later reports the -/// sa
D11624: dirstate-v2: Change the representation of negative directory mtime
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Change it from how I previously thought C’s `timespec` works to how it actually works. See code comments. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11624 AFFECTED FILES rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -185,8 +185,7 @@ /// In `0 .. 1_000_000_000`. /// -/// This timestamp is later or earlier than `(seconds, 0)` by this many -/// nanoseconds, if `seconds` is non-negative or negative, respectively. +/// This timestamp is after `(seconds, 0)` by this many nanoseconds. nanoseconds: U32Be, } @@ -503,15 +502,33 @@ } } +const NSEC_PER_SEC: u32 = 1_000_000_000; + impl From for Timestamp { fn from(system_time: SystemTime) -> Self { +// On Unix, `SystemTime` is a wrapper for the `timespec` C struct: +// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec +// We want to effectively access its fields, but the Rust standard +// library does not expose them. The best we can do is: let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) { Ok(duration) => { (duration.as_secs() as i64, duration.subsec_nanos()) } Err(error) => { +// `system_time` is before `UNIX_EPOCH`. +// We need to undo this algorithm: +// https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41 let negative = error.duration(); -(-(negative.as_secs() as i64), negative.subsec_nanos()) +let negative_secs = negative.as_secs() as i64; +let negative_nanos = negative.subsec_nanos(); +if negative_nanos == 0 { +(-negative_secs, 0) +} else { +// For example if `system_time` was 4.3 seconds before +// the Unix epoch we get a Duration that represents +// `(-4, -0.3)` but we want `(-5, +0.7)`: +(-1 - negative_secs, NSEC_PER_SEC - negative_nanos) +} } }; Timestamp { @@ -525,10 +542,17 @@ fn from(timestamp: &'_ Timestamp) -> Self { let secs = timestamp.seconds.get(); let nanos = timestamp.nanoseconds.get(); -if secs >= 0 { +if secs < 0 { +let (s_to_subtract, ns_to_subtract) = if nanos == 0 { +(-secs, 0) +} else { +// Example: `(-5, +0.7)` → `(-4, -0.3)` +// See `impl From for Timestamp` above +(1 - secs, NSEC_PER_SEC - nanos) +}; +UNIX_EPOCH - Duration::new(s_to_subtract as u64, ns_to_subtract) +} else { UNIX_EPOCH + Duration::new(secs as u64, nanos) -} else { -UNIX_EPOCH - Duration::new((-secs) as u64, nanos) } } } To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11623: dirstate-v2: Use "byte sequence" in docs
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The patch originally sent as https://phab.mercurial-scm.org/D11546 used "byte string" but that was changed during review to avoid suggesting Unicode or character encodings. However "byte range" sounds to be like a range of *indices* within a byte string/sequence elsewhere. This changes to "byte sequence". Python docs use "sequence" a lot when discussing the `bytes` type: https://docs.python.org/3/library/stdtypes.html REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11623 AFFECTED FILES mercurial/helptext/internals/dirstate-v2.txt CHANGE DETAILS diff --git a/mercurial/helptext/internals/dirstate-v2.txt b/mercurial/helptext/internals/dirstate-v2.txt --- a/mercurial/helptext/internals/dirstate-v2.txt +++ b/mercurial/helptext/internals/dirstate-v2.txt @@ -285,10 +285,10 @@ This hash is defined as the SHA-1 of the concatenation (in sorted order) of the "expanded contents" of each "root" ignore file. -(Note that computing this does not require actually concatenating byte ranges into -contiguous memory. -Instead a SHA-1 hasher object can be created and fed separate byte ranges one by -one.) +(Note that computing this does not require actually concatenating +into a single contiguous byte sequence. +Instead a SHA-1 hasher object can be created +and fed separate chunks one by one.) The data file format @@ -299,11 +299,12 @@ The data file contains two types of data: paths and nodes. Paths and nodes can be organized in any order in the file, except that sibling -nodes must be next to each other and sorted by their path. Contiguity lets -the parent refer to them all by their count with a single pseudo-pointer, -instead of storing one pseudo-pointer per child node. Sorting allows using -binary seach to find a child node with a given name in `O(log(n))` byte ranges -comparisons. +nodes must be next to each other and sorted by their path. +Contiguity lets the parent refer to them all +by their count and a single pseudo-pointer, +instead of storing one pseudo-pointer per child node. +Sorting allows using binary seach to find a child node with a given name +in `O(log(n))` byte sequence comparisons. The current implemention writes paths and child node before a given node for ease of figuring out the value of pseudo-pointers by the time the are to be To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11621: dirstate: Move more methods to the _dirstatemapcommon base class
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This reduces duplication slightly and will help with supporting dirstate-v2 when Rust is not enabled. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11621 AFFECTED FILES mercurial/dirstatemap.py CHANGE DETAILS diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -56,6 +56,7 @@ self._nodelen = 20 # Also update Rust code when changing this! self._parents = None self._dirtyparents = False +self._docket = None # for consistent view between _pl() and _read() invocations self._pendingmode = None @@ -208,6 +209,93 @@ ) self._insert_entry(filename, entry) +### disk interaction + +def _opendirstatefile(self): +fp, mode = txnutil.trypending(self._root, self._opener, self._filename) +if self._pendingmode is not None and self._pendingmode != mode: +fp.close() +raise error.Abort( +_(b'working directory state may be changed parallelly') +) +self._pendingmode = mode +return fp + +def _readdirstatefile(self, size=-1): +try: +with self._opendirstatefile() as fp: +return fp.read(size) +except IOError as err: +if err.errno != errno.ENOENT: +raise +# File doesn't exist, so the current state is empty +return b'' + +@property +def docket(self): +if not self._docket: +if not self._use_dirstate_v2: +raise error.ProgrammingError( +b'dirstate only has a docket in v2 format' +) +self._docket = docketmod.DirstateDocket.parse( +self._readdirstatefile(), self._nodeconstants +) +return self._docket + +def write_v2_no_append(self, tr, st, meta, packed): +old_docket = self.docket +new_docket = docketmod.DirstateDocket.with_new_uuid( +self.parents(), len(packed), meta +) +data_filename = new_docket.data_filename() +if tr: +tr.add(data_filename, 0) +self._opener.write(data_filename, packed) +# Write the new docket after the new data file has been +# written. Because `st` was opened with `atomictemp=True`, +# the actual `.hg/dirstate` file is only affected on close. +st.write(new_docket.serialize()) +st.close() +# Remove the old data file after the new docket pointing to +# the new data file was written. +if old_docket.uuid: +data_filename = old_docket.data_filename() +unlink = lambda _tr=None: self._opener.unlink(data_filename) +if tr: +category = b"dirstate-v2-clean-" + old_docket.uuid +tr.addpostclose(category, unlink) +else: +unlink() +self._docket = new_docket + +### reading/setting parents + +def parents(self): +if not self._parents: +if self._use_dirstate_v2: +self._parents = self.docket.parents +else: +read_len = self._nodelen * 2 +st = self._readdirstatefile(read_len) +l = len(st) +if l == read_len: +self._parents = ( +st[: self._nodelen], +st[self._nodelen : 2 * self._nodelen], +) +elif l == 0: +self._parents = ( +self._nodeconstants.nullid, +self._nodeconstants.nullid, +) +else: +raise error.Abort( +_(b'working directory state appears damaged!') +) + +return self._parents + class dirstatemap(_dirstatemapcommon): """Map encapsulating the dirstate's contents. @@ -295,36 +383,6 @@ ### reading/setting parents -def parents(self): -if not self._parents: -try: -fp = self._opendirstatefile() -st = fp.read(2 * self._nodelen) -fp.close() -except IOError as err: -if err.errno != errno.ENOENT: -raise -# File doesn't exist, so the current state is empty -st = b'' - -l = len(st) -if l == self._nodelen * 2: -self._parents = ( -st[: self._nodelen], -st[self._nodelen : 2 * self._nodelen], -) -elif l == 0: -self._parents = ( -s
D11620: dirstate-v2: Change swap the order of size and mtime on disk
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This makes the dirstate-v2 file format match dirstate-v2 for the order of `mode`, `size`, and `mtime`. This order does not matter as long as these components are handled through named fields/attributes in code, but in a few places we still have tuples so having the same order everywhere might help avoid a bug that might not be obvious since those components have the same type. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11620 AFFECTED FILES rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -173,8 +173,8 @@ #[repr(C)] struct Entry { mode: I32Be, +size: I32Be, mtime: I32Be, -size: I32Be, } /// Duration since the Unix epoch To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11618: rust: Make the hg-cpython crate default to Python 3
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This default is used when running `cargo` manually such as for `cargo test`. `setup.py` and `Makefile` both configure the Python major version explicitly. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11618 AFFECTED FILES rust/hg-cpython/Cargo.toml setup.py CHANGE DETAILS diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -1428,12 +1428,9 @@ rusttargetdir = os.path.join('rust', 'target', 'release') -def __init__( -self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw -): +def __init__(self, mpath, sources, rustlibname, subcrate, **kw): Extension.__init__(self, mpath, sources, **kw) srcdir = self.rustsrcdir = os.path.join('rust', subcrate) -self.py3_features = py3_features # adding Rust source and control files to depends so that the extension # gets rebuilt if they've changed @@ -1481,9 +1478,11 @@ feature_flags = [] -if sys.version_info[0] == 3 and self.py3_features is not None: -feature_flags.append(self.py3_features) -cargocmd.append('--no-default-features') +cargocmd.append('--no-default-features') +if sys.version_info[0] == 2: +feature_flags.append('python27') +elif sys.version_info[0] == 3: +feature_flags.append('python3') rust_features = env.get("HG_RUST_FEATURES") if rust_features: @@ -1605,7 +1604,9 @@ extra_compile_args=common_cflags, ), RustStandaloneExtension( -'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3' +'mercurial.rustext', +'hg-cpython', +'librusthg', ), ] diff --git a/rust/hg-cpython/Cargo.toml b/rust/hg-cpython/Cargo.toml --- a/rust/hg-cpython/Cargo.toml +++ b/rust/hg-cpython/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [features] -default = ["python27"] +default = ["python3"] # Features to build an extension module: python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"] To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11546: dirstate-v2: Add internal documentation
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY It can be viewed by running `hg help internals.dirstate-v2` Since that command rewraps paragraphs, the source text is written with semantic line breaks: https://sembr.org/ REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11546 AFFECTED FILES mercurial/help.py mercurial/helptext/internals/dirstate-v2.txt rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -1,22 +1,6 @@ //! The "version 2" disk representation of the dirstate //! -//! # File format -//! -//! In dirstate-v2 format, the `.hg/dirstate` file is a "docket that starts -//! with a fixed-sized header whose layout is defined by the `DocketHeader` -//! struct, followed by the data file identifier. -//! -//! A separate `.hg/dirstate.{uuid}.d` file contains most of the data. That -//! file may be longer than the size given in the docket, but not shorter. Only -//! the start of the data file up to the given size is considered. The -//! fixed-size "root" of the dirstate tree whose layout is defined by the -//! `Root` struct is found at the end of that slice of data. -//! -//! Its `root_nodes` field contains the slice (offset and length) to -//! the nodes representing the files and directories at the root of the -//! repository. Each node is also fixed-size, defined by the `Node` struct. -//! Nodes in turn contain slices to variable-size paths, and to their own child -//! nodes (if any) for nested files and directories. +//! See `mercurial/helptext/internals/dirstate-v2.txt` use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef}; use crate::dirstate_tree::path_with_basename::WithBasename; diff --git a/mercurial/helptext/internals/dirstate-v2.txt b/mercurial/helptext/internals/dirstate-v2.txt new file mode 100644 --- /dev/null +++ b/mercurial/helptext/internals/dirstate-v2.txt @@ -0,0 +1,374 @@ +The *dirstate* is what Mercurial uses internally to track +the state of files in the working directory, +such as set by commands like `hg add` and `hg rm`. +It also contains some cached data that help make `hg status` faster. +The name refers both to `.hg/dirstate` on the filesystem +and the corresponding data structure in memory while a Mercurial process +is running. + +The original file format, retroactively dubbed `dirstate-v1`, +is described at https://www.mercurial-scm.org/wiki/DirState. +It is made of a flat sequence of unordered variable-size entries, +so accessing any information in it requires parsing all of it. +Similarly, saving changes requires rewriting the entire file. + +The newer `dirsate-v2` file format is designed to fix these limitations +and make `hg status` faster. + +User guide +== + +Compatibility +- + +The file format is experimental and may still change. +Different versions of Mercurial may not be compatible with each other +when working on a local repository that uses this format. +When using an incompatible version with the experimental format, +anything can happen including data corruption. + +Since the dirstate is entirely local and not relevant to the wire protocol, +`dirstate-v2` does not affect compatibility with remote Mercurial versions. + +When `share-safe` is enabled, different repositories sharing the same store +can use different dirstate formats. + +Enabling `dirsate-v2` for new local repositories + + +When creating a new local repository such as with `hg init` or `hg clone`, +the `exp-dirstate-v2` boolean in the `format` configuration section +controls whether to use this file format. +This is disabled by default as of this writing. +To enable it for a single repository, run for example:: + +$ hg init my-project --config format.exp-dirstate-v2=1 + +Checking the format of an existing local repsitory +-- + +The `debugformat` commands prints information about +which of multiple optional formats are used in the current repository, +including `dirstate-v2`:: + +$ hg debugformat +format-variant repo +fncache:yes +dirstate-v2:yes +[…] + +Upgrading or downgrading an existing local repository +- + +The `debugupgrade` command does various upgrades or downgrades +on a local repository +based on the current Mercurial version and on configuration. +The same `format.exp-dirstate-v2` configuration is used again. + +Example to upgrade:: + +$ hg debugupgrade --config format.exp-dirstate-v2=1 + +Example to downgrade to `dirstate-v1`:: + +$ hg debugupgrade --config format.
D11545: dirstate-v2: Move data file info in the docket closer together
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Having `data_size` next to `uuid_size` (and the UUID itself) makes more sense. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11545 AFFECTED FILES mercurial/dirstateutils/docket.py rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -67,11 +67,11 @@ parent_1: [u8; STORED_NODE_ID_BYTES], parent_2: [u8; STORED_NODE_ID_BYTES], +metadata: TreeMetadata, + /// Counted in bytes data_size: Size, -metadata: TreeMetadata, - uuid_size: u8, } diff --git a/mercurial/dirstateutils/docket.py b/mercurial/dirstateutils/docket.py --- a/mercurial/dirstateutils/docket.py +++ b/mercurial/dirstateutils/docket.py @@ -21,15 +21,15 @@ # * 12 bytes: format marker # * 32 bytes: node ID of the working directory's first parent # * 32 bytes: node ID of the working directory's second parent +# * {TREE_METADATA_SIZE} bytes: tree metadata, parsed separately # * 4 bytes: big-endian used size of the data file -# * {TREE_METADATA_SIZE} bytes: tree metadata, parsed separately # * 1 byte: length of the data file's UUID # * variable: data file's UUID # # Node IDs are null-padded if shorter than 32 bytes. # A data file shorter than the specified used size is corrupted (truncated) HEADER = struct.Struct( -">{}s32s32sL{}sB".format(len(V2_FORMAT_MARKER), TREE_METADATA_SIZE) +">{}s32s32s{}sLB".format(len(V2_FORMAT_MARKER), TREE_METADATA_SIZE) ) @@ -51,7 +51,7 @@ if not data: parents = (nodeconstants.nullid, nodeconstants.nullid) return cls(parents, 0, b'', None) -marker, p1, p2, data_size, meta, uuid_size = HEADER.unpack_from(data) +marker, p1, p2, meta, data_size, uuid_size = HEADER.unpack_from(data) if marker != V2_FORMAT_MARKER: raise ValueError("expected dirstate-v2 marker") uuid = data[HEADER.size : HEADER.size + uuid_size] @@ -65,8 +65,8 @@ V2_FORMAT_MARKER, p1, p2, +self.tree_metadata, self.data_size, -self.tree_metadata, len(self.uuid), ) return header + self.uuid To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11520: dirstate-v2: Add support when Rust is not enabled
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This wires into `dirstatemap` the parser and serializer added in previous changesets. The memory representation is still the same, with a flat `dict` for `DirstateItem`s and another one for copy sources. Serialization always creates a new dirstate-v2 data file and does not support (when Rust is not enabled) appending to an existing one, since we don’t keep track of which tree nodes are new or modified. Instead the tree is reconstructed during serialization. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11520 AFFECTED FILES mercurial/dirstate.py mercurial/dirstatemap.py mercurial/localrepo.py tests/test-dirstate-race.t tests/test-dirstate-race2.t tests/test-dirstate.t tests/test-hgignore.t tests/test-permissions.t tests/test-purge.t tests/test-status.t tests/test-symlinks.t CHANGE DETAILS diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t --- a/tests/test-symlinks.t +++ b/tests/test-symlinks.t @@ -3,7 +3,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif diff --git a/tests/test-status.t b/tests/test-status.t --- a/tests/test-status.t +++ b/tests/test-status.t @@ -1,13 +1,6 @@ #testcases dirstate-v1 dirstate-v2 -#if no-rust - $ hg init repo0 --config format.exp-dirstate-v2=1 - abort: dirstate v2 format requested by config but not supported (requires Rust extensions) - [255] -#endif - #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif @@ -743,7 +736,7 @@ if also listing unknowns. The tree-based dirstate and status algorithm fix this: -#if symlink no-dirstate-v1 +#if symlink no-dirstate-v1 rust $ cd .. $ hg init issue6335 @@ -759,11 +752,11 @@ ? bar/a ? foo - $ hg status -c # incorrect output with `dirstate-v1` + $ hg status -c # incorrect output without the Rust implementation $ hg status -cu ? bar/a ? foo - $ hg status -d # incorrect output with `dirstate-v1` + $ hg status -d # incorrect output without the Rust implementation ! foo/a $ hg status -du ! foo/a @@ -910,7 +903,7 @@ I B.hs I ignored-folder/ctest.hs -#if dirstate-v2 +#if rust dirstate-v2 Check read_dir caching diff --git a/tests/test-purge.t b/tests/test-purge.t --- a/tests/test-purge.t +++ b/tests/test-purge.t @@ -1,7 +1,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif diff --git a/tests/test-permissions.t b/tests/test-permissions.t --- a/tests/test-permissions.t +++ b/tests/test-permissions.t @@ -3,7 +3,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif diff --git a/tests/test-hgignore.t b/tests/test-hgignore.t --- a/tests/test-hgignore.t +++ b/tests/test-hgignore.t @@ -1,7 +1,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif @@ -397,9 +396,10 @@ #endif -#if dirstate-v2 +#if dirstate-v2 rust Check the hash of ignore patterns written in the dirstate +This is an optimization that is only relevant when using the Rust extensions $ hg status > /dev/null $ cat .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1 diff --git a/tests/test-dirstate.t b/tests/test-dirstate.t --- a/tests/test-dirstate.t +++ b/tests/test-dirstate.t @@ -1,7 +1,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif diff --git a/tests/test-dirstate-race2.t b/tests/test-dirstate-race2.t --- a/tests/test-dirstate-race2.t +++ b/tests/test-dirstate-race2.t @@ -1,7 +1,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif diff --git a/tests/test-dirstate-race.t b/tests/test-dirstate-race.t --- a/tests/test-dirstate-race.t +++ b/tests/test-dirstate-race.t @@ -1,7 +1,6 @@ #testcases dirstate-v1 dirstate-v2 #if dirstate-v2 -#require rust $ echo '[format]' >> $HGRCPATH $ echo 'exp-dirstate-v2=1' >> $HGRCPATH #endif diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -917,9 +917,6 @@ # Start with all requirements supported by this file. supported = set(localrepository._basesupported) -if dirstate.SUPPORTS_DIRSTATE_V2: -
D11518: dirstate-v2: Initial Python parser
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The dirstate-v2 file format should be supported even if Rust extensions are not enabled. This changeset adds parsing code that is not used yet. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11518 AFFECTED FILES mercurial/dirstatemap.py mercurial/dirstateutils/docket.py mercurial/dirstateutils/v2.py rust/hg-core/src/dirstate_tree/on_disk.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -47,16 +47,16 @@ pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20; pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN]; -/// Must match the constant of the same name in -/// `mercurial/dirstateutils/docket.py` +/// Must match constants of the same names in `mercurial/dirstateutils/v2.py` const TREE_METADATA_SIZE: usize = 44; +const NODE_SIZE: usize = 43; /// Make sure that size-affecting changes are made knowingly #[allow(unused)] fn static_assert_size_of() { let _ = std::mem::transmute::; let _ = std::mem::transmute::; -let _ = std::mem::transmute::; +let _ = std::mem::transmute::; } // Must match `HEADER` in `mercurial/dirstateutils/docket.py` @@ -169,8 +169,8 @@ #[repr(C)] struct Entry { mode: I32Be, +size: I32Be, mtime: I32Be, -size: I32Be, } /// Duration since the Unix epoch diff --git a/mercurial/dirstateutils/v2.py b/mercurial/dirstateutils/v2.py new file mode 100644 --- /dev/null +++ b/mercurial/dirstateutils/v2.py @@ -0,0 +1,106 @@ +# v2.py - Pure-Python implementation of the dirstate-v2 file format +# +# Copyright Mercurial Contributors +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import struct + +from .. import policy + +parsers = policy.importmod('parsers') +DirstateItem = parsers.DirstateItem + + +# Must match the constant of the same name in +# `rust/hg-core/src/dirstate_tree/on_disk.rs` +TREE_METADATA_SIZE = 44 +NODE_SIZE = 43 + + +# Must match the `TreeMetadata` Rust struct in +# `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there. +# +# * 4 bytes: start offset of root nodes +# * 4 bytes: number of root nodes +# * 4 bytes: total number of nodes in the tree that have an entry +# * 4 bytes: total number of nodes in the tree that have a copy source +# * 4 bytes: number of bytes in the data file that are not used anymore +# * 4 bytes: unused +# * 20 bytes: SHA-1 hash of ignore patterns +TREE_METADATA = struct.Struct('>L4s20s') + + +# Must match the `Node` Rust struct in +# `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there. +# +# * 4 bytes: start offset of full path +# * 2 bytes: length of the full path +# * 2 bytes: length within the full path before its "base name" +# * 4 bytes: start offset of the copy source if any, or zero for no copy source +# * 2 bytes: length of the copy source if any, or unused +# * 4 bytes: start offset of child nodes +# * 4 bytes: number of child nodes +# * 4 bytes: number of descendant nodes that have an entry +# * 4 bytes: number of descendant nodes that have a "tracked" state +# * 1 byte: state +# * 4 bytes: entry mode +# * 4 bytes: entry size +# * 4 bytes: entry mtime +NODE = struct.Struct('>LHHLHclll') + + +assert TREE_METADATA_SIZE == TREE_METADATA.size +assert NODE_SIZE == NODE.size + + +def parse_dirstate(map, copy_map, data, tree_metadata): +( +root_nodes_start, +root_nodes_len, +_nodes_with_entry_count, +_nodes_with_copy_source_count, +_unreachable_bytes, +_unused, +_ignore_patterns_hash, +) = TREE_METADATA.unpack(tree_metadata) +parse_nodes(map, copy_map, data, root_nodes_start, root_nodes_len) + + +def parse_nodes(map, copy_map, data, start, len): +for i in range(len): +node_start = start + NODE_SIZE * i +node_bytes = slice_with_len(data, node_start, NODE_SIZE) +( +path_start, +path_len, +_basename_strat, +copy_source_start, +copy_source_len, +children_start, +children_count, +_descendants_with_entry_count, +_tracked_descendants_count, +state, +mode, +size, +mtime, +) = NODE.unpack(node_bytes) +# Recurse +parse_nodes(map, copy_map, data, children_start, children_count) + +if state not in b'narm': +continue +path = slice_with_len(data, path_start, path_len) +map[path] = DirstateItem.from_v1_data(state, mode, size, mtime) +
D11519: dirstate-v2: Initial Python serializer
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This adds code seralizing a `map` and `copy_map` dicts into dirstate-v2 file formate. This is not used yet. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11519 AFFECTED FILES mercurial/dirstateutils/v2.py CHANGE DETAILS diff --git a/mercurial/dirstateutils/v2.py b/mercurial/dirstateutils/v2.py --- a/mercurial/dirstateutils/v2.py +++ b/mercurial/dirstateutils/v2.py @@ -9,7 +9,8 @@ import struct -from .. import policy +from ..thirdparty import attr +from .. import error, policy parsers = policy.importmod('parsers') DirstateItem = parsers.DirstateItem @@ -77,7 +78,7 @@ ( path_start, path_len, -_basename_strat, +_basename_start, copy_source_start, copy_source_len, children_start, @@ -104,3 +105,297 @@ def slice_with_len(data, start, len): return data[start : start + len] + + +@attr.s +class Node(object): +path = attr.ib() +entry = attr.ib() +parent = attr.ib(default=None) +children_count = attr.ib(default=0) +children_offset = attr.ib(default=0) +descendants_with_entry = attr.ib(default=0) +tracked_descendants = attr.ib(default=0) + +def pack(self, copy_map, paths_offset): +path = self.path +copy = copy_map.get(path) +entry = self.entry + +path_start = paths_offset +path_len = len(path) +basename_start = path.rfind(b'/') + 1 # 0 if rfind returns -1 +if copy is not None: +copy_source_start = paths_offset + len(path) +copy_source_len = len(copy) +else: +copy_source_start = 0 +copy_source_len = 0 +if entry is not None: +state = entry.v1_state() +mode = entry.v1_mode() +size = entry.v1_size() +mtime = entry.v1_mtime() +else: +# There are no mtime-cached directories in the Python implementation +state = b'\0' +mode = 0 +size = 0 +mtime = 0 +return NODE.pack( +path_start, +path_len, +basename_start, +copy_source_start, +copy_source_len, +self.children_offset, +self.children_count, +self.descendants_with_entry, +self.tracked_descendants, +state, +mode, +size, +mtime, +) + + +def pack_dirstate(map, copy_map, now): +""" +Pack `map` and `copy_map` into the dirstate v2 binary format and return +the bytearray. +`now` is a timestamp of the current filesystem time used to detect race +conditions in writing the dirstate to disk, see inline comment. + +The on-disk format expects a tree-like structure where the leaves are +written first (and sorted per-directory), going up levels until the root +node and writing that one to the docket. See more details on the on-disk +format in `mercurial/helptext/internals/dirstate-v2`. + +Since both `map` and `copy_map` are flat dicts we need to figure out the +hierarchy. This algorithm does so without having to build the entire tree +in-memory: it only keeps the minimum number of nodes around to satisfy the +format. + +# Algorithm explanation + +This explanation does not talk about the different counters for tracked +descendents and storing the copies, but that work is pretty simple once this +algorithm is in place. + +## Building a subtree + +First, sort `map`: this makes it so the leaves of the tree are contiguous +per directory (i.e. a/b/c and a/b/d will be next to each other in the list), +and enables us to use the ordering of folders to have a "cursor" of the +current folder we're in without ever going twice in the same branch of the +tree. The cursor is a node that remembers its parent and any information +relevant to the format (see the `Node` class), building the relevant part +of the tree lazily. +Then, for each file in `map`, move the cursor into the tree to the +corresponding folder of the file: for example, if the very first file +is "a/b/c", we start from `Node[""]`, create `Node["a"]` which points to +its parent `Node[""]`, then create `Node["a/b"]`, which points to its parent +`Node["a"]`. These nodes are kept around in a stack. +If the next file in `map` is in the same subtree ("a/b/d" or "a/b/e/f"), we +add it to the stack and keep looping with the same logic of creating the +tree nodes as needed. If however the next file in `map` is *not* in the same +subtree ("a/other", if we're
D11516: dirstate: Remove the flat Rust DirstateMap implementation
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Before this changeset we had two Rust implementations of `DirstateMap`. This removes the "flat" DirstateMap so that the "tree" DirstateMap is always used when Rust enabled. This simplifies the code a lot, and will enable (in the next changeset) further removal of a trait abstraction. This is a performance regression when: - Rust is enabled, and - The repository uses the legacy dirstate-v1 file format, and - For `hg status`, unknown files are not listed (such as with `-mard`) The regression is about 100 milliseconds for `hg status -mard` on a semi-large repository (mozilla-central), from ~320ms to ~420ms. We deem this to be small enough to be worth it. The new dirstate-v2 is still experimental at this point, but we aim to stabilize it (though not yet enable it by default for new repositories) in Mercurial 6.0. Eventually, upgrating repositories to dirsate-v2 will eliminate this regression (and enable other performance improvements). Background == The flat DirstateMap was introduced with the first Rust implementation of the status algorithm. It works similarly to the previous Python + C one, with a single `HashMap` that associates file paths to a `DirstateEntry` (where Python has a dict). We later added the tree DirstateMap where the root of the tree contains nodes for files and directories that are directly at the root of the repository, and nodes for directories can contain child nodes representing the files and directly that *they* contain directly. The shape of this tree mirrors that of the working directory in the filesystem. This enables the status algorithm to traverse this tree in tandem with traversing the filesystem tree, which in turns enables a more efficient algorithm. Furthermore, the new dirstate-v2 file format is also based on a tree of the same shape. The tree DirstateMap can access a dirstate-v2 file without parsing it: binary data in a single large (possibly memory-mapped) bytes buffer is traversed on demand. This allows `DirstateMap` creation to take `O(1)` time. (Mutation works by creating new in-memory nodes with copy-on-write semantics, and serialization is append-mostly.) The tradeoff is that for "legacy" repositories that use the dirstate-v1 file format, parsing that file into a tree DirstateMap takes more time. Profiling shows that this time is dominated by `HashMap`. For a dirstate containing `F` files with an average `D` directory depth, the flat DirstateMap does parsing in `O(F)` number of HashMap operations but the tree DirstateMap in `O(F × D)` operations, since each node has its own HashMap containing its child nodes. This slower costs ~140ms on an old snapshot of mozilla-central, and ~80ms on an old snapshot of the Netbeans repository. The status algorithm is faster, but with `-mard` (when not listing unknown files) it is typically not faster *enough* to compensate the slower parsing. Both Rust implementations are always faster than the Python + C implementation Benchmark results = All benchmarks are run on changeset 98c0408324e6 <https://phab.mercurial-scm.org/rHG98c0408324e6bdf9dc4d306b467167b869092d5a>, with repositories that use the dirstate-v1 file format, on a server with 4 CPU cores and 4 CPU threads (no HyperThreading). `hg status` benchmarks show wall clock times of the entire command as the average and standard deviation of serveral runs, collected by https://github.com/sharkdp/hyperfine and reformated. Parsing benchmarks are wall clock time of the Rust function that converts a bytes buffer of the dirstate file into the `DirstateMap` data structure as used by the status algorithm. A single run each, collected by running `hg status` this environment variable: RUST_LOG=hg::dirstate::dirstate_map=trace,hg::dirstate_tree::dirstate_map=trace Benchmark 1: Rust flat DirstateMap → Rust tree DirstateMap hg status mozilla-clean 562.3 ms ± 2.0 ms → 462.5 ms ± 0.6 ms 1.22 ± 0.00 times faster mozilla-dirty 859.6 ms ± 2.2 ms → 719.5 ms ± 3.2 ms 1.19 ± 0.01 times faster mozilla-ignored 558.2 ms ± 3.0 ms → 457.9 ms ± 2.9 ms 1.22 ± 0.01 times faster mozilla-unknowns859.4 ms ± 5.7 ms → 716.0 ms ± 4.7 ms 1.20 ± 0.01 times faster netbeans-clean 336.5 ms ± 0.9 ms → 339.5 ms ± 0.4 ms 0.99 ± 0.00 times faster netbeans-dirty 491.4 ms ± 1.6 ms → 475.1 ms ± 1.2 ms 1.03 ± 0.00 times faster netbeans-ignored343.7 ms ± 1.0 ms → 347.8 ms ± 0.4 ms 0.99 ± 0.00 times faster netbeans-unknowns 484.3 ms ± 1.0 ms →
D11517: dirstate: Remove the Rust abstraction DirstateMapMethods
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This Rust trait used to exist in order to allow the DirstateMap class exposed to Python to be backed by either of two implementations: one similar to the Python implementation based on a "flat" `HashMap`, and the newer one based on a tree of nodes matching the directory structure of tracked files. A boxed trait object was used with dynamic dispatch. With the flat implementation removed and only the tree one remaining, this abstraction is not useful anymore and the concrete type can be stored directly. It remains that the trait was implemented separately for `DirstateMap<'_>` (which takes a lifetime parameter) and `OwningDirstateMap` (whose job is to wrap the former and hide the lifetime parameter), with the latter impl only forwarding calls. This changeset also removes this forwarding. Instead, the methods formerly of the `DirstateMapMethods` trait are now inherent methods implemented for `OwningDirstateMap` (where they will actually be used) but in the module that defines `DirstateMap`. This unusual setup gives access to the private fields of `DirstateMap` from those methods. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11517 AFFECTED FILES rust/hg-core/src/dirstate_tree.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/dispatch.rs rust/hg-core/src/dirstate_tree/owning.rs rust/hg-core/src/dirstate_tree/owning_dispatch.rs rust/hg-core/src/repo.rs rust/hg-cpython/src/dirstate/dirstate_map.rs rust/rhg/src/commands/status.rs CHANGE DETAILS diff --git a/rust/rhg/src/commands/status.rs b/rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs +++ b/rust/rhg/src/commands/status.rs @@ -9,7 +9,6 @@ use crate::ui::Ui; use clap::{Arg, SubCommand}; use hg; -use hg::dirstate_tree::dispatch::DirstateMapMethods; use hg::errors::HgError; use hg::manifest::Manifest; use hg::matchers::AlwaysMatcher; diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -25,7 +25,6 @@ dirstate::parsers::Timestamp, dirstate::StateMapIter, dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, -dirstate_tree::dispatch::DirstateMapMethods, dirstate_tree::on_disk::DirstateV2ParseError, dirstate_tree::owning::OwningDirstateMap, revlog::Node, @@ -47,7 +46,7 @@ // All attributes also have to have a separate refcount data attribute for // leaks, with all methods that go along for reference sharing. py_class!(pub class DirstateMap |py| { -@shared data inner: Box; +@shared data inner: OwningDirstateMap; /// Returns a `(dirstate_map, parents)` tuple @staticmethod @@ -56,12 +55,12 @@ ) -> PyResult { let on_disk = PyBytesDeref::new(py, on_disk); let mut map = OwningDirstateMap::new_empty(on_disk); -let (on_disk, map_placeholder) = map.get_mut_pair(); +let (on_disk, map_placeholder) = map.get_pair_mut(); let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk) .map_err(|e| dirstate_error(py, e))?; *map_placeholder = actual_map; -let map = Self::create_instance(py, Box::new(map))?; +let map = Self::create_instance(py, map)?; let parents = parents.map(|p| { let p1 = PyBytes::new(py, p.p1.as_bytes()); let p2 = PyBytes::new(py, p.p2.as_bytes()); @@ -82,11 +81,11 @@ }; let on_disk = PyBytesDeref::new(py, on_disk); let mut map = OwningDirstateMap::new_empty(on_disk); -let (on_disk, map_placeholder) = map.get_mut_pair(); +let (on_disk, map_placeholder) = map.get_pair_mut(); *map_placeholder = TreeDirstateMap::new_v2( on_disk, data_size, tree_metadata.data(py), ).map_err(dirstate_error)?; -let map = Self::create_instance(py, Box::new(map))?; +let map = Self::create_instance(py, map)?; Ok(map.into_object()) } @@ -452,7 +451,7 @@ pub fn get_inner_mut<'a>( &'a self, py: Python<'a>, -) -> RefMut<'a, Box> { +) -> RefMut<'a, OwningDirstateMap> { self.inner(py).borrow_mut() } fn translate_key( diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -294,12 +294,12 @@ } else { OwningDirstateMap::new_empty(Vec::new()) }; -let (on_disk, placeholder) = map.get_mut_pair(); +let (on_disk, placeholder) = map.get_pair_mut(); *placeholder = DirstateMap::new_v2(on_disk, data_s
D11507: dirstate: drop all logic around the "non-normal" sets
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The dirstate has a lot of code to compute a set of all "non-normal" and "from_other_parent" entries. This is all used in a single location, when `setparent` is called and moved from a merge to a non merge. At that time, any "merge related" information has to be dropped. This is mostly useful for command like `graft` or `shelve` that move to a single-parent state -before- the commit. Otherwise the commit will already have removed all traces of the merge information in the dirstate (e.g. for a regular merges). The bookkeeping for these set is quite invasive. And it seems simpler to just drop it and do the full computation in the single location where we actually use it (since we have to do the computation at least once anyway). This simplify the code a lot, and clarify why since kind of computation is needed. The possible drawback from before are: - if the operation happens in a loop, we will end up doing it multiple time, - the C code to detect entry of interest have been dropped, for now. It will be re-introduced later, with a processing code directly in C for even faster operation. I did not touch the Rust related code as I expect that Simon Sapin will take of it in a larger scale refactoring. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11507 AFFECTED FILES contrib/dirstatenonnormalcheck.py mercurial/cext/parsers.c mercurial/dirstatemap.py mercurial/pure/parsers.py rust/hg-core/src/dirstate/dirstate_map.rs rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/dispatch.rs rust/hg-core/src/dirstate_tree/owning_dispatch.rs rust/hg-cpython/src/dirstate.rs rust/hg-cpython/src/dirstate/dirstate_map.rs rust/hg-cpython/src/dirstate/item.rs rust/hg-cpython/src/dirstate/non_normal_entries.rs tests/test-dirstate-nonnormalset.t CHANGE DETAILS diff --git a/tests/test-dirstate-nonnormalset.t b/tests/test-dirstate-nonnormalset.t deleted file mode 100644 --- a/tests/test-dirstate-nonnormalset.t +++ /dev/null @@ -1,22 +0,0 @@ - $ cat >> $HGRCPATH << EOF - > [command-templates] - > log="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n" - > [extensions] - > dirstateparanoidcheck = $TESTDIR/../contrib/dirstatenonnormalcheck.py - > [experimental] - > nonnormalparanoidcheck = True - > [devel] - > all-warnings=True - > EOF - $ mkcommit() { - >echo "$1" > "$1" - >hg add "$1" - >hg ci -m "add $1" - > } - - $ hg init testrepo - $ cd testrepo - $ mkcommit a - $ mkcommit b - $ mkcommit c - $ hg status diff --git a/rust/hg-cpython/src/dirstate/non_normal_entries.rs b/rust/hg-cpython/src/dirstate/non_normal_entries.rs deleted file mode 100644 --- a/rust/hg-cpython/src/dirstate/non_normal_entries.rs +++ /dev/null @@ -1,83 +0,0 @@ -// non_normal_other_parent_entries.rs -// -// Copyright 2020 Raphaël Gomès -// -// This software may be used and distributed according to the terms of the -// GNU General Public License version 2 or any later version. - -use cpython::{ -exc::NotImplementedError, CompareOp, ObjectProtocol, PyBytes, PyClone, -PyErr, PyObject, PyResult, PyString, Python, PythonObject, ToPyObject, -UnsafePyLeaked, -}; - -use crate::dirstate::dirstate_map::v2_error; -use crate::dirstate::DirstateMap; -use hg::dirstate_tree::on_disk::DirstateV2ParseError; -use hg::utils::hg_path::HgPath; -use std::cell::RefCell; - -py_class!(pub class NonNormalEntries |py| { -data dmap: DirstateMap; - -def __contains__(, key: PyObject) -> PyResult { -self.dmap(py).non_normal_entries_contains(py, key) -} -def remove(, key: PyObject) -> PyResult { -self.dmap(py).non_normal_entries_remove(py, key) -} -def add(, key: PyObject) -> PyResult { -self.dmap(py).non_normal_entries_add(py, key) -} -def discard(, key: PyObject) -> PyResult { -self.dmap(py).non_normal_entries_discard(py, key) -} -def __richcmp__(, other: PyObject, op: CompareOp) -> PyResult { -match op { -CompareOp::Eq => self.is_equal_to(py, other), -CompareOp::Ne => Ok(!self.is_equal_to(py, other)?), -_ => Err(PyErr::new::(py, "")) -} -} -def __repr__() -> PyResult { -self.dmap(py).non_normal_entries_display(py) -} - -def __iter__() -> PyResult { -self.dmap(py).non_normal_entries_iter(py) -} -}); - -impl NonNormalEntries { -pub fn from_inner(py: Python, dm: DirstateMap) -> PyResult { -Self::create_instance(py, dm) -} - -fn is_equal_to(, py:
D11502: dirstate: drop the `clearambiguoustimes` method for the map
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This is no longer called anywhere. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11502 AFFECTED FILES mercurial/dirstatemap.py rust/hg-core/src/dirstate/dirstate_map.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/dispatch.rs rust/hg-core/src/dirstate_tree/owning_dispatch.rs rust/hg-cpython/src/dirstate/dirstate_map.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -12,9 +12,9 @@ use std::convert::TryInto; use cpython::{ -exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, -PyNone, PyObject, PyResult, PySet, PyString, Python, PythonObject, -ToPyObject, UnsafePyLeaked, +exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject, +PyResult, PySet, PyString, Python, PythonObject, ToPyObject, +UnsafePyLeaked, }; use crate::{ @@ -185,26 +185,6 @@ Ok(PyNone) } -def clearambiguoustimes( -, -files: PyObject, -now: PyObject -) -> PyResult { -let files: PyResult> = files -.iter(py)? -.map(|filename| { -Ok(HgPathBuf::from_bytes( -filename?.extract::(py)?.data(py), -)) -}) -.collect(); -self.inner(py) -.borrow_mut() -.clear_ambiguous_times(files?, now.extract(py)?) -.map_err(|e| v2_error(py, e))?; -Ok(py.None()) -} - def other_parent_entries() -> PyResult { let mut inner_shared = self.inner(py).borrow_mut(); let set = PySet::empty(py)?; diff --git a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs --- a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs @@ -51,14 +51,6 @@ self.get_mut().drop_entry_and_copy_source(filename) } -fn clear_ambiguous_times( - self, -filenames: Vec, -now: i32, -) -> Result<(), DirstateV2ParseError> { -self.get_mut().clear_ambiguous_times(filenames, now) -} - fn non_normal_entries_contains( self, key: , diff --git a/rust/hg-core/src/dirstate_tree/dispatch.rs b/rust/hg-core/src/dirstate_tree/dispatch.rs --- a/rust/hg-core/src/dirstate_tree/dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/dispatch.rs @@ -67,15 +67,6 @@ filename: , ) -> Result<(), DirstateError>; -/// Among given files, mark the stored `mtime` as ambiguous if there is one -/// (if `state == EntryState::Normal`) equal to the given current Unix -/// timestamp. -fn clear_ambiguous_times( - self, -filenames: Vec, -now: i32, -) -> Result<(), DirstateV2ParseError>; - /// Return whether the map has an "non-normal" entry for the given /// filename. That is, any entry with a `state` other than /// `EntryState::Normal` or with an ambiguous `mtime`. @@ -165,20 +156,18 @@ /// file with a dirstate entry. fn has_dir( self, directory: ) -> Result; -/// Clear mtimes that are ambigous with `now` (similar to -/// `clear_ambiguous_times` but for all files in the dirstate map), and -/// serialize bytes to write the `.hg/dirstate` file to disk in dirstate-v1 -/// format. +/// Clear mtimes equal to `now` in entries with `state == +/// EntryState::Normal`, and serialize bytes to write the `.hg/dirstate` +/// file to disk in dirstate-v1 format. fn pack_v1( self, parents: DirstateParents, now: Timestamp, ) -> Result, DirstateError>; -/// Clear mtimes that are ambigous with `now` (similar to -/// `clear_ambiguous_times` but for all files in the dirstate map), and -/// serialize bytes to write a dirstate data file to disk in dirstate-v2 -/// format. +/// Clear mtimes equal to `now` in entries with `state == +/// EntryState::Normal`, and serialize bytes to write a dirstate data file +/// to disk in dirstate-v2 format. /// /// Returns new data and metadata together with whether that data should be /// appended to the existing data file whose content is at @@ -341,14 +330,6 @@ self.drop_entry_and_copy_source(filename) } -fn clear_ambiguous_times( - self, -filenames: Vec, -now: i32, -) -> Result<(), DirstateV2ParseError> { -Ok(self.clear_ambiguous_times(filenames, now)) -} - fn non_normal_entries_contains( self, key: , diff -
D11506: dirstate: use a new `drop_merge_data` in `setparent`
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY What is happening this `setparent` loop is that we remove all `merge` related information when the dirstate is moved out of a `merge` situation. So instead of suffling state to get them where we want, we simply add a method on the DirstateItem to do exactly that. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11506 AFFECTED FILES mercurial/cext/parsers.c mercurial/dirstatemap.py mercurial/pure/parsers.py CHANGE DETAILS diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py --- a/mercurial/pure/parsers.py +++ b/mercurial/pure/parsers.py @@ -263,6 +263,24 @@ self._size = None self._mtime = None +def drop_merge_data(self): +"""remove all "merge-only" from a DirstateItem + +This is to be call by the dirstatemap code when the second parent is dropped +""" +if not (self.merged or self.from_p2): +return +self._p1_tracked = self.merged # why is this not already properly set ? + +self._merged = False +self._clean_p1 = False +self._clean_p2 = False +self._p2_tracked = False +self._possibly_dirty = True +self._mode = None +self._size = None +self._mtime = None + @property def mode(self): return self.v1_mode() diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -441,26 +441,11 @@ continue # Discard "merged" markers when moving away from a merge state -if s.merged: -source = self.copymap.get(f) +if s.merged or s.from_p2: +source = self.copymap.pop(f, None) if source: copies[f] = source -self.reset_state( -f, -wc_tracked=True, -p1_tracked=True, -possibly_dirty=True, -) -# Also fix up otherparent markers -elif s.from_p2: -source = self.copymap.get(f) -if source: -copies[f] = source -self.reset_state( -f, -p1_tracked=False, -wc_tracked=True, -) +s.drop_merge_data() return copies def read(self): diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c --- a/mercurial/cext/parsers.c +++ b/mercurial/cext/parsers.c @@ -515,6 +515,27 @@ Py_RETURN_NONE; } +static PyObject *dirstate_item_drop_merge_data(dirstateItemObject *self) +{ + if (dirstate_item_c_merged(self) || dirstate_item_c_from_p2(self)) { + if (dirstate_item_c_merged(self)) { + self->flags |= dirstate_flag_p1_tracked; + } else { + self->flags &= ~dirstate_flag_p1_tracked; + } + self->flags &= + ~(dirstate_flag_merged | dirstate_flag_clean_p1 | + dirstate_flag_clean_p2 | dirstate_flag_p2_tracked); + self->flags |= dirstate_flag_possibly_dirty; + self->mode = 0; + self->mtime = 0; + /* size = None on the python size turn into size = NON_NORMAL +* when accessed. So the next line is currently required, but a +* some future clean up would be welcome. */ + self->size = dirstate_v1_nonnormal; + } + Py_RETURN_NONE; +} static PyMethodDef dirstate_item_methods[] = { {"v1_state", (PyCFunction)dirstate_item_v1_state, METH_NOARGS, "return a \"state\" suitable for v1 serialization"}, @@ -551,6 +572,8 @@ "mark a file as \"tracked\""}, {"set_untracked", (PyCFunction)dirstate_item_set_untracked, METH_NOARGS, "mark a file as \"untracked\""}, +{"drop_merge_data", (PyCFunction)dirstate_item_drop_merge_data, METH_NOARGS, + "remove all \"merge-only\" from a DirstateItem"}, {NULL} /* Sentinel */ }; To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11505: dirstate: move parent state handling in the dirstatemap
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This involves dirstatemap data mostly. Moving this one level down will remove the needs for the dirstatemap to expose some of its internals. This will help us to simplify more code further. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11505 AFFECTED FILES mercurial/dirstate.py mercurial/dirstatemap.py CHANGE DETAILS diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -428,9 +428,40 @@ return self._parents -def setparents(self, p1, p2): +def setparents(self, p1, p2, fold_p2=False): self._parents = (p1, p2) self._dirtyparents = True +copies = {} +if fold_p2: +candidatefiles = self.non_normal_or_other_parent_paths() + +for f in candidatefiles: +s = self.get(f) +if s is None: +continue + +# Discard "merged" markers when moving away from a merge state +if s.merged: +source = self.copymap.get(f) +if source: +copies[f] = source +self.reset_state( +f, +wc_tracked=True, +p1_tracked=True, +possibly_dirty=True, +) +# Also fix up otherparent markers +elif s.from_p2: +source = self.copymap.get(f) +if source: +copies[f] = source +self.reset_state( +f, +p1_tracked=False, +wc_tracked=True, +) +return copies def read(self): # ignore HG_PENDING because identity is used only for writing @@ -769,9 +800,40 @@ # File doesn't exist, so the current state is empty return b'' -def setparents(self, p1, p2): +def setparents(self, p1, p2, fold_p2=False): self._parents = (p1, p2) self._dirtyparents = True +copies = {} +if fold_p2: +candidatefiles = self.non_normal_or_other_parent_paths() + +for f in candidatefiles: +s = self.get(f) +if s is None: +continue + +# Discard "merged" markers when moving away from a merge state +if s.merged: +source = self.copymap.get(f) +if source: +copies[f] = source +self.reset_state( +f, +wc_tracked=True, +p1_tracked=True, +possibly_dirty=True, +) +# Also fix up otherparent markers +elif s.from_p2: +source = self.copymap.get(f) +if source: +copies[f] = source +self.reset_state( +f, +p1_tracked=False, +wc_tracked=True, +) +return copies def parents(self): if not self._parents: diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -381,39 +381,10 @@ oldp2 = self._pl[1] if self._origpl is None: self._origpl = self._pl -self._map.setparents(p1, p2) -copies = {} nullid = self._nodeconstants.nullid -if oldp2 != nullid and p2 == nullid: -candidatefiles = self._map.non_normal_or_other_parent_paths() - -for f in candidatefiles: -s = self._map.get(f) -if s is None: -continue - -# Discard "merged" markers when moving away from a merge state -if s.merged: -source = self._map.copymap.get(f) -if source: -copies[f] = source -self._map.reset_state( -f, -wc_tracked=True, -p1_tracked=True, -possibly_dirty=True, -) -# Also fix up otherparent markers -elif s.from_p2: -source = self._map.copymap.get(f) -if source: -copies[f] = source -
D11504: dirstate: stop checking for path collision when adjusting parents
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This was already checked at a earlier point when adding the file. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11504 AFFECTED FILES mercurial/dirstate.py CHANGE DETAILS diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -408,7 +408,6 @@ source = self._map.copymap.get(f) if source: copies[f] = source -self._check_new_tracked_filename(f) self._map.reset_state( f, p1_tracked=False, To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11503: dirstate: drop the `_updatedfiles` set
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY This is a lot of book keeping for something that was only used to clear ambiguous time. Since this is no no longer in use, we can drop it. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11503 AFFECTED FILES mercurial/dirstate.py CHANGE DETAILS diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -130,7 +130,6 @@ self._pendingfilename = b'%s.pending' % self._filename self._plchangecallbacks = {} self._origpl = None -self._updatedfiles = set() self._mapcls = dirstatemap.dirstatemap # Access and cache cwd early, so we don't access it for the first time # after a working-copy update caused it to not exist (accessing it then @@ -410,7 +409,6 @@ if source: copies[f] = source self._check_new_tracked_filename(f) -self._updatedfiles.add(f) self._map.reset_state( f, p1_tracked=False, @@ -446,7 +444,6 @@ delattr(self, a) self._lastnormaltime = 0 self._dirty = False -self._updatedfiles.clear() self._parentwriters = 0 self._origpl = None @@ -457,10 +454,8 @@ self._dirty = True if source is not None: self._map.copymap[dest] = source -self._updatedfiles.add(source) -self._updatedfiles.add(dest) -elif self._map.copymap.pop(dest, None): -self._updatedfiles.add(dest) +else: +self._map.copymap.pop(dest, None) def copied(self, file): return self._map.copymap.get(file, None) @@ -478,7 +473,6 @@ return True the file was previously untracked, False otherwise. """ self._dirty = True -self._updatedfiles.add(filename) entry = self._map.get(filename) if entry is None or not entry.tracked: self._check_new_tracked_filename(filename) @@ -496,14 +490,12 @@ ret = self._map.set_untracked(filename) if ret: self._dirty = True -self._updatedfiles.add(filename) return ret @requires_no_parents_change def set_clean(self, filename, parentfiledata=None): """record that the current state of the file on disk is known to be clean""" self._dirty = True -self._updatedfiles.add(filename) if parentfiledata: (mode, size, mtime) = parentfiledata else: @@ -521,7 +513,6 @@ def set_possibly_dirty(self, filename): """record that the current state of the file on disk is unknown""" self._dirty = True -self._updatedfiles.add(filename) self._map.set_possibly_dirty(filename) @requires_parents_change @@ -556,7 +547,6 @@ if self._map.get(filename) is not None: self._map.reset_state(filename) self._dirty = True -self._updatedfiles.add(filename) elif (not p1_tracked) and wc_tracked: if entry is not None and entry.added: return # avoid dropping copy information (maybe?) @@ -572,7 +562,6 @@ if wc_tracked: parentfiledata = self._get_filedata(filename) -self._updatedfiles.add(filename) self._map.reset_state( filename, wc_tracked, @@ -622,7 +611,6 @@ # this. The test agrees self._dirty = True -self._updatedfiles.add(filename) need_parent_file_data = ( not (possibly_dirty or clean_p2 or merged) @@ -768,7 +756,6 @@ def clear(self): self._map.clear() self._lastnormaltime = 0 -self._updatedfiles.clear() self._dirty = True def rebuild(self, parent, allfiles, changedfiles=None): @@ -809,10 +796,8 @@ p1_tracked=True, possibly_dirty=True, ) -self._updatedfiles.add(f) for f in to_drop: self._map.reset_state(f) -self._updatedfiles.add(f) self._dirty = True @@ -839,9 +824,6 @@ # record when mtime start to be ambiguous now = _getfsnow(self._opener) -# emulate that all 'dirstate.normal' results are written out -self._updatedfiles.clear() - # delay writing in-memory changes out tr.addfilegenerator( b'dirstate', To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercu
D11501: dirstate: simplify the ambiguity clearing at write time
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY The serialization function is already doing this, so we don't need to do it manually. We just need to propagate the right definition of "now". REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11501 AFFECTED FILES mercurial/dirstate.py CHANGE DETAILS diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -836,19 +836,17 @@ # See also the wiki page below for detail: # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan -# emulate dropping timestamp in 'parsers.pack_dirstate' +# record when mtime start to be ambiguous now = _getfsnow(self._opener) -self._map.clearambiguoustimes(self._updatedfiles, now) # emulate that all 'dirstate.normal' results are written out -self._lastnormaltime = 0 self._updatedfiles.clear() # delay writing in-memory changes out tr.addfilegenerator( b'dirstate', (self._filename,), -lambda f: self._writedirstate(tr, f), +lambda f: self._writedirstate(tr, f, now=now), location=b'plain', ) return @@ -867,7 +865,7 @@ """ self._plchangecallbacks[category] = callback -def _writedirstate(self, tr, st): +def _writedirstate(self, tr, st, now=None): # notify callbacks about parents change if self._origpl is not None and self._origpl != self._pl: for c, callback in sorted( @@ -875,9 +873,11 @@ ): callback(self, self._origpl, self._pl) self._origpl = None -# use the modification time of the newly created temporary file as the -# filesystem's notion of 'now' -now = util.fstat(st)[stat.ST_MTIME] & _rangemask + +if now is None: +# use the modification time of the newly created temporary file as the +# filesystem's notion of 'now' +now = util.fstat(st)[stat.ST_MTIME] & _rangemask # enough 'delaywrite' prevents 'pack_dirstate' from dropping # timestamp of each entries in dirstate, because of 'now > mtime' To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11500: dirstate: Appease pytype
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY test-check-pytype.t was failing since 98c0408324e6 <https://phab.mercurial-scm.org/rHG98c0408324e6bdf9dc4d306b467167b869092d5a>: File "/home/simon/projects/hg/mercurial/dirstatemap.py", line 572, in addfile: unsupported operand type(s) for &: 'size: None' and 'rangemask: int' [unsupported-operands] No attribute '__and__' on 'size: None' or '__rand__' on 'rangemask: int' File "/home/simon/projects/hg/mercurial/dirstatemap.py", line 573, in addfile: unsupported operand type(s) for &: 'mtime: None' and 'rangemask: int' [unsupported-operands] No attribute '__and__' on 'mtime: None' or '__rand__' on 'rangemask: int' `None` is the default value of the `size` and `mtime` parameters of the `addfile` method. However, the relevant lines are only used in a code path where those defaults are never used. These `size` and `mtime` are passed to `DirstateItem.new_normal` which (in the C implementation) calls `dirstate_item_new_normal` which uses: PyArg_ParseTuple(args, "iii", , , ) So `None` values would cause an exception to be raised anyway. The new `assert`s only move that exception earlier, and informs pytype that we expect `None` to never happen in this code path. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11500 AFFECTED FILES mercurial/dirstatemap.py CHANGE DETAILS diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -575,6 +575,8 @@ elif possibly_dirty: item = DirstateItem.new_possibly_dirty() else: +assert size is not None +assert mtime is not None size = size & rangemask mtime = mtime & rangemask item = DirstateItem.new_normal(mode, size, mtime) To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D11494: dirstate: Pass the final DirstateItem to _rustmap.addfile()
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Now that the Python DirstateItem class wraps a Rust DirstateEntry value, use that value directly instead of converting through v1 data + 5 booleans. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11494 AFFECTED FILES mercurial/dirstatemap.py rust/hg-core/src/dirstate/dirstate_map.rs rust/hg-core/src/dirstate/entry.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/dispatch.rs rust/hg-core/src/dirstate_tree/owning_dispatch.rs rust/hg-cpython/src/dirstate/dirstate_map.rs rust/hg-cpython/src/dirstate/item.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/item.rs b/rust/hg-cpython/src/dirstate/item.rs --- a/rust/hg-cpython/src/dirstate/item.rs +++ b/rust/hg-cpython/src/dirstate/item.rs @@ -146,6 +146,36 @@ DirstateItem::create_instance(py, Cell::new(entry)) } +@classmethod +def new_added(_cls) -> PyResult { +let entry = DirstateEntry::new_added(); +DirstateItem::create_instance(py, Cell::new(entry)) +} + +@classmethod +def new_merged(_cls) -> PyResult { +let entry = DirstateEntry::new_merged(); +DirstateItem::create_instance(py, Cell::new(entry)) +} + +@classmethod +def new_from_p2(_cls) -> PyResult { +let entry = DirstateEntry::new_from_p2(); +DirstateItem::create_instance(py, Cell::new(entry)) +} + +@classmethod +def new_possibly_dirty(_cls) -> PyResult { +let entry = DirstateEntry::new_possibly_dirty(); +DirstateItem::create_instance(py, Cell::new(entry)) +} + +@classmethod +def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult { +let entry = DirstateEntry::new_normal(mode, size, mtime); +DirstateItem::create_instance(py, Cell::new(entry)) +} + def set_clean( , mode: i32, diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -27,8 +27,6 @@ }; use hg::{ dirstate::parsers::Timestamp, -dirstate::MTIME_UNSET, -dirstate::SIZE_NON_NORMAL, dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, dirstate_tree::dispatch::DirstateMapMethods, dirstate_tree::on_disk::DirstateV2ParseError, @@ -145,50 +143,16 @@ def addfile( , -f: PyObject, -mode: PyObject, -size: PyObject, -mtime: PyObject, -added: PyObject, -merged: PyObject, -from_p2: PyObject, -possibly_dirty: PyObject, -) -> PyResult { -let f = f.extract::(py)?; +f: PyBytes, +item: DirstateItem, +) -> PyResult { let filename = HgPath::new(f.data(py)); -let mode = if mode.is_none(py) { -// fallback default value -0 -} else { -mode.extract(py)? -}; -let size = if size.is_none(py) { -// fallback default value -SIZE_NON_NORMAL -} else { -size.extract(py)? -}; -let mtime = if mtime.is_none(py) { -// fallback default value -MTIME_UNSET -} else { -mtime.extract(py)? -}; -let entry = DirstateEntry::new_for_add_file(mode, size, mtime); -let added = added.extract::(py)?.is_true(); -let merged = merged.extract::(py)?.is_true(); -let from_p2 = from_p2.extract::(py)?.is_true(); -let possibly_dirty = possibly_dirty.extract::(py)?.is_true(); -self.inner(py).borrow_mut().add_file( -filename, -entry, -added, -merged, -from_p2, -possibly_dirty -).and(Ok(py.None())).or_else(|e: DirstateError| { -Err(PyErr::new::(py, e.to_string())) -}) +let entry = item.get_entry(py); +self.inner(py) +.borrow_mut() +.add_file(filename, entry) +.map_err(|e |dirstate_error(py, e))?; +Ok(PyNone) } def removefile( diff --git a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs --- a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs @@ -32,19 +32,8 @@ self, filename: , entry: DirstateEntry, -added: bool, -merged: bool, -from_p2: bool, -possibly_dirty: bool, ) -> Result<(), DirstateError> { -self.get_mut().add_file( -filename, -entry, -added, -merged, -from_p2, -possibly_dirty, -) +
D11493: dirstate: Replace dropfile with drop_item_and_copy_source
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY Those removing a DirstateItem and a copy source are always done together REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11493 AFFECTED FILES mercurial/dirstatemap.py rust/hg-core/src/dirstate/dirstate_map.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/dispatch.rs rust/hg-core/src/dirstate_tree/owning_dispatch.rs rust/hg-cpython/src/dirstate/dirstate_map.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -210,13 +210,13 @@ Ok(py.None()) } -def dropfile( +def drop_item_and_copy_source( , f: PyBytes, ) -> PyResult { self.inner(py) .borrow_mut() -.drop_file(HgPath::new(f.data(py))) +.drop_entry_and_copy_source(HgPath::new(f.data(py))) .map_err(|e |dirstate_error(py, e))?; Ok(PyNone) } diff --git a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs --- a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs @@ -55,8 +55,11 @@ self.get_mut().remove_file(filename, in_merge) } -fn drop_file( self, filename: ) -> Result<(), DirstateError> { -self.get_mut().drop_file(filename) +fn drop_entry_and_copy_source( + self, +filename: , +) -> Result<(), DirstateError> { +self.get_mut().drop_entry_and_copy_source(filename) } fn clear_ambiguous_times( diff --git a/rust/hg-core/src/dirstate_tree/dispatch.rs b/rust/hg-core/src/dirstate_tree/dispatch.rs --- a/rust/hg-core/src/dirstate_tree/dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/dispatch.rs @@ -66,7 +66,10 @@ /// Drop information about this file from the map if any. /// /// `get` will now return `None` for this filename. -fn drop_file( self, filename: ) -> Result<(), DirstateError>; +fn drop_entry_and_copy_source( + self, +filename: , +) -> Result<(), DirstateError>; /// Among given files, mark the stored `mtime` as ambiguous if there is one /// (if `state == EntryState::Normal`) equal to the given current Unix @@ -339,8 +342,11 @@ self.remove_file(filename, in_merge) } -fn drop_file( self, filename: ) -> Result<(), DirstateError> { -self.drop_file(filename) +fn drop_entry_and_copy_source( + self, +filename: , +) -> Result<(), DirstateError> { +self.drop_entry_and_copy_source(filename) } fn clear_ambiguous_times( diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -845,7 +845,10 @@ Ok(self.add_or_remove_file(filename, old_state, entry)?) } -fn drop_file( self, filename: ) -> Result<(), DirstateError> { +fn drop_entry_and_copy_source( + self, +filename: , +) -> Result<(), DirstateError> { let was_tracked = self .get(filename)? .map_or(false, |e| e.state().is_tracked()); @@ -907,7 +910,8 @@ node.data = NodeData::None } if let Some(source) = _source { -DirstateMap::count_dropped_path(unreachable_bytes, source) +DirstateMap::count_dropped_path(unreachable_bytes, source); +node.copy_source = None } dropped = Dropped { was_tracked: node diff --git a/rust/hg-core/src/dirstate/dirstate_map.rs b/rust/hg-core/src/dirstate/dirstate_map.rs --- a/rust/hg-core/src/dirstate/dirstate_map.rs +++ b/rust/hg-core/src/dirstate/dirstate_map.rs @@ -195,7 +195,7 @@ /// Remove a file from the dirstate. /// Returns `true` if the file was previously recorded. -pub fn drop_file( +pub fn drop_entry_and_copy_source( self, filename: , ) -> Result<(), DirstateError> { @@ -216,6 +216,8 @@ .0 .remove(filename); +self.copy_map.remove(filename); + Ok(()) } diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -606,7 +606,7 @@ self.copymap.pop(filename, None) if not (p1_tracked or p2_tracked or wc_tracked): -self.dropfile(filename) +self._rustmap.drop_item_and_co
D11491: dirstate: Remove return boolean from dirstatemap.dropfile
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY None of the remaining callers use it. REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11491 AFFECTED FILES mercurial/dirstatemap.py rust/hg-core/src/dirstate/dirstate_map.rs rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/dispatch.rs rust/hg-core/src/dirstate_tree/owning_dispatch.rs rust/hg-cpython/src/dirstate/dirstate_map.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -13,8 +13,8 @@ use cpython::{ exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, -PyObject, PyResult, PySet, PyString, Python, PythonObject, ToPyObject, -UnsafePyLeaked, +PyNone, PyObject, PyResult, PySet, PyString, Python, PythonObject, +ToPyObject, UnsafePyLeaked, }; use crate::{ @@ -212,19 +212,13 @@ def dropfile( , -f: PyObject, -) -> PyResult { -self.inner(py).borrow_mut() -.drop_file( -HgPath::new(f.extract::(py)?.data(py)), -) -.and_then(|b| Ok(b.to_py_object(py))) -.or_else(|e| { -Err(PyErr::new::( -py, -format!("Dirstate error: {}", e.to_string()), -)) -}) +f: PyBytes, +) -> PyResult { +self.inner(py) +.borrow_mut() +.drop_file(HgPath::new(f.data(py))) +.map_err(|e |dirstate_error(py, e))?; +Ok(PyNone) } def clearambiguoustimes( diff --git a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs --- a/rust/hg-core/src/dirstate_tree/owning_dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/owning_dispatch.rs @@ -55,7 +55,7 @@ self.get_mut().remove_file(filename, in_merge) } -fn drop_file( self, filename: ) -> Result { +fn drop_file( self, filename: ) -> Result<(), DirstateError> { self.get_mut().drop_file(filename) } diff --git a/rust/hg-core/src/dirstate_tree/dispatch.rs b/rust/hg-core/src/dirstate_tree/dispatch.rs --- a/rust/hg-core/src/dirstate_tree/dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/dispatch.rs @@ -81,7 +81,7 @@ /// /// `old_state` is the state in the entry that `get` would have returned /// before this call, or `EntryState::Unknown` if there was no such entry. -fn drop_file( self, filename: ) -> Result; +fn drop_file( self, filename: ) -> Result<(), DirstateError>; /// Among given files, mark the stored `mtime` as ambiguous if there is one /// (if `state == EntryState::Normal`) equal to the given current Unix @@ -354,7 +354,7 @@ self.remove_file(filename, in_merge) } -fn drop_file( self, filename: ) -> Result { +fn drop_file( self, filename: ) -> Result<(), DirstateError> { self.drop_file(filename) } diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -845,7 +845,7 @@ Ok(self.add_or_remove_file(filename, old_state, entry)?) } -fn drop_file( self, filename: ) -> Result { +fn drop_file( self, filename: ) -> Result<(), DirstateError> { let was_tracked = self .get(filename)? .map_or(false, |e| e.state().is_tracked()); @@ -946,11 +946,10 @@ if dropped.had_copy_source { self.nodes_with_copy_source_count -= 1 } -Ok(dropped.had_entry) } else { debug_assert!(!was_tracked); -Ok(false) } +Ok(()) } fn clear_ambiguous_times( diff --git a/rust/hg-core/src/dirstate/dirstate_map.rs b/rust/hg-core/src/dirstate/dirstate_map.rs --- a/rust/hg-core/src/dirstate/dirstate_map.rs +++ b/rust/hg-core/src/dirstate/dirstate_map.rs @@ -198,7 +198,7 @@ pub fn drop_file( self, filename: , -) -> Result { +) -> Result<(), DirstateError> { let old_state = self.get(filename).map(|e| e.state()); let exists = self.state_map.remove(filename).is_some(); @@ -216,7 +216,7 @@ .0 .remove(filename); -Ok(exists) +Ok(()) } pub fn clear_ambiguous_times( diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -695,7 +695,7 @@ def dropfile(self, f, *args, **kwargs): self._rustma
D11492: rust: Remove some obsolete doc-comments
SimonSapin created this revision. Herald added a reviewer: hg-reviewers. Herald added a subscriber: mercurial-patches. REVISION SUMMARY About parameters that have been removed or replaced REPOSITORY rHG Mercurial BRANCH default REVISION DETAIL https://phab.mercurial-scm.org/D11492 AFFECTED FILES rust/hg-core/src/dirstate_tree/dispatch.rs CHANGE DETAILS diff --git a/rust/hg-core/src/dirstate_tree/dispatch.rs b/rust/hg-core/src/dirstate_tree/dispatch.rs --- a/rust/hg-core/src/dirstate_tree/dispatch.rs +++ b/rust/hg-core/src/dirstate_tree/dispatch.rs @@ -46,11 +46,6 @@ ) -> Result<(), DirstateV2ParseError>; /// Add or change the information associated to a given file. -/// -/// `old_state` is the state in the entry that `get` would have returned -/// before this call, or `EntryState::Unknown` if there was no such entry. -/// -/// `entry.state` should never be `EntryState::Unknown`. fn add_file( self, filename: , @@ -62,25 +57,15 @@ ) -> Result<(), DirstateError>; /// Mark a file as "removed" (as in `hg rm`). -/// -/// `old_state` is the state in the entry that `get` would have returned -/// before this call, or `EntryState::Unknown` if there was no such entry. -/// -/// `size` is not actually a size but the 0 or -1 or -2 value that would be -/// put in the size field in the dirstate-v1 format. fn remove_file( self, filename: , in_merge: bool, ) -> Result<(), DirstateError>; -/// Drop information about this file from the map if any, and return -/// whether there was any. +/// Drop information about this file from the map if any. /// /// `get` will now return `None` for this filename. -/// -/// `old_state` is the state in the entry that `get` would have returned -/// before this call, or `EntryState::Unknown` if there was no such entry. fn drop_file( self, filename: ) -> Result<(), DirstateError>; /// Among given files, mark the stored `mtime` as ambiguous if there is one To: SimonSapin, #hg-reviewers Cc: mercurial-patches, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel