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 @@ -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 { - 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 { + 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 diff --git a/rust/rhg/src/commands/cat.rs b/rust/rhg/src/commands/cat.rs --- a/rust/rhg/src/commands/cat.rs +++ b/rust/rhg/src/commands/cat.rs @@ -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![]; diff --git a/rust/rhg/src/commands/debugdata.rs b/rust/rhg/src/commands/debugdata.rs --- a/rust/rhg/src/commands/debugdata.rs +++ b/rust/rhg/src/commands/debugdata.rs @@ -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(); diff --git a/rust/rhg/src/commands/debugrequirements.rs b/rust/rhg/src/commands/debugrequirements.rs --- a/rust/rhg/src/commands/debugrequirements.rs +++ b/rust/rhg/src/commands/debugrequirements.rs @@ -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(); 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 @@ -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, ) -> 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()?; diff --git a/rust/rhg/src/commands/root.rs b/rust/rhg/src/commands/root.rs --- a/rust/rhg/src/commands/root.rs +++ b/rust/rhg/src/commands/root.rs @@ -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(()) diff --git a/rust/rhg/src/error.rs b/rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs +++ b/rust/rhg/src/error.rs @@ -54,10 +54,10 @@ impl From 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(), 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,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, &ArgMatches, ) -> Result<(), CommandError>> { match name { diff --git a/tests/test-rhg.t b/tests/test-rhg.t --- a/tests/test-rhg.t +++ b/tests/test-rhg.t @@ -15,7 +15,7 @@ error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context USAGE: - rhg + rhg [OPTIONS] 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