requirements: move loading to hg-core and add parsing
No functional change, checking comes later.
Differential Revision: https://phab.mercurial-scm.org/D9398
--- a/rust/hg-core/src/lib.rs Tue Nov 24 15:11:58 2020 +0100
+++ b/rust/hg-core/src/lib.rs Tue Nov 24 17:49:16 2020 +0100
@@ -8,6 +8,7 @@
pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
mod dirstate;
pub mod discovery;
+pub mod requirements;
pub mod testing; // unconditionally built, for use from integration tests
pub use dirstate::{
dirs_multiset::{DirsMultiset, DirsMultisetIter},
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hg-core/src/requirements.rs Tue Nov 24 17:49:16 2020 +0100
@@ -0,0 +1,53 @@
+use std::io;
+use std::path::Path;
+
+#[derive(Debug)]
+pub enum RequirementsError {
+ // TODO: include a path?
+ Io(io::Error),
+ /// The `requires` file is corrupted
+ Corrupted,
+ /// The repository requires a feature that we don’t support
+ Unsupported {
+ feature: String,
+ },
+}
+
+fn parse(bytes: &[u8]) -> Result<Vec<String>, ()> {
+ // The Python code reading this file uses `str.splitlines`
+ // which looks for a number of line separators (even including a couple of
+ // non-ASCII ones), but Python code writing it always uses `\n`.
+ let lines = bytes.split(|&byte| byte == b'\n');
+
+ lines
+ .filter(|line| !line.is_empty())
+ .map(|line| {
+ // Python uses Unicode `str.isalnum` but feature names are all
+ // ASCII
+ if line[0].is_ascii_alphanumeric() {
+ Ok(String::from_utf8(line.into()).unwrap())
+ } else {
+ Err(())
+ }
+ })
+ .collect()
+}
+
+pub fn load(repo_root: &Path) -> Result<Vec<String>, RequirementsError> {
+ match std::fs::read(repo_root.join(".hg").join("requires")) {
+ Ok(bytes) => parse(&bytes).map_err(|()| RequirementsError::Corrupted),
+
+ // Treat a missing file the same as an empty file.
+ // From `mercurial/localrepo.py`:
+ // > requires file contains a newline-delimited list of
+ // > features/capabilities the opener (us) must have in order to use
+ // > the repository. This file was introduced in Mercurial 0.9.2,
+ // > which means very old repositories may not have one. We assume
+ // > a missing file translates to no requirements.
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
+ Ok(Vec::new())
+ }
+
+ Err(error) => Err(RequirementsError::Io(error))?,
+ }
+}
--- a/rust/rhg/src/commands/debugrequirements.rs Tue Nov 24 15:11:58 2020 +0100
+++ b/rust/rhg/src/commands/debugrequirements.rs Tue Nov 24 17:49:16 2020 +0100
@@ -1,7 +1,8 @@
use crate::commands::Command;
-use crate::error::{CommandError, CommandErrorKind};
+use crate::error::CommandError;
use crate::ui::Ui;
use hg::operations::FindRoot;
+use hg::requirements;
pub const HELP_TEXT: &str = "
Print the current repo requirements.
@@ -18,23 +19,12 @@
impl Command for DebugRequirementsCommand {
fn run(&self, ui: &Ui) -> Result<(), CommandError> {
let root = FindRoot::new().run()?;
- let requires = root.join(".hg").join("requires");
- let requirements = match std::fs::read(requires) {
- Ok(bytes) => bytes,
-
- // Treat a missing file the same as an empty file.
- // From `mercurial/localrepo.py`:
- // > requires file contains a newline-delimited list of
- // > features/capabilities the opener (us) must have in order to use
- // > the repository. This file was introduced in Mercurial 0.9.2,
- // > which means very old repositories may not have one. We assume
- // > a missing file translates to no requirements.
- Err(error) if error.kind() == std::io::ErrorKind::NotFound => Vec::new(),
-
- Err(error) => Err(CommandErrorKind::FileError(error))?,
- };
-
- ui.write_stdout(&requirements)?;
+ let mut output = String::new();
+ for req in requirements::load(&root)? {
+ output.push_str(&req);
+ output.push('\n');
+ }
+ ui.write_stdout(output.as_bytes())?;
Ok(())
}
}
--- a/rust/rhg/src/error.rs Tue Nov 24 15:11:58 2020 +0100
+++ b/rust/rhg/src/error.rs Tue Nov 24 17:49:16 2020 +0100
@@ -1,6 +1,7 @@
use crate::exitcode;
use crate::ui::UiError;
use hg::operations::{FindRootError, FindRootErrorKind};
+use hg::requirements::RequirementsError;
use hg::utils::files::get_bytes_from_path;
use std::convert::From;
use std::path::PathBuf;
@@ -12,9 +13,8 @@
RootNotFound(PathBuf),
/// The current directory cannot be found
CurrentDirNotFound(std::io::Error),
- /// Error while reading or writing a file
- // TODO: add the file name/path?
- FileError(std::io::Error),
+ /// `.hg/requires`
+ RequirementsError(RequirementsError),
/// The standard output stream cannot be written to
StdoutError,
/// The standard error stream cannot be written to
@@ -30,7 +30,7 @@
match self {
CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
- CommandErrorKind::FileError(_) => exitcode::ABORT,
+ CommandErrorKind::RequirementsError(_) => exitcode::ABORT,
CommandErrorKind::StdoutError => exitcode::ABORT,
CommandErrorKind::StderrError => exitcode::ABORT,
CommandErrorKind::Abort(_) => exitcode::ABORT,
@@ -62,6 +62,11 @@
]
.concat(),
),
+ CommandErrorKind::RequirementsError(
+ RequirementsError::Corrupted,
+ ) => Some(
+ "abort: .hg/requires is corrupted\n".as_bytes().to_owned(),
+ ),
CommandErrorKind::Abort(message) => message.to_owned(),
_ => None,
}
@@ -115,3 +120,11 @@
}
}
}
+
+impl From<RequirementsError> for CommandError {
+ fn from(err: RequirementsError) -> Self {
+ CommandError {
+ kind: CommandErrorKind::RequirementsError(err),
+ }
+ }
+}