… rather than in each sub-command that needs a local repository.
This will allow accessing e.g. .hg/blackbox.log before dispatching
to sub-commands.
( )
… rather than in each sub-command that needs a local repository.
This will allow accessing e.g. .hg/blackbox.log before dispatching
to sub-commands.
| No Linters Available |
| No Unit Test Coverage |
| Path | Packages | |||
|---|---|---|---|---|
| M | rust/hg-core/src/repo.rs (22 lines) | |||
| M | rust/rhg/src/commands/cat.rs (3 lines) | |||
| M | rust/rhg/src/commands/config.rs (10 lines) | |||
| M | rust/rhg/src/commands/debugdata.rs (5 lines) | |||
| M | rust/rhg/src/commands/debugrequirements.rs (3 lines) | |||
| M | rust/rhg/src/commands/files.rs (11 lines) | |||
| M | rust/rhg/src/commands/root.rs (3 lines) | |||
| M | rust/rhg/src/error.rs (15 lines) | |||
| M | rust/rhg/src/main.rs (34 lines) |
| Commit | Parents | Author | Summary | Date |
|---|---|---|---|---|
| e53329e47e60 | 1974195ecc91 | Simon Sapin | Feb 15 2021, 2:13 PM |
| Status | Author | Revision | |
|---|---|---|---|
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin | ||
| Closed | SimonSapin |
| } | } | ||||
| } | } | ||||
| Err(RepoError::NotFound { | Err(RepoError::NotFound { | ||||
| at: current_directory, | at: current_directory, | ||||
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| /// 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 | /// To be called after checking that `.hg` is a sub-directory | ||||
| fn new_at_path( | fn new_at_path( | ||||
| working_directory: PathBuf, | working_directory: PathBuf, | ||||
| config: &Config, | config: &Config, | ||||
| ) -> Result<Self, RepoError> { | ) -> Result<Self, RepoError> { | ||||
| let dot_hg = working_directory.join(".hg"); | let dot_hg = working_directory.join(".hg"); | ||||
| let mut repo_config_files = Vec::new(); | let mut repo_config_files = Vec::new(); | ||||
| use crate::error::CommandError; | use crate::error::CommandError; | ||||
| use clap::Arg; | use clap::Arg; | ||||
| use format_bytes::format_bytes; | use format_bytes::format_bytes; | ||||
| use hg::errors::HgError; | use hg::errors::HgError; | ||||
| use hg::repo::Repo; | |||||
| use hg::utils::SliceExt; | use hg::utils::SliceExt; | ||||
| pub const HELP_TEXT: &str = " | pub const HELP_TEXT: &str = " | ||||
| With one argument of the form section.name, print just the value of that config item. | With one argument of the form section.name, print just the value of that config item. | ||||
| "; | "; | ||||
| pub fn args() -> clap::App<'static, 'static> { | pub fn args() -> clap::App<'static, 'static> { | ||||
| clap::SubCommand::with_name("config") | clap::SubCommand::with_name("config") | ||||
| .arg( | .arg( | ||||
| Arg::with_name("name") | Arg::with_name("name") | ||||
| .help("the section.name to print") | .help("the section.name to print") | ||||
| .value_name("NAME") | .value_name("NAME") | ||||
| .required(true) | .required(true) | ||||
| .takes_value(true), | .takes_value(true), | ||||
| ) | ) | ||||
| .about(HELP_TEXT) | .about(HELP_TEXT) | ||||
| } | } | ||||
| pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | ||||
| let opt_repo = | |||||
| Repo::find_optional(invocation.non_repo_config, invocation.repo_path)?; | |||||
| let config = if let Some(repo) = &opt_repo { | |||||
| repo.config() | |||||
| } else { | |||||
| invocation.non_repo_config | |||||
| }; | |||||
| let (section, name) = invocation | let (section, name) = invocation | ||||
| .subcommand_args | .subcommand_args | ||||
| .value_of("name") | .value_of("name") | ||||
| .expect("missing required CLI argument") | .expect("missing required CLI argument") | ||||
| .as_bytes() | .as_bytes() | ||||
| .split_2(b'.') | .split_2(b'.') | ||||
| .ok_or_else(|| HgError::abort(""))?; | .ok_or_else(|| HgError::abort(""))?; | ||||
| let value = config.get(section, name).unwrap_or(b""); | let value = invocation.config().get(section, name).unwrap_or(b""); | ||||
| invocation.ui.write_stdout(&format_bytes!(b"{}\n", value))?; | invocation.ui.write_stdout(&format_bytes!(b"{}\n", value))?; | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| use crate::error::CommandError; | use crate::error::CommandError; | ||||
| use clap::Arg; | use clap::Arg; | ||||
| use clap::ArgGroup; | use clap::ArgGroup; | ||||
| use hg::operations::{debug_data, DebugDataKind}; | use hg::operations::{debug_data, DebugDataKind}; | ||||
| use hg::repo::Repo; | |||||
| use micro_timer::timed; | use micro_timer::timed; | ||||
| pub const HELP_TEXT: &str = " | pub const HELP_TEXT: &str = " | ||||
| Dump the contents of a data file revision | Dump the contents of a data file revision | ||||
| "; | "; | ||||
| pub fn args() -> clap::App<'static, 'static> { | pub fn args() -> clap::App<'static, 'static> { | ||||
| clap::SubCommand::with_name("debugdata") | clap::SubCommand::with_name("debugdata") | ||||
| (true, true) => { | (true, true) => { | ||||
| unreachable!("Should not happen since options are exclusive") | unreachable!("Should not happen since options are exclusive") | ||||
| } | } | ||||
| (false, false) => { | (false, false) => { | ||||
| unreachable!("Should not happen since options are required") | unreachable!("Should not happen since options are required") | ||||
| } | } | ||||
| }; | }; | ||||
| let repo = Repo::find(invocation.non_repo_config, invocation.repo_path)?; | let repo = invocation.repo?; | ||||
| let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?; | let data = debug_data(repo, rev, kind).map_err(|e| (e, rev))?; | ||||
| let mut stdout = invocation.ui.stdout_buffer(); | let mut stdout = invocation.ui.stdout_buffer(); | ||||
| stdout.write_all(&data)?; | stdout.write_all(&data)?; | ||||
| stdout.flush()?; | stdout.flush()?; | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| use crate::error::CommandError; | use crate::error::CommandError; | ||||
| use hg::repo::Repo; | |||||
| pub const HELP_TEXT: &str = " | pub const HELP_TEXT: &str = " | ||||
| Print the current repo requirements. | Print the current repo requirements. | ||||
| "; | "; | ||||
| pub fn args() -> clap::App<'static, 'static> { | pub fn args() -> clap::App<'static, 'static> { | ||||
| clap::SubCommand::with_name("debugrequirements").about(HELP_TEXT) | clap::SubCommand::with_name("debugrequirements").about(HELP_TEXT) | ||||
| } | } | ||||
| pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | ||||
| let repo = Repo::find(invocation.non_repo_config, invocation.repo_path)?; | let repo = invocation.repo?; | ||||
| let mut output = String::new(); | let mut output = String::new(); | ||||
| let mut requirements: Vec<_> = repo.requirements().iter().collect(); | let mut requirements: Vec<_> = repo.requirements().iter().collect(); | ||||
| requirements.sort(); | requirements.sort(); | ||||
| for req in requirements { | for req in requirements { | ||||
| output.push_str(req); | output.push_str(req); | ||||
| output.push('\n'); | output.push('\n'); | ||||
| } | } | ||||
| invocation.ui.write_stdout(output.as_bytes())?; | invocation.ui.write_stdout(output.as_bytes())?; | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| .takes_value(true), | .takes_value(true), | ||||
| ) | ) | ||||
| .about(HELP_TEXT) | .about(HELP_TEXT) | ||||
| } | } | ||||
| pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | ||||
| let rev = invocation.subcommand_args.value_of("rev"); | let rev = invocation.subcommand_args.value_of("rev"); | ||||
| let repo = Repo::find(invocation.non_repo_config, invocation.repo_path)?; | let repo = invocation.repo?; | ||||
| if let Some(rev) = rev { | if let Some(rev) = rev { | ||||
| let files = | let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?; | ||||
| list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?; | display_files(invocation.ui, repo, files.iter()) | ||||
| display_files(invocation.ui, &repo, files.iter()) | |||||
| } else { | } else { | ||||
| let distate = Dirstate::new(&repo)?; | let distate = Dirstate::new(repo)?; | ||||
| let files = distate.tracked_files()?; | let files = distate.tracked_files()?; | ||||
| display_files(invocation.ui, &repo, files) | display_files(invocation.ui, repo, files) | ||||
| } | } | ||||
| } | } | ||||
| fn display_files<'a>( | fn display_files<'a>( | ||||
| ui: &Ui, | ui: &Ui, | ||||
| repo: &Repo, | repo: &Repo, | ||||
| files: impl IntoIterator<Item = &'a HgPath>, | files: impl IntoIterator<Item = &'a HgPath>, | ||||
| ) -> Result<(), CommandError> { | ) -> Result<(), CommandError> { | ||||
| use crate::error::CommandError; | use crate::error::CommandError; | ||||
| use format_bytes::format_bytes; | use format_bytes::format_bytes; | ||||
| use hg::repo::Repo; | |||||
| use hg::utils::files::get_bytes_from_path; | use hg::utils::files::get_bytes_from_path; | ||||
| pub const HELP_TEXT: &str = " | pub const HELP_TEXT: &str = " | ||||
| Print the root directory of the current repository. | Print the root directory of the current repository. | ||||
| Returns 0 on success. | Returns 0 on success. | ||||
| "; | "; | ||||
| pub fn args() -> clap::App<'static, 'static> { | pub fn args() -> clap::App<'static, 'static> { | ||||
| clap::SubCommand::with_name("root").about(HELP_TEXT) | clap::SubCommand::with_name("root").about(HELP_TEXT) | ||||
| } | } | ||||
| pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> { | ||||
| let repo = Repo::find(invocation.non_repo_config, invocation.repo_path)?; | let repo = invocation.repo?; | ||||
| let bytes = get_bytes_from_path(repo.working_directory_path()); | let bytes = get_bytes_from_path(repo.working_directory_path()); | ||||
| invocation | invocation | ||||
| .ui | .ui | ||||
| .write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; | .write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; | ||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| use crate::ui::utf8_to_local; | use crate::ui::utf8_to_local; | ||||
| use crate::ui::UiError; | use crate::ui::UiError; | ||||
| use crate::NoRepoInCwdError; | |||||
| use format_bytes::format_bytes; | use format_bytes::format_bytes; | ||||
| use hg::config::{ConfigError, ConfigParseError}; | use hg::config::{ConfigError, ConfigParseError}; | ||||
| use hg::errors::HgError; | use hg::errors::HgError; | ||||
| use hg::repo::RepoError; | use hg::repo::RepoError; | ||||
| use hg::revlog::revlog::RevlogError; | use hg::revlog::revlog::RevlogError; | ||||
| use hg::utils::files::get_bytes_from_path; | use hg::utils::files::get_bytes_from_path; | ||||
| use std::convert::From; | use std::convert::From; | ||||
| } | } | ||||
| } | } | ||||
| impl From<RepoError> for CommandError { | impl From<RepoError> for CommandError { | ||||
| fn from(error: RepoError) -> Self { | fn from(error: RepoError) -> Self { | ||||
| match error { | match error { | ||||
| RepoError::NotFound { at } => CommandError::Abort { | RepoError::NotFound { at } => CommandError::Abort { | ||||
| message: format_bytes!( | message: format_bytes!( | ||||
| b"no repository found in '{}' (.hg not found)!", | b"repository {} not found", | ||||
| get_bytes_from_path(at) | get_bytes_from_path(at) | ||||
| ), | ), | ||||
| }, | }, | ||||
| RepoError::ConfigParseError(error) => error.into(), | RepoError::ConfigParseError(error) => error.into(), | ||||
| RepoError::Other(error) => error.into(), | RepoError::Other(error) => error.into(), | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| impl<'a> From<&'a NoRepoInCwdError> for CommandError { | |||||
| fn from(error: &'a NoRepoInCwdError) -> Self { | |||||
| let NoRepoInCwdError { cwd } = error; | |||||
| CommandError::Abort { | |||||
| message: format_bytes!( | |||||
| b"no repository found in '{}' (.hg not found)!", | |||||
| get_bytes_from_path(cwd) | |||||
| ), | |||||
| } | |||||
| } | |||||
| } | |||||
| impl From<ConfigError> for CommandError { | impl From<ConfigError> for CommandError { | ||||
| fn from(error: ConfigError) -> Self { | fn from(error: ConfigError) -> Self { | ||||
| match error { | match error { | ||||
| ConfigError::Parse(error) => error.into(), | ConfigError::Parse(error) => error.into(), | ||||
| ConfigError::Other(error) => error.into(), | ConfigError::Other(error) => error.into(), | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| extern crate log; | extern crate log; | ||||
| use crate::ui::Ui; | use crate::ui::Ui; | ||||
| use clap::App; | use clap::App; | ||||
| use clap::AppSettings; | use clap::AppSettings; | ||||
| use clap::Arg; | use clap::Arg; | ||||
| use clap::ArgMatches; | use clap::ArgMatches; | ||||
| use format_bytes::format_bytes; | use format_bytes::format_bytes; | ||||
| use hg::config::Config; | use hg::config::Config; | ||||
| use std::path::Path; | use hg::repo::{Repo, RepoError}; | ||||
| use std::path::{Path, PathBuf}; | |||||
| mod error; | mod error; | ||||
| mod exitcode; | mod exitcode; | ||||
| mod ui; | mod ui; | ||||
| use error::CommandError; | use error::CommandError; | ||||
| fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { | fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { | ||||
| app.arg( | app.arg( | ||||
| }; | }; | ||||
| let config_args = values_of_global_arg("config") | let config_args = values_of_global_arg("config") | ||||
| // `get_bytes_from_path` works for OsStr the same as for Path | // `get_bytes_from_path` works for OsStr the same as for Path | ||||
| .map(hg::utils::files::get_bytes_from_path); | .map(hg::utils::files::get_bytes_from_path); | ||||
| let non_repo_config = &hg::config::Config::load(config_args)?; | let non_repo_config = &hg::config::Config::load(config_args)?; | ||||
| let repo_path = value_of_global_arg("repository").map(Path::new); | let repo_path = value_of_global_arg("repository").map(Path::new); | ||||
| let repo = match Repo::find(non_repo_config, repo_path) { | |||||
| Ok(repo) => Ok(repo), | |||||
| Err(RepoError::NotFound { at }) if repo_path.is_none() => { | |||||
| // Not finding a repo is not fatal yet, if `-R` was not given | |||||
| Err(NoRepoInCwdError { cwd: at }) | |||||
| } | |||||
| Err(error) => return Err(error.into()), | |||||
| }; | |||||
| run(&CliInvocation { | run(&CliInvocation { | ||||
| ui, | ui, | ||||
| subcommand_args, | subcommand_args, | ||||
| non_repo_config, | non_repo_config, | ||||
| repo_path, | repo: repo.as_ref(), | ||||
| }) | }) | ||||
| } | } | ||||
| fn main() { | fn main() { | ||||
| let ui = Ui::new(); | let ui = ui::Ui::new(); | ||||
| let exit_code = match main_with_result(&ui) { | let exit_code = match main_with_result(&ui) { | ||||
| Ok(()) => exitcode::OK, | Ok(()) => exitcode::OK, | ||||
| // Exit with a specific code and no error message to let a potential | // Exit with a specific code and no error message to let a potential | ||||
| // wrapper script fallback to Python-based Mercurial. | // wrapper script fallback to Python-based Mercurial. | ||||
| Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, | Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, | ||||
| files | files | ||||
| root | root | ||||
| config | config | ||||
| } | } | ||||
| pub struct CliInvocation<'a> { | pub struct CliInvocation<'a> { | ||||
| ui: &'a Ui, | ui: &'a Ui, | ||||
| subcommand_args: &'a ArgMatches<'a>, | subcommand_args: &'a ArgMatches<'a>, | ||||
| non_repo_config: &'a Config, | non_repo_config: &'a Config, | ||||
| repo_path: Option<&'a Path>, | /// References inside `Result` is a bit peculiar but allow | ||||
| /// `invocation.repo?` to work out with `&CliInvocation` since this | |||||
| /// `Result` type is `Copy`. | |||||
| repo: Result<&'a Repo, &'a NoRepoInCwdError>, | |||||
| } | |||||
| struct NoRepoInCwdError { | |||||
| cwd: PathBuf, | |||||
| } | |||||
| impl CliInvocation<'_> { | |||||
| fn config(&self) -> &Config { | |||||
| if let Ok(repo) = self.repo { | |||||
| repo.config() | |||||
| } else { | |||||
| self.non_repo_config | |||||
| } | |||||
| } | |||||
| } | } | ||||
I really like this, because it makes it an error very easily for all commands that do require a repo with no boilerplate.