Patchwork D9972: rhg: add limited support for the `config` sub-command

login
register
mail settings
Submitter phabricator
Date Feb. 8, 2021, 10:45 p.m.
Message ID <differential-rev-PHID-DREV-zodjnwst2wnahvpucfq5-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/48287/
State Superseded
Headers show

Comments

phabricator - Feb. 8, 2021, 10:45 p.m.
SimonSapin created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  Only with one argument and no flag. This is mostly for testing.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D9972

AFFECTED FILES
  rust/hg-core/src/config/layer.rs
  rust/hg-core/src/repo.rs
  rust/hg-core/src/utils.rs
  rust/rhg/src/commands/config.rs
  rust/rhg/src/commands/root.rs
  rust/rhg/src/main.rs
  tests/test-rhg.t

CHANGE DETAILS




To: SimonSapin, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

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)
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/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/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/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<Option<Self>, 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/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<u8>, Vec<u8>, Vec<u8>)> {
             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();