… 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.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
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) |
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.