Show More
@@ -16,7 +16,6 b' use std::env;' | |||||
16 | use std::path::{Path, PathBuf}; |
|
16 | use std::path::{Path, PathBuf}; | |
17 |
|
17 | |||
18 | use crate::errors::{HgResultExt, IoResultExt}; |
|
18 | use crate::errors::{HgResultExt, IoResultExt}; | |
19 | use crate::repo::Repo; |
|
|||
20 |
|
19 | |||
21 | /// Holds the config values for the current repository |
|
20 | /// Holds the config values for the current repository | |
22 | /// TODO update this docstring once we support more sources |
|
21 | /// TODO update this docstring once we support more sources | |
@@ -196,12 +195,28 b' impl Config {' | |||||
196 | Ok(Config { layers }) |
|
195 | Ok(Config { layers }) | |
197 | } |
|
196 | } | |
198 |
|
197 | |||
199 | /// Loads the local config. In a future version, this will also load the |
|
198 | /// Loads the per-repository config into a new `Config` which is combined | |
200 | /// `$HOME/.hgrc` and more to mirror the Python implementation. |
|
199 | /// with `self`. | |
201 | pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> { |
|
200 | pub(crate) fn combine_with_repo( | |
202 | Ok(Self::load_from_explicit_sources(vec![ |
|
201 | &self, | |
203 | ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")), |
|
202 | repo_config_files: &[PathBuf], | |
204 | ])?) |
|
203 | ) -> Result<Self, ConfigError> { | |
|
204 | let (cli_layers, other_layers) = self | |||
|
205 | .layers | |||
|
206 | .iter() | |||
|
207 | .cloned() | |||
|
208 | .partition(ConfigLayer::is_from_command_line); | |||
|
209 | ||||
|
210 | let mut repo_config = Self { | |||
|
211 | layers: other_layers, | |||
|
212 | }; | |||
|
213 | for path in repo_config_files { | |||
|
214 | // TODO: check if this file should be trusted: | |||
|
215 | // `mercurial/ui.py:427` | |||
|
216 | repo_config.add_trusted_file(path)?; | |||
|
217 | } | |||
|
218 | repo_config.layers.extend(cli_layers); | |||
|
219 | Ok(repo_config) | |||
205 | } |
|
220 | } | |
206 |
|
221 | |||
207 | /// Returns an `Err` if the first value found is not a valid boolean. |
|
222 | /// Returns an `Err` if the first value found is not a valid boolean. | |
@@ -297,8 +312,6 b' mod tests {' | |||||
297 | let config = Config::load_from_explicit_sources(sources) |
|
312 | let config = Config::load_from_explicit_sources(sources) | |
298 | .expect("expected valid config"); |
|
313 | .expect("expected valid config"); | |
299 |
|
314 | |||
300 | dbg!(&config); |
|
|||
301 |
|
||||
302 | let (_, value) = config.get_inner(b"section", b"item").unwrap(); |
|
315 | let (_, value) = config.get_inner(b"section", b"item").unwrap(); | |
303 | assert_eq!( |
|
316 | assert_eq!( | |
304 | value, |
|
317 | value, |
@@ -51,6 +51,15 b' impl ConfigLayer {' | |||||
51 | } |
|
51 | } | |
52 | } |
|
52 | } | |
53 |
|
53 | |||
|
54 | /// Returns whether this layer comes from `--config` CLI arguments | |||
|
55 | pub(crate) fn is_from_command_line(&self) -> bool { | |||
|
56 | if let ConfigOrigin::CommandLine = self.origin { | |||
|
57 | true | |||
|
58 | } else { | |||
|
59 | false | |||
|
60 | } | |||
|
61 | } | |||
|
62 | ||||
54 | /// Add an entry to the config, overwriting the old one if already present. |
|
63 | /// Add an entry to the config, overwriting the old one if already present. | |
55 | pub fn add( |
|
64 | pub fn add( | |
56 | &mut self, |
|
65 | &mut self, | |
@@ -97,11 +106,13 b' impl ConfigLayer {' | |||||
97 | if let Some(m) = INCLUDE_RE.captures(&bytes) { |
|
106 | if let Some(m) = INCLUDE_RE.captures(&bytes) { | |
98 | let filename_bytes = &m[1]; |
|
107 | let filename_bytes = &m[1]; | |
99 | // `Path::parent` only fails for the root directory, |
|
108 | // `Path::parent` only fails for the root directory, | |
100 |
// which `src` canβt be since weβve managed to open it as a |
|
109 | // which `src` canβt be since weβve managed to open it as a | |
|
110 | // file. | |||
101 | let dir = src |
|
111 | let dir = src | |
102 | .parent() |
|
112 | .parent() | |
103 | .expect("Path::parent fail on a file weβve read"); |
|
113 | .expect("Path::parent fail on a file weβve read"); | |
104 |
// `Path::join` with an absolute argument correctly ignores the |
|
114 | // `Path::join` with an absolute argument correctly ignores the | |
|
115 | // base path | |||
105 | let filename = dir.join(&get_path_from_bytes(&filename_bytes)); |
|
116 | let filename = dir.join(&get_path_from_bytes(&filename_bytes)); | |
106 | let data = std::fs::read(&filename).for_file(&filename)?; |
|
117 | let data = std::fs::read(&filename).for_file(&filename)?; | |
107 | layers.push(current_layer); |
|
118 | layers.push(current_layer); | |
@@ -200,9 +211,11 b' pub struct ConfigValue {' | |||||
200 |
|
211 | |||
201 | #[derive(Clone, Debug)] |
|
212 | #[derive(Clone, Debug)] | |
202 | pub enum ConfigOrigin { |
|
213 | pub enum ConfigOrigin { | |
203 |
/// |
|
214 | /// From a configuration file | |
204 | File(PathBuf), |
|
215 | File(PathBuf), | |
205 | /// The value comes from the environment like `$PAGER` or `$EDITOR` |
|
216 | /// From a `--config` CLI argument | |
|
217 | CommandLine, | |||
|
218 | /// From environment variables like `$PAGER` or `$EDITOR` | |||
206 | Environment(Vec<u8>), |
|
219 | Environment(Vec<u8>), | |
207 | /* TODO cli |
|
220 | /* TODO cli | |
208 | * TODO defaults (configitems.py) |
|
221 | * TODO defaults (configitems.py) | |
@@ -216,6 +229,7 b' impl ConfigOrigin {' | |||||
216 | pub fn to_bytes(&self) -> Vec<u8> { |
|
229 | pub fn to_bytes(&self) -> Vec<u8> { | |
217 | match self { |
|
230 | match self { | |
218 | ConfigOrigin::File(p) => get_bytes_from_path(p), |
|
231 | ConfigOrigin::File(p) => get_bytes_from_path(p), | |
|
232 | ConfigOrigin::CommandLine => b"--config".to_vec(), | |||
219 | ConfigOrigin::Environment(e) => format_bytes!(b"${}", e), |
|
233 | ConfigOrigin::Environment(e) => format_bytes!(b"${}", e), | |
220 | } |
|
234 | } | |
221 | } |
|
235 | } |
@@ -1,4 +1,4 b'' | |||||
1 | use crate::config::Config; |
|
1 | use crate::config::{Config, ConfigError, ConfigParseError}; | |
2 | use crate::errors::{HgError, IoResultExt}; |
|
2 | use crate::errors::{HgError, IoResultExt}; | |
3 | use crate::requirements; |
|
3 | use crate::requirements; | |
4 | use crate::utils::files::get_path_from_bytes; |
|
4 | use crate::utils::files::get_path_from_bytes; | |
@@ -12,17 +12,29 b' pub struct Repo {' | |||||
12 | dot_hg: PathBuf, |
|
12 | dot_hg: PathBuf, | |
13 | store: PathBuf, |
|
13 | store: PathBuf, | |
14 | requirements: HashSet<String>, |
|
14 | requirements: HashSet<String>, | |
|
15 | config: Config, | |||
15 | } |
|
16 | } | |
16 |
|
17 | |||
17 | #[derive(Debug, derive_more::From)] |
|
18 | #[derive(Debug, derive_more::From)] | |
18 |
pub enum Repo |
|
19 | pub enum RepoError { | |
19 | NotFoundInCurrentDirectoryOrAncestors { |
|
20 | NotFound { | |
20 | current_directory: PathBuf, |
|
21 | current_directory: PathBuf, | |
21 | }, |
|
22 | }, | |
22 | #[from] |
|
23 | #[from] | |
|
24 | ConfigParseError(ConfigParseError), | |||
|
25 | #[from] | |||
23 | Other(HgError), |
|
26 | Other(HgError), | |
24 | } |
|
27 | } | |
25 |
|
28 | |||
|
29 | impl From<ConfigError> for RepoError { | |||
|
30 | fn from(error: ConfigError) -> Self { | |||
|
31 | match error { | |||
|
32 | ConfigError::Parse(error) => error.into(), | |||
|
33 | ConfigError::Other(error) => error.into(), | |||
|
34 | } | |||
|
35 | } | |||
|
36 | } | |||
|
37 | ||||
26 | /// Filesystem access abstraction for the contents of a given "base" diretory |
|
38 | /// Filesystem access abstraction for the contents of a given "base" diretory | |
27 | #[derive(Clone, Copy)] |
|
39 | #[derive(Clone, Copy)] | |
28 | pub(crate) struct Vfs<'a> { |
|
40 | pub(crate) struct Vfs<'a> { | |
@@ -32,7 +44,7 b" pub(crate) struct Vfs<'a> {" | |||||
32 | impl Repo { |
|
44 | impl Repo { | |
33 | /// Search the current directory and its ancestores for a repository: |
|
45 | /// Search the current directory and its ancestores for a repository: | |
34 | /// a working directory that contains a `.hg` sub-directory. |
|
46 | /// a working directory that contains a `.hg` sub-directory. | |
35 |
pub fn find(config: &Config) -> Result<Self, Repo |
|
47 | pub fn find(config: &Config) -> Result<Self, RepoError> { | |
36 | let current_directory = crate::utils::current_dir()?; |
|
48 | let current_directory = crate::utils::current_dir()?; | |
37 | // ancestors() is inclusive: it first yields `current_directory` as-is. |
|
49 | // ancestors() is inclusive: it first yields `current_directory` as-is. | |
38 | for ancestor in current_directory.ancestors() { |
|
50 | for ancestor in current_directory.ancestors() { | |
@@ -40,18 +52,20 b' impl Repo {' | |||||
40 | return Ok(Self::new_at_path(ancestor.to_owned(), config)?); |
|
52 | return Ok(Self::new_at_path(ancestor.to_owned(), config)?); | |
41 | } |
|
53 | } | |
42 | } |
|
54 | } | |
43 |
Err(Repo |
|
55 | Err(RepoError::NotFound { current_directory }) | |
44 | current_directory, |
|
|||
45 | }) |
|
|||
46 | } |
|
56 | } | |
47 |
|
57 | |||
48 | /// To be called after checking that `.hg` is a sub-directory |
|
58 | /// To be called after checking that `.hg` is a sub-directory | |
49 | fn new_at_path( |
|
59 | fn new_at_path( | |
50 | working_directory: PathBuf, |
|
60 | working_directory: PathBuf, | |
51 | config: &Config, |
|
61 | config: &Config, | |
52 |
) -> Result<Self, |
|
62 | ) -> Result<Self, RepoError> { | |
53 | let dot_hg = working_directory.join(".hg"); |
|
63 | let dot_hg = working_directory.join(".hg"); | |
54 |
|
64 | |||
|
65 | let mut repo_config_files = Vec::new(); | |||
|
66 | repo_config_files.push(dot_hg.join("hgrc")); | |||
|
67 | repo_config_files.push(dot_hg.join("hgrc-not-shared")); | |||
|
68 | ||||
55 | let hg_vfs = Vfs { base: &dot_hg }; |
|
69 | let hg_vfs = Vfs { base: &dot_hg }; | |
56 | let mut reqs = requirements::load_if_exists(hg_vfs)?; |
|
70 | let mut reqs = requirements::load_if_exists(hg_vfs)?; | |
57 | let relative = |
|
71 | let relative = | |
@@ -89,7 +103,8 b' impl Repo {' | |||||
89 | return Err(HgError::corrupted(format!( |
|
103 | return Err(HgError::corrupted(format!( | |
90 | ".hg/sharedpath points to nonexistent directory {}", |
|
104 | ".hg/sharedpath points to nonexistent directory {}", | |
91 | shared_path.display() |
|
105 | shared_path.display() | |
92 |
)) |
|
106 | )) | |
|
107 | .into()); | |||
93 | } |
|
108 | } | |
94 |
|
109 | |||
95 | store_path = shared_path.join("store"); |
|
110 | store_path = shared_path.join("store"); | |
@@ -99,12 +114,15 b' impl Repo {' | |||||
99 | .contains(requirements::SHARESAFE_REQUIREMENT); |
|
114 | .contains(requirements::SHARESAFE_REQUIREMENT); | |
100 |
|
115 | |||
101 | if share_safe && !source_is_share_safe { |
|
116 | if share_safe && !source_is_share_safe { | |
102 |
return Err(match config |
|
117 | return Err(match config | |
|
118 | .get(b"safe-mismatch", b"source-not-safe") | |||
|
119 | { | |||
103 | Some(b"abort") | None => HgError::abort( |
|
120 | Some(b"abort") | None => HgError::abort( | |
104 | "share source does not support share-safe requirement" |
|
121 | "share source does not support share-safe requirement", | |
105 | ), |
|
122 | ), | |
106 | _ => HgError::unsupported("share-safe downgrade") |
|
123 | _ => HgError::unsupported("share-safe downgrade"), | |
107 |
} |
|
124 | } | |
|
125 | .into()); | |||
108 | } else if source_is_share_safe && !share_safe { |
|
126 | } else if source_is_share_safe && !share_safe { | |
109 | return Err( |
|
127 | return Err( | |
110 | match config.get(b"safe-mismatch", b"source-safe") { |
|
128 | match config.get(b"safe-mismatch", b"source-safe") { | |
@@ -113,16 +131,24 b' impl Repo {' | |||||
113 | functionality while the current share does not", |
|
131 | functionality while the current share does not", | |
114 | ), |
|
132 | ), | |
115 | _ => HgError::unsupported("share-safe upgrade"), |
|
133 | _ => HgError::unsupported("share-safe upgrade"), | |
116 |
} |
|
134 | } | |
|
135 | .into(), | |||
117 | ); |
|
136 | ); | |
118 | } |
|
137 | } | |
|
138 | ||||
|
139 | if share_safe { | |||
|
140 | repo_config_files.insert(0, shared_path.join("hgrc")) | |||
|
141 | } | |||
119 | } |
|
142 | } | |
120 |
|
143 | |||
|
144 | let repo_config = config.combine_with_repo(&repo_config_files)?; | |||
|
145 | ||||
121 | let repo = Self { |
|
146 | let repo = Self { | |
122 | requirements: reqs, |
|
147 | requirements: reqs, | |
123 | working_directory, |
|
148 | working_directory, | |
124 | store: store_path, |
|
149 | store: store_path, | |
125 | dot_hg, |
|
150 | dot_hg, | |
|
151 | config: repo_config, | |||
126 | }; |
|
152 | }; | |
127 |
|
153 | |||
128 | requirements::check(&repo)?; |
|
154 | requirements::check(&repo)?; | |
@@ -138,6 +164,10 b' impl Repo {' | |||||
138 | &self.requirements |
|
164 | &self.requirements | |
139 | } |
|
165 | } | |
140 |
|
166 | |||
|
167 | pub fn config(&self) -> &Config { | |||
|
168 | &self.config | |||
|
169 | } | |||
|
170 | ||||
141 | /// For accessing repository files (in `.hg`), except for the store |
|
171 | /// For accessing repository files (in `.hg`), except for the store | |
142 | /// (`.hg/store`). |
|
172 | /// (`.hg/store`). | |
143 | pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
|
173 | pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
@@ -3,7 +3,7 b' use crate::ui::UiError;' | |||||
3 | use format_bytes::format_bytes; |
|
3 | use format_bytes::format_bytes; | |
4 | use hg::config::{ConfigError, ConfigParseError}; |
|
4 | use hg::config::{ConfigError, ConfigParseError}; | |
5 | use hg::errors::HgError; |
|
5 | use hg::errors::HgError; | |
6 |
use hg::repo::Repo |
|
6 | use hg::repo::RepoError; | |
7 | use hg::revlog::revlog::RevlogError; |
|
7 | use hg::revlog::revlog::RevlogError; | |
8 | use hg::utils::files::get_bytes_from_path; |
|
8 | use hg::utils::files::get_bytes_from_path; | |
9 | use std::convert::From; |
|
9 | use std::convert::From; | |
@@ -51,18 +51,17 b' impl From<UiError> for CommandError {' | |||||
51 | } |
|
51 | } | |
52 | } |
|
52 | } | |
53 |
|
53 | |||
54 |
impl From<Repo |
|
54 | impl From<RepoError> for CommandError { | |
55 |
fn from(error: Repo |
|
55 | fn from(error: RepoError) -> Self { | |
56 | match error { |
|
56 | match error { | |
57 |
Repo |
|
57 | RepoError::NotFound { current_directory } => CommandError::Abort { | |
58 | current_directory, |
|
|||
59 | } => CommandError::Abort { |
|
|||
60 | message: format_bytes!( |
|
58 | message: format_bytes!( | |
61 | b"no repository found in '{}' (.hg not found)!", |
|
59 | b"no repository found in '{}' (.hg not found)!", | |
62 | get_bytes_from_path(current_directory) |
|
60 | get_bytes_from_path(current_directory) | |
63 | ), |
|
61 | ), | |
64 | }, |
|
62 | }, | |
65 |
Repo |
|
63 | RepoError::ConfigParseError(error) => error.into(), | |
|
64 | RepoError::Other(error) => error.into(), | |||
66 | } |
|
65 | } | |
67 | } |
|
66 | } | |
68 | } |
|
67 | } | |
@@ -70,33 +69,35 b' impl From<RepoFindError> for CommandErro' | |||||
70 | impl From<ConfigError> for CommandError { |
|
69 | impl From<ConfigError> for CommandError { | |
71 | fn from(error: ConfigError) -> Self { |
|
70 | fn from(error: ConfigError) -> Self { | |
72 | match error { |
|
71 | match error { | |
73 |
ConfigError::Parse( |
|
72 | ConfigError::Parse(error) => error.into(), | |
74 | origin, |
|
|||
75 | line, |
|
|||
76 | bytes, |
|
|||
77 | }) => { |
|
|||
78 | let line_message = if let Some(line_number) = line { |
|
|||
79 | format_bytes!( |
|
|||
80 | b" at line {}", |
|
|||
81 | line_number.to_string().into_bytes() |
|
|||
82 | ) |
|
|||
83 | } else { |
|
|||
84 | Vec::new() |
|
|||
85 | }; |
|
|||
86 | CommandError::Abort { |
|
|||
87 | message: format_bytes!( |
|
|||
88 | b"config parse error in {}{}: '{}'", |
|
|||
89 | origin.to_bytes(), |
|
|||
90 | line_message, |
|
|||
91 | bytes |
|
|||
92 | ), |
|
|||
93 | } |
|
|||
94 | } |
|
|||
95 | ConfigError::Other(error) => error.into(), |
|
73 | ConfigError::Other(error) => error.into(), | |
96 | } |
|
74 | } | |
97 | } |
|
75 | } | |
98 | } |
|
76 | } | |
99 |
|
77 | |||
|
78 | impl From<ConfigParseError> for CommandError { | |||
|
79 | fn from(error: ConfigParseError) -> Self { | |||
|
80 | let ConfigParseError { | |||
|
81 | origin, | |||
|
82 | line, | |||
|
83 | bytes, | |||
|
84 | } = error; | |||
|
85 | let line_message = if let Some(line_number) = line { | |||
|
86 | format_bytes!(b" at line {}", line_number.to_string().into_bytes()) | |||
|
87 | } else { | |||
|
88 | Vec::new() | |||
|
89 | }; | |||
|
90 | CommandError::Abort { | |||
|
91 | message: format_bytes!( | |||
|
92 | b"config parse error in {}{}: '{}'", | |||
|
93 | origin.to_bytes(), | |||
|
94 | line_message, | |||
|
95 | bytes | |||
|
96 | ), | |||
|
97 | } | |||
|
98 | } | |||
|
99 | } | |||
|
100 | ||||
100 | impl From<(RevlogError, &str)> for CommandError { |
|
101 | impl From<(RevlogError, &str)> for CommandError { | |
101 | fn from((err, rev): (RevlogError, &str)) -> CommandError { |
|
102 | fn from((err, rev): (RevlogError, &str)) -> CommandError { | |
102 | match err { |
|
103 | match err { |
General Comments 0
You need to be logged in to leave comments.
Login now