##// END OF EJS Templates
requirements: move loading to hg-core and add parsing...
Simon Sapin -
r46536:a2eda1ff default
parent child Browse files
Show More
@@ -0,0 +1,53
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 }
@@ -1,192 +1,193
1 1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 2 // and Mercurial contributors
3 3 //
4 4 // This software may be used and distributed according to the terms of the
5 5 // GNU General Public License version 2 or any later version.
6 6 mod ancestors;
7 7 pub mod dagops;
8 8 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
9 9 mod dirstate;
10 10 pub mod discovery;
11 pub mod requirements;
11 12 pub mod testing; // unconditionally built, for use from integration tests
12 13 pub use dirstate::{
13 14 dirs_multiset::{DirsMultiset, DirsMultisetIter},
14 15 dirstate_map::DirstateMap,
15 16 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
16 17 status::{
17 18 status, BadMatch, BadType, DirstateStatus, StatusError, StatusOptions,
18 19 },
19 20 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
20 21 StateMap, StateMapIter,
21 22 };
22 23 mod filepatterns;
23 24 pub mod matchers;
24 25 pub mod revlog;
25 26 pub use revlog::*;
26 27 pub mod operations;
27 28 pub mod utils;
28 29
29 30 // Remove this to see (potential) non-artificial compile failures. MacOS
30 31 // *should* compile, but fail to compile tests for example as of 2020-03-06
31 32 #[cfg(not(target_os = "linux"))]
32 33 compile_error!(
33 34 "`hg-core` has only been tested on Linux and will most \
34 35 likely not behave correctly on other platforms."
35 36 );
36 37
37 38 use crate::utils::hg_path::{HgPathBuf, HgPathError};
38 39 pub use filepatterns::{
39 40 parse_pattern_syntax, read_pattern_file, IgnorePattern,
40 41 PatternFileWarning, PatternSyntax,
41 42 };
42 43 use std::collections::HashMap;
43 44 use twox_hash::RandomXxHashBuilder64;
44 45
45 46 /// This is a contract between the `micro-timer` crate and us, to expose
46 47 /// the `log` crate as `crate::log`.
47 48 use log;
48 49
49 50 pub type LineNumber = usize;
50 51
51 52 /// Rust's default hasher is too slow because it tries to prevent collision
52 53 /// attacks. We are not concerned about those: if an ill-minded person has
53 54 /// write access to your repository, you have other issues.
54 55 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
55 56
56 57 #[derive(Clone, Debug, PartialEq)]
57 58 pub enum DirstateParseError {
58 59 TooLittleData,
59 60 Overflow,
60 61 // TODO refactor to use bytes instead of String
61 62 CorruptedEntry(String),
62 63 Damaged,
63 64 }
64 65
65 66 impl From<std::io::Error> for DirstateParseError {
66 67 fn from(e: std::io::Error) -> Self {
67 68 DirstateParseError::CorruptedEntry(e.to_string())
68 69 }
69 70 }
70 71
71 72 impl ToString for DirstateParseError {
72 73 fn to_string(&self) -> String {
73 74 use crate::DirstateParseError::*;
74 75 match self {
75 76 TooLittleData => "Too little data for dirstate.".to_string(),
76 77 Overflow => "Overflow in dirstate.".to_string(),
77 78 CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e),
78 79 Damaged => "Dirstate appears to be damaged.".to_string(),
79 80 }
80 81 }
81 82 }
82 83
83 84 #[derive(Debug, PartialEq)]
84 85 pub enum DirstatePackError {
85 86 CorruptedEntry(String),
86 87 CorruptedParent,
87 88 BadSize(usize, usize),
88 89 }
89 90
90 91 impl From<std::io::Error> for DirstatePackError {
91 92 fn from(e: std::io::Error) -> Self {
92 93 DirstatePackError::CorruptedEntry(e.to_string())
93 94 }
94 95 }
95 96 #[derive(Debug, PartialEq)]
96 97 pub enum DirstateMapError {
97 98 PathNotFound(HgPathBuf),
98 99 EmptyPath,
99 100 InvalidPath(HgPathError),
100 101 }
101 102
102 103 impl ToString for DirstateMapError {
103 104 fn to_string(&self) -> String {
104 105 match self {
105 106 DirstateMapError::PathNotFound(_) => {
106 107 "expected a value, found none".to_string()
107 108 }
108 109 DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(),
109 110 DirstateMapError::InvalidPath(e) => e.to_string(),
110 111 }
111 112 }
112 113 }
113 114
114 115 #[derive(Debug)]
115 116 pub enum DirstateError {
116 117 Parse(DirstateParseError),
117 118 Pack(DirstatePackError),
118 119 Map(DirstateMapError),
119 120 IO(std::io::Error),
120 121 }
121 122
122 123 impl From<DirstateParseError> for DirstateError {
123 124 fn from(e: DirstateParseError) -> Self {
124 125 DirstateError::Parse(e)
125 126 }
126 127 }
127 128
128 129 impl From<DirstatePackError> for DirstateError {
129 130 fn from(e: DirstatePackError) -> Self {
130 131 DirstateError::Pack(e)
131 132 }
132 133 }
133 134
134 135 #[derive(Debug)]
135 136 pub enum PatternError {
136 137 Path(HgPathError),
137 138 UnsupportedSyntax(String),
138 139 UnsupportedSyntaxInFile(String, String, usize),
139 140 TooLong(usize),
140 141 IO(std::io::Error),
141 142 /// Needed a pattern that can be turned into a regex but got one that
142 143 /// can't. This should only happen through programmer error.
143 144 NonRegexPattern(IgnorePattern),
144 145 }
145 146
146 147 impl ToString for PatternError {
147 148 fn to_string(&self) -> String {
148 149 match self {
149 150 PatternError::UnsupportedSyntax(syntax) => {
150 151 format!("Unsupported syntax {}", syntax)
151 152 }
152 153 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
153 154 format!(
154 155 "{}:{}: unsupported syntax {}",
155 156 file_path, line, syntax
156 157 )
157 158 }
158 159 PatternError::TooLong(size) => {
159 160 format!("matcher pattern is too long ({} bytes)", size)
160 161 }
161 162 PatternError::IO(e) => e.to_string(),
162 163 PatternError::Path(e) => e.to_string(),
163 164 PatternError::NonRegexPattern(pattern) => {
164 165 format!("'{:?}' cannot be turned into a regex", pattern)
165 166 }
166 167 }
167 168 }
168 169 }
169 170
170 171 impl From<DirstateMapError> for DirstateError {
171 172 fn from(e: DirstateMapError) -> Self {
172 173 DirstateError::Map(e)
173 174 }
174 175 }
175 176
176 177 impl From<std::io::Error> for DirstateError {
177 178 fn from(e: std::io::Error) -> Self {
178 179 DirstateError::IO(e)
179 180 }
180 181 }
181 182
182 183 impl From<std::io::Error> for PatternError {
183 184 fn from(e: std::io::Error) -> Self {
184 185 PatternError::IO(e)
185 186 }
186 187 }
187 188
188 189 impl From<HgPathError> for PatternError {
189 190 fn from(e: HgPathError) -> Self {
190 191 PatternError::Path(e)
191 192 }
192 193 }
@@ -1,40 +1,30
1 1 use crate::commands::Command;
2 use crate::error::{CommandError, CommandErrorKind};
2 use crate::error::CommandError;
3 3 use crate::ui::Ui;
4 4 use hg::operations::FindRoot;
5 use hg::requirements;
5 6
6 7 pub const HELP_TEXT: &str = "
7 8 Print the current repo requirements.
8 9 ";
9 10
10 11 pub struct DebugRequirementsCommand {}
11 12
12 13 impl DebugRequirementsCommand {
13 14 pub fn new() -> Self {
14 15 DebugRequirementsCommand {}
15 16 }
16 17 }
17 18
18 19 impl Command for DebugRequirementsCommand {
19 20 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
20 21 let root = FindRoot::new().run()?;
21 let requires = root.join(".hg").join("requires");
22 let requirements = match std::fs::read(requires) {
23 Ok(bytes) => bytes,
24
25 // Treat a missing file the same as an empty file.
26 // From `mercurial/localrepo.py`:
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)?;
22 let mut output = String::new();
23 for req in requirements::load(&root)? {
24 output.push_str(&req);
25 output.push('\n');
26 }
27 ui.write_stdout(output.as_bytes())?;
38 28 Ok(())
39 29 }
40 30 }
@@ -1,117 +1,130
1 1 use crate::exitcode;
2 2 use crate::ui::UiError;
3 3 use hg::operations::{FindRootError, FindRootErrorKind};
4 use hg::requirements::RequirementsError;
4 5 use hg::utils::files::get_bytes_from_path;
5 6 use std::convert::From;
6 7 use std::path::PathBuf;
7 8
8 9 /// The kind of command error
9 10 #[derive(Debug)]
10 11 pub enum CommandErrorKind {
11 12 /// The root of the repository cannot be found
12 13 RootNotFound(PathBuf),
13 14 /// The current directory cannot be found
14 15 CurrentDirNotFound(std::io::Error),
15 /// Error while reading or writing a file
16 // TODO: add the file name/path?
17 FileError(std::io::Error),
16 /// `.hg/requires`
17 RequirementsError(RequirementsError),
18 18 /// The standard output stream cannot be written to
19 19 StdoutError,
20 20 /// The standard error stream cannot be written to
21 21 StderrError,
22 22 /// The command aborted
23 23 Abort(Option<Vec<u8>>),
24 24 /// A mercurial capability as not been implemented.
25 25 Unimplemented,
26 26 }
27 27
28 28 impl CommandErrorKind {
29 29 pub fn get_exit_code(&self) -> exitcode::ExitCode {
30 30 match self {
31 31 CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
32 32 CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
33 CommandErrorKind::FileError(_) => exitcode::ABORT,
33 CommandErrorKind::RequirementsError(_) => exitcode::ABORT,
34 34 CommandErrorKind::StdoutError => exitcode::ABORT,
35 35 CommandErrorKind::StderrError => exitcode::ABORT,
36 36 CommandErrorKind::Abort(_) => exitcode::ABORT,
37 37 CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
38 38 }
39 39 }
40 40
41 41 /// Return the message corresponding to the error kind if any
42 42 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
43 43 match self {
44 44 // TODO use formating macro
45 45 CommandErrorKind::RootNotFound(path) => {
46 46 let bytes = get_bytes_from_path(path);
47 47 Some(
48 48 [
49 49 b"abort: no repository found in '",
50 50 bytes.as_slice(),
51 51 b"' (.hg not found)!\n",
52 52 ]
53 53 .concat(),
54 54 )
55 55 }
56 56 // TODO use formating macro
57 57 CommandErrorKind::CurrentDirNotFound(e) => Some(
58 58 [
59 59 b"abort: error getting current working directory: ",
60 60 e.to_string().as_bytes(),
61 61 b"\n",
62 62 ]
63 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 70 CommandErrorKind::Abort(message) => message.to_owned(),
66 71 _ => None,
67 72 }
68 73 }
69 74 }
70 75
71 76 /// The error type for the Command trait
72 77 #[derive(Debug)]
73 78 pub struct CommandError {
74 79 pub kind: CommandErrorKind,
75 80 }
76 81
77 82 impl CommandError {
78 83 /// Exist the process with the corresponding exit code.
79 84 pub fn exit(&self) {
80 85 std::process::exit(self.kind.get_exit_code())
81 86 }
82 87
83 88 /// Return the message corresponding to the command error if any
84 89 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
85 90 self.kind.get_error_message_bytes()
86 91 }
87 92 }
88 93
89 94 impl From<CommandErrorKind> for CommandError {
90 95 fn from(kind: CommandErrorKind) -> Self {
91 96 CommandError { kind }
92 97 }
93 98 }
94 99
95 100 impl From<UiError> for CommandError {
96 101 fn from(error: UiError) -> Self {
97 102 CommandError {
98 103 kind: match error {
99 104 UiError::StdoutError(_) => CommandErrorKind::StdoutError,
100 105 UiError::StderrError(_) => CommandErrorKind::StderrError,
101 106 },
102 107 }
103 108 }
104 109 }
105 110
106 111 impl From<FindRootError> for CommandError {
107 112 fn from(err: FindRootError) -> Self {
108 113 match err.kind {
109 114 FindRootErrorKind::RootNotFound(path) => CommandError {
110 115 kind: CommandErrorKind::RootNotFound(path),
111 116 },
112 117 FindRootErrorKind::GetCurrentDirError(e) => CommandError {
113 118 kind: CommandErrorKind::CurrentDirNotFound(e),
114 119 },
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