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(&mut self) { + self.peeked = None + } + + fn peek_mut(&mut self) -> Option<&mut I::Item> { + let iter = &mut 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( + self.layers + .iter() + .rev() + .map(move |layer| layer.iter_section(section)), + ); + std::iter::from_fn(move || loop { + if let Some(pair) = layer_iters.peek_mut()?.find(&mut 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)] 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(&self, section: &[u8]) -> bool { self.sections 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: &str = " 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( &AlwaysMatcher, 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: &Repo, config: &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(