D12317: dirstate-tree: optimize HashMap lookups with raw_entry_mut

2022-03-03 Thread SimonSapin
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

2022-03-02 Thread SimonSapin
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

2022-02-14 Thread SimonSapin
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

2022-02-14 Thread SimonSapin
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

2022-02-14 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-02-10 Thread SimonSapin
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

2022-01-17 Thread SimonSapin
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

2022-01-06 Thread SimonSapin
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

2022-01-06 Thread SimonSapin
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

2022-01-06 Thread SimonSapin
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()

2022-01-06 Thread SimonSapin
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

2022-01-06 Thread SimonSapin
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

2022-01-06 Thread SimonSapin
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

2022-01-06 Thread SimonSapin
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

2021-12-18 Thread SimonSapin
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

2021-12-17 Thread SimonSapin
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

2021-12-17 Thread SimonSapin
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

2021-12-17 Thread SimonSapin
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

2021-12-17 Thread SimonSapin
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

2021-12-17 Thread SimonSapin
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

2021-12-16 Thread SimonSapin
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

2021-12-14 Thread SimonSapin
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

2021-12-14 Thread SimonSapin
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

2021-12-13 Thread SimonSapin
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

2021-12-13 Thread SimonSapin
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`

2021-12-10 Thread SimonSapin
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

2021-12-10 Thread SimonSapin
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

2021-12-10 Thread SimonSapin
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

2021-12-10 Thread SimonSapin
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

2021-12-09 Thread SimonSapin
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

2021-12-09 Thread SimonSapin
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

2021-12-09 Thread SimonSapin
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

2021-12-02 Thread SimonSapin
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

2021-12-02 Thread SimonSapin
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

2021-12-02 Thread SimonSapin
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

2021-12-02 Thread SimonSapin
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

2021-12-02 Thread SimonSapin
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

2021-12-02 Thread SimonSapin
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`

2021-11-26 Thread SimonSapin
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

2021-11-23 Thread SimonSapin
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

2021-11-23 Thread SimonSapin
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

2021-11-23 Thread SimonSapin
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

2021-11-23 Thread SimonSapin
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

2021-11-19 Thread SimonSapin
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

2021-11-15 Thread SimonSapin
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

2021-11-15 Thread SimonSapin
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

2021-11-15 Thread SimonSapin
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`

2021-11-15 Thread SimonSapin
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

2021-11-15 Thread SimonSapin
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

2021-11-15 Thread SimonSapin
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

2021-11-09 Thread SimonSapin
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

2021-10-19 Thread SimonSapin
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`

2021-10-19 Thread SimonSapin
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

2021-10-18 Thread SimonSapin
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

2021-10-15 Thread SimonSapin
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

2021-10-14 Thread SimonSapin
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

2021-10-14 Thread SimonSapin
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()

2021-10-14 Thread SimonSapin
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

2021-10-14 Thread SimonSapin
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

2021-10-14 Thread SimonSapin
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

2021-10-13 Thread SimonSapin
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

2021-10-13 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-12 Thread SimonSapin
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

2021-10-11 Thread SimonSapin
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

2021-10-11 Thread SimonSapin
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

2021-10-11 Thread SimonSapin
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

2021-10-08 Thread SimonSapin
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

2021-10-08 Thread SimonSapin
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

2021-10-08 Thread SimonSapin
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

2021-10-01 Thread SimonSapin
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

2021-10-01 Thread SimonSapin
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

2021-10-01 Thread SimonSapin
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

2021-10-01 Thread SimonSapin
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

2021-10-01 Thread SimonSapin
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

2021-09-29 Thread SimonSapin
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

2021-09-29 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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`

2021-09-28 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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

2021-09-28 Thread SimonSapin
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()

2021-09-23 Thread SimonSapin
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

2021-09-23 Thread SimonSapin
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

2021-09-23 Thread SimonSapin
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

2021-09-23 Thread SimonSapin
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


  1   2   3   4   >