##// END OF EJS Templates
rhg: Parse per-repository configuration...
Simon Sapin -
r47215:d7685105 default
parent child Browse files
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 file.
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 base path
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 /// The value comes from a configuration file
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 RepoFindError {
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, RepoFindError> {
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(RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
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, HgError> {
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.get(b"safe-mismatch", b"source-not-safe") {
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::RepoFindError;
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<RepoFindError> for CommandError {
54 impl From<RepoError> for CommandError {
55 fn from(error: RepoFindError) -> Self {
55 fn from(error: RepoError) -> Self {
56 match error {
56 match error {
57 RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
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 RepoFindError::Other(error) => error.into(),
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(ConfigParseError {
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