Show More
@@ -0,0 +1,53 b'' | |||||
|
1 | use std::io; | |||
|
2 | use std::path::Path; | |||
|
3 | ||||
|
4 | #[derive(Debug)] | |||
|
5 | pub enum RequirementsError { | |||
|
6 | // TODO: include a path? | |||
|
7 | Io(io::Error), | |||
|
8 | /// The `requires` file is corrupted | |||
|
9 | Corrupted, | |||
|
10 | /// The repository requires a feature that we donοΏ½t support | |||
|
11 | Unsupported { | |||
|
12 | feature: String, | |||
|
13 | }, | |||
|
14 | } | |||
|
15 | ||||
|
16 | fn parse(bytes: &[u8]) -> Result<Vec<String>, ()> { | |||
|
17 | // The Python code reading this file uses `str.splitlines` | |||
|
18 | // which looks for a number of line separators (even including a couple of | |||
|
19 | // non-ASCII ones), but Python code writing it always uses `\n`. | |||
|
20 | let lines = bytes.split(|&byte| byte == b'\n'); | |||
|
21 | ||||
|
22 | lines | |||
|
23 | .filter(|line| !line.is_empty()) | |||
|
24 | .map(|line| { | |||
|
25 | // Python uses Unicode `str.isalnum` but feature names are all | |||
|
26 | // ASCII | |||
|
27 | if line[0].is_ascii_alphanumeric() { | |||
|
28 | Ok(String::from_utf8(line.into()).unwrap()) | |||
|
29 | } else { | |||
|
30 | Err(()) | |||
|
31 | } | |||
|
32 | }) | |||
|
33 | .collect() | |||
|
34 | } | |||
|
35 | ||||
|
36 | pub fn load(repo_root: &Path) -> Result<Vec<String>, RequirementsError> { | |||
|
37 | match std::fs::read(repo_root.join(".hg").join("requires")) { | |||
|
38 | Ok(bytes) => parse(&bytes).map_err(|()| RequirementsError::Corrupted), | |||
|
39 | ||||
|
40 | // Treat a missing file the same as an empty file. | |||
|
41 | // From `mercurial/localrepo.py`: | |||
|
42 | // > requires file contains a newline-delimited list of | |||
|
43 | // > features/capabilities the opener (us) must have in order to use | |||
|
44 | // > the repository. This file was introduced in Mercurial 0.9.2, | |||
|
45 | // > which means very old repositories may not have one. We assume | |||
|
46 | // > a missing file translates to no requirements. | |||
|
47 | Err(error) if error.kind() == std::io::ErrorKind::NotFound => { | |||
|
48 | Ok(Vec::new()) | |||
|
49 | } | |||
|
50 | ||||
|
51 | Err(error) => Err(RequirementsError::Io(error))?, | |||
|
52 | } | |||
|
53 | } |
@@ -8,6 +8,7 b' pub mod dagops;' | |||||
8 | pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; |
|
8 | pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; | |
9 | mod dirstate; |
|
9 | mod dirstate; | |
10 | pub mod discovery; |
|
10 | pub mod discovery; | |
|
11 | pub mod requirements; | |||
11 | pub mod testing; // unconditionally built, for use from integration tests |
|
12 | pub mod testing; // unconditionally built, for use from integration tests | |
12 | pub use dirstate::{ |
|
13 | pub use dirstate::{ | |
13 | dirs_multiset::{DirsMultiset, DirsMultisetIter}, |
|
14 | dirs_multiset::{DirsMultiset, DirsMultisetIter}, |
@@ -1,7 +1,8 b'' | |||||
1 | use crate::commands::Command; |
|
1 | use crate::commands::Command; | |
2 |
use crate::error:: |
|
2 | use crate::error::CommandError; | |
3 | use crate::ui::Ui; |
|
3 | use crate::ui::Ui; | |
4 | use hg::operations::FindRoot; |
|
4 | use hg::operations::FindRoot; | |
|
5 | use hg::requirements; | |||
5 |
|
6 | |||
6 | pub const HELP_TEXT: &str = " |
|
7 | pub const HELP_TEXT: &str = " | |
7 | Print the current repo requirements. |
|
8 | Print the current repo requirements. | |
@@ -18,23 +19,12 b' impl DebugRequirementsCommand {' | |||||
18 | impl Command for DebugRequirementsCommand { |
|
19 | impl Command for DebugRequirementsCommand { | |
19 | fn run(&self, ui: &Ui) -> Result<(), CommandError> { |
|
20 | fn run(&self, ui: &Ui) -> Result<(), CommandError> { | |
20 | let root = FindRoot::new().run()?; |
|
21 | let root = FindRoot::new().run()?; | |
21 | let requires = root.join(".hg").join("requires"); |
|
22 | let mut output = String::new(); | |
22 | let requirements = match std::fs::read(requires) { |
|
23 | for req in requirements::load(&root)? { | |
23 | Ok(bytes) => bytes, |
|
24 | output.push_str(&req); | |
24 |
|
25 | output.push('\n'); | ||
25 | // Treat a missing file the same as an empty file. |
|
26 | } | |
26 | // From `mercurial/localrepo.py`: |
|
27 | ui.write_stdout(output.as_bytes())?; | |
27 | // > requires file contains a newline-delimited list of |
|
|||
28 | // > features/capabilities the opener (us) must have in order to use |
|
|||
29 | // > the repository. This file was introduced in Mercurial 0.9.2, |
|
|||
30 | // > which means very old repositories may not have one. We assume |
|
|||
31 | // > a missing file translates to no requirements. |
|
|||
32 | Err(error) if error.kind() == std::io::ErrorKind::NotFound => Vec::new(), |
|
|||
33 |
|
||||
34 | Err(error) => Err(CommandErrorKind::FileError(error))?, |
|
|||
35 | }; |
|
|||
36 |
|
||||
37 | ui.write_stdout(&requirements)?; |
|
|||
38 | Ok(()) |
|
28 | Ok(()) | |
39 | } |
|
29 | } | |
40 | } |
|
30 | } |
@@ -1,6 +1,7 b'' | |||||
1 | use crate::exitcode; |
|
1 | use crate::exitcode; | |
2 | use crate::ui::UiError; |
|
2 | use crate::ui::UiError; | |
3 | use hg::operations::{FindRootError, FindRootErrorKind}; |
|
3 | use hg::operations::{FindRootError, FindRootErrorKind}; | |
|
4 | use hg::requirements::RequirementsError; | |||
4 | use hg::utils::files::get_bytes_from_path; |
|
5 | use hg::utils::files::get_bytes_from_path; | |
5 | use std::convert::From; |
|
6 | use std::convert::From; | |
6 | use std::path::PathBuf; |
|
7 | use std::path::PathBuf; | |
@@ -12,9 +13,8 b' pub enum CommandErrorKind {' | |||||
12 | RootNotFound(PathBuf), |
|
13 | RootNotFound(PathBuf), | |
13 | /// The current directory cannot be found |
|
14 | /// The current directory cannot be found | |
14 | CurrentDirNotFound(std::io::Error), |
|
15 | CurrentDirNotFound(std::io::Error), | |
15 | /// Error while reading or writing a file |
|
16 | /// `.hg/requires` | |
16 | // TODO: add the file name/path? |
|
17 | RequirementsError(RequirementsError), | |
17 | FileError(std::io::Error), |
|
|||
18 | /// The standard output stream cannot be written to |
|
18 | /// The standard output stream cannot be written to | |
19 | StdoutError, |
|
19 | StdoutError, | |
20 | /// The standard error stream cannot be written to |
|
20 | /// The standard error stream cannot be written to | |
@@ -30,7 +30,7 b' impl CommandErrorKind {' | |||||
30 | match self { |
|
30 | match self { | |
31 | CommandErrorKind::RootNotFound(_) => exitcode::ABORT, |
|
31 | CommandErrorKind::RootNotFound(_) => exitcode::ABORT, | |
32 | CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT, |
|
32 | CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT, | |
33 |
CommandErrorKind:: |
|
33 | CommandErrorKind::RequirementsError(_) => exitcode::ABORT, | |
34 | CommandErrorKind::StdoutError => exitcode::ABORT, |
|
34 | CommandErrorKind::StdoutError => exitcode::ABORT, | |
35 | CommandErrorKind::StderrError => exitcode::ABORT, |
|
35 | CommandErrorKind::StderrError => exitcode::ABORT, | |
36 | CommandErrorKind::Abort(_) => exitcode::ABORT, |
|
36 | CommandErrorKind::Abort(_) => exitcode::ABORT, | |
@@ -62,6 +62,11 b' impl CommandErrorKind {' | |||||
62 | ] |
|
62 | ] | |
63 | .concat(), |
|
63 | .concat(), | |
64 | ), |
|
64 | ), | |
|
65 | CommandErrorKind::RequirementsError( | |||
|
66 | RequirementsError::Corrupted, | |||
|
67 | ) => Some( | |||
|
68 | "abort: .hg/requires is corrupted\n".as_bytes().to_owned(), | |||
|
69 | ), | |||
65 | CommandErrorKind::Abort(message) => message.to_owned(), |
|
70 | CommandErrorKind::Abort(message) => message.to_owned(), | |
66 | _ => None, |
|
71 | _ => None, | |
67 | } |
|
72 | } | |
@@ -115,3 +120,11 b' impl From<FindRootError> for CommandErro' | |||||
115 | } |
|
120 | } | |
116 | } |
|
121 | } | |
117 | } |
|
122 | } | |
|
123 | ||||
|
124 | impl From<RequirementsError> for CommandError { | |||
|
125 | fn from(err: RequirementsError) -> Self { | |||
|
126 | CommandError { | |||
|
127 | kind: CommandErrorKind::RequirementsError(err), | |||
|
128 | } | |||
|
129 | } | |||
|
130 | } |
General Comments 0
You need to be logged in to leave comments.
Login now