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 @@ -58,8 +58,8 @@ fn parse_one(arg: &[u8]) -> Option<(Vec, Vec, Vec)> { use crate::utils::SliceExt; - let (section_and_item, value) = split_2(arg, b'=')?; - let (section, item) = split_2(section_and_item.trim(), b'.')?; + let (section_and_item, value) = arg.split_2(b'=')?; + let (section, item) = section_and_item.trim().split_2(b'.')?; Some(( section.to_owned(), item.to_owned(), @@ -67,13 +67,6 @@ )) } - fn split_2(bytes: &[u8], separator: u8) -> Option<(&[u8], &[u8])> { - let mut iter = bytes.splitn(2, |&byte| byte == separator); - let a = iter.next()?; - let b = iter.next()?; - Some((a, b)) - } - let mut layer = Self::new(ConfigOrigin::CommandLine); for arg in cli_config_args { let arg = arg.as_ref(); 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 @@ -43,10 +43,14 @@ } impl Repo { - /// Search the current directory and its ancestores for a repository: - /// a working directory that contains a `.hg` sub-directory. + /// Find a repository, either at the given path (which must contain a `.hg` + /// sub-directory) or by searching the current directory and its + /// ancestors. /// - /// `explicit_path` is for `--repository` command-line arguments. + /// A method with two very different "modes" like this usually a code smell + /// to make two methods instead, but in this case an `Option` is what rhg + /// sub-commands get from Clap for the `-R` / `--repository` CLI argument. + /// Having two methods would just move that `if` to almost all callers. pub fn find( config: &Config, explicit_path: Option<&Path>, @@ -77,6 +81,28 @@ } } + /// Like `Repo::find`, but not finding a repository is not an error if no + /// explicit path is given. `Ok(None)` is returned in that case. + /// + /// If an explicit path *is* given, not finding a repository there is still + /// an error. + /// + /// For sub-commands that don’t need a repository, configuration should + /// still be affected by a repository’s `.hg/hgrc` file. This is the + /// constructor to use. + pub fn find_optional( + config: &Config, + explicit_path: Option<&Path>, + ) -> Result, RepoError> { + match Self::find(config, explicit_path) { + Ok(repo) => Ok(Some(repo)), + Err(RepoError::NotFound { .. }) if explicit_path.is_none() => { + Ok(None) + } + Err(error) => Err(error), + } + } + /// To be called after checking that `.hg` is a sub-directory fn new_at_path( working_directory: PathBuf, 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 @@ -67,6 +67,7 @@ fn trim_start(&self) -> &Self; fn trim(&self) -> &Self; fn drop_prefix(&self, needle: &Self) -> Option<&Self>; + fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])>; } #[allow(clippy::trivially_copy_pass_by_ref)] @@ -116,6 +117,13 @@ None } } + + fn split_2(&self, separator: u8) -> Option<(&[u8], &[u8])> { + let mut iter = self.splitn(2, |&byte| byte == separator); + let a = iter.next()?; + let b = iter.next()?; + Some((a, b)) + } } pub trait Escaped { diff --git a/rust/rhg/src/commands/root.rs b/rust/rhg/src/commands/config.rs copy from rust/rhg/src/commands/root.rs copy to rust/rhg/src/commands/config.rs --- a/rust/rhg/src/commands/root.rs +++ b/rust/rhg/src/commands/config.rs @@ -1,30 +1,52 @@ use crate::error::CommandError; use crate::ui::Ui; +use clap::Arg; use clap::ArgMatches; use format_bytes::format_bytes; use hg::config::Config; +use hg::errors::HgError; use hg::repo::Repo; -use hg::utils::files::get_bytes_from_path; +use hg::utils::SliceExt; use std::path::Path; pub const HELP_TEXT: &str = " -Print the root directory of the current repository. - -Returns 0 on success. +With one argument of the form section.name, print just the value of that config item. "; pub fn args() -> clap::App<'static, 'static> { - clap::SubCommand::with_name("root").about(HELP_TEXT) + clap::SubCommand::with_name("config") + .arg( + Arg::with_name("name") + .help("the section.name to print") + .value_name("NAME") + .required(true) + .takes_value(true), + ) + .about(HELP_TEXT) } pub fn run( ui: &Ui, config: &Config, repo_path: Option<&Path>, - _args: &ArgMatches, + args: &ArgMatches, ) -> Result<(), CommandError> { - let repo = Repo::find(config, repo_path)?; - let bytes = get_bytes_from_path(repo.working_directory_path()); - ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; + let opt_repo = Repo::find_optional(config, repo_path)?; + let config = if let Some(repo) = &opt_repo { + repo.config() + } else { + config + }; + + let (section, name) = args + .value_of("name") + .expect("missing required CLI argument") + .as_bytes() + .split_2(b'.') + .ok_or_else(|| HgError::abort(""))?; + + let value = config.get(section, name).unwrap_or(b""); + + ui.write_stdout(&format_bytes!(b"{}\n", value))?; Ok(()) } 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 @@ -134,4 +134,5 @@ debugrequirements files root + config } diff --git a/tests/test-rhg.t b/tests/test-rhg.t --- a/tests/test-rhg.t +++ b/tests/test-rhg.t @@ -30,6 +30,18 @@ $ rhg root $TESTTMP/repository +Reading and setting configuration + $ echo "[ui]" >> $HGRCPATH + $ echo "username = user1" >> $HGRCPATH + $ rhg config ui.username + user1 + $ echo "[ui]" >> .hg/hgrc + $ echo "username = user2" >> .hg/hgrc + $ rhg config ui.username + user2 + $ rhg --config ui.username=user3 config ui.username + user3 + Unwritable file descriptor $ rhg root > /dev/full abort: No space left on device (os error 28)