rhg: Add support for -R and --repository command-line arguments
authorSimon Sapin <simon.sapin@octobus.net>
Mon, 08 Feb 2021 21:37:30 +0100
changeset 46503 d8730ff51d5a
parent 46502 95d37db31479
child 46504 2e5dd18d6dc3
rhg: Add support for -R and --repository command-line arguments Differential Revision: https://phab.mercurial-scm.org/D9970
rust/hg-core/src/repo.rs
rust/rhg/src/commands/cat.rs
rust/rhg/src/commands/debugdata.rs
rust/rhg/src/commands/debugrequirements.rs
rust/rhg/src/commands/files.rs
rust/rhg/src/commands/root.rs
rust/rhg/src/error.rs
rust/rhg/src/main.rs
tests/test-rhg.t
--- a/rust/hg-core/src/repo.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/hg-core/src/repo.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -1,6 +1,7 @@
 use crate::config::{Config, ConfigError, ConfigParseError};
 use crate::errors::{HgError, IoResultExt};
 use crate::requirements;
+use crate::utils::current_dir;
 use crate::utils::files::get_path_from_bytes;
 use memmap::{Mmap, MmapOptions};
 use std::collections::HashSet;
@@ -18,7 +19,7 @@
 #[derive(Debug, derive_more::From)]
 pub enum RepoError {
     NotFound {
-        current_directory: PathBuf,
+        at: PathBuf,
     },
     #[from]
     ConfigParseError(ConfigParseError),
@@ -44,15 +45,36 @@
 impl Repo {
     /// Search the current directory and its ancestores for a repository:
     /// a working directory that contains a `.hg` sub-directory.
-    pub fn find(config: &Config) -> Result<Self, RepoError> {
-        let current_directory = crate::utils::current_dir()?;
-        // ancestors() is inclusive: it first yields `current_directory` as-is.
-        for ancestor in current_directory.ancestors() {
-            if ancestor.join(".hg").is_dir() {
-                return Ok(Self::new_at_path(ancestor.to_owned(), config)?);
+    ///
+    /// `explicit_path` is for `--repository` command-line arguments.
+    pub fn find(
+        config: &Config,
+        explicit_path: Option<&Path>,
+    ) -> Result<Self, RepoError> {
+        if let Some(root) = explicit_path {
+            // Having an absolute path isn’t necessary here but can help code
+            // elsewhere
+            let root = current_dir()?.join(root);
+            if root.join(".hg").is_dir() {
+                Self::new_at_path(root, config)
+            } else {
+                Err(RepoError::NotFound {
+                    at: root.to_owned(),
+                })
             }
+        } else {
+            let current_directory = crate::utils::current_dir()?;
+            // ancestors() is inclusive: it first yields `current_directory`
+            // as-is.
+            for ancestor in current_directory.ancestors() {
+                if ancestor.join(".hg").is_dir() {
+                    return Self::new_at_path(ancestor.to_owned(), config);
+                }
+            }
+            Err(RepoError::NotFound {
+                at: current_directory,
+            })
         }
-        Err(RepoError::NotFound { current_directory })
     }
 
     /// To be called after checking that `.hg` is a sub-directory
--- a/rust/rhg/src/commands/cat.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/commands/cat.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -8,6 +8,7 @@
 use hg::utils::hg_path::HgPathBuf;
 use micro_timer::timed;
 use std::convert::TryFrom;
+use std::path::Path;
 
 pub const HELP_TEXT: &str = "
 Output the current or given revision of files
@@ -38,6 +39,7 @@
 pub fn run(
     ui: &Ui,
     config: &Config,
+    repo_path: Option<&Path>,
     args: &ArgMatches,
 ) -> Result<(), CommandError> {
     let rev = args.value_of("rev");
@@ -46,7 +48,7 @@
         None => vec![],
     };
 
-    let repo = Repo::find(config)?;
+    let repo = Repo::find(config, repo_path)?;
     let cwd = hg::utils::current_dir()?;
 
     let mut files = vec![];
--- a/rust/rhg/src/commands/debugdata.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/commands/debugdata.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -7,6 +7,7 @@
 use hg::operations::{debug_data, DebugDataKind};
 use hg::repo::Repo;
 use micro_timer::timed;
+use std::path::Path;
 
 pub const HELP_TEXT: &str = "
 Dump the contents of a data file revision
@@ -44,6 +45,7 @@
 pub fn run(
     ui: &Ui,
     config: &Config,
+    repo_path: Option<&Path>,
     args: &ArgMatches,
 ) -> Result<(), CommandError> {
     let rev = args
@@ -61,7 +63,7 @@
             }
         };
 
-    let repo = Repo::find(config)?;
+    let repo = Repo::find(config, repo_path)?;
     let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?;
 
     let mut stdout = ui.stdout_buffer();
--- a/rust/rhg/src/commands/debugrequirements.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/commands/debugrequirements.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -3,6 +3,7 @@
 use clap::ArgMatches;
 use hg::config::Config;
 use hg::repo::Repo;
+use std::path::Path;
 
 pub const HELP_TEXT: &str = "
 Print the current repo requirements.
@@ -15,9 +16,10 @@
 pub fn run(
     ui: &Ui,
     config: &Config,
+    repo_path: Option<&Path>,
     _args: &ArgMatches,
 ) -> Result<(), CommandError> {
-    let repo = Repo::find(config)?;
+    let repo = Repo::find(config, repo_path)?;
     let mut output = String::new();
     let mut requirements: Vec<_> = repo.requirements().iter().collect();
     requirements.sort();
--- a/rust/rhg/src/commands/files.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/commands/files.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -8,6 +8,7 @@
 use hg::repo::Repo;
 use hg::utils::files::{get_bytes_from_path, relativize_path};
 use hg::utils::hg_path::{HgPath, HgPathBuf};
+use std::path::Path;
 
 pub const HELP_TEXT: &str = "
 List tracked files.
@@ -31,11 +32,12 @@
 pub fn run(
     ui: &Ui,
     config: &Config,
+    repo_path: Option<&Path>,
     args: &ArgMatches,
 ) -> Result<(), CommandError> {
     let rev = args.value_of("rev");
 
-    let repo = Repo::find(config)?;
+    let repo = Repo::find(config, repo_path)?;
     if let Some(rev) = rev {
         let files =
             list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?;
@@ -52,16 +54,15 @@
     repo: &Repo,
     files: impl IntoIterator<Item = &'a HgPath>,
 ) -> Result<(), CommandError> {
-    let cwd = hg::utils::current_dir()?;
-    let rooted_cwd = cwd
-        .strip_prefix(repo.working_directory_path())
-        .expect("cwd was already checked within the repository");
-    let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
+    let cwd = HgPathBuf::from(get_bytes_from_path(hg::utils::current_dir()?));
+    let working_directory =
+        HgPathBuf::from(get_bytes_from_path(repo.working_directory_path()));
 
     let mut stdout = ui.stdout_buffer();
 
     for file in files {
-        stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
+        let file = working_directory.join(file);
+        stdout.write_all(relativize_path(&file, &cwd).as_ref())?;
         stdout.write_all(b"\n")?;
     }
     stdout.flush()?;
--- a/rust/rhg/src/commands/root.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/commands/root.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -5,6 +5,7 @@
 use hg::config::Config;
 use hg::repo::Repo;
 use hg::utils::files::get_bytes_from_path;
+use std::path::Path;
 
 pub const HELP_TEXT: &str = "
 Print the root directory of the current repository.
@@ -19,9 +20,10 @@
 pub fn run(
     ui: &Ui,
     config: &Config,
+    repo_path: Option<&Path>,
     _args: &ArgMatches,
 ) -> Result<(), CommandError> {
-    let repo = Repo::find(config)?;
+    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()))?;
     Ok(())
--- a/rust/rhg/src/error.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/error.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -54,10 +54,10 @@
 impl From<RepoError> for CommandError {
     fn from(error: RepoError) -> Self {
         match error {
-            RepoError::NotFound { current_directory } => CommandError::Abort {
+            RepoError::NotFound { at } => CommandError::Abort {
                 message: format_bytes!(
                     b"no repository found in '{}' (.hg not found)!",
-                    get_bytes_from_path(current_directory)
+                    get_bytes_from_path(at)
                 ),
             },
             RepoError::ConfigParseError(error) => error.into(),
--- a/rust/rhg/src/main.rs	Mon Feb 08 21:28:52 2021 +0100
+++ b/rust/rhg/src/main.rs	Mon Feb 08 21:37:30 2021 +0100
@@ -1,14 +1,27 @@
 extern crate log;
 use clap::App;
 use clap::AppSettings;
+use clap::Arg;
 use clap::ArgMatches;
 use format_bytes::format_bytes;
+use std::path::Path;
 
 mod error;
 mod exitcode;
 mod ui;
 use error::CommandError;
 
+fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
+    app.arg(
+        Arg::with_name("repository")
+            .help("repository root directory")
+            .short("-R")
+            .long("--repository")
+            .value_name("REPO")
+            .takes_value(true),
+    )
+}
+
 fn main() {
     env_logger::init();
     let app = App::new("rhg")
@@ -16,6 +29,7 @@
         .setting(AppSettings::SubcommandRequired)
         .setting(AppSettings::VersionlessSubcommands)
         .version("0.0.1");
+    let app = add_global_args(app);
     let app = add_subcommand_args(app);
 
     let ui = ui::Ui::new();
@@ -24,15 +38,22 @@
         let _ = ui.writeln_stderr_str(&err.message);
         std::process::exit(exitcode::UNIMPLEMENTED)
     });
+
     let (subcommand_name, subcommand_matches) = matches.subcommand();
     let run = subcommand_run_fn(subcommand_name)
         .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
     let args = subcommand_matches
         .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
 
+    // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s.
+    // `hg log -R ./foo`
+    let global_arg =
+        |name| args.value_of_os(name).or_else(|| matches.value_of_os(name));
+
+    let repo_path = global_arg("repository").map(Path::new);
     let result = (|| -> Result<(), CommandError> {
         let config = hg::config::Config::load()?;
-        run(&ui, &config, args)
+        run(&ui, &config, repo_path, args)
     })();
 
     let exit_code = match result {
@@ -66,13 +87,14 @@
         fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
             app
             $(
-                .subcommand(commands::$command::args())
+                .subcommand(add_global_args(commands::$command::args()))
             )+
         }
 
         fn subcommand_run_fn(name: &str) -> Option<fn(
             &ui::Ui,
             &hg::config::Config,
+            Option<&Path>,
             &ArgMatches,
         ) -> Result<(), CommandError>> {
             match name {
--- a/tests/test-rhg.t	Mon Feb 08 21:28:52 2021 +0100
+++ b/tests/test-rhg.t	Mon Feb 08 21:37:30 2021 +0100
@@ -15,7 +15,7 @@
   error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
   
   USAGE:
-      rhg <SUBCOMMAND>
+      rhg [OPTIONS] <SUBCOMMAND>
   
   For more information try --help
   [252]
@@ -204,35 +204,30 @@
 
   $ cd $TESTTMP
   $ hg init repo1
-  $ cd repo1
-  $ echo a > a
-  $ hg commit -A -m'init'
+  $ echo a > repo1/a
+  $ hg -R repo1 commit -A -m'init'
   adding a
 
-  $ cd ..
   $ hg share repo1 repo2
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 And check that basic rhg commands work with sharing
 
-  $ cd repo2
-  $ rhg files
-  a
-  $ rhg cat -r 0 a
+  $ rhg files -R repo2
+  repo2/a
+  $ rhg -R repo2 cat -r 0 repo2/a
   a
 
 Same with relative sharing
 
-  $ cd ..
   $ hg share repo2 repo3 --relative
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-  $ cd repo3
-  $ rhg files
-  a
-  $ rhg cat -r 0 a
+  $ rhg files -R repo3
+  repo3/a
+  $ rhg -R repo3 cat -r 0 repo3/a
   a
 
 Same with share-safe